Merge "WebView: make notes in docs stand out"
diff --git a/Android.bp b/Android.bp
index 43fb48a..c89cc40 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,6 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Build ext.jar
+// ============================================================
+java_library {
+    name: "ext",
+    no_framework_libs: true,
+    static_libs: [
+        "libphonenumber-platform",
+        "nist-sip",
+        "tagsoup",
+    ],
+    dxflags: ["--core-library"],
+}
+
 // ====  c++ proto device library  ==============================
 cc_library {
     name: "libplatformprotos",
@@ -21,6 +34,11 @@
         include_dirs: ["external/protobuf/src"],
     },
 
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
     target: {
         host: {
             proto: {
@@ -54,9 +72,10 @@
 
 subdirs = [
     "cmds/*",
-    "core/jni",
+    "core/*",
     "libs/*",
     "media/*",
+    "proto",
     "tools/*",
     "native/android",
     "native/graphics/jni",
@@ -65,3 +84,25 @@
 optional_subdirs = [
     "core/tests/utiltests/jni",
 ]
+
+java_library {
+    name: "hwbinder",
+    no_framework_libs: true,
+
+    srcs: [
+        "core/java/android/os/HidlSupport.java",
+        "core/java/android/annotation/NonNull.java",
+        "core/java/android/os/HwBinder.java",
+        "core/java/android/os/HwBlob.java",
+        "core/java/android/os/HwParcel.java",
+        "core/java/android/os/IHwBinder.java",
+        "core/java/android/os/IHwInterface.java",
+        "core/java/android/os/DeadObjectException.java",
+        "core/java/android/os/DeadSystemException.java",
+        "core/java/android/os/RemoteException.java",
+        "core/java/android/util/AndroidException.java",
+    ],
+
+    dxflags: ["--core-library"],
+    installable: false,
+}
diff --git a/Android.mk b/Android.mk
index aa7caa5..9843f17 100644
--- a/Android.mk
+++ b/Android.mk
@@ -128,7 +128,7 @@
 	../../system/bt/binder/android/bluetooth/IBluetoothHeadsetPhone.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothHealth.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothHealthCallback.aidl \
-	../../system/bt/binder/android/bluetooth/IBluetoothInputDevice.aidl \
+	../../system/bt/binder/android/bluetooth/IBluetoothHidHost.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothPan.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothManager.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothManagerCallback.aidl \
@@ -139,7 +139,7 @@
 	../../system/bt/binder/android/bluetooth/IBluetoothSap.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothStateChangeCallback.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothHeadsetClient.aidl \
-	../../system/bt/binder/android/bluetooth/IBluetoothInputHost.aidl \
+	../../system/bt/binder/android/bluetooth/IBluetoothHidDevice.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothGatt.aidl \
 	../../system/bt/binder/android/bluetooth/IBluetoothGattCallback.aidl \
@@ -270,6 +270,7 @@
 	core/java/android/os/IRecoverySystemProgressListener.aidl \
 	core/java/android/os/IRemoteCallback.aidl \
 	core/java/android/os/ISchedulingPolicyService.aidl \
+	core/java/android/os/IStatsCallbacks.aidl \
 	core/java/android/os/IStatsCompanionService.aidl \
 	core/java/android/os/IStatsManager.aidl \
 	core/java/android/os/IThermalEventListener.aidl \
@@ -379,7 +380,7 @@
 	core/java/android/speech/tts/ITextToSpeechService.aidl \
 	core/java/com/android/internal/app/IAppOpsCallback.aidl \
 	core/java/com/android/internal/app/IAppOpsService.aidl \
-	core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
+	core/java/com/android/internal/app/IAssistDataReceiver.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
 	core/java/com/android/internal/app/ISoundTriggerService.aidl \
 	core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
@@ -425,7 +426,6 @@
 	core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl \
 	core/java/com/android/internal/widget/ILockSettings.aidl \
 	core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
-	core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
 	keystore/java/android/security/IKeyChainAliasCallback.aidl \
 	keystore/java/android/security/IKeyChainService.aidl \
 	location/java/android/location/IBatchedLocationCallback.aidl \
@@ -548,10 +548,13 @@
 	telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
 	telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl \
 	wifi/java/android/net/wifi/IWifiManager.aidl \
-	wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl \
-	wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl \
 	wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl \
+	wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl \
+	wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl \
+	wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
+	wifi/java/android/net/wifi/rtt/IRttCallback.aidl \
+	wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \
 	wifi/java/android/net/wifi/IWifiScanner.aidl \
 	wifi/java/android/net/wifi/IRttManager.aidl \
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
@@ -567,6 +570,7 @@
 	../../system/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl \
 
 LOCAL_SRC_FILES += \
+	../../system/core/storaged/binder/android/os/IStoraged.aidl \
 	../../system/netd/server/binder/android/net/INetd.aidl \
 	../../system/vold/binder/android/os/IVold.aidl \
 	../../system/vold/binder/android/os/IVoldListener.aidl \
@@ -575,6 +579,8 @@
 
 LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
 
+LOCAL_AIDL_INCLUDES += core/java/android/os/StatsLogEventWrapper.aidl
+
 LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
 LOCAL_SRC_FILES += \
 	lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl \
@@ -584,6 +590,17 @@
 	lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl \
 	lowpan/java/android/net/lowpan/ILowpanManager.aidl
 
+# StatsLog generated functions
+statslog_src_dir := $(call intermediates-dir-for,JAVA_LIBRARIES,framework,,COMMON)/statslog
+gen := $(statslog_src_dir)/android/util/StatsLog.java
+$(gen): PRIVATE_PATH := $(LOCAL_PATH)
+$(gen): PRIVATE_CUSTOM_TOOL = $(HOST_OUT_EXECUTABLES)/stats-log-api-gen --java $@
+$(gen): $(HOST_OUT_EXECUTABLES)/stats-log-api-gen
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(gen)
+statslog_src_dir:=
+gen:=
+
 # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
 LOCAL_AIDL_INCLUDES += \
       $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
@@ -594,6 +611,7 @@
 	frameworks/av/drm/libmediadrm/aidl \
 	frameworks/av/media/libaudioclient/aidl \
 	frameworks/native/aidl/gui \
+	system/core/storaged/binder \
 	system/netd/server/binder \
 	system/vold/binder \
 	system/bt/binder
@@ -634,6 +652,8 @@
 
 LOCAL_MODULE := framework
 
+LOCAL_JAVAC_SHARD_SIZE := 150
+
 LOCAL_DX_FLAGS := --core-library --multi-dex
 LOCAL_JACK_FLAGS := --multi-dex native
 
@@ -650,32 +670,6 @@
 
 framework_built := $(call java-lib-deps,framework)
 
-# HwBinder
-# =======================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := \
-        core/java/android/os/HidlSupport.java \
-        core/java/android/annotation/NonNull.java \
-        core/java/android/os/HwBinder.java \
-        core/java/android/os/HwBlob.java \
-        core/java/android/os/HwParcel.java \
-        core/java/android/os/IHwBinder.java \
-        core/java/android/os/IHwInterface.java \
-        core/java/android/os/DeadObjectException.java \
-        core/java/android/os/DeadSystemException.java \
-        core/java/android/os/RemoteException.java \
-        core/java/android/util/AndroidException.java \
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := hwbinder
-
-LOCAL_DX_FLAGS := --core-library
-LOCAL_UNINSTALLABLE_MODULE := true
-include $(BUILD_JAVA_LIBRARY)
-
 # Copy AIDL files to be preprocessed and included in the SDK,
 # specified relative to the root of the build tree.
 # ============================================================
@@ -721,6 +715,8 @@
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pGroup.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl \
+	frameworks/base/wifi/java/android/net/wifi/rtt/RangingRequest.aidl \
+	frameworks/base/wifi/java/android/net/wifi/rtt/RangingResult.aidl \
 	frameworks/base/wifi/java/android/net/wifi/WpsInfo.aidl \
 	frameworks/base/wifi/java/android/net/wifi/ScanResult.aidl \
 	frameworks/base/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.aidl \
@@ -993,7 +989,8 @@
 framework_docs_LOCAL_INTERMEDIATE_SOURCES := \
 	$(framework_res_source_path)/android/R.java \
 	$(framework_res_source_path)/android/Manifest.java \
-	$(framework_res_source_path)/com/android/internal/R.java
+	$(framework_res_source_path)/com/android/internal/R.java \
+	$(patsubst $(TARGET_OUT_COMMON_INTERMEDIATES)/%,%,$(libcore_to_document_generated))
 
 framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
 	core-oj \
@@ -1052,6 +1049,7 @@
     -since $(SRC_API_DIR)/24.txt 24 \
     -since $(SRC_API_DIR)/25.txt 25 \
     -since $(SRC_API_DIR)/26.txt 26 \
+    -since $(SRC_API_DIR)/27.txt 27 \
     -werror -lerror -hide 111 -hide 113 -hide 121 -hide 125 -hide 126 -hide 127 -hide 128 \
     -overview $(LOCAL_PATH)/core/java/overview.html \
 
@@ -1063,7 +1061,7 @@
 
 framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES := \
     frameworks/base/docs/knowntags.txt \
-    libcore/Docs.mk
+    $(libcore_to_document_generated)
 
 samples_dir := development/samples/browseable
 
@@ -1547,35 +1545,6 @@
 
 include $(BUILD_DROIDDOC)
 
-# Build ext.jar
-# ============================================================
-
-ext_dirs := \
-	../../external/nist-sip/java \
-	../../external/tagsoup/src \
-
-ext_src_files := $(call all-java-files-under,$(ext_dirs))
-
-# ====  the library  =========================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(ext_src_files)
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart
-LOCAL_STATIC_JAVA_LIBRARIES := libphonenumber-platform
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := ext
-
-LOCAL_DX_FLAGS := --core-library
-
-ifneq ($(INCREMENTAL_BUILDS),)
-    LOCAL_PROGUARD_ENABLED := disabled
-    LOCAL_JACK_ENABLED := incremental
-endif
-
-include $(BUILD_JAVA_LIBRARY)
-
 # ====  java proto host library  ==============================
 include $(CLEAR_VARS)
 LOCAL_MODULE := platformprotos
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 200f92f..f08b402 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -4,11 +4,14 @@
 LOCAL_MODULE_TAGS := tests
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+  $(call all-java-files-under, src) \
+  src/android/os/ISomeService.aidl
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
     apct-perftests-utils \
+    guava \
     legacy-android-test
 
 LOCAL_PACKAGE_NAME := CorePerfTests
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index a709ee3..4889941 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -2,9 +2,13 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.perftests.core">
 
+    <uses-permission
+        android:name="android.permission.GET_ACCOUNTS" />
+
     <application>
         <uses-library android:name="android.test.runner" />
         <activity android:name="android.perftests.utils.StubActivity" />
+        <service android:name="android.os.SomeService" android:exported="false" android:process=":some_service" />
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java b/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java
new file mode 100644
index 0000000..b9411fa
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/accounts/AccountManagerPerfTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 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.accounts;
+
+import static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AccountManagerPerfTest {
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testGetAccounts() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            fail("Missing required GET_ACCOUNTS permission");
+        }
+        AccountManager accountManager = AccountManager.get(context);
+        while (state.keepRunning()) {
+            accountManager.getAccounts();
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java
new file mode 100644
index 0000000..7c5316d
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabaseIoPerfTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.database;
+
+import android.app.Activity;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Performance tests for measuring amount of data written during typical DB operations
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabaseIoPerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabaseIoPerfTest {
+    private static final String TAG = "SQLiteDatabaseIoPerfTest";
+    private static final String DB_NAME = "db_io_perftest";
+    private static final int DEFAULT_DATASET_SIZE = 500;
+
+    private Long mWriteBytes;
+
+    private SQLiteDatabase mDatabase;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContext.deleteDatabase(DB_NAME);
+        mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+        mDatabase.execSQL("CREATE TABLE T1 "
+                + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+    }
+
+    @After
+    public void tearDown() {
+        mDatabase.close();
+        mContext.deleteDatabase(DB_NAME);
+    }
+
+    @Test
+    public void testDatabaseModifications() {
+        startMeasuringWrites();
+        ContentValues cv = new ContentValues();
+        String[] whereArg = new String[1];
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            cv.put("_ID", i);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "NewValue");
+            cv.put("COL_C", 1.0);
+            assertEquals(i, mDatabase.insert("T1", null, cv));
+        }
+        cv = new ContentValues();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            cv.put("COL_B", "UpdatedValue");
+            cv.put("COL_C", 1.1);
+            whereArg[0] = String.valueOf(i);
+            assertEquals(1, mDatabase.update("T1", cv, "_ID=?", whereArg));
+        }
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            whereArg[0] = String.valueOf(i);
+            assertEquals(1, mDatabase.delete("T1", "_ID=?", whereArg));
+        }
+        // Make sure all changes are written to disk
+        mDatabase.close();
+        long bytes = endMeasuringWrites();
+        sendResults("testDatabaseModifications" , bytes);
+    }
+
+    @Test
+    public void testInsertsWithTransactions() {
+        startMeasuringWrites();
+        final int txSize = 10;
+        ContentValues cv = new ContentValues();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE * 5; i++) {
+            if (i % txSize == 0) {
+                mDatabase.beginTransaction();
+            }
+            if (i % txSize == txSize-1) {
+                mDatabase.setTransactionSuccessful();
+                mDatabase.endTransaction();
+
+            }
+            cv.put("_ID", i);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "NewValue");
+            cv.put("COL_C", 1.0);
+            assertEquals(i, mDatabase.insert("T1", null, cv));
+        }
+        // Make sure all changes are written to disk
+        mDatabase.close();
+        long bytes = endMeasuringWrites();
+        sendResults("testInsertsWithTransactions" , bytes);
+    }
+
+    private void startMeasuringWrites() {
+        Preconditions.checkState(mWriteBytes == null, "Measurement already started");
+        mWriteBytes = getIoStats().get("write_bytes");
+    }
+
+    private long endMeasuringWrites() {
+        Preconditions.checkState(mWriteBytes != null, "Measurement wasn't started");
+        Long newWriteBytes = getIoStats().get("write_bytes");
+        return newWriteBytes - mWriteBytes;
+    }
+
+    private void sendResults(String testName, long writeBytes) {
+        Log.i(TAG, testName + " write_bytes: " + writeBytes);
+        Bundle status = new Bundle();
+        status.putLong("write_bytes", writeBytes);
+        InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+    }
+
+    private static Map<String, Long> getIoStats() {
+        String ioStat = "/proc/self/io";
+        Map<String, Long> results = new ArrayMap<>();
+        try {
+            List<String> lines = Files.readAllLines(new File(ioStat).toPath());
+            for (String line : lines) {
+                line = line.trim();
+                String[] split = line.split(":");
+                if (split.length == 2) {
+                    try {
+                        String key = split[0].trim();
+                        Long value = Long.valueOf(split[1].trim());
+                        results.put(key, value);
+                    } catch (NumberFormatException e) {
+                        Log.e(TAG, "Cannot parse number from " + line);
+                    }
+                } else if (line.isEmpty()) {
+                    Log.e(TAG, "Cannot parse line " + line);
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Can't read: " + ioStat, e);
+        }
+        return results;
+    }
+
+}
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
new file mode 100644
index 0000000..7a32c0c
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.database;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Performance tests for typical CRUD operations and loading rows into the Cursor
+ *
+ * <p>To run: bit CorePerfTests:android.database.SQLiteDatabasePerfTest
+ */
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SQLiteDatabasePerfTest {
+    // TODO b/64262688 Add Concurrency tests to compare WAL vs DELETE read/write
+    private static final String DB_NAME = "dbperftest";
+    private static final int DEFAULT_DATASET_SIZE = 1000;
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+    private SQLiteDatabase mDatabase;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContext.deleteDatabase(DB_NAME);
+        mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+        mDatabase.execSQL("CREATE TABLE T1 "
+                + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+        mDatabase.execSQL("CREATE TABLE T2 ("
+                + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+                + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+    }
+
+    @After
+    public void tearDown() {
+        mDatabase.close();
+        mContext.deleteDatabase(DB_NAME);
+    }
+
+    @Test
+    public void testSelect() {
+        insertT1TestDataSet();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Random rnd = new Random(0);
+        while (state.keepRunning()) {
+            int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+                    + "WHERE _ID=?", new String[]{String.valueOf(index)})) {
+                assertTrue(cursor.moveToNext());
+                assertEquals(index, cursor.getInt(0));
+                assertEquals(index, cursor.getInt(1));
+                assertEquals("T1Value" + index, cursor.getString(2));
+                assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+            }
+        }
+    }
+
+    @Test
+    public void testSelectMultipleRows() {
+        insertT1TestDataSet();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        Random rnd = new Random(0);
+        final int querySize = 50;
+        while (state.keepRunning()) {
+            int index = rnd.nextInt(DEFAULT_DATASET_SIZE - querySize - 1);
+            try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+                            + "WHERE _ID BETWEEN ? and ? ORDER BY _ID",
+                    new String[]{String.valueOf(index), String.valueOf(index + querySize - 1)})) {
+                int i = 0;
+                while(cursor.moveToNext()) {
+                    assertEquals(index, cursor.getInt(0));
+                    assertEquals(index, cursor.getInt(1));
+                    assertEquals("T1Value" + index, cursor.getString(2));
+                    assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+                    index++;
+                    i++;
+                }
+                assertEquals(querySize, i);
+            }
+        }
+    }
+
+    @Test
+    public void testInnerJoin() {
+        mDatabase.setForeignKeyConstraintsEnabled(true);
+        mDatabase.beginTransaction();
+        insertT1TestDataSet();
+        insertT2TestDataSet();
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Random rnd = new Random(0);
+        while (state.keepRunning()) {
+            int index = rnd.nextInt(1000);
+            try (Cursor cursor = mDatabase.rawQuery(
+                    "SELECT T1._ID, T1.COL_A, T1.COL_B, T1.COL_C, T2.COL_A FROM T1 "
+                    + "INNER JOIN T2 on T2.T1_ID=T1._ID WHERE T1._ID = ?",
+                    new String[]{String.valueOf(index)})) {
+                assertTrue(cursor.moveToNext());
+                assertEquals(index, cursor.getInt(0));
+                assertEquals(index, cursor.getInt(1));
+                assertEquals("T1Value" + index, cursor.getString(2));
+                assertEquals(1.1 * index, cursor.getDouble(3), 0.0000001d);
+                assertEquals("T2Value" + index, cursor.getString(4));
+            }
+        }
+    }
+
+    @Test
+    public void testInsert() {
+        insertT1TestDataSet();
+
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        ContentValues cv = new ContentValues();
+        cv.put("_ID", DEFAULT_DATASET_SIZE);
+        cv.put("COL_B", "NewValue");
+        cv.put("COL_C", 1.1);
+        String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+        while (state.keepRunning()) {
+            assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
+            state.pauseTiming();
+            assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+            state.resumeTiming();
+        }
+    }
+
+    @Test
+    public void testDelete() {
+        insertT1TestDataSet();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+        Object[] insertsArgs = new Object[]{DEFAULT_DATASET_SIZE, DEFAULT_DATASET_SIZE,
+                "ValueToDelete", 1.1};
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)", insertsArgs);
+            state.resumeTiming();
+            assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
+        }
+    }
+
+    @Test
+    public void testUpdate() {
+        insertT1TestDataSet();
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        Random rnd = new Random(0);
+        int i = 0;
+        ContentValues cv = new ContentValues();
+        String[] argArray = new String[1];
+        while (state.keepRunning()) {
+            int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "UpdatedValue");
+            cv.put("COL_C", i);
+            argArray[0] = String.valueOf(id);
+            assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
+            i++;
+        }
+    }
+
+    private void insertT1TestDataSet() {
+        mDatabase.beginTransaction();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            mDatabase.execSQL("INSERT INTO T1 VALUES (?, ?, ?, ?)",
+                    new Object[]{i, i, "T1Value" + i, i * 1.1});
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+    }
+
+    private void insertT2TestDataSet() {
+        mDatabase.beginTransaction();
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; i++) {
+            mDatabase.execSQL("INSERT INTO T2 VALUES (?, ?, ?)",
+                    new Object[]{i, "T2Value" + i, i});
+        }
+        mDatabase.setTransactionSuccessful();
+        mDatabase.endTransaction();
+    }
+}
+
diff --git a/apct-tests/perftests/core/src/android/os/ISomeService.aidl b/apct-tests/perftests/core/src/android/os/ISomeService.aidl
new file mode 100644
index 0000000..0658b69
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/ISomeService.aidl
@@ -0,0 +1,5 @@
+package android.os;
+
+interface ISomeService {
+    void readDisk(int times);
+}
\ No newline at end of file
diff --git a/apct-tests/perftests/core/src/android/os/SomeService.java b/apct-tests/perftests/core/src/android/os/SomeService.java
new file mode 100644
index 0000000..bdfaa44
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/SomeService.java
@@ -0,0 +1,42 @@
+package android.os;
+
+import android.app.Service;
+import android.content.Intent;
+import java.io.File;
+import java.io.IOException;
+
+/** Service in separate process available for calling over binder. */
+public class SomeService extends Service {
+
+    private File mTempFile;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        try {
+            mTempFile = File.createTempFile("foo", "bar");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final ISomeService.Stub mBinder =
+            new ISomeService.Stub() {
+                public void readDisk(int times) {
+                    for (int i = 0; i < times; i++) {
+                        mTempFile.exists();
+                    }
+                }
+            };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mTempFile.delete();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/os/StrictModeTest.java b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
new file mode 100644
index 0000000..d973c20
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/StrictModeTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.os;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import com.google.common.util.concurrent.SettableFuture;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class StrictModeTest {
+    @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void timeVmViolation() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+        causeVmViolations(state);
+    }
+
+    @Test
+    public void timeVmViolationNoStrictMode() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeVmViolations(state);
+    }
+
+    private static void causeVmViolations(BenchmarkState state) {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setDataAndType(Uri.parse("content://com.example/foobar"), "image/jpeg");
+        final Context context = InstrumentationRegistry.getTargetContext();
+        while (state.keepRunning()) {
+            context.startActivity(intent);
+        }
+    }
+
+    @Test
+    public void timeThreadViolation() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeThreadViolations(state);
+    }
+
+    @Test
+    public void timeThreadViolationNoStrictMode() throws IOException {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeThreadViolations(state);
+    }
+
+    private static void causeThreadViolations(BenchmarkState state) throws IOException {
+        final File test = File.createTempFile("foo", "bar");
+        while (state.keepRunning()) {
+            test.exists();
+        }
+        test.delete();
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolation() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+        causeCrossProcessThreadViolations(state);
+    }
+
+    @Test
+    public void timeCrossBinderThreadViolationNoStrictMode() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        causeCrossProcessThreadViolations(state);
+    }
+
+    private static void causeCrossProcessThreadViolations(BenchmarkState state)
+            throws ExecutionException, InterruptedException, RemoteException {
+        final Context context = InstrumentationRegistry.getTargetContext();
+
+        SettableFuture<IBinder> binder = SettableFuture.create();
+        ServiceConnection connection =
+                new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName className, IBinder service) {
+                        binder.set(service);
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName arg0) {
+                        binder.set(null);
+                    }
+                };
+        context.bindService(
+                new Intent(context, SomeService.class), connection, Context.BIND_AUTO_CREATE);
+        ISomeService someService = ISomeService.Stub.asInterface(binder.get());
+        while (state.keepRunning()) {
+            // Violate strictmode heavily.
+            someService.readDisk(10);
+        }
+        context.unbindService(connection);
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
new file mode 100644
index 0000000..586c385
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for {@link BoringLayout} create and draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class BoringLayoutCreateDrawPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+    private static final float SPACING_ADD = 10f;
+    private static final float SPACING_MULT = 1.5f;
+
+    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                for (TextType textType : new TextType[]{TextType.STRING,
+                        TextType.SPANNABLE_BUILDER}) {
+                    params.add(new Object[]{textType.name(), length, textType, cached});
+                }
+            }
+        }
+        return params;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public BoringLayoutCreateDrawPerfTest(String label, int length, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mCached = cached;
+        mTextType = textType;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+    }
+
+    /**
+     * Measures the creation time for {@link BoringLayout}.
+     */
+    @Test
+    public void timeCreate() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText();
+        // isBoring result is calculated in another test, we want to measure only the
+        // create time for Boring without isBoring check. Therefore it is calculated here.
+        final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint);
+        if (mCached) createLayout(text, metrics);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            createLayout(text, metrics);
+        }
+    }
+
+    /**
+     * Measures the draw time for {@link BoringLayout} or {@link StaticLayout}.
+     */
+    @Test
+    public void timeDraw() throws Throwable {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final CharSequence text = createRandomText();
+        final BoringLayout.Metrics metrics = BoringLayout.isBoring(text, mTextPaint);
+        final Layout layout = createLayout(text, metrics);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            final int save = canvas.save();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            layout.draw(canvas);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private CharSequence createRandomText() {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(mLength)
+                .setCreateBoring(true)
+                .setTextType(mTextType)
+                .build();
+    }
+
+    private Layout createLayout(CharSequence text,
+            BoringLayout.Metrics metrics) {
+        return BoringLayout.make(text, mTextPaint, Integer.MAX_VALUE /*width*/,
+                ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, metrics, true /*includePad*/);
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
new file mode 100644
index 0000000..9d11f29
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.text;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for {@link BoringLayout#isBoring(CharSequence, TextPaint)}.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class BoringLayoutIsBoringPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean boring : BOOLEANS) {
+                for (boolean cached : BOOLEANS) {
+                    for (TextType textType : new TextType[]{TextType.STRING,
+                            TextType.SPANNABLE_BUILDER}) {
+                        params.add(new Object[]{
+                                (boring ? "Boring" : "NotBoring") + "," + textType.name(),
+                                length, boring, textType, cached});
+                    }
+                }
+            }
+        }
+        return params;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCreateBoring;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public BoringLayoutIsBoringPerfTest(String label, int length, boolean boring, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mCreateBoring = boring;
+        mCached = cached;
+        mTextType = textType;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+    }
+
+    /**
+     * Measure the time for the {@link BoringLayout#isBoring(CharSequence, TextPaint)}.
+     */
+    @Test
+    public void timeIsBoring() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText();
+        if (mCached) BoringLayout.isBoring(text, mTextPaint);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            BoringLayout.isBoring(text, mTextPaint);
+        }
+    }
+
+    private CharSequence createRandomText() {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(mLength)
+                .setCreateBoring(mCreateBoring)
+                .setTextType(mTextType)
+                .build();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
index e644a1f..b4c7f54 100644
--- a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
@@ -16,35 +16,28 @@
 
 package android.text;
 
-import android.app.Activity;
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
-import android.os.Bundle;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
-
-import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
 import android.text.style.ReplacementSpan;
 import android.util.ArraySet;
 
-import static android.text.Layout.Alignment.ALIGN_NORMAL;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-import java.util.Random;
-
-import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Random;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java b/apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java
new file mode 100644
index 0000000..7c0cf0e
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/NonEditableTextGenerator.java
@@ -0,0 +1,138 @@
+package android.text;
+
+import static android.text.Spanned.SPAN_INCLUSIVE_INCLUSIVE;
+
+import android.text.style.BulletSpan;
+
+import java.util.Random;
+
+/**
+ *
+ */
+public class NonEditableTextGenerator {
+
+    enum TextType {
+        STRING,
+        SPANNED,
+        SPANNABLE_BUILDER
+    }
+
+    private boolean mCreateBoring;
+    private TextType mTextType;
+    private int mSequenceLength;
+    private final Random mRandom;
+
+    public NonEditableTextGenerator(Random random) {
+        mRandom = random;
+    }
+
+    public NonEditableTextGenerator setCreateBoring(boolean createBoring) {
+        mCreateBoring = createBoring;
+        return this;
+    }
+
+    public NonEditableTextGenerator setTextType(TextType textType) {
+        mTextType = textType;
+        return this;
+    }
+
+    public NonEditableTextGenerator setSequenceLength(int sequenceLength) {
+        mSequenceLength = sequenceLength;
+        return this;
+    }
+
+    /**
+     * Sample charSequence generated:
+     * NRjPzjvUadHmH ExoEoTqfx pCLw qtndsqfpk AqajVCbgjGZ igIeC dfnXRgA
+     */
+    public CharSequence build() {
+        final RandomCharSequenceGenerator sequenceGenerator = new RandomCharSequenceGenerator(
+                mRandom);
+        if (mSequenceLength > 0) {
+            sequenceGenerator.setSequenceLength(mSequenceLength);
+        }
+
+        final CharSequence charSequence = sequenceGenerator.buildLatinSequence();
+
+        switch (mTextType) {
+            case SPANNED:
+            case SPANNABLE_BUILDER:
+                return createSpannable(charSequence);
+            case STRING:
+            default:
+                return createString(charSequence);
+        }
+    }
+
+    private Spannable createSpannable(CharSequence charSequence) {
+        final Spannable spannable = (mTextType == TextType.SPANNABLE_BUILDER) ?
+                new SpannableStringBuilder(charSequence) : new SpannableString(charSequence);
+
+        if (!mCreateBoring) {
+            // add a paragraph style to make it non boring
+            spannable.setSpan(new BulletSpan(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE);
+        }
+
+        spannable.setSpan(new Object(), 0, spannable.length(), SPAN_INCLUSIVE_INCLUSIVE);
+        spannable.setSpan(new Object(), 0, 1, SPAN_INCLUSIVE_INCLUSIVE);
+
+        return spannable;
+    }
+
+    private String createString(CharSequence charSequence) {
+        if (mCreateBoring) {
+            return charSequence.toString();
+        } else {
+            // BoringLayout checks to see if there is a surrogate pair and if so tells that
+            // the charSequence is not suitable for boring. Add an emoji to make it non boring.
+            // Emoji is added instead of RTL, since emoji stays in the same run and is a more
+            // common case.
+            return charSequence.toString() + "\uD83D\uDC68\uD83C\uDFFF";
+        }
+    }
+
+    public static class RandomCharSequenceGenerator {
+
+        private static final int DEFAULT_MIN_WORD_LENGTH = 3;
+        private static final int DEFAULT_MAX_WORD_LENGTH = 15;
+        private static final int DEFAULT_SEQUENCE_LENGTH = 256;
+
+        private int mMinWordLength = DEFAULT_MIN_WORD_LENGTH;
+        private int mMaxWordLength = DEFAULT_MAX_WORD_LENGTH;
+        private int mSequenceLength = DEFAULT_SEQUENCE_LENGTH;
+        private final Random mRandom;
+
+        public RandomCharSequenceGenerator(Random random) {
+            mRandom = random;
+        }
+
+        public RandomCharSequenceGenerator setSequenceLength(int sequenceLength) {
+            mSequenceLength = sequenceLength;
+            return this;
+        }
+
+        public CharSequence buildLatinSequence() {
+            final StringBuilder result = new StringBuilder();
+            while (result.length() < mSequenceLength) {
+                // add random word
+                result.append(buildLatinWord());
+                result.append(' ');
+            }
+            return result.substring(0, mSequenceLength);
+        }
+
+        public CharSequence buildLatinWord() {
+            final StringBuilder result = new StringBuilder();
+            // create a random length that is (mMinWordLength + random amount of chars) where
+            // total size is less than mMaxWordLength
+            final int length = mRandom.nextInt(mMaxWordLength - mMinWordLength) + mMinWordLength;
+            while (result.length() < length) {
+                // add random letter
+                int base = mRandom.nextInt(2) == 0 ? 'A' : 'a';
+                result.append(Character.toChars(mRandom.nextInt(26) + base));
+            }
+            return result.toString();
+        }
+    }
+
+}
diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
new file mode 100644
index 0000000..6768798
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.text;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for single line measure and draw using {@link Paint} and {@link Canvas}.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class PaintMeasureDrawPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    @Parameterized.Parameters(name = "cached={1},{0}chars")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                params.add(new Object[]{length, cached});
+            }
+        }
+        return params;
+    }
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    private final int mLength;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+
+    public PaintMeasureDrawPerfTest(int length, boolean cached) {
+        mLength = length;
+        mCached = cached;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+    }
+
+    /**
+     * Measure the time for {@link Paint#measureText(String)}
+     */
+    @Test
+    public void timeMeasure() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final String text = createRandomText();
+        if (mCached) mTextPaint.measureText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            mTextPaint.measureText(text);
+        }
+    }
+
+    /**
+     * Measures the time for {@link Canvas#drawText(String, float, float, Paint)}
+     */
+    @Test
+    public void timeDraw() throws Throwable {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final String text = createRandomText();
+        if (mCached) mTextPaint.measureText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            final int save = canvas.save();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            canvas.drawText(text, 0 /*x*/, 100 /*y*/, mTextPaint);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private String createRandomText() {
+        return (String) new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(mLength)
+                .setCreateBoring(true)
+                .setTextType(NonEditableTextGenerator.TextType.STRING)
+                .build();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
new file mode 100644
index 0000000..bfdb758
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text;
+
+import static android.text.Layout.Alignment.ALIGN_NORMAL;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Performance test for multi line, single style {@link StaticLayout} creation/draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class StaticLayoutCreateDrawPerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    private static final float SPACING_ADD = 10f;
+    private static final float SPACING_MULT = 1.5f;
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                for (TextType textType : new TextType[]{TextType.STRING,
+                        TextType.SPANNABLE_BUILDER}) {
+                    params.add(new Object[]{textType.name(), length, textType, cached});
+                }
+            }
+        }
+        return params;
+    }
+
+    private final int mLineWidth;
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public StaticLayoutCreateDrawPerfTest(String label, int length, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mTextType = textType;
+        mCached = cached;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+        mLineWidth = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Measures the creation time for a multi line {@link StaticLayout}.
+     */
+    @Test
+    public void timeCreate() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText(mLength);
+        createLayout(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            createLayout(text);
+        }
+    }
+
+    /**
+     * Measures the draw time for a multi line {@link StaticLayout}.
+     */
+    @Test
+    public void timeDraw() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final CharSequence text = createRandomText(mLength);
+        final Layout layout = createLayout(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            int save = canvas.save();
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            layout.draw(canvas);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private Layout createLayout(CharSequence text) {
+        return StaticLayout.Builder.obtain(text, 0 /*start*/, text.length() /*end*/, mTextPaint,
+                mLineWidth)
+                .setAlignment(ALIGN_NORMAL)
+                .setIncludePad(true)
+                .setLineSpacing(SPACING_ADD, SPACING_MULT)
+                .build();
+    }
+
+    private CharSequence createRandomText(int length) {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(length)
+                .setTextType(mTextType)
+                .build();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
new file mode 100644
index 0000000..ff2d57e
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.text;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
+import android.graphics.Canvas;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.text.NonEditableTextGenerator.TextType;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.widget.TextView;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+
+/**
+ * Performance test for {@link TextView} measure/draw.
+ */
+@LargeTest
+@RunWith(Parameterized.class)
+public class TextViewSetTextMeasurePerfTest {
+
+    private static final boolean[] BOOLEANS = new boolean[]{false, true};
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+    public static Collection cases() {
+        final List<Object[]> params = new ArrayList<>();
+        for (int length : new int[]{128}) {
+            for (boolean cached : BOOLEANS) {
+                for (TextType textType : new TextType[]{TextType.STRING,
+                        TextType.SPANNABLE_BUILDER}) {
+                    params.add(new Object[]{textType.name(), length, textType, cached});
+                }
+            }
+        }
+        return params;
+    }
+
+    private final int mLineWidth;
+    private final int mLength;
+    private final TextType mTextType;
+    private final boolean mCached;
+    private final TextPaint mTextPaint;
+
+    public TextViewSetTextMeasurePerfTest(String label, int length, TextType textType,
+            boolean cached) {
+        mLength = length;
+        mTextType = textType;
+        mCached = cached;
+        mTextPaint = new TextPaint();
+        mTextPaint.setTextSize(10);
+        mLineWidth = Integer.MAX_VALUE;
+    }
+
+    /**
+     * Measures the time to setText and measure for a {@link TextView}.
+     */
+    @Test
+    public void timeCreate() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final CharSequence text = createRandomText(mLength);
+        final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
+        textView.setText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            textView.setTextLocale(Locale.UK);
+            textView.setTextLocale(Locale.US);
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            textView.setText(text);
+            textView.measure(AT_MOST | mLineWidth, UNSPECIFIED);
+        }
+    }
+
+    /**
+     * Measures the time to draw for a {@link TextView}.
+     */
+    @Test
+    public void timeDraw() throws Exception {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        state.pauseTiming();
+        Canvas.freeTextLayoutCaches();
+        final RenderNode node = RenderNode.create("benchmark", null);
+        final CharSequence text = createRandomText(mLength);
+        final TextView textView = new TextView(InstrumentationRegistry.getTargetContext());
+        textView.setText(text);
+        state.resumeTiming();
+
+        while (state.keepRunning()) {
+
+            state.pauseTiming();
+            final DisplayListCanvas canvas = node.start(1200, 200);
+            int save = canvas.save();
+            textView.setTextLocale(Locale.UK);
+            textView.setTextLocale(Locale.US);
+            if (!mCached) Canvas.freeTextLayoutCaches();
+            state.resumeTiming();
+
+            textView.draw(canvas);
+
+            state.pauseTiming();
+            canvas.restoreToCount(save);
+            node.end(canvas);
+            state.resumeTiming();
+        }
+    }
+
+    private CharSequence createRandomText(int length) {
+        return new NonEditableTextGenerator(new Random(0))
+                .setSequenceLength(length)
+                .setCreateBoring(false)
+                .setTextType(mTextType)
+                .build();
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
index 40b56f4..d219d3a 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
@@ -16,31 +16,26 @@
 
 package android.widget;
 
-import android.app.Activity;
-import android.os.Bundle;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
 import android.text.Selection;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
index b100acb..b6cf7d3 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
@@ -16,31 +16,26 @@
 
 package android.widget;
 
-import android.app.Activity;
-import android.os.Bundle;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.rule.ActivityTestRule;
 import android.text.Selection;
 import android.view.KeyEvent;
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Locale;
-
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized.Parameters;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.Arrays;
+import java.util.Collection;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
index 7fc5e4f..e95676b 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
@@ -16,27 +16,21 @@
 
 package android.widget;
 
-import android.app.Activity;
-import android.os.Bundle;
-import android.perftests.utils.PerfStatusReporter;
-import android.util.Log;
-
 import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.StubActivity;
 import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.support.test.rule.ActivityTestRule;
-import android.support.test.InstrumentationRegistry;
 
-import java.util.Locale;
-import java.util.Collection;
-import java.util.Arrays;
-
-import org.junit.Test;
 import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
-import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Locale;
 
 @LargeTest
 @RunWith(Parameterized.class)
diff --git a/apct-tests/perftests/multiuser/Android.mk b/apct-tests/perftests/multiuser/Android.mk
index e3f7775..2db0dd6 100644
--- a/apct-tests/perftests/multiuser/Android.mk
+++ b/apct-tests/perftests/multiuser/Android.mk
@@ -20,7 +20,8 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test
+    android-support-test \
+    ub-uiautomator
 
 LOCAL_PACKAGE_NAME := MultiUserPerfTests
 
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index c7bebf3..629e6f4 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -17,13 +17,16 @@
 
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
 
+import java.io.IOException;
 import java.util.ArrayList;
 
 // Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
 public class BenchmarkRunner {
 
-    private static long COOL_OFF_PERIOD_MS = 2000;
+    private static final long COOL_OFF_PERIOD_MS = 1000;
 
     private static final int NUM_ITERATIONS = 4;
 
@@ -70,9 +73,13 @@
     }
 
     private void prepareForNextRun() {
-        // TODO: Once http://b/63115387 is fixed, look into using "am wait-for-broadcast-idle"
-        // command instead of waiting for a fixed amount of time.
         SystemClock.sleep(COOL_OFF_PERIOD_MS);
+        try {
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                    .executeShellCommand("am wait-for-broadcast-idle");
+        } catch (IOException e) {
+            throw new IllegalStateException("Cannot execute shell command", e);
+        }
         mStartTimeNs = System.nanoTime();
         mPausedDurationNs = 0;
     }
diff --git a/api/current.txt b/api/current.txt
index f641083..5b986bc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -35,6 +35,7 @@
     field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
+    field public static final java.lang.String BIND_SLICE = "android.permission.BIND_SLICE";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
@@ -362,6 +363,7 @@
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
+    field public static final int cantSaveState = 16844142; // 0x101056e
     field public static final deprecated int capitalize = 16843113; // 0x1010169
     field public static final int category = 16843752; // 0x10103e8
     field public static final int centerBright = 16842956; // 0x10100cc
@@ -610,6 +612,7 @@
     field public static final int fontProviderPackage = 16844119; // 0x1010557
     field public static final int fontProviderQuery = 16844113; // 0x1010551
     field public static final int fontStyle = 16844095; // 0x101053f
+    field public static final int fontVariationSettings = 16844144; // 0x1010570
     field public static final int fontWeight = 16844083; // 0x1010533
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
     field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
@@ -919,6 +922,7 @@
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int navigationBarColor = 16843858; // 0x1010452
+    field public static final int navigationBarDividerColor = 16844141; // 0x101056d
     field public static final int navigationContentDescription = 16843969; // 0x10104c1
     field public static final int navigationIcon = 16843968; // 0x10104c0
     field public static final int navigationMode = 16843471; // 0x10102cf
@@ -1439,6 +1443,7 @@
     field public static final int trimPathEnd = 16843785; // 0x1010409
     field public static final int trimPathOffset = 16843786; // 0x101040a
     field public static final int trimPathStart = 16843784; // 0x1010408
+    field public static final int ttcIndex = 16844143; // 0x101056f
     field public static final int tunerCount = 16844061; // 0x101051d
     field public static final int turnScreenOn = 16844138; // 0x101056a
     field public static final int type = 16843169; // 0x10101a1
@@ -4004,8 +4009,10 @@
   }
 
   public static class ActivityManager.TaskDescription implements android.os.Parcelable {
-    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
-    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public deprecated ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
+    ctor public ActivityManager.TaskDescription(java.lang.String, int, int);
+    ctor public deprecated ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public ActivityManager.TaskDescription(java.lang.String, int);
     ctor public ActivityManager.TaskDescription(java.lang.String);
     ctor public ActivityManager.TaskDescription();
     ctor public ActivityManager.TaskDescription(android.app.ActivityManager.TaskDescription);
@@ -4021,6 +4028,7 @@
   public class ActivityOptions {
     method public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
+    method public boolean getLockTaskMode();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4033,6 +4041,7 @@
     method public android.app.ActivityOptions setAppVerificationBundle(android.os.Bundle);
     method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.app.ActivityOptions setLaunchDisplayId(int);
+    method public android.app.ActivityOptions setLockTaskMode(boolean);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -5642,8 +5651,10 @@
     method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
+    field public static final int PRIORITY_CATEGORY_ALARMS = 32; // 0x20
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
     field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2
+    field public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 64; // 0x40
     field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4
     field public static final int PRIORITY_CATEGORY_REMINDERS = 1; // 0x1
     field public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 16; // 0x10
@@ -6318,6 +6329,7 @@
     method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
+    method public int getLockTaskFeatures(android.content.ComponentName);
     method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
     method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -6404,6 +6416,7 @@
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
+    method public void setLockTaskFeatures(android.content.ComponentName, int);
     method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
     method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
@@ -6441,6 +6454,8 @@
     method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
     method public int setStorageEncryption(android.content.ComponentName, boolean);
     method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
+    method public boolean setTime(android.content.ComponentName, long);
+    method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
     method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
     method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
     method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
@@ -6448,6 +6463,7 @@
     method public void uninstallAllUserCaCerts(android.content.ComponentName);
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
+    method public void wipeDataWithReason(int, java.lang.CharSequence);
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
@@ -6517,6 +6533,13 @@
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
     field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
     field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
+    field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
+    field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
+    field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
+    field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
+    field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
+    field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+    field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
     field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
     field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -6667,6 +6690,9 @@
     method public int getInputType();
     method public int getLeft();
     method public android.os.LocaleList getLocaleList();
+    method public int getMaxTextEms();
+    method public int getMaxTextLength();
+    method public int getMinTextEms();
     method public int getScrollX();
     method public int getScrollY();
     method public java.lang.CharSequence getText();
@@ -6682,6 +6708,7 @@
     method public android.graphics.Matrix getTransformation();
     method public int getVisibility();
     method public java.lang.String getWebDomain();
+    method public java.lang.String getWebScheme();
     method public int getWidth();
     method public boolean isAccessibilityFocused();
     method public boolean isActivated();
@@ -6808,6 +6835,7 @@
     method public int getBackoffPolicy();
     method public android.content.ClipData getClipData();
     method public int getClipGrantFlags();
+    method public long getEstimatedNetworkBytes();
     method public android.os.PersistableBundle getExtras();
     method public long getFlexMillis();
     method public int getId();
@@ -6835,6 +6863,7 @@
     field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
     field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+    field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
     field public static final int NETWORK_TYPE_METERED = 4; // 0x4
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
@@ -6848,6 +6877,7 @@
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
     method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
+    method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -6882,6 +6912,7 @@
     method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method public android.net.Network getNetwork();
     method public android.os.Bundle getTransientExtras();
     method public java.lang.String[] getTriggeredContentAuthorities();
     method public android.net.Uri[] getTriggeredContentUris();
@@ -6921,8 +6952,10 @@
 
   public final class JobWorkItem implements android.os.Parcelable {
     ctor public JobWorkItem(android.content.Intent);
+    ctor public JobWorkItem(android.content.Intent, long);
     method public int describeContents();
     method public int getDeliveryCount();
+    method public long getEstimatedNetworkBytes();
     method public android.content.Intent getIntent();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
@@ -6930,6 +6963,109 @@
 
 }
 
+package android.app.slice {
+
+  public final class Slice implements android.os.Parcelable {
+    ctor protected Slice(android.os.Parcel);
+    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
+    method public int describeContents();
+    method public java.util.List<java.lang.String> getHints();
+    method public java.util.List<android.app.slice.SliceItem> getItems();
+    method public android.net.Uri getUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
+    field public static final java.lang.String HINT_ACTIONS = "actions";
+    field public static final java.lang.String HINT_HORIZONTAL = "horizontal";
+    field public static final java.lang.String HINT_LARGE = "large";
+    field public static final java.lang.String HINT_LIST = "list";
+    field public static final java.lang.String HINT_LIST_ITEM = "list_item";
+    field public static final java.lang.String HINT_MESSAGE = "message";
+    field public static final java.lang.String HINT_NO_TINT = "no_tint";
+    field public static final java.lang.String HINT_PARTIAL = "partial";
+    field public static final java.lang.String HINT_SELECTED = "selected";
+    field public static final java.lang.String HINT_SOURCE = "source";
+    field public static final java.lang.String HINT_TITLE = "title";
+  }
+
+  public static class Slice.Builder {
+    ctor public Slice.Builder(android.net.Uri);
+    ctor public Slice.Builder(android.app.slice.Slice.Builder);
+    method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
+    method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addHints(java.lang.String...);
+    method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+    method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice build();
+  }
+
+  public final class SliceItem implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.app.PendingIntent getAction();
+    method public int getColor();
+    method public java.util.List<java.lang.String> getHints();
+    method public android.graphics.drawable.Icon getIcon();
+    method public android.app.RemoteInput getRemoteInput();
+    method public android.app.slice.Slice getSlice();
+    method public java.lang.CharSequence getText();
+    method public long getTimestamp();
+    method public int getType();
+    method public boolean hasHint(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
+    field public static final int TYPE_ACTION = 4; // 0x4
+    field public static final int TYPE_COLOR = 6; // 0x6
+    field public static final int TYPE_IMAGE = 3; // 0x3
+    field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
+    field public static final int TYPE_SLICE = 1; // 0x1
+    field public static final int TYPE_TEXT = 2; // 0x2
+    field public static final int TYPE_TIMESTAMP = 8; // 0x8
+  }
+
+  public abstract class SliceProvider extends android.content.ContentProvider {
+    ctor public SliceProvider();
+    method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+    method public final java.lang.String getType(android.net.Uri);
+    method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+    method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
+    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
+    field public static final java.lang.String SLICE_TYPE = "vnd.android.slice";
+  }
+
+}
+
+package android.app.slice.widget {
+
+  public class SliceView extends android.view.ViewGroup {
+    ctor public SliceView(android.content.Context);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+    method public void clearSlice();
+    method public java.lang.String getMode();
+    method protected void onLayout(boolean, int, int, int, int);
+    method public void setMode(java.lang.String);
+    method public void setScrollable(boolean);
+    method public boolean setSlice(android.net.Uri);
+    method public void showSlice(android.app.slice.Slice);
+    field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
+    field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
+    field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+  }
+
+}
+
 package android.app.usage {
 
   public final class ConfigurationStats implements android.os.Parcelable {
@@ -8972,6 +9108,7 @@
     field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
+    field public static final java.lang.String IPSEC_SERVICE = "ipsec";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -10995,6 +11132,7 @@
     method public android.content.ComponentName getActivity();
     method public java.util.Set<java.lang.String> getCategories();
     method public java.lang.CharSequence getDisabledMessage();
+    method public int getDisabledReason();
     method public android.os.PersistableBundle getExtras();
     method public java.lang.String getId();
     method public android.content.Intent getIntent();
@@ -11013,6 +11151,13 @@
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final int DISABLED_REASON_APP_CHANGED = 2; // 0x2
+    field public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; // 0x65
+    field public static final int DISABLED_REASON_BY_APP = 1; // 0x1
+    field public static final int DISABLED_REASON_NOT_DISABLED = 0; // 0x0
+    field public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; // 0x67
+    field public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; // 0x66
+    field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
   }
 
@@ -11585,6 +11730,7 @@
 
   public class CursorWindow extends android.database.sqlite.SQLiteClosable implements android.os.Parcelable {
     ctor public CursorWindow(java.lang.String);
+    ctor public CursorWindow(java.lang.String, long);
     ctor public deprecated CursorWindow(boolean);
     method public boolean allocRow();
     method public void clear();
@@ -22670,6 +22816,10 @@
     field public static final java.lang.String KEY_DURATION = "durationUs";
     field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
     field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+    field public static final java.lang.String KEY_GRID_COLS = "grid-cols";
+    field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height";
+    field public static final java.lang.String KEY_GRID_ROWS = "grid-rows";
+    field public static final java.lang.String KEY_GRID_WIDTH = "grid-width";
     field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final java.lang.String KEY_HEIGHT = "height";
     field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -22713,6 +22863,7 @@
     field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
     field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+    field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
     field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
     field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
@@ -22805,9 +22956,13 @@
     ctor public MediaMetadataRetriever();
     method public java.lang.String extractMetadata(int);
     method public byte[] getEmbeddedPicture();
+    method public android.graphics.Bitmap getFrameAtIndex(int);
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap[] getFramesAtIndex(int, int);
+    method public android.graphics.Bitmap getImageAtIndex(int);
+    method public android.graphics.Bitmap getPrimaryImage();
     method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
@@ -22830,11 +22985,18 @@
     field public static final int METADATA_KEY_DURATION = 9; // 0x9
     field public static final int METADATA_KEY_GENRE = 6; // 0x6
     field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
+    field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
     field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11
+    field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b
+    field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e
+    field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c
+    field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f
+    field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d
     field public static final int METADATA_KEY_LOCATION = 23; // 0x17
     field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc
     field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa
     field public static final int METADATA_KEY_TITLE = 7; // 0x7
+    field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20
     field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13
     field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18
     field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12
@@ -25639,6 +25801,69 @@
     field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
   }
 
+  public final class IpSecAlgorithm implements android.os.Parcelable {
+    ctor public IpSecAlgorithm(java.lang.String, byte[]);
+    ctor public IpSecAlgorithm(java.lang.String, byte[], int);
+    method public int describeContents();
+    method public byte[] getKey();
+    method public java.lang.String getName();
+    method public int getTruncationLengthBits();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+    field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
+    field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
+    field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
+    field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
+    field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
+    field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+    field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
+  }
+
+  public final class IpSecManager {
+    method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
+  }
+
+  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+  }
+
+  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+    method public void close();
+    method protected void finalize();
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public void close() throws java.io.IOException;
+    method public int getPort();
+    method public java.io.FileDescriptor getSocket();
+  }
+
+  public final class IpSecTransform implements java.lang.AutoCloseable {
+    method public void close();
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
+  }
+
+  public static class IpSecTransform.Builder {
+    ctor public IpSecTransform.Builder(android.content.Context);
+    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
+    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
+  }
+
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -25661,7 +25886,7 @@
     field public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
   }
 
-  public class LocalServerSocket {
+  public class LocalServerSocket implements java.io.Closeable {
     ctor public LocalServerSocket(java.lang.String) throws java.io.IOException;
     ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException;
     method public android.net.LocalSocket accept() throws java.io.IOException;
@@ -31745,6 +31970,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+    field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -31769,6 +31995,7 @@
     field public static final java.lang.String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
     field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
     field public static final java.lang.String DISALLOW_SMS = "no_sms";
+    field public static final java.lang.String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
     field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
     field public static final java.lang.String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
     field public static final java.lang.String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
@@ -34903,6 +35130,7 @@
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+    field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -37034,6 +37262,19 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
   }
 
+  public final class BatchUpdates implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.BatchUpdates> CREATOR;
+  }
+
+  public static class BatchUpdates.Builder {
+    ctor public BatchUpdates.Builder();
+    method public android.service.autofill.BatchUpdates build();
+    method public android.service.autofill.BatchUpdates.Builder transformChild(int, android.service.autofill.Transformation);
+    method public android.service.autofill.BatchUpdates.Builder updateTemplate(android.widget.RemoteViews);
+  }
+
   public final class CharSequenceTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -37055,6 +37296,7 @@
   public static class CustomDescription.Builder {
     ctor public CustomDescription.Builder(android.widget.RemoteViews);
     method public android.service.autofill.CustomDescription.Builder addChild(int, android.service.autofill.Transformation);
+    method public android.service.autofill.CustomDescription.Builder batchUpdate(android.service.autofill.Validator, android.service.autofill.BatchUpdates);
     method public android.service.autofill.CustomDescription build();
   }
 
@@ -37072,6 +37314,8 @@
     method public android.service.autofill.Dataset.Builder setId(java.lang.String);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews);
   }
 
   public final class FillCallback {
@@ -37096,10 +37340,15 @@
   }
 
   public static final class FillEventHistory.Event {
+    method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+    method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+    method public java.util.Set<java.lang.String> getSelectedDatasetIds();
     method public int getType();
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+    field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
     field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -37120,14 +37369,18 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
+    field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
 
   public static final class FillResponse.Builder {
     ctor public FillResponse.Builder();
     method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
     method public android.service.autofill.FillResponse build();
+    method public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
   }
@@ -37139,8 +37392,10 @@
   }
 
   public static class ImageTransformation.Builder {
-    ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int);
-    method public android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int);
+    ctor public deprecated ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int);
+    ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int, java.lang.CharSequence);
+    method public deprecated android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int);
+    method public android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int, java.lang.CharSequence);
     method public android.service.autofill.ImageTransformation build();
   }
 
@@ -37158,6 +37413,9 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.RegexValidator> CREATOR;
   }
 
+  public abstract interface Sanitizer {
+  }
+
   public final class SaveCallback {
     method public void onFailure(java.lang.CharSequence);
     method public void onSuccess();
@@ -37167,6 +37425,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
+    field public static final int FLAG_DONT_SAVE_ON_FINISH = 2; // 0x2
     field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
     field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
     field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
@@ -37181,12 +37440,14 @@
   public static final class SaveInfo.Builder {
     ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
     ctor public SaveInfo.Builder(int);
+    method public android.service.autofill.SaveInfo.Builder addSanitizer(android.service.autofill.Sanitizer, android.view.autofill.AutofillId...);
     method public android.service.autofill.SaveInfo build();
     method public android.service.autofill.SaveInfo.Builder setCustomDescription(android.service.autofill.CustomDescription);
     method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
     method public android.service.autofill.SaveInfo.Builder setFlags(int);
     method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
     method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
+    method public android.service.autofill.SaveInfo.Builder setTriggerId(android.view.autofill.AutofillId);
     method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
   }
 
@@ -37199,6 +37460,13 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
   }
 
+  public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
+    ctor public TextValueSanitizer(java.util.regex.Pattern, java.lang.String);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.TextValueSanitizer> CREATOR;
+  }
+
   public abstract interface Transformation {
   }
 
@@ -37538,8 +37806,12 @@
     method public java.lang.String getOverrideGroupKey();
     method public int getRank();
     method public int getSuppressedVisualEffects();
+    method public int getUserSentiment();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
+    field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
+    field public static final int USER_SENTIMENT_NEUTRAL = 0; // 0x0
+    field public static final int USER_SENTIMENT_POSITIVE = 1; // 0x1
   }
 
   public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
@@ -39651,12 +39923,14 @@
     field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
     field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
     field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
+    field public static final java.lang.String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
     field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+    field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
     field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -39671,6 +39945,7 @@
     field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
     field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
+    field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
     field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -39761,6 +40036,8 @@
     method public int getLatitude();
     method public int getLongitude();
     method public int getNetworkId();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getSystemId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
@@ -39772,8 +40049,13 @@
     method public int getBsic();
     method public int getCid();
     method public int getLac();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public deprecated int getPsc();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
@@ -39783,8 +40065,13 @@
     method public int describeContents();
     method public int getCi();
     method public int getEarfcn();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getPci();
     method public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
@@ -39795,8 +40082,13 @@
     method public int describeContents();
     method public int getCid();
     method public int getLac();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getPsc();
     method public int getUarfcn();
     method public void writeToParcel(android.os.Parcel, int);
@@ -39949,8 +40241,12 @@
     field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
     field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
     field public static final int RESULT_CANCELLED = 2; // 0x2
+    field public static final int RESULT_DOWNLOAD_FAILURE = 6; // 0x6
     field public static final int RESULT_EXPIRED = 3; // 0x3
+    field public static final int RESULT_FILE_ROOT_UNREACHABLE = 8; // 0x8
     field public static final int RESULT_IO_ERROR = 4; // 0x4
+    field public static final int RESULT_OUT_OF_STORAGE = 7; // 0x7
+    field public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5; // 0x5
     field public static final int RESULT_SUCCESSFUL = 1; // 0x1
     field public static final int STATUS_ACTIVELY_DOWNLOADING = 1; // 0x1
     field public static final int STATUS_PENDING_DOWNLOAD = 2; // 0x2
@@ -40225,6 +40521,8 @@
     field public static final int ENCODING_7BIT = 1; // 0x1
     field public static final int ENCODING_8BIT = 2; // 0x2
     field public static final int ENCODING_UNKNOWN = 0; // 0x0
+    field public static final java.lang.String FORMAT_3GPP = "3gpp";
+    field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
     field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
     field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
     field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
@@ -40381,6 +40679,10 @@
     field public static final int CALL_STATE_IDLE = 0; // 0x0
     field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
     field public static final int CALL_STATE_RINGING = 1; // 0x1
+    field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
+    field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
+    field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
+    field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff
     field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4
     field public static final int DATA_ACTIVITY_IN = 1; // 0x1
     field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3
@@ -40617,18 +40919,22 @@
   }
 
   public static class DownloadRequest.Builder {
-    ctor public DownloadRequest.Builder();
+    ctor public DownloadRequest.Builder(android.net.Uri);
     method public android.telephony.mbms.DownloadRequest build();
     method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
     method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
-    method public android.telephony.mbms.DownloadRequest.Builder setSource(android.net.Uri);
     method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int);
   }
 
   public class DownloadStateCallback {
     ctor public DownloadStateCallback();
+    ctor public DownloadStateCallback(int);
+    method public final boolean isFilterFlagSet(int);
     method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
     method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+    field public static final int ALL_UPDATES = 0; // 0x0
+    field public static final int PROGRESS_UPDATES = 1; // 0x1
+    field public static final int STATE_UPDATES = 2; // 0x2
   }
 
   public final class FileInfo implements android.os.Parcelable {
@@ -40702,6 +41008,7 @@
   public class ServiceInfo {
     method public java.util.List<java.util.Locale> getLocales();
     method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+    method public java.util.Set<java.util.Locale> getNamedContentLocales();
     method public java.lang.String getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
@@ -45655,8 +45962,8 @@
     method protected boolean awakenScrollBars(int);
     method protected boolean awakenScrollBars(int, boolean);
     method public void bringToFront();
-    method public void buildDrawingCache();
-    method public void buildDrawingCache(boolean);
+    method public deprecated void buildDrawingCache();
+    method public deprecated void buildDrawingCache(boolean);
     method public void buildLayer();
     method public boolean callOnClick();
     method public boolean canResolveLayoutDirection();
@@ -45681,7 +45988,7 @@
     method protected int computeVerticalScrollRange();
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
-    method public void destroyDrawingCache();
+    method public deprecated void destroyDrawingCache();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
@@ -45762,10 +46069,10 @@
     method public static int getDefaultSize(int, int);
     method public android.view.Display getDisplay();
     method public final int[] getDrawableState();
-    method public android.graphics.Bitmap getDrawingCache();
-    method public android.graphics.Bitmap getDrawingCache(boolean);
-    method public int getDrawingCacheBackgroundColor();
-    method public int getDrawingCacheQuality();
+    method public deprecated android.graphics.Bitmap getDrawingCache();
+    method public deprecated android.graphics.Bitmap getDrawingCache(boolean);
+    method public deprecated int getDrawingCacheBackgroundColor();
+    method public deprecated int getDrawingCacheQuality();
     method public void getDrawingRect(android.graphics.Rect);
     method public long getDrawingTime();
     method public float getElevation();
@@ -45893,8 +46200,8 @@
     method public boolean hasTransientState();
     method public boolean hasWindowFocus();
     method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
-    method public void invalidate(android.graphics.Rect);
-    method public void invalidate(int, int, int, int);
+    method public deprecated void invalidate(android.graphics.Rect);
+    method public deprecated void invalidate(int, int, int, int);
     method public void invalidate();
     method public void invalidateDrawable(android.graphics.drawable.Drawable);
     method public void invalidateOutline();
@@ -45904,7 +46211,7 @@
     method public boolean isClickable();
     method public boolean isContextClickable();
     method public boolean isDirty();
-    method public boolean isDrawingCacheEnabled();
+    method public deprecated boolean isDrawingCacheEnabled();
     method public boolean isDuplicateParentStateEnabled();
     method public boolean isEnabled();
     method public final boolean isFocusable();
@@ -46075,9 +46382,9 @@
     method public void setContentDescription(java.lang.CharSequence);
     method public void setContextClickable(boolean);
     method public void setDefaultFocusHighlightEnabled(boolean);
-    method public void setDrawingCacheBackgroundColor(int);
-    method public void setDrawingCacheEnabled(boolean);
-    method public void setDrawingCacheQuality(int);
+    method public deprecated void setDrawingCacheBackgroundColor(int);
+    method public deprecated void setDrawingCacheEnabled(boolean);
+    method public deprecated void setDrawingCacheQuality(int);
     method public void setDuplicateParentStateEnabled(boolean);
     method public void setElevation(float);
     method public void setEnabled(boolean);
@@ -46228,9 +46535,9 @@
     field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
     field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
-    field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
-    field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
-    field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
+    field public static final deprecated int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
+    field public static final deprecated int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
+    field public static final deprecated int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
     field protected static final int[] EMPTY_STATE_SET;
     field protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET;
     field protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
@@ -46622,7 +46929,7 @@
     method public android.animation.LayoutTransition getLayoutTransition();
     method public int getNestedScrollAxes();
     method public android.view.ViewGroupOverlay getOverlay();
-    method public int getPersistentDrawingCache();
+    method public deprecated int getPersistentDrawingCache();
     method public boolean getTouchscreenBlocksFocus();
     method public int indexOfChild(android.view.View);
     method public final deprecated void invalidateChild(android.view.View, android.graphics.Rect);
@@ -46675,7 +46982,7 @@
     method public void setAddStatesFromChildren(boolean);
     method public deprecated void setAlwaysDrawnWithCacheEnabled(boolean);
     method public deprecated void setAnimationCacheEnabled(boolean);
-    method protected void setChildrenDrawingCacheEnabled(boolean);
+    method protected deprecated void setChildrenDrawingCacheEnabled(boolean);
     method protected void setChildrenDrawingOrderEnabled(boolean);
     method protected deprecated void setChildrenDrawnWithCacheEnabled(boolean);
     method public void setClipChildren(boolean);
@@ -46687,7 +46994,7 @@
     method public void setLayoutTransition(android.animation.LayoutTransition);
     method public void setMotionEventSplittingEnabled(boolean);
     method public void setOnHierarchyChangeListener(android.view.ViewGroup.OnHierarchyChangeListener);
-    method public void setPersistentDrawingCache(int);
+    method public deprecated void setPersistentDrawingCache(int);
     method protected void setStaticTransformationsEnabled(boolean);
     method public void setTouchscreenBlocksFocus(boolean);
     method public void setTransitionGroup(boolean);
@@ -46705,10 +47012,10 @@
     field public static final int FOCUS_BLOCK_DESCENDANTS = 393216; // 0x60000
     field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
     field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
-    field public static final int PERSISTENT_ALL_CACHES = 3; // 0x3
-    field public static final int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
-    field public static final int PERSISTENT_NO_CACHE = 0; // 0x0
-    field public static final int PERSISTENT_SCROLLING_CACHE = 2; // 0x2
+    field public static final deprecated int PERSISTENT_ALL_CACHES = 3; // 0x3
+    field public static final deprecated int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
+    field public static final deprecated int PERSISTENT_NO_CACHE = 0; // 0x0
+    field public static final deprecated int PERSISTENT_SCROLLING_CACHE = 2; // 0x2
   }
 
   public static class ViewGroup.LayoutParams {
@@ -46906,6 +47213,9 @@
     method public abstract void setInputType(int);
     method public abstract void setLocaleList(android.os.LocaleList);
     method public abstract void setLongClickable(boolean);
+    method public void setMaxTextEms(int);
+    method public void setMaxTextLength(int);
+    method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
@@ -48630,19 +48940,26 @@
 package android.view.textclassifier {
 
   public final class TextClassification {
+    method public int getActionCount();
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
     method public int getEntityCount();
+    method public android.graphics.drawable.Drawable getIcon(int);
     method public android.graphics.drawable.Drawable getIcon();
+    method public android.content.Intent getIntent(int);
     method public android.content.Intent getIntent();
+    method public java.lang.CharSequence getLabel(int);
     method public java.lang.CharSequence getLabel();
+    method public android.view.View.OnClickListener getOnClickListener(int);
     method public android.view.View.OnClickListener getOnClickListener();
     method public java.lang.String getText();
   }
 
   public static final class TextClassification.Builder {
     ctor public TextClassification.Builder();
+    method public android.view.textclassifier.TextClassification.Builder addAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
     method public android.view.textclassifier.TextClassification build();
+    method public android.view.textclassifier.TextClassification.Builder clearActions();
     method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
     method public android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
     method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
@@ -52324,6 +52641,8 @@
     field public static final int OP_CONST_CLASS = 28; // 0x1c
     field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
     field public static final int OP_CONST_HIGH16 = 21; // 0x15
+    field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+    field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
     field public static final int OP_CONST_STRING = 26; // 0x1a
     field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
     field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/api/system-current.txt b/api/system-current.txt
index eee24de..df8e034 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -58,6 +58,7 @@
     field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
     field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
     field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE";
+    field public static final java.lang.String BIND_SLICE = "android.permission.BIND_SLICE";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
@@ -494,6 +495,7 @@
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
+    field public static final int cantSaveState = 16844142; // 0x101056e
     field public static final deprecated int capitalize = 16843113; // 0x1010169
     field public static final int category = 16843752; // 0x10103e8
     field public static final int centerBright = 16842956; // 0x10100cc
@@ -742,6 +744,7 @@
     field public static final int fontProviderPackage = 16844119; // 0x1010557
     field public static final int fontProviderQuery = 16844113; // 0x1010551
     field public static final int fontStyle = 16844095; // 0x101053f
+    field public static final int fontVariationSettings = 16844144; // 0x1010570
     field public static final int fontWeight = 16844083; // 0x1010533
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
     field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
@@ -1051,6 +1054,7 @@
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int navigationBarColor = 16843858; // 0x1010452
+    field public static final int navigationBarDividerColor = 16844141; // 0x101056d
     field public static final int navigationContentDescription = 16843969; // 0x10104c1
     field public static final int navigationIcon = 16843968; // 0x10104c0
     field public static final int navigationMode = 16843471; // 0x10102cf
@@ -1577,6 +1581,7 @@
     field public static final int trimPathEnd = 16843785; // 0x1010409
     field public static final int trimPathOffset = 16843786; // 0x101040a
     field public static final int trimPathStart = 16843784; // 0x1010408
+    field public static final int ttcIndex = 16844143; // 0x101056f
     field public static final int tunerCount = 16844061; // 0x101051d
     field public static final int turnScreenOn = 16844138; // 0x101056a
     field public static final int type = 16843169; // 0x10101a1
@@ -4166,8 +4171,10 @@
   }
 
   public static class ActivityManager.TaskDescription implements android.os.Parcelable {
-    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
-    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public deprecated ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
+    ctor public ActivityManager.TaskDescription(java.lang.String, int, int);
+    ctor public deprecated ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public ActivityManager.TaskDescription(java.lang.String, int);
     ctor public ActivityManager.TaskDescription(java.lang.String);
     ctor public ActivityManager.TaskDescription();
     ctor public ActivityManager.TaskDescription(android.app.ActivityManager.TaskDescription);
@@ -4183,6 +4190,7 @@
   public class ActivityOptions {
     method public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
+    method public boolean getLockTaskMode();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4195,6 +4203,7 @@
     method public android.app.ActivityOptions setAppVerificationBundle(android.os.Bundle);
     method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.app.ActivityOptions setLaunchDisplayId(int);
+    method public android.app.ActivityOptions setLockTaskMode(boolean);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -5850,8 +5859,10 @@
     method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
+    field public static final int PRIORITY_CATEGORY_ALARMS = 32; // 0x20
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
     field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2
+    field public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 64; // 0x40
     field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4
     field public static final int PRIORITY_CATEGORY_REMINDERS = 1; // 0x1
     field public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 16; // 0x10
@@ -6313,6 +6324,7 @@
   }
 
   public class VrManager {
+    method public void setAndBindVrCompositor(android.content.ComponentName);
     method public void setPersistentVrModeEnabled(boolean);
   }
 
@@ -6539,6 +6551,7 @@
     method public java.lang.CharSequence getDeviceOwnerOrganizationName();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
+    method public int getLockTaskFeatures(android.content.ComponentName);
     method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
     method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -6638,6 +6651,7 @@
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
+    method public void setLockTaskFeatures(android.content.ComponentName, int);
     method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
     method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
@@ -6675,6 +6689,8 @@
     method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
     method public int setStorageEncryption(android.content.ComponentName, boolean);
     method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
+    method public boolean setTime(android.content.ComponentName, long);
+    method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
     method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
     method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
     method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
@@ -6682,6 +6698,7 @@
     method public void uninstallAllUserCaCerts(android.content.ComponentName);
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
+    method public void wipeDataWithReason(int, java.lang.CharSequence);
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
@@ -6762,6 +6779,13 @@
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
     field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
     field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
+    field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
+    field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
+    field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
+    field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
+    field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
+    field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+    field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
     field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
     field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -6917,6 +6941,9 @@
     method public int getInputType();
     method public int getLeft();
     method public android.os.LocaleList getLocaleList();
+    method public int getMaxTextEms();
+    method public int getMaxTextLength();
+    method public int getMinTextEms();
     method public int getScrollX();
     method public int getScrollY();
     method public java.lang.CharSequence getText();
@@ -6932,6 +6959,7 @@
     method public android.graphics.Matrix getTransformation();
     method public int getVisibility();
     method public java.lang.String getWebDomain();
+    method public java.lang.String getWebScheme();
     method public int getWidth();
     method public boolean isAccessibilityFocused();
     method public boolean isActivated();
@@ -7249,6 +7277,7 @@
     method public int getBackoffPolicy();
     method public android.content.ClipData getClipData();
     method public int getClipGrantFlags();
+    method public long getEstimatedNetworkBytes();
     method public android.os.PersistableBundle getExtras();
     method public long getFlexMillis();
     method public int getId();
@@ -7276,6 +7305,7 @@
     field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
     field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+    field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
     field public static final int NETWORK_TYPE_METERED = 4; // 0x4
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
@@ -7289,6 +7319,7 @@
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
     method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
+    method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -7323,6 +7354,7 @@
     method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method public android.net.Network getNetwork();
     method public android.os.Bundle getTransientExtras();
     method public java.lang.String[] getTriggeredContentAuthorities();
     method public android.net.Uri[] getTriggeredContentUris();
@@ -7363,8 +7395,10 @@
 
   public final class JobWorkItem implements android.os.Parcelable {
     ctor public JobWorkItem(android.content.Intent);
+    ctor public JobWorkItem(android.content.Intent, long);
     method public int describeContents();
     method public int getDeliveryCount();
+    method public long getEstimatedNetworkBytes();
     method public android.content.Intent getIntent();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
@@ -7372,6 +7406,109 @@
 
 }
 
+package android.app.slice {
+
+  public final class Slice implements android.os.Parcelable {
+    ctor protected Slice(android.os.Parcel);
+    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
+    method public int describeContents();
+    method public java.util.List<java.lang.String> getHints();
+    method public java.util.List<android.app.slice.SliceItem> getItems();
+    method public android.net.Uri getUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
+    field public static final java.lang.String HINT_ACTIONS = "actions";
+    field public static final java.lang.String HINT_HORIZONTAL = "horizontal";
+    field public static final java.lang.String HINT_LARGE = "large";
+    field public static final java.lang.String HINT_LIST = "list";
+    field public static final java.lang.String HINT_LIST_ITEM = "list_item";
+    field public static final java.lang.String HINT_MESSAGE = "message";
+    field public static final java.lang.String HINT_NO_TINT = "no_tint";
+    field public static final java.lang.String HINT_PARTIAL = "partial";
+    field public static final java.lang.String HINT_SELECTED = "selected";
+    field public static final java.lang.String HINT_SOURCE = "source";
+    field public static final java.lang.String HINT_TITLE = "title";
+  }
+
+  public static class Slice.Builder {
+    ctor public Slice.Builder(android.net.Uri);
+    ctor public Slice.Builder(android.app.slice.Slice.Builder);
+    method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
+    method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addHints(java.lang.String...);
+    method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+    method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice build();
+  }
+
+  public final class SliceItem implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.app.PendingIntent getAction();
+    method public int getColor();
+    method public java.util.List<java.lang.String> getHints();
+    method public android.graphics.drawable.Icon getIcon();
+    method public android.app.RemoteInput getRemoteInput();
+    method public android.app.slice.Slice getSlice();
+    method public java.lang.CharSequence getText();
+    method public long getTimestamp();
+    method public int getType();
+    method public boolean hasHint(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
+    field public static final int TYPE_ACTION = 4; // 0x4
+    field public static final int TYPE_COLOR = 6; // 0x6
+    field public static final int TYPE_IMAGE = 3; // 0x3
+    field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
+    field public static final int TYPE_SLICE = 1; // 0x1
+    field public static final int TYPE_TEXT = 2; // 0x2
+    field public static final int TYPE_TIMESTAMP = 8; // 0x8
+  }
+
+  public abstract class SliceProvider extends android.content.ContentProvider {
+    ctor public SliceProvider();
+    method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+    method public final java.lang.String getType(android.net.Uri);
+    method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+    method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
+    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
+    field public static final java.lang.String SLICE_TYPE = "vnd.android.slice";
+  }
+
+}
+
+package android.app.slice.widget {
+
+  public class SliceView extends android.view.ViewGroup {
+    ctor public SliceView(android.content.Context);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+    method public void clearSlice();
+    method public java.lang.String getMode();
+    method protected void onLayout(boolean, int, int, int, int);
+    method public void setMode(java.lang.String);
+    method public void setScrollable(boolean);
+    method public boolean setSlice(android.net.Uri);
+    method public void showSlice(android.app.slice.Slice);
+    field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
+    field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
+    field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+  }
+
+}
+
 package android.app.usage {
 
   public final class CacheQuotaHint implements android.os.Parcelable {
@@ -7704,6 +7841,7 @@
     method public boolean disableBLE();
     method public boolean enable();
     method public boolean enableBLE();
+    method public boolean enableNoAutoConnect();
     method public java.lang.String getAddress();
     method public android.bluetooth.le.BluetoothLeAdvertiser getBluetoothLeAdvertiser();
     method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
@@ -8093,6 +8231,7 @@
   }
 
   public final class BluetoothDevice implements android.os.Parcelable {
+    method public boolean cancelBondProcess();
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
     method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
@@ -8110,7 +8249,9 @@
     method public android.os.ParcelUuid[] getUuids();
     method public boolean isConnected();
     method public boolean isEncrypted();
+    method public boolean removeBond();
     method public boolean setPairingConfirmation(boolean);
+    method public boolean setPhonebookAccessPermission(int);
     method public boolean setPin(byte[]);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final java.lang.String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
@@ -8341,11 +8482,14 @@
   }
 
   public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
+    method public boolean connect(android.bluetooth.BluetoothDevice);
+    method public boolean disconnect(android.bluetooth.BluetoothDevice);
     method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
     method public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, java.lang.String, java.lang.String);
+    method public boolean setPriority(android.bluetooth.BluetoothDevice, int);
     method public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
     method public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
     field public static final java.lang.String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
@@ -9484,6 +9628,7 @@
     field public static final java.lang.String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
+    field public static final java.lang.String IPSEC_SERVICE = "ipsec";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -11717,6 +11862,7 @@
     method public android.content.ComponentName getActivity();
     method public java.util.Set<java.lang.String> getCategories();
     method public java.lang.CharSequence getDisabledMessage();
+    method public int getDisabledReason();
     method public android.os.PersistableBundle getExtras();
     method public java.lang.String getId();
     method public android.content.Intent getIntent();
@@ -11735,6 +11881,13 @@
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final int DISABLED_REASON_APP_CHANGED = 2; // 0x2
+    field public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; // 0x65
+    field public static final int DISABLED_REASON_BY_APP = 1; // 0x1
+    field public static final int DISABLED_REASON_NOT_DISABLED = 0; // 0x0
+    field public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; // 0x67
+    field public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; // 0x66
+    field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
   }
 
@@ -12321,6 +12474,7 @@
 
   public class CursorWindow extends android.database.sqlite.SQLiteClosable implements android.os.Parcelable {
     ctor public CursorWindow(java.lang.String);
+    ctor public CursorWindow(java.lang.String, long);
     ctor public deprecated CursorWindow(boolean);
     method public boolean allocRow();
     method public void clear();
@@ -24552,6 +24706,10 @@
     field public static final java.lang.String KEY_DURATION = "durationUs";
     field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
     field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+    field public static final java.lang.String KEY_GRID_COLS = "grid-cols";
+    field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height";
+    field public static final java.lang.String KEY_GRID_ROWS = "grid-rows";
+    field public static final java.lang.String KEY_GRID_WIDTH = "grid-width";
     field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final java.lang.String KEY_HEIGHT = "height";
     field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -24595,6 +24753,7 @@
     field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
     field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+    field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
     field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
     field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
@@ -24687,9 +24846,13 @@
     ctor public MediaMetadataRetriever();
     method public java.lang.String extractMetadata(int);
     method public byte[] getEmbeddedPicture();
+    method public android.graphics.Bitmap getFrameAtIndex(int);
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap[] getFramesAtIndex(int, int);
+    method public android.graphics.Bitmap getImageAtIndex(int);
+    method public android.graphics.Bitmap getPrimaryImage();
     method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
@@ -24712,11 +24875,18 @@
     field public static final int METADATA_KEY_DURATION = 9; // 0x9
     field public static final int METADATA_KEY_GENRE = 6; // 0x6
     field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
+    field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
     field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11
+    field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b
+    field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e
+    field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c
+    field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f
+    field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d
     field public static final int METADATA_KEY_LOCATION = 23; // 0x17
     field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc
     field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa
     field public static final int METADATA_KEY_TITLE = 7; // 0x7
+    field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20
     field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13
     field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18
     field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12
@@ -27212,7 +27382,6 @@
   }
 
   public static final class TvInputManager.Hardware {
-    method public boolean dispatchKeyEventToHdmi(android.view.KeyEvent);
     method public void overrideAudioSink(int, java.lang.String, int, int, int);
     method public void setStreamVolume(float);
     method public boolean setSurface(android.view.Surface, android.media.tv.TvStreamConfig);
@@ -27874,6 +28043,71 @@
     field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
   }
 
+  public final class IpSecAlgorithm implements android.os.Parcelable {
+    ctor public IpSecAlgorithm(java.lang.String, byte[]);
+    ctor public IpSecAlgorithm(java.lang.String, byte[], int);
+    method public int describeContents();
+    method public byte[] getKey();
+    method public java.lang.String getName();
+    method public int getTruncationLengthBits();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+    field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
+    field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
+    field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
+    field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
+    field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
+    field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+    field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
+  }
+
+  public final class IpSecManager {
+    method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
+  }
+
+  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+  }
+
+  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+    method public void close();
+    method protected void finalize();
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public void close() throws java.io.IOException;
+    method public int getPort();
+    method public java.io.FileDescriptor getSocket();
+  }
+
+  public final class IpSecTransform implements java.lang.AutoCloseable {
+    method public void close();
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
+  }
+
+  public static class IpSecTransform.Builder {
+    ctor public IpSecTransform.Builder(android.content.Context);
+    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
+    method public android.net.IpSecTransform.Builder setNattKeepalive(int);
+    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
+    method public android.net.IpSecTransform.Builder setUnderlyingNetwork(android.net.Network);
+  }
+
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -27896,7 +28130,7 @@
     field public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
   }
 
-  public class LocalServerSocket {
+  public class LocalServerSocket implements java.io.Closeable {
     ctor public LocalServerSocket(java.lang.String) throws java.io.IOException;
     ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException;
     method public android.net.LocalSocket accept() throws java.io.IOException;
@@ -34585,6 +34819,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+    field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -34610,6 +34845,7 @@
     field public static final java.lang.String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
     field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
     field public static final java.lang.String DISALLOW_SMS = "no_sms";
+    field public static final java.lang.String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
     field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
     field public static final java.lang.String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
     field public static final java.lang.String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
@@ -37958,6 +38194,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+    field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -40127,6 +40364,19 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
   }
 
+  public final class BatchUpdates implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.BatchUpdates> CREATOR;
+  }
+
+  public static class BatchUpdates.Builder {
+    ctor public BatchUpdates.Builder();
+    method public android.service.autofill.BatchUpdates build();
+    method public android.service.autofill.BatchUpdates.Builder transformChild(int, android.service.autofill.Transformation);
+    method public android.service.autofill.BatchUpdates.Builder updateTemplate(android.widget.RemoteViews);
+  }
+
   public final class CharSequenceTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -40148,6 +40398,7 @@
   public static class CustomDescription.Builder {
     ctor public CustomDescription.Builder(android.widget.RemoteViews);
     method public android.service.autofill.CustomDescription.Builder addChild(int, android.service.autofill.Transformation);
+    method public android.service.autofill.CustomDescription.Builder batchUpdate(android.service.autofill.Validator, android.service.autofill.BatchUpdates);
     method public android.service.autofill.CustomDescription build();
   }
 
@@ -40165,6 +40416,8 @@
     method public android.service.autofill.Dataset.Builder setId(java.lang.String);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews);
   }
 
   public final class FillCallback {
@@ -40189,10 +40442,15 @@
   }
 
   public static final class FillEventHistory.Event {
+    method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+    method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+    method public java.util.Set<java.lang.String> getSelectedDatasetIds();
     method public int getType();
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+    field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
     field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -40213,14 +40471,18 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
+    field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
 
   public static final class FillResponse.Builder {
     ctor public FillResponse.Builder();
     method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
     method public android.service.autofill.FillResponse build();
+    method public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
   }
@@ -40232,8 +40494,10 @@
   }
 
   public static class ImageTransformation.Builder {
-    ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int);
-    method public android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int);
+    ctor public deprecated ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int);
+    ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int, java.lang.CharSequence);
+    method public deprecated android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int);
+    method public android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int, java.lang.CharSequence);
     method public android.service.autofill.ImageTransformation build();
   }
 
@@ -40251,6 +40515,9 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.RegexValidator> CREATOR;
   }
 
+  public abstract interface Sanitizer {
+  }
+
   public final class SaveCallback {
     method public void onFailure(java.lang.CharSequence);
     method public void onSuccess();
@@ -40260,6 +40527,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
+    field public static final int FLAG_DONT_SAVE_ON_FINISH = 2; // 0x2
     field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
     field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
     field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
@@ -40274,12 +40542,14 @@
   public static final class SaveInfo.Builder {
     ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
     ctor public SaveInfo.Builder(int);
+    method public android.service.autofill.SaveInfo.Builder addSanitizer(android.service.autofill.Sanitizer, android.view.autofill.AutofillId...);
     method public android.service.autofill.SaveInfo build();
     method public android.service.autofill.SaveInfo.Builder setCustomDescription(android.service.autofill.CustomDescription);
     method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
     method public android.service.autofill.SaveInfo.Builder setFlags(int);
     method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
     method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
+    method public android.service.autofill.SaveInfo.Builder setTriggerId(android.view.autofill.AutofillId);
     method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
   }
 
@@ -40292,6 +40562,13 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
   }
 
+  public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
+    ctor public TextValueSanitizer(java.util.regex.Pattern, java.lang.String);
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.TextValueSanitizer> CREATOR;
+  }
+
   public abstract interface Transformation {
   }
 
@@ -40519,6 +40796,7 @@
     field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final java.lang.String KEY_PEOPLE = "key_people";
     field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
+    field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
 
   public final class Condition implements android.os.Parcelable {
@@ -40573,6 +40851,7 @@
     method public final void adjustNotifications(java.util.List<android.service.notification.Adjustment>);
     method public final android.os.IBinder onBind(android.content.Intent);
     method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
     method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String);
     method public final void unsnoozeNotification(java.lang.String);
     field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -40657,8 +40936,12 @@
     method public java.lang.String getOverrideGroupKey();
     method public int getRank();
     method public int getSuppressedVisualEffects();
+    method public int getUserSentiment();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
+    field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
+    field public static final int USER_SENTIMENT_NEUTRAL = 0; // 0x0
+    field public static final int USER_SENTIMENT_POSITIVE = 1; // 0x1
   }
 
   public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
@@ -40669,6 +40952,32 @@
     field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
   }
 
+  public final class NotificationStats implements android.os.Parcelable {
+    ctor public NotificationStats();
+    ctor protected NotificationStats(android.os.Parcel);
+    method public int describeContents();
+    method public int getDismissalSurface();
+    method public boolean hasDirectReplied();
+    method public boolean hasExpanded();
+    method public boolean hasInteracted();
+    method public boolean hasSeen();
+    method public boolean hasSnoozed();
+    method public boolean hasViewedSettings();
+    method public void setDirectReplied();
+    method public void setDismissalSurface(int);
+    method public void setExpanded();
+    method public void setSeen();
+    method public void setSnoozed();
+    method public void setViewedSettings();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+    field public static final int DISMISSAL_AOD = 2; // 0x2
+    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+    field public static final int DISMISSAL_OTHER = 0; // 0x0
+    field public static final int DISMISSAL_PEEK = 1; // 0x1
+    field public static final int DISMISSAL_SHADE = 3; // 0x3
+  }
+
   public final class SnoozeCriterion implements android.os.Parcelable {
     ctor public SnoozeCriterion(java.lang.String, java.lang.CharSequence, java.lang.CharSequence);
     ctor protected SnoozeCriterion(android.os.Parcel);
@@ -40825,17 +41134,22 @@
 
   public final class Suggestion implements android.os.Parcelable {
     method public int describeContents();
+    method public int getFlags();
+    method public android.graphics.drawable.Icon getIcon();
     method public java.lang.String getId();
     method public android.app.PendingIntent getPendingIntent();
     method public java.lang.CharSequence getSummary();
     method public java.lang.CharSequence getTitle();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.settings.suggestions.Suggestion> CREATOR;
+    field public static final int FLAG_HAS_BUTTON = 1; // 0x1
   }
 
   public static class Suggestion.Builder {
     ctor public Suggestion.Builder(java.lang.String);
     method public android.service.settings.suggestions.Suggestion build();
+    method public android.service.settings.suggestions.Suggestion.Builder setFlags(int);
+    method public android.service.settings.suggestions.Suggestion.Builder setIcon(android.graphics.drawable.Icon);
     method public android.service.settings.suggestions.Suggestion.Builder setPendingIntent(android.app.PendingIntent);
     method public android.service.settings.suggestions.Suggestion.Builder setSummary(java.lang.CharSequence);
     method public android.service.settings.suggestions.Suggestion.Builder setTitle(java.lang.CharSequence);
@@ -40846,6 +41160,7 @@
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract java.util.List<android.service.settings.suggestions.Suggestion> onGetSuggestions();
     method public abstract void onSuggestionDismissed(android.service.settings.suggestions.Suggestion);
+    method public abstract void onSuggestionLaunched(android.service.settings.suggestions.Suggestion);
   }
 
 }
@@ -43123,6 +43438,7 @@
     field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
     field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
     field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
+    field public static final java.lang.String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
@@ -43130,6 +43446,7 @@
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
     field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
     field public static final java.lang.String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
+    field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
     field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -43144,6 +43461,7 @@
     field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
     field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
+    field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
     field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -43234,6 +43552,8 @@
     method public int getLatitude();
     method public int getLongitude();
     method public int getNetworkId();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getSystemId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
@@ -43245,8 +43565,13 @@
     method public int getBsic();
     method public int getCid();
     method public int getLac();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public deprecated int getPsc();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
@@ -43256,8 +43581,13 @@
     method public int describeContents();
     method public int getCi();
     method public int getEarfcn();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getPci();
     method public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43268,8 +43598,13 @@
     method public int describeContents();
     method public int getCid();
     method public int getLac();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getPsc();
     method public int getUarfcn();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43423,8 +43758,12 @@
     field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
     field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_ACTION = "android.telephony.action.EmbmsDownload";
     field public static final int RESULT_CANCELLED = 2; // 0x2
+    field public static final int RESULT_DOWNLOAD_FAILURE = 6; // 0x6
     field public static final int RESULT_EXPIRED = 3; // 0x3
+    field public static final int RESULT_FILE_ROOT_UNREACHABLE = 8; // 0x8
     field public static final int RESULT_IO_ERROR = 4; // 0x4
+    field public static final int RESULT_OUT_OF_STORAGE = 7; // 0x7
+    field public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5; // 0x5
     field public static final int RESULT_SUCCESSFUL = 1; // 0x1
     field public static final int STATUS_ACTIVELY_DOWNLOADING = 1; // 0x1
     field public static final int STATUS_PENDING_DOWNLOAD = 2; // 0x2
@@ -43702,6 +44041,8 @@
     field public static final int ENCODING_7BIT = 1; // 0x1
     field public static final int ENCODING_8BIT = 2; // 0x2
     field public static final int ENCODING_UNKNOWN = 0; // 0x0
+    field public static final java.lang.String FORMAT_3GPP = "3gpp";
+    field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
     field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
     field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
     field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
@@ -43960,6 +44301,10 @@
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
     field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
     field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+    field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
+    field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
+    field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
+    field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff
     field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4
     field public static final int DATA_ACTIVITY_IN = 1; // 0x1
     field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3
@@ -44210,20 +44555,24 @@
   }
 
   public static class DownloadRequest.Builder {
-    ctor public DownloadRequest.Builder();
+    ctor public DownloadRequest.Builder(android.net.Uri);
     method public android.telephony.mbms.DownloadRequest build();
     method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
     method public android.telephony.mbms.DownloadRequest.Builder setOpaqueData(byte[]);
     method public android.telephony.mbms.DownloadRequest.Builder setServiceId(java.lang.String);
     method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
-    method public android.telephony.mbms.DownloadRequest.Builder setSource(android.net.Uri);
     method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int);
   }
 
   public class DownloadStateCallback {
     ctor public DownloadStateCallback();
+    ctor public DownloadStateCallback(int);
+    method public final boolean isFilterFlagSet(int);
     method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
     method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+    field public static final int ALL_UPDATES = 0; // 0x0
+    field public static final int PROGRESS_UPDATES = 1; // 0x1
+    field public static final int STATE_UPDATES = 2; // 0x2
   }
 
   public final class FileInfo implements android.os.Parcelable {
@@ -44306,6 +44655,7 @@
   public class ServiceInfo {
     method public java.util.List<java.util.Locale> getLocales();
     method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+    method public java.util.Set<java.util.Locale> getNamedContentLocales();
     method public java.lang.String getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
@@ -49345,8 +49695,8 @@
     method protected boolean awakenScrollBars(int);
     method protected boolean awakenScrollBars(int, boolean);
     method public void bringToFront();
-    method public void buildDrawingCache();
-    method public void buildDrawingCache(boolean);
+    method public deprecated void buildDrawingCache();
+    method public deprecated void buildDrawingCache(boolean);
     method public void buildLayer();
     method public boolean callOnClick();
     method public boolean canResolveLayoutDirection();
@@ -49371,7 +49721,7 @@
     method protected int computeVerticalScrollRange();
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
-    method public void destroyDrawingCache();
+    method public deprecated void destroyDrawingCache();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
@@ -49452,10 +49802,10 @@
     method public static int getDefaultSize(int, int);
     method public android.view.Display getDisplay();
     method public final int[] getDrawableState();
-    method public android.graphics.Bitmap getDrawingCache();
-    method public android.graphics.Bitmap getDrawingCache(boolean);
-    method public int getDrawingCacheBackgroundColor();
-    method public int getDrawingCacheQuality();
+    method public deprecated android.graphics.Bitmap getDrawingCache();
+    method public deprecated android.graphics.Bitmap getDrawingCache(boolean);
+    method public deprecated int getDrawingCacheBackgroundColor();
+    method public deprecated int getDrawingCacheQuality();
     method public void getDrawingRect(android.graphics.Rect);
     method public long getDrawingTime();
     method public float getElevation();
@@ -49583,8 +49933,8 @@
     method public boolean hasTransientState();
     method public boolean hasWindowFocus();
     method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
-    method public void invalidate(android.graphics.Rect);
-    method public void invalidate(int, int, int, int);
+    method public deprecated void invalidate(android.graphics.Rect);
+    method public deprecated void invalidate(int, int, int, int);
     method public void invalidate();
     method public void invalidateDrawable(android.graphics.drawable.Drawable);
     method public void invalidateOutline();
@@ -49594,7 +49944,7 @@
     method public boolean isClickable();
     method public boolean isContextClickable();
     method public boolean isDirty();
-    method public boolean isDrawingCacheEnabled();
+    method public deprecated boolean isDrawingCacheEnabled();
     method public boolean isDuplicateParentStateEnabled();
     method public boolean isEnabled();
     method public final boolean isFocusable();
@@ -49765,9 +50115,9 @@
     method public void setContentDescription(java.lang.CharSequence);
     method public void setContextClickable(boolean);
     method public void setDefaultFocusHighlightEnabled(boolean);
-    method public void setDrawingCacheBackgroundColor(int);
-    method public void setDrawingCacheEnabled(boolean);
-    method public void setDrawingCacheQuality(int);
+    method public deprecated void setDrawingCacheBackgroundColor(int);
+    method public deprecated void setDrawingCacheEnabled(boolean);
+    method public deprecated void setDrawingCacheQuality(int);
     method public void setDuplicateParentStateEnabled(boolean);
     method public void setElevation(float);
     method public void setEnabled(boolean);
@@ -49918,9 +50268,9 @@
     field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
     field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
-    field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
-    field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
-    field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
+    field public static final deprecated int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
+    field public static final deprecated int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
+    field public static final deprecated int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
     field protected static final int[] EMPTY_STATE_SET;
     field protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET;
     field protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
@@ -50312,7 +50662,7 @@
     method public android.animation.LayoutTransition getLayoutTransition();
     method public int getNestedScrollAxes();
     method public android.view.ViewGroupOverlay getOverlay();
-    method public int getPersistentDrawingCache();
+    method public deprecated int getPersistentDrawingCache();
     method public boolean getTouchscreenBlocksFocus();
     method public int indexOfChild(android.view.View);
     method public final deprecated void invalidateChild(android.view.View, android.graphics.Rect);
@@ -50365,7 +50715,7 @@
     method public void setAddStatesFromChildren(boolean);
     method public deprecated void setAlwaysDrawnWithCacheEnabled(boolean);
     method public deprecated void setAnimationCacheEnabled(boolean);
-    method protected void setChildrenDrawingCacheEnabled(boolean);
+    method protected deprecated void setChildrenDrawingCacheEnabled(boolean);
     method protected void setChildrenDrawingOrderEnabled(boolean);
     method protected deprecated void setChildrenDrawnWithCacheEnabled(boolean);
     method public void setClipChildren(boolean);
@@ -50377,7 +50727,7 @@
     method public void setLayoutTransition(android.animation.LayoutTransition);
     method public void setMotionEventSplittingEnabled(boolean);
     method public void setOnHierarchyChangeListener(android.view.ViewGroup.OnHierarchyChangeListener);
-    method public void setPersistentDrawingCache(int);
+    method public deprecated void setPersistentDrawingCache(int);
     method protected void setStaticTransformationsEnabled(boolean);
     method public void setTouchscreenBlocksFocus(boolean);
     method public void setTransitionGroup(boolean);
@@ -50395,10 +50745,10 @@
     field public static final int FOCUS_BLOCK_DESCENDANTS = 393216; // 0x60000
     field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
     field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
-    field public static final int PERSISTENT_ALL_CACHES = 3; // 0x3
-    field public static final int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
-    field public static final int PERSISTENT_NO_CACHE = 0; // 0x0
-    field public static final int PERSISTENT_SCROLLING_CACHE = 2; // 0x2
+    field public static final deprecated int PERSISTENT_ALL_CACHES = 3; // 0x3
+    field public static final deprecated int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
+    field public static final deprecated int PERSISTENT_NO_CACHE = 0; // 0x0
+    field public static final deprecated int PERSISTENT_SCROLLING_CACHE = 2; // 0x2
   }
 
   public static class ViewGroup.LayoutParams {
@@ -50596,6 +50946,9 @@
     method public abstract void setInputType(int);
     method public abstract void setLocaleList(android.os.LocaleList);
     method public abstract void setLongClickable(boolean);
+    method public void setMaxTextEms(int);
+    method public void setMaxTextLength(int);
+    method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
@@ -52324,19 +52677,26 @@
 package android.view.textclassifier {
 
   public final class TextClassification {
+    method public int getActionCount();
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
     method public int getEntityCount();
+    method public android.graphics.drawable.Drawable getIcon(int);
     method public android.graphics.drawable.Drawable getIcon();
+    method public android.content.Intent getIntent(int);
     method public android.content.Intent getIntent();
+    method public java.lang.CharSequence getLabel(int);
     method public java.lang.CharSequence getLabel();
+    method public android.view.View.OnClickListener getOnClickListener(int);
     method public android.view.View.OnClickListener getOnClickListener();
     method public java.lang.String getText();
   }
 
   public static final class TextClassification.Builder {
     ctor public TextClassification.Builder();
+    method public android.view.textclassifier.TextClassification.Builder addAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
     method public android.view.textclassifier.TextClassification build();
+    method public android.view.textclassifier.TextClassification.Builder clearActions();
     method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
     method public android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
     method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
@@ -56385,6 +56745,8 @@
     field public static final int OP_CONST_CLASS = 28; // 0x1c
     field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
     field public static final int OP_CONST_HIGH16 = 21; // 0x15
+    field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+    field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
     field public static final int OP_CONST_STRING = 26; // 0x1a
     field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
     field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 7ee261e..e9afa70 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -275,6 +275,10 @@
     method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo);
   }
 
+  public static final class TvInputManager.Hardware {
+    method public boolean dispatchKeyEventToHdmi(android.view.KeyEvent);
+  }
+
   public class TvView extends android.view.ViewGroup {
     method public void requestUnblockContent(android.media.tv.TvContentRating);
   }
@@ -326,7 +330,6 @@
     method public deprecated java.util.List<android.net.wifi.BatchedScanResult> getBatchedScanResults();
     method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
     method public deprecated boolean isBatchedScanSupported();
-    method public deprecated boolean setWifiApEnabled(android.net.wifi.WifiConfiguration, boolean);
     method public deprecated boolean startLocationRestrictedScan(android.os.WorkSource);
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index a62bffc..6f251e9 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -35,6 +35,7 @@
     field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
+    field public static final java.lang.String BIND_SLICE = "android.permission.BIND_SLICE";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
     field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
@@ -362,6 +363,7 @@
     field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
+    field public static final int cantSaveState = 16844142; // 0x101056e
     field public static final deprecated int capitalize = 16843113; // 0x1010169
     field public static final int category = 16843752; // 0x10103e8
     field public static final int centerBright = 16842956; // 0x10100cc
@@ -610,6 +612,7 @@
     field public static final int fontProviderPackage = 16844119; // 0x1010557
     field public static final int fontProviderQuery = 16844113; // 0x1010551
     field public static final int fontStyle = 16844095; // 0x101053f
+    field public static final int fontVariationSettings = 16844144; // 0x1010570
     field public static final int fontWeight = 16844083; // 0x1010533
     field public static final int footerDividersEnabled = 16843311; // 0x101022f
     field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521
@@ -919,6 +922,7 @@
     field public static final int multiprocess = 16842771; // 0x1010013
     field public static final int name = 16842755; // 0x1010003
     field public static final int navigationBarColor = 16843858; // 0x1010452
+    field public static final int navigationBarDividerColor = 16844141; // 0x101056d
     field public static final int navigationContentDescription = 16843969; // 0x10104c1
     field public static final int navigationIcon = 16843968; // 0x10104c0
     field public static final int navigationMode = 16843471; // 0x10102cf
@@ -1439,6 +1443,7 @@
     field public static final int trimPathEnd = 16843785; // 0x1010409
     field public static final int trimPathOffset = 16843786; // 0x101040a
     field public static final int trimPathStart = 16843784; // 0x1010408
+    field public static final int ttcIndex = 16844143; // 0x101056f
     field public static final int tunerCount = 16844061; // 0x101051d
     field public static final int turnScreenOn = 16844138; // 0x101056a
     field public static final int type = 16843169; // 0x10101a1
@@ -3863,9 +3868,15 @@
     method public void moveTaskToFront(int, int);
     method public void moveTaskToFront(int, int, android.os.Bundle);
     method public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
+    method public void removeStacksInWindowingModes(int[]) throws java.lang.SecurityException;
+    method public void removeStacksWithActivityTypes(int[]) throws java.lang.SecurityException;
+    method public void resizeStack(int, android.graphics.Rect) throws java.lang.SecurityException;
     method public deprecated void restartPackage(java.lang.String);
+    method public void setTaskWindowingMode(int, int, boolean) throws java.lang.SecurityException;
     method public static void setVrThread(int);
     method public void setWatchHeapLimit(long);
+    method public static boolean supportsMultiWindow(android.content.Context);
+    method public static boolean supportsSplitScreenMultiWindow(android.content.Context);
     field public static final java.lang.String ACTION_REPORT_HEAP_LIMIT = "android.app.action.REPORT_HEAP_LIMIT";
     field public static final int LOCK_TASK_MODE_LOCKED = 1; // 0x1
     field public static final int LOCK_TASK_MODE_NONE = 0; // 0x0
@@ -4014,24 +4025,21 @@
   }
 
   public static class ActivityManager.StackId {
-    field public static final int ASSISTANT_STACK_ID = 6; // 0x6
-    field public static final int DOCKED_STACK_ID = 3; // 0x3
-    field public static final int FREEFORM_WORKSPACE_STACK_ID = 2; // 0x2
-    field public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1; // 0x1
-    field public static final int HOME_STACK_ID = 0; // 0x0
     field public static final int INVALID_STACK_ID = -1; // 0xffffffff
-    field public static final int PINNED_STACK_ID = 4; // 0x4
-    field public static final int RECENTS_STACK_ID = 5; // 0x5
   }
 
   public static class ActivityManager.TaskDescription implements android.os.Parcelable {
-    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
-    ctor public ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public deprecated ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap, int);
+    ctor public ActivityManager.TaskDescription(java.lang.String, int, int);
+    ctor public deprecated ActivityManager.TaskDescription(java.lang.String, android.graphics.Bitmap);
+    ctor public ActivityManager.TaskDescription(java.lang.String, int);
     ctor public ActivityManager.TaskDescription(java.lang.String);
     ctor public ActivityManager.TaskDescription();
     ctor public ActivityManager.TaskDescription(android.app.ActivityManager.TaskDescription);
     method public int describeContents();
     method public android.graphics.Bitmap getIcon();
+    method public java.lang.String getIconFilename();
+    method public int getIconResource();
     method public java.lang.String getLabel();
     method public int getPrimaryColor();
     method public void readFromParcel(android.os.Parcel);
@@ -4042,6 +4050,7 @@
   public class ActivityOptions {
     method public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
+    method public boolean getLockTaskMode();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4057,6 +4066,7 @@
     method public android.app.ActivityOptions setLaunchDisplayId(int);
     method public void setLaunchTaskId(int);
     method public void setLaunchWindowingMode(int);
+    method public android.app.ActivityOptions setLockTaskMode(boolean);
     method public void setTaskOverlay(boolean, boolean);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
@@ -5669,8 +5679,10 @@
     method public static java.lang.String suppressedEffectsToString(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.NotificationManager.Policy> CREATOR;
+    field public static final int PRIORITY_CATEGORY_ALARMS = 32; // 0x20
     field public static final int PRIORITY_CATEGORY_CALLS = 8; // 0x8
     field public static final int PRIORITY_CATEGORY_EVENTS = 2; // 0x2
+    field public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 64; // 0x40
     field public static final int PRIORITY_CATEGORY_MESSAGES = 4; // 0x4
     field public static final int PRIORITY_CATEGORY_REMINDERS = 1; // 0x1
     field public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 16; // 0x10
@@ -6382,6 +6394,7 @@
     method public long getLastBugReportRequestTime();
     method public long getLastNetworkLogRetrievalTime();
     method public long getLastSecurityLogRetrievalTime();
+    method public int getLockTaskFeatures(android.content.ComponentName);
     method public java.lang.String[] getLockTaskPackages(android.content.ComponentName);
     method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
@@ -6471,6 +6484,7 @@
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
+    method public void setLockTaskFeatures(android.content.ComponentName, int);
     method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException;
     method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence);
     method public void setMasterVolumeMuted(android.content.ComponentName, boolean);
@@ -6508,6 +6522,8 @@
     method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
     method public int setStorageEncryption(android.content.ComponentName, boolean);
     method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
+    method public boolean setTime(android.content.ComponentName, long);
+    method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
     method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
     method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
     method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
@@ -6515,6 +6531,7 @@
     method public void uninstallAllUserCaCerts(android.content.ComponentName);
     method public void uninstallCaCert(android.content.ComponentName, byte[]);
     method public void wipeData(int);
+    method public void wipeDataWithReason(int, java.lang.CharSequence);
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
@@ -6587,6 +6604,13 @@
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
     field public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 8; // 0x8
     field public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1; // 0x1
+    field public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 16; // 0x10
+    field public static final int LOCK_TASK_FEATURE_HOME = 4; // 0x4
+    field public static final int LOCK_TASK_FEATURE_KEYGUARD = 32; // 0x20
+    field public static final int LOCK_TASK_FEATURE_NONE = 0; // 0x0
+    field public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 2; // 0x2
+    field public static final int LOCK_TASK_FEATURE_RECENTS = 8; // 0x8
+    field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final java.lang.String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
     field public static final int PASSWORD_QUALITY_ALPHABETIC = 262144; // 0x40000
     field public static final int PASSWORD_QUALITY_ALPHANUMERIC = 327680; // 0x50000
@@ -6737,6 +6761,9 @@
     method public int getInputType();
     method public int getLeft();
     method public android.os.LocaleList getLocaleList();
+    method public int getMaxTextEms();
+    method public int getMaxTextLength();
+    method public int getMinTextEms();
     method public int getScrollX();
     method public int getScrollY();
     method public java.lang.CharSequence getText();
@@ -6752,6 +6779,7 @@
     method public android.graphics.Matrix getTransformation();
     method public int getVisibility();
     method public java.lang.String getWebDomain();
+    method public java.lang.String getWebScheme();
     method public int getWidth();
     method public boolean isAccessibilityFocused();
     method public boolean isActivated();
@@ -6878,6 +6906,7 @@
     method public int getBackoffPolicy();
     method public android.content.ClipData getClipData();
     method public int getClipGrantFlags();
+    method public long getEstimatedNetworkBytes();
     method public android.os.PersistableBundle getExtras();
     method public long getFlexMillis();
     method public int getId();
@@ -6905,6 +6934,7 @@
     field public static final android.os.Parcelable.Creator<android.app.job.JobInfo> CREATOR;
     field public static final long DEFAULT_INITIAL_BACKOFF_MILLIS = 30000L; // 0x7530L
     field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
+    field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
     field public static final int NETWORK_TYPE_ANY = 1; // 0x1
     field public static final int NETWORK_TYPE_METERED = 4; // 0x4
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
@@ -6918,6 +6948,7 @@
     method public android.app.job.JobInfo build();
     method public android.app.job.JobInfo.Builder setBackoffCriteria(long, int);
     method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
+    method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
@@ -6952,6 +6983,7 @@
     method public int getClipGrantFlags();
     method public android.os.PersistableBundle getExtras();
     method public int getJobId();
+    method public android.net.Network getNetwork();
     method public android.os.Bundle getTransientExtras();
     method public java.lang.String[] getTriggeredContentAuthorities();
     method public android.net.Uri[] getTriggeredContentUris();
@@ -6991,8 +7023,10 @@
 
   public final class JobWorkItem implements android.os.Parcelable {
     ctor public JobWorkItem(android.content.Intent);
+    ctor public JobWorkItem(android.content.Intent, long);
     method public int describeContents();
     method public int getDeliveryCount();
+    method public long getEstimatedNetworkBytes();
     method public android.content.Intent getIntent();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
@@ -7000,6 +7034,109 @@
 
 }
 
+package android.app.slice {
+
+  public final class Slice implements android.os.Parcelable {
+    ctor protected Slice(android.os.Parcel);
+    method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
+    method public int describeContents();
+    method public java.util.List<java.lang.String> getHints();
+    method public java.util.List<android.app.slice.SliceItem> getItems();
+    method public android.net.Uri getUri();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR;
+    field public static final java.lang.String HINT_ACTIONS = "actions";
+    field public static final java.lang.String HINT_HORIZONTAL = "horizontal";
+    field public static final java.lang.String HINT_LARGE = "large";
+    field public static final java.lang.String HINT_LIST = "list";
+    field public static final java.lang.String HINT_LIST_ITEM = "list_item";
+    field public static final java.lang.String HINT_MESSAGE = "message";
+    field public static final java.lang.String HINT_NO_TINT = "no_tint";
+    field public static final java.lang.String HINT_PARTIAL = "partial";
+    field public static final java.lang.String HINT_SELECTED = "selected";
+    field public static final java.lang.String HINT_SOURCE = "source";
+    field public static final java.lang.String HINT_TITLE = "title";
+  }
+
+  public static class Slice.Builder {
+    ctor public Slice.Builder(android.net.Uri);
+    ctor public Slice.Builder(android.app.slice.Slice.Builder);
+    method public android.app.slice.Slice.Builder addAction(android.app.PendingIntent, android.app.slice.Slice);
+    method public android.app.slice.Slice.Builder addColor(int, java.lang.String...);
+    method public android.app.slice.Slice.Builder addColor(int, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addHints(java.lang.String...);
+    method public android.app.slice.Slice.Builder addHints(java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.lang.String...);
+    method public android.app.slice.Slice.Builder addIcon(android.graphics.drawable.Icon, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addRemoteInput(android.app.RemoteInput, java.lang.String...);
+    method public android.app.slice.Slice.Builder addSubSlice(android.app.slice.Slice);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.lang.String...);
+    method public android.app.slice.Slice.Builder addText(java.lang.CharSequence, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.lang.String...);
+    method public android.app.slice.Slice.Builder addTimestamp(long, java.util.List<java.lang.String>);
+    method public android.app.slice.Slice build();
+  }
+
+  public final class SliceItem implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.app.PendingIntent getAction();
+    method public int getColor();
+    method public java.util.List<java.lang.String> getHints();
+    method public android.graphics.drawable.Icon getIcon();
+    method public android.app.RemoteInput getRemoteInput();
+    method public android.app.slice.Slice getSlice();
+    method public java.lang.CharSequence getText();
+    method public long getTimestamp();
+    method public int getType();
+    method public boolean hasHint(java.lang.String);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.slice.SliceItem> CREATOR;
+    field public static final int TYPE_ACTION = 4; // 0x4
+    field public static final int TYPE_COLOR = 6; // 0x6
+    field public static final int TYPE_IMAGE = 3; // 0x3
+    field public static final int TYPE_REMOTE_INPUT = 9; // 0x9
+    field public static final int TYPE_SLICE = 1; // 0x1
+    field public static final int TYPE_TEXT = 2; // 0x2
+    field public static final int TYPE_TIMESTAMP = 8; // 0x8
+  }
+
+  public abstract class SliceProvider extends android.content.ContentProvider {
+    ctor public SliceProvider();
+    method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+    method public final java.lang.String getType(android.net.Uri);
+    method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+    method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
+    method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
+    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
+    field public static final java.lang.String SLICE_TYPE = "vnd.android.slice";
+  }
+
+}
+
+package android.app.slice.widget {
+
+  public class SliceView extends android.view.ViewGroup {
+    ctor public SliceView(android.content.Context);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int);
+    ctor public SliceView(android.content.Context, android.util.AttributeSet, int, int);
+    method public void clearSlice();
+    method public java.lang.String getMode();
+    method protected void onLayout(boolean, int, int, int, int);
+    method public void setMode(java.lang.String);
+    method public void setScrollable(boolean);
+    method public boolean setSlice(android.net.Uri);
+    method public void showSlice(android.app.slice.Slice);
+    field public static final java.lang.String MODE_LARGE = "SLICE_LARGE";
+    field public static final java.lang.String MODE_SHORTCUT = "SLICE_ICON";
+    field public static final java.lang.String MODE_SMALL = "SLICE_SMALL";
+  }
+
+}
+
 package android.app.usage {
 
   public final class ConfigurationStats implements android.os.Parcelable {
@@ -9045,6 +9182,7 @@
     field public static final java.lang.String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final java.lang.String INPUT_METHOD_SERVICE = "input_method";
     field public static final java.lang.String INPUT_SERVICE = "input";
+    field public static final java.lang.String IPSEC_SERVICE = "ipsec";
     field public static final java.lang.String JOB_SCHEDULER_SERVICE = "jobscheduler";
     field public static final java.lang.String KEYGUARD_SERVICE = "keyguard";
     field public static final java.lang.String LAUNCHER_APPS_SERVICE = "launcherapps";
@@ -10505,6 +10643,7 @@
     method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
     method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
     field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
+    field public static final int FLAG_MATCH_ALL_PINNED = 1024; // 0x400
     field public static final int FLAG_MATCH_DYNAMIC = 1; // 0x1
     field public static final int FLAG_MATCH_MANIFEST = 8; // 0x8
     field public static final int FLAG_MATCH_PINNED = 2; // 0x2
@@ -11077,6 +11216,7 @@
     method public android.content.ComponentName getActivity();
     method public java.util.Set<java.lang.String> getCategories();
     method public java.lang.CharSequence getDisabledMessage();
+    method public int getDisabledReason();
     method public android.os.PersistableBundle getExtras();
     method public java.lang.String getId();
     method public android.content.Intent getIntent();
@@ -11093,8 +11233,16 @@
     method public boolean isEnabled();
     method public boolean isImmutable();
     method public boolean isPinned();
+    method public boolean isVisibleToPublisher();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
+    field public static final int DISABLED_REASON_APP_CHANGED = 2; // 0x2
+    field public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101; // 0x65
+    field public static final int DISABLED_REASON_BY_APP = 1; // 0x1
+    field public static final int DISABLED_REASON_NOT_DISABLED = 0; // 0x0
+    field public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103; // 0x67
+    field public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102; // 0x66
+    field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
     field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
   }
 
@@ -11669,6 +11817,7 @@
 
   public class CursorWindow extends android.database.sqlite.SQLiteClosable implements android.os.Parcelable {
     ctor public CursorWindow(java.lang.String);
+    ctor public CursorWindow(java.lang.String, long);
     ctor public deprecated CursorWindow(boolean);
     method public boolean allocRow();
     method public void clear();
@@ -22867,6 +23016,10 @@
     field public static final java.lang.String KEY_DURATION = "durationUs";
     field public static final java.lang.String KEY_FLAC_COMPRESSION_LEVEL = "flac-compression-level";
     field public static final java.lang.String KEY_FRAME_RATE = "frame-rate";
+    field public static final java.lang.String KEY_GRID_COLS = "grid-cols";
+    field public static final java.lang.String KEY_GRID_HEIGHT = "grid-height";
+    field public static final java.lang.String KEY_GRID_ROWS = "grid-rows";
+    field public static final java.lang.String KEY_GRID_WIDTH = "grid-width";
     field public static final java.lang.String KEY_HDR_STATIC_INFO = "hdr-static-info";
     field public static final java.lang.String KEY_HEIGHT = "height";
     field public static final java.lang.String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
@@ -22910,6 +23063,7 @@
     field public static final java.lang.String MIMETYPE_AUDIO_RAW = "audio/raw";
     field public static final java.lang.String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
     field public static final java.lang.String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+    field public static final java.lang.String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
     field public static final java.lang.String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
     field public static final java.lang.String MIMETYPE_TEXT_VTT = "text/vtt";
     field public static final java.lang.String MIMETYPE_VIDEO_AVC = "video/avc";
@@ -23002,9 +23156,13 @@
     ctor public MediaMetadataRetriever();
     method public java.lang.String extractMetadata(int);
     method public byte[] getEmbeddedPicture();
+    method public android.graphics.Bitmap getFrameAtIndex(int);
     method public android.graphics.Bitmap getFrameAtTime(long, int);
     method public android.graphics.Bitmap getFrameAtTime(long);
     method public android.graphics.Bitmap getFrameAtTime();
+    method public android.graphics.Bitmap[] getFramesAtIndex(int, int);
+    method public android.graphics.Bitmap getImageAtIndex(int);
+    method public android.graphics.Bitmap getPrimaryImage();
     method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
     method public void release();
     method public void setDataSource(java.lang.String) throws java.lang.IllegalArgumentException;
@@ -23027,11 +23185,18 @@
     field public static final int METADATA_KEY_DURATION = 9; // 0x9
     field public static final int METADATA_KEY_GENRE = 6; // 0x6
     field public static final int METADATA_KEY_HAS_AUDIO = 16; // 0x10
+    field public static final int METADATA_KEY_HAS_IMAGE = 26; // 0x1a
     field public static final int METADATA_KEY_HAS_VIDEO = 17; // 0x11
+    field public static final int METADATA_KEY_IMAGE_COUNT = 27; // 0x1b
+    field public static final int METADATA_KEY_IMAGE_HEIGHT = 30; // 0x1e
+    field public static final int METADATA_KEY_IMAGE_PRIMARY = 28; // 0x1c
+    field public static final int METADATA_KEY_IMAGE_ROTATION = 31; // 0x1f
+    field public static final int METADATA_KEY_IMAGE_WIDTH = 29; // 0x1d
     field public static final int METADATA_KEY_LOCATION = 23; // 0x17
     field public static final int METADATA_KEY_MIMETYPE = 12; // 0xc
     field public static final int METADATA_KEY_NUM_TRACKS = 10; // 0xa
     field public static final int METADATA_KEY_TITLE = 7; // 0x7
+    field public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32; // 0x20
     field public static final int METADATA_KEY_VIDEO_HEIGHT = 19; // 0x13
     field public static final int METADATA_KEY_VIDEO_ROTATION = 24; // 0x18
     field public static final int METADATA_KEY_VIDEO_WIDTH = 18; // 0x12
@@ -25836,6 +26001,69 @@
     field public static final android.os.Parcelable.Creator<android.net.IpPrefix> CREATOR;
   }
 
+  public final class IpSecAlgorithm implements android.os.Parcelable {
+    ctor public IpSecAlgorithm(java.lang.String, byte[]);
+    ctor public IpSecAlgorithm(java.lang.String, byte[], int);
+    method public int describeContents();
+    method public byte[] getKey();
+    method public java.lang.String getName();
+    method public int getTruncationLengthBits();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+    field public static final java.lang.String AUTH_HMAC_MD5 = "hmac(md5)";
+    field public static final java.lang.String AUTH_HMAC_SHA1 = "hmac(sha1)";
+    field public static final java.lang.String AUTH_HMAC_SHA256 = "hmac(sha256)";
+    field public static final java.lang.String AUTH_HMAC_SHA384 = "hmac(sha384)";
+    field public static final java.lang.String AUTH_HMAC_SHA512 = "hmac(sha512)";
+    field public static final android.os.Parcelable.Creator<android.net.IpSecAlgorithm> CREATOR;
+    field public static final java.lang.String CRYPT_AES_CBC = "cbc(aes)";
+  }
+
+  public final class IpSecManager {
+    method public void applyTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket(int) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.UdpEncapsulationSocket openUdpEncapsulationSocket() throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException;
+    method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
+    method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
+  }
+
+  public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
+  }
+
+  public static final class IpSecManager.SecurityParameterIndex implements java.lang.AutoCloseable {
+    method public void close();
+    method protected void finalize();
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.SpiUnavailableException extends android.util.AndroidException {
+    method public int getSpi();
+  }
+
+  public static final class IpSecManager.UdpEncapsulationSocket implements java.lang.AutoCloseable {
+    method public void close() throws java.io.IOException;
+    method public int getPort();
+    method public java.io.FileDescriptor getSocket();
+  }
+
+  public final class IpSecTransform implements java.lang.AutoCloseable {
+    method public void close();
+    field public static final int DIRECTION_IN = 0; // 0x0
+    field public static final int DIRECTION_OUT = 1; // 0x1
+  }
+
+  public static class IpSecTransform.Builder {
+    ctor public IpSecTransform.Builder(android.content.Context);
+    method public android.net.IpSecTransform buildTransportModeTransform(java.net.InetAddress) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
+    method public android.net.IpSecTransform.Builder setAuthenticatedEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setAuthentication(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setEncryption(int, android.net.IpSecAlgorithm);
+    method public android.net.IpSecTransform.Builder setIpv4Encapsulation(android.net.IpSecManager.UdpEncapsulationSocket, int);
+    method public android.net.IpSecTransform.Builder setSpi(int, android.net.IpSecManager.SecurityParameterIndex);
+  }
+
   public class LinkAddress implements android.os.Parcelable {
     method public int describeContents();
     method public java.net.InetAddress getAddress();
@@ -25858,7 +26086,7 @@
     field public static final android.os.Parcelable.Creator<android.net.LinkProperties> CREATOR;
   }
 
-  public class LocalServerSocket {
+  public class LocalServerSocket implements java.io.Closeable {
     ctor public LocalServerSocket(java.lang.String) throws java.io.IOException;
     ctor public LocalServerSocket(java.io.FileDescriptor) throws java.io.IOException;
     method public android.net.LocalSocket accept() throws java.io.IOException;
@@ -31821,6 +32049,21 @@
     method public static void setThreadPolicy(android.os.StrictMode.ThreadPolicy);
     method public static void setViolationLogger(android.os.StrictMode.ViolationLogger);
     method public static void setVmPolicy(android.os.StrictMode.VmPolicy);
+    field public static final int DETECT_CUSTOM = 8; // 0x8
+    field public static final int DETECT_DISK_READ = 2; // 0x2
+    field public static final int DETECT_DISK_WRITE = 1; // 0x1
+    field public static final int DETECT_NETWORK = 4; // 0x4
+    field public static final int DETECT_RESOURCE_MISMATCH = 16; // 0x10
+    field public static final int DETECT_UNBUFFERED_IO = 32; // 0x20
+    field public static final int DETECT_VM_ACTIVITY_LEAKS = 1024; // 0x400
+    field public static final int DETECT_VM_CLEARTEXT_NETWORK = 16384; // 0x4000
+    field public static final int DETECT_VM_CLOSABLE_LEAKS = 512; // 0x200
+    field public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 32768; // 0x8000
+    field public static final int DETECT_VM_CURSOR_LEAKS = 256; // 0x100
+    field public static final int DETECT_VM_FILE_URI_EXPOSURE = 8192; // 0x2000
+    field public static final int DETECT_VM_INSTANCE_LEAKS = 2048; // 0x800
+    field public static final int DETECT_VM_REGISTRATION_LEAKS = 4096; // 0x1000
+    field public static final int DETECT_VM_UNTAGGED_SOCKET = -2147483648; // 0x80000000
   }
 
   public static final class StrictMode.ThreadPolicy {
@@ -31856,17 +32099,16 @@
   public static final class StrictMode.ViolationInfo implements android.os.Parcelable {
     ctor public StrictMode.ViolationInfo();
     ctor public StrictMode.ViolationInfo(java.lang.Throwable, int);
-    ctor public StrictMode.ViolationInfo(java.lang.String, java.lang.Throwable, int);
     ctor public StrictMode.ViolationInfo(android.os.Parcel);
     ctor public StrictMode.ViolationInfo(android.os.Parcel, boolean);
     method public int describeContents();
     method public void dump(android.util.Printer, java.lang.String);
+    method public java.lang.String getStackTrace();
+    method public java.lang.String getViolationDetails();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.os.StrictMode.ViolationInfo> CREATOR;
     field public java.lang.String broadcastIntentAction;
-    field public final android.app.ApplicationErrorReport.CrashInfo crashInfo;
     field public int durationMillis;
-    field public final java.lang.String message;
     field public int numAnimationsRunning;
     field public long numInstances;
     field public final int policy;
@@ -31994,6 +32236,7 @@
     field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
     field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+    field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
     field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
     field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
     field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -32018,6 +32261,7 @@
     field public static final java.lang.String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
     field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
     field public static final java.lang.String DISALLOW_SMS = "no_sms";
+    field public static final java.lang.String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
     field public static final java.lang.String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
     field public static final java.lang.String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
     field public static final java.lang.String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
@@ -35156,6 +35400,7 @@
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
     field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+    field public static final java.lang.String ACTION_FINGERPRINT_ENROLL = "android.settings.FINGERPRINT_ENROLL";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
@@ -35302,6 +35547,7 @@
     method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
     method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
     field public static final java.lang.String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled";
+    field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled";
     field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled";
     field public static final deprecated java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
     field public static final deprecated java.lang.String ADB_ENABLED = "adb_enabled";
@@ -37306,7 +37552,20 @@
     field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
   }
 
-  public final class CharSequenceTransformation implements android.os.Parcelable android.service.autofill.Transformation {
+  public final class BatchUpdates implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.BatchUpdates> CREATOR;
+  }
+
+  public static class BatchUpdates.Builder {
+    ctor public BatchUpdates.Builder();
+    method public android.service.autofill.BatchUpdates build();
+    method public android.service.autofill.BatchUpdates.Builder transformChild(int, android.service.autofill.Transformation);
+    method public android.service.autofill.BatchUpdates.Builder updateTemplate(android.widget.RemoteViews);
+  }
+
+  public final class CharSequenceTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -37328,6 +37587,7 @@
   public static class CustomDescription.Builder {
     ctor public CustomDescription.Builder(android.widget.RemoteViews);
     method public android.service.autofill.CustomDescription.Builder addChild(int, android.service.autofill.Transformation);
+    method public android.service.autofill.CustomDescription.Builder batchUpdate(android.service.autofill.Validator, android.service.autofill.BatchUpdates);
     method public android.service.autofill.CustomDescription build();
   }
 
@@ -37345,6 +37605,8 @@
     method public android.service.autofill.Dataset.Builder setId(java.lang.String);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue);
     method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, android.widget.RemoteViews);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern);
+    method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews);
   }
 
   public final class FillCallback {
@@ -37369,10 +37631,15 @@
   }
 
   public static final class FillEventHistory.Event {
+    method public java.util.Map<android.view.autofill.AutofillId, java.lang.String> getChangedFields();
     method public android.os.Bundle getClientState();
     method public java.lang.String getDatasetId();
+    method public java.util.Set<java.lang.String> getIgnoredDatasetIds();
+    method public java.util.Map<android.view.autofill.AutofillId, java.util.Set<java.lang.String>> getManuallyEnteredField();
+    method public java.util.Set<java.lang.String> getSelectedDatasetIds();
     method public int getType();
     field public static final int TYPE_AUTHENTICATION_SELECTED = 2; // 0x2
+    field public static final int TYPE_CONTEXT_COMMITTED = 4; // 0x4
     field public static final int TYPE_DATASET_AUTHENTICATION_SELECTED = 1; // 0x1
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
@@ -37393,19 +37660,23 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.FillResponse> CREATOR;
+    field public static final int FLAG_DISABLE_ACTIVITY_ONLY = 2; // 0x2
+    field public static final int FLAG_TRACK_CONTEXT_COMMITED = 1; // 0x1
   }
 
   public static final class FillResponse.Builder {
     ctor public FillResponse.Builder();
     method public android.service.autofill.FillResponse.Builder addDataset(android.service.autofill.Dataset);
     method public android.service.autofill.FillResponse build();
+    method public android.service.autofill.FillResponse.Builder disableAutofill(long);
     method public android.service.autofill.FillResponse.Builder setAuthentication(android.view.autofill.AutofillId[], android.content.IntentSender, android.widget.RemoteViews);
     method public android.service.autofill.FillResponse.Builder setClientState(android.os.Bundle);
+    method public android.service.autofill.FillResponse.Builder setFlags(int);
     method public android.service.autofill.FillResponse.Builder setIgnoredIds(android.view.autofill.AutofillId...);
     method public android.service.autofill.FillResponse.Builder setSaveInfo(android.service.autofill.SaveInfo);
   }
 
-  public final class ImageTransformation implements android.os.Parcelable android.service.autofill.Transformation {
+  public final class ImageTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
     method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception;
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -37413,12 +37684,27 @@
   }
 
   public static class ImageTransformation.Builder {
-    ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int);
-    method public android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int);
+    ctor public deprecated ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int);
+    ctor public ImageTransformation.Builder(android.view.autofill.AutofillId, java.util.regex.Pattern, int, java.lang.CharSequence);
+    method public deprecated android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int);
+    method public android.service.autofill.ImageTransformation.Builder addOption(java.util.regex.Pattern, int, java.lang.CharSequence);
     method public android.service.autofill.ImageTransformation build();
   }
 
-  public final class LuhnChecksumValidator implements android.os.Parcelable android.service.autofill.Validator {
+  public abstract class InternalSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
+    ctor public InternalSanitizer();
+  }
+
+  public abstract class InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
+    ctor public InternalTransformation();
+  }
+
+  public abstract class InternalValidator implements android.os.Parcelable android.service.autofill.Validator {
+    ctor public InternalValidator();
+    method public abstract boolean isValid(android.service.autofill.ValueFinder);
+  }
+
+  public final class LuhnChecksumValidator extends android.service.autofill.InternalValidator implements android.os.Parcelable android.service.autofill.Validator {
     ctor public LuhnChecksumValidator(android.view.autofill.AutofillId...);
     method public int describeContents();
     method public boolean isValid(android.service.autofill.ValueFinder);
@@ -37426,7 +37712,7 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.LuhnChecksumValidator> CREATOR;
   }
 
-  public final class RegexValidator implements android.os.Parcelable android.service.autofill.Validator {
+  public final class RegexValidator extends android.service.autofill.InternalValidator implements android.os.Parcelable android.service.autofill.Validator {
     ctor public RegexValidator(android.view.autofill.AutofillId, java.util.regex.Pattern);
     method public int describeContents();
     method public boolean isValid(android.service.autofill.ValueFinder);
@@ -37434,6 +37720,9 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.RegexValidator> CREATOR;
   }
 
+  public abstract interface Sanitizer {
+  }
+
   public final class SaveCallback {
     method public void onFailure(java.lang.CharSequence);
     method public void onSuccess();
@@ -37443,6 +37732,7 @@
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveInfo> CREATOR;
+    field public static final int FLAG_DONT_SAVE_ON_FINISH = 2; // 0x2
     field public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 1; // 0x1
     field public static final int NEGATIVE_BUTTON_STYLE_CANCEL = 0; // 0x0
     field public static final int NEGATIVE_BUTTON_STYLE_REJECT = 1; // 0x1
@@ -37457,12 +37747,14 @@
   public static final class SaveInfo.Builder {
     ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]);
     ctor public SaveInfo.Builder(int);
+    method public android.service.autofill.SaveInfo.Builder addSanitizer(android.service.autofill.Sanitizer, android.view.autofill.AutofillId...);
     method public android.service.autofill.SaveInfo build();
     method public android.service.autofill.SaveInfo.Builder setCustomDescription(android.service.autofill.CustomDescription);
     method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence);
     method public android.service.autofill.SaveInfo.Builder setFlags(int);
     method public android.service.autofill.SaveInfo.Builder setNegativeAction(int, android.content.IntentSender);
     method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]);
+    method public android.service.autofill.SaveInfo.Builder setTriggerId(android.view.autofill.AutofillId);
     method public android.service.autofill.SaveInfo.Builder setValidator(android.service.autofill.Validator);
   }
 
@@ -37475,6 +37767,14 @@
     field public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
   }
 
+  public final class TextValueSanitizer extends android.service.autofill.InternalSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
+    ctor public TextValueSanitizer(java.util.regex.Pattern, java.lang.String);
+    method public int describeContents();
+    method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.autofill.TextValueSanitizer> CREATOR;
+  }
+
   public abstract interface Transformation {
   }
 
@@ -37706,6 +38006,7 @@
     field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
     field public static final java.lang.String KEY_PEOPLE = "key_people";
     field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
+    field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
   }
 
   public final class Condition implements android.os.Parcelable {
@@ -37792,6 +38093,7 @@
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
     method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+    method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int);
     method public final void requestInterruptionFilter(int);
     method public final void requestListenerHints(int);
     method public static void requestRebind(android.content.ComponentName);
@@ -37844,8 +38146,12 @@
     method public java.lang.String getOverrideGroupKey();
     method public int getRank();
     method public int getSuppressedVisualEffects();
+    method public int getUserSentiment();
     method public boolean isAmbient();
     method public boolean matchesInterruptionFilter();
+    field public static final int USER_SENTIMENT_NEGATIVE = -1; // 0xffffffff
+    field public static final int USER_SENTIMENT_NEUTRAL = 0; // 0x0
+    field public static final int USER_SENTIMENT_POSITIVE = 1; // 0x1
   }
 
   public static class NotificationListenerService.RankingMap implements android.os.Parcelable {
@@ -37856,6 +38162,32 @@
     field public static final android.os.Parcelable.Creator<android.service.notification.NotificationListenerService.RankingMap> CREATOR;
   }
 
+  public final class NotificationStats implements android.os.Parcelable {
+    ctor public NotificationStats();
+    ctor protected NotificationStats(android.os.Parcel);
+    method public int describeContents();
+    method public int getDismissalSurface();
+    method public boolean hasDirectReplied();
+    method public boolean hasExpanded();
+    method public boolean hasInteracted();
+    method public boolean hasSeen();
+    method public boolean hasSnoozed();
+    method public boolean hasViewedSettings();
+    method public void setDirectReplied();
+    method public void setDismissalSurface(int);
+    method public void setExpanded();
+    method public void setSeen();
+    method public void setSnoozed();
+    method public void setViewedSettings();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.notification.NotificationStats> CREATOR;
+    field public static final int DISMISSAL_AOD = 2; // 0x2
+    field public static final int DISMISSAL_NOT_DISMISSED = -1; // 0xffffffff
+    field public static final int DISMISSAL_OTHER = 0; // 0x0
+    field public static final int DISMISSAL_PEEK = 1; // 0x1
+    field public static final int DISMISSAL_SHADE = 3; // 0x3
+  }
+
   public final class SnoozeCriterion implements android.os.Parcelable {
     ctor public SnoozeCriterion(java.lang.String, java.lang.CharSequence, java.lang.CharSequence);
     ctor protected SnoozeCriterion(android.os.Parcel);
@@ -39985,12 +40317,14 @@
     field public static final java.lang.String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
     field public static final java.lang.String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
     field public static final java.lang.String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
+    field public static final java.lang.String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final java.lang.String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
     field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string";
     field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string";
+    field public static final java.lang.String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
     field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
     field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
     field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
@@ -40005,6 +40339,7 @@
     field public static final java.lang.String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
     field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
     field public static final java.lang.String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
+    field public static final java.lang.String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
     field public static final java.lang.String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final java.lang.String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final java.lang.String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
@@ -40095,6 +40430,8 @@
     method public int getLatitude();
     method public int getLongitude();
     method public int getNetworkId();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getSystemId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;
@@ -40106,8 +40443,13 @@
     method public int getBsic();
     method public int getCid();
     method public int getLac();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public deprecated int getPsc();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;
@@ -40117,8 +40459,13 @@
     method public int describeContents();
     method public int getCi();
     method public int getEarfcn();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getPci();
     method public int getTac();
     method public void writeToParcel(android.os.Parcel, int);
@@ -40129,8 +40476,13 @@
     method public int describeContents();
     method public int getCid();
     method public int getLac();
-    method public int getMcc();
-    method public int getMnc();
+    method public deprecated int getMcc();
+    method public java.lang.String getMccStr();
+    method public deprecated int getMnc();
+    method public java.lang.String getMncStr();
+    method public java.lang.String getMobileNetworkOperator();
+    method public java.lang.CharSequence getOperatorAlphaLong();
+    method public java.lang.CharSequence getOperatorAlphaShort();
     method public int getPsc();
     method public int getUarfcn();
     method public void writeToParcel(android.os.Parcel, int);
@@ -40282,9 +40634,14 @@
     field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_REQUEST = "android.telephony.extra.MBMS_DOWNLOAD_REQUEST";
     field public static final java.lang.String EXTRA_MBMS_DOWNLOAD_RESULT = "android.telephony.extra.MBMS_DOWNLOAD_RESULT";
     field public static final java.lang.String EXTRA_MBMS_FILE_INFO = "android.telephony.extra.MBMS_FILE_INFO";
+    field public static final java.lang.String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA = "mbms-download-service-override";
     field public static final int RESULT_CANCELLED = 2; // 0x2
+    field public static final int RESULT_DOWNLOAD_FAILURE = 6; // 0x6
     field public static final int RESULT_EXPIRED = 3; // 0x3
+    field public static final int RESULT_FILE_ROOT_UNREACHABLE = 8; // 0x8
     field public static final int RESULT_IO_ERROR = 4; // 0x4
+    field public static final int RESULT_OUT_OF_STORAGE = 7; // 0x7
+    field public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5; // 0x5
     field public static final int RESULT_SUCCESSFUL = 1; // 0x1
     field public static final int STATUS_ACTIVELY_DOWNLOADING = 1; // 0x1
     field public static final int STATUS_PENDING_DOWNLOAD = 2; // 0x2
@@ -40299,6 +40656,7 @@
     method public static android.telephony.MbmsStreamingSession create(android.content.Context, android.telephony.mbms.MbmsStreamingSessionCallback, android.os.Handler);
     method public void requestUpdateStreamingServices(java.util.List<java.lang.String>);
     method public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, android.telephony.mbms.StreamingServiceCallback, android.os.Handler);
+    field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
   }
 
   public class NeighboringCellInfo implements android.os.Parcelable {
@@ -40559,6 +40917,8 @@
     field public static final int ENCODING_7BIT = 1; // 0x1
     field public static final int ENCODING_8BIT = 2; // 0x2
     field public static final int ENCODING_UNKNOWN = 0; // 0x0
+    field public static final java.lang.String FORMAT_3GPP = "3gpp";
+    field public static final java.lang.String FORMAT_3GPP2 = "3gpp2";
     field public static final int MAX_USER_DATA_BYTES = 140; // 0x8c
     field public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; // 0x86
     field public static final int MAX_USER_DATA_SEPTETS = 160; // 0xa0
@@ -40715,6 +41075,10 @@
     field public static final int CALL_STATE_IDLE = 0; // 0x0
     field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
     field public static final int CALL_STATE_RINGING = 1; // 0x1
+    field public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; // 0x1
+    field public static final int CDMA_ROAMING_MODE_ANY = 2; // 0x2
+    field public static final int CDMA_ROAMING_MODE_HOME = 0; // 0x0
+    field public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; // 0xffffffff
     field public static final int DATA_ACTIVITY_DORMANT = 4; // 0x4
     field public static final int DATA_ACTIVITY_IN = 1; // 0x1
     field public static final int DATA_ACTIVITY_INOUT = 3; // 0x3
@@ -40951,18 +41315,22 @@
   }
 
   public static class DownloadRequest.Builder {
-    ctor public DownloadRequest.Builder();
+    ctor public DownloadRequest.Builder(android.net.Uri);
     method public android.telephony.mbms.DownloadRequest build();
     method public android.telephony.mbms.DownloadRequest.Builder setAppIntent(android.content.Intent);
     method public android.telephony.mbms.DownloadRequest.Builder setServiceInfo(android.telephony.mbms.FileServiceInfo);
-    method public android.telephony.mbms.DownloadRequest.Builder setSource(android.net.Uri);
     method public android.telephony.mbms.DownloadRequest.Builder setSubscriptionId(int);
   }
 
   public class DownloadStateCallback {
     ctor public DownloadStateCallback();
+    ctor public DownloadStateCallback(int);
+    method public final boolean isFilterFlagSet(int);
     method public void onProgressUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int, int, int, int);
     method public void onStateUpdated(android.telephony.mbms.DownloadRequest, android.telephony.mbms.FileInfo, int);
+    field public static final int ALL_UPDATES = 0; // 0x0
+    field public static final int PROGRESS_UPDATES = 1; // 0x1
+    field public static final int STATE_UPDATES = 2; // 0x2
   }
 
   public final class FileInfo implements android.os.Parcelable {
@@ -41036,6 +41404,7 @@
   public class ServiceInfo {
     method public java.util.List<java.util.Locale> getLocales();
     method public java.lang.CharSequence getNameForLocale(java.util.Locale);
+    method public java.util.Set<java.util.Locale> getNamedContentLocales();
     method public java.lang.String getServiceClassName();
     method public java.lang.String getServiceId();
     method public java.util.Date getSessionEndTime();
@@ -41071,6 +41440,7 @@
   }
 
   public final class StreamingServiceInfo extends android.telephony.mbms.ServiceInfo implements android.os.Parcelable {
+    ctor public StreamingServiceInfo(java.util.Map<java.util.Locale, java.lang.String>, java.lang.String, java.util.List<java.util.Locale>, java.lang.String, java.util.Date, java.util.Date);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.mbms.StreamingServiceInfo> CREATOR;
@@ -41078,6 +41448,21 @@
 
 }
 
+package android.telephony.mbms.vendor {
+
+  public class MbmsStreamingServiceBase extends android.os.Binder {
+    ctor public MbmsStreamingServiceBase();
+    method public void dispose(int) throws android.os.RemoteException;
+    method public android.net.Uri getPlaybackUri(int, java.lang.String) throws android.os.RemoteException;
+    method public int initialize(android.telephony.mbms.MbmsStreamingSessionCallback, int) throws android.os.RemoteException;
+    method public void onAppCallbackDied(int, int);
+    method public int requestUpdateStreamingServices(int, java.util.List<java.lang.String>) throws android.os.RemoteException;
+    method public int startStreaming(int, java.lang.String, android.telephony.mbms.StreamingServiceCallback) throws android.os.RemoteException;
+    method public void stopStreaming(int, java.lang.String) throws android.os.RemoteException;
+  }
+
+}
+
 package android.test {
 
   public abstract deprecated class ActivityInstrumentationTestCase<T extends android.app.Activity> extends android.test.ActivityTestCase {
@@ -46178,8 +46563,8 @@
     method protected boolean awakenScrollBars(int);
     method protected boolean awakenScrollBars(int, boolean);
     method public void bringToFront();
-    method public void buildDrawingCache();
-    method public void buildDrawingCache(boolean);
+    method public deprecated void buildDrawingCache();
+    method public deprecated void buildDrawingCache(boolean);
     method public void buildLayer();
     method public boolean callOnClick();
     method public boolean canResolveLayoutDirection();
@@ -46204,7 +46589,7 @@
     method protected int computeVerticalScrollRange();
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
-    method public void destroyDrawingCache();
+    method public deprecated void destroyDrawingCache();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public boolean dispatchCapturedPointerEvent(android.view.MotionEvent);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
@@ -46285,10 +46670,10 @@
     method public static int getDefaultSize(int, int);
     method public android.view.Display getDisplay();
     method public final int[] getDrawableState();
-    method public android.graphics.Bitmap getDrawingCache();
-    method public android.graphics.Bitmap getDrawingCache(boolean);
-    method public int getDrawingCacheBackgroundColor();
-    method public int getDrawingCacheQuality();
+    method public deprecated android.graphics.Bitmap getDrawingCache();
+    method public deprecated android.graphics.Bitmap getDrawingCache(boolean);
+    method public deprecated int getDrawingCacheBackgroundColor();
+    method public deprecated int getDrawingCacheQuality();
     method public void getDrawingRect(android.graphics.Rect);
     method public long getDrawingTime();
     method public float getElevation();
@@ -46417,8 +46802,8 @@
     method public boolean hasTransientState();
     method public boolean hasWindowFocus();
     method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup);
-    method public void invalidate(android.graphics.Rect);
-    method public void invalidate(int, int, int, int);
+    method public deprecated void invalidate(android.graphics.Rect);
+    method public deprecated void invalidate(int, int, int, int);
     method public void invalidate();
     method public void invalidateDrawable(android.graphics.drawable.Drawable);
     method public void invalidateOutline();
@@ -46429,7 +46814,7 @@
     method public boolean isContextClickable();
     method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
     method public boolean isDirty();
-    method public boolean isDrawingCacheEnabled();
+    method public deprecated boolean isDrawingCacheEnabled();
     method public boolean isDuplicateParentStateEnabled();
     method public boolean isEnabled();
     method public final boolean isFocusable();
@@ -46603,9 +46988,9 @@
     method public void setContentDescription(java.lang.CharSequence);
     method public void setContextClickable(boolean);
     method public void setDefaultFocusHighlightEnabled(boolean);
-    method public void setDrawingCacheBackgroundColor(int);
-    method public void setDrawingCacheEnabled(boolean);
-    method public void setDrawingCacheQuality(int);
+    method public deprecated void setDrawingCacheBackgroundColor(int);
+    method public deprecated void setDrawingCacheEnabled(boolean);
+    method public deprecated void setDrawingCacheQuality(int);
     method public void setDuplicateParentStateEnabled(boolean);
     method public void setElevation(float);
     method public void setEnabled(boolean);
@@ -46757,9 +47142,9 @@
     field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
     field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
-    field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
-    field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
-    field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
+    field public static final deprecated int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
+    field public static final deprecated int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
+    field public static final deprecated int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
     field protected static final int[] EMPTY_STATE_SET;
     field protected static final int[] ENABLED_FOCUSED_SELECTED_STATE_SET;
     field protected static final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
@@ -47155,7 +47540,7 @@
     method public android.animation.LayoutTransition getLayoutTransition();
     method public int getNestedScrollAxes();
     method public android.view.ViewGroupOverlay getOverlay();
-    method public int getPersistentDrawingCache();
+    method public deprecated int getPersistentDrawingCache();
     method public boolean getTouchscreenBlocksFocus();
     method public int indexOfChild(android.view.View);
     method public final deprecated void invalidateChild(android.view.View, android.graphics.Rect);
@@ -47208,7 +47593,7 @@
     method public void setAddStatesFromChildren(boolean);
     method public deprecated void setAlwaysDrawnWithCacheEnabled(boolean);
     method public deprecated void setAnimationCacheEnabled(boolean);
-    method protected void setChildrenDrawingCacheEnabled(boolean);
+    method protected deprecated void setChildrenDrawingCacheEnabled(boolean);
     method protected void setChildrenDrawingOrderEnabled(boolean);
     method protected deprecated void setChildrenDrawnWithCacheEnabled(boolean);
     method public void setClipChildren(boolean);
@@ -47220,7 +47605,7 @@
     method public void setLayoutTransition(android.animation.LayoutTransition);
     method public void setMotionEventSplittingEnabled(boolean);
     method public void setOnHierarchyChangeListener(android.view.ViewGroup.OnHierarchyChangeListener);
-    method public void setPersistentDrawingCache(int);
+    method public deprecated void setPersistentDrawingCache(int);
     method protected void setStaticTransformationsEnabled(boolean);
     method public void setTouchscreenBlocksFocus(boolean);
     method public void setTransitionGroup(boolean);
@@ -47238,10 +47623,10 @@
     field public static final int FOCUS_BLOCK_DESCENDANTS = 393216; // 0x60000
     field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
     field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
-    field public static final int PERSISTENT_ALL_CACHES = 3; // 0x3
-    field public static final int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
-    field public static final int PERSISTENT_NO_CACHE = 0; // 0x0
-    field public static final int PERSISTENT_SCROLLING_CACHE = 2; // 0x2
+    field public static final deprecated int PERSISTENT_ALL_CACHES = 3; // 0x3
+    field public static final deprecated int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
+    field public static final deprecated int PERSISTENT_NO_CACHE = 0; // 0x0
+    field public static final deprecated int PERSISTENT_SCROLLING_CACHE = 2; // 0x2
   }
 
   public static class ViewGroup.LayoutParams {
@@ -47439,6 +47824,9 @@
     method public abstract void setInputType(int);
     method public abstract void setLocaleList(android.os.LocaleList);
     method public abstract void setLongClickable(boolean);
+    method public void setMaxTextEms(int);
+    method public void setMaxTextLength(int);
+    method public void setMinTextEms(int);
     method public abstract void setOpaque(boolean);
     method public abstract void setSelected(boolean);
     method public abstract void setText(java.lang.CharSequence);
@@ -49170,19 +49558,26 @@
 package android.view.textclassifier {
 
   public final class TextClassification {
+    method public int getActionCount();
     method public float getConfidenceScore(java.lang.String);
     method public java.lang.String getEntity(int);
     method public int getEntityCount();
+    method public android.graphics.drawable.Drawable getIcon(int);
     method public android.graphics.drawable.Drawable getIcon();
+    method public android.content.Intent getIntent(int);
     method public android.content.Intent getIntent();
+    method public java.lang.CharSequence getLabel(int);
     method public java.lang.CharSequence getLabel();
+    method public android.view.View.OnClickListener getOnClickListener(int);
     method public android.view.View.OnClickListener getOnClickListener();
     method public java.lang.String getText();
   }
 
   public static final class TextClassification.Builder {
     ctor public TextClassification.Builder();
+    method public android.view.textclassifier.TextClassification.Builder addAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
     method public android.view.textclassifier.TextClassification build();
+    method public android.view.textclassifier.TextClassification.Builder clearActions();
     method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
     method public android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable);
     method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
@@ -52879,6 +53274,8 @@
     field public static final int OP_CONST_CLASS = 28; // 0x1c
     field public static final int OP_CONST_CLASS_JUMBO = 255; // 0xff
     field public static final int OP_CONST_HIGH16 = 21; // 0x15
+    field public static final int OP_CONST_METHOD_HANDLE = 254; // 0xfe
+    field public static final int OP_CONST_METHOD_TYPE = 255; // 0xff
     field public static final int OP_CONST_STRING = 26; // 0x1a
     field public static final int OP_CONST_STRING_JUMBO = 27; // 0x1b
     field public static final int OP_CONST_WIDE = 24; // 0x18
diff --git a/cmds/am/Android.bp b/cmds/am/Android.bp
index 7eb4edf..bb16df1 100644
--- a/cmds/am/Android.bp
+++ b/cmds/am/Android.bp
@@ -4,6 +4,7 @@
 cc_library_host_static {
     name: "libinstrumentation",
     srcs: ["**/*.proto"],
+    cflags: ["-Wall", "-Werror"],
     proto: {
         type: "full",
         export_proto_headers: true,
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index ab075ee..79e7fac 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -98,7 +98,8 @@
     static final class MyShellCallback extends ShellCallback {
         boolean mActive = true;
 
-        @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+        @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+                String mode) {
             if (!mActive) {
                 System.err.println("Open attempt after active for: " + path);
                 return null;
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 73ec63f..e5d35b3 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -24,16 +24,15 @@
     BootAnimationUtil.cpp \
 
 ifeq ($(PRODUCT_IOT),true)
-LOCAL_SRC_FILES += \
-    iot/iotbootanimation_main.cpp \
-    iot/BootAction.cpp
 
 LOCAL_SHARED_LIBRARIES += \
     libandroidthings \
-    libbase \
-    libbinder
+    libchrome \
 
-LOCAL_STATIC_LIBRARIES += cpufeatures
+LOCAL_SRC_FILES += \
+    iot/iotbootanimation_main.cpp \
+    iot/BootAction.cpp \
+    iot/BootParameters.cpp \
 
 else
 
@@ -77,13 +76,19 @@
     libutils \
     libbinder \
     libui \
-    libskia \
+    libhwui \
     libEGL \
     libGLESv1_CM \
     libgui \
     libtinyalsa \
     libbase
 
+ifeq ($(PRODUCT_IOT),true)
+
+LOCAL_INIT_RC := iot/bootanim_iot.rc
+
+endif # PRODUCT_IOT
+
 ifdef TARGET_32_BIT_SURFACEFLINGER
 LOCAL_32_BIT_ONLY := true
 endif
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 6526123..d1af71d 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -260,9 +260,9 @@
     sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
             dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
 
-    SurfaceComposerClient::openGlobalTransaction();
-    control->setLayer(0x40000000);
-    SurfaceComposerClient::closeGlobalTransaction();
+    SurfaceComposerClient::Transaction t;
+    t.setLayer(control, 0x40000000)
+        .apply();
 
     sp<Surface> s = control->getSurface();
 
diff --git a/cmds/bootanimation/iot/BootAction.cpp b/cmds/bootanimation/iot/BootAction.cpp
index 889eb2f..fa79744 100644
--- a/cmds/bootanimation/iot/BootAction.cpp
+++ b/cmds/bootanimation/iot/BootAction.cpp
@@ -18,17 +18,11 @@
 
 #define LOG_TAG "BootAction"
 
-#include <android-base/strings.h>
-#include <cpu-features.h>
 #include <dlfcn.h>
+
 #include <pio/peripheral_manager_client.h>
 #include <utils/Log.h>
 
-using android::base::Split;
-using android::base::Join;
-using android::base::StartsWith;
-using android::base::EndsWith;
-
 namespace android {
 
 BootAction::~BootAction() {
@@ -37,7 +31,8 @@
     }
 }
 
-bool BootAction::init(const std::string& libraryPath) {
+bool BootAction::init(const std::string& libraryPath,
+                      const std::vector<ABootActionParameter>& parameters) {
     APeripheralManagerClient* client = nullptr;
     ALOGD("Connecting to peripheralmanager");
     // Wait for peripheral manager to come up.
@@ -51,6 +46,7 @@
     ALOGD("Peripheralmanager is up.");
     APeripheralManagerClient_delete(client);
 
+
     ALOGI("Loading boot action %s", libraryPath.c_str());
     mLibHandle = dlopen(libraryPath.c_str(), RTLD_NOW);
     if (mLibHandle == nullptr) {
@@ -82,7 +78,7 @@
     }
 
     ALOGD("Entering boot_action_init");
-    bool result = mLibInit();
+    bool result = mLibInit(parameters.data(), parameters.size());
     ALOGD("Returned from boot_action_init");
     return result;
 }
diff --git a/cmds/bootanimation/iot/BootAction.h b/cmds/bootanimation/iot/BootAction.h
index 495aa4f..5e2495f 100644
--- a/cmds/bootanimation/iot/BootAction.h
+++ b/cmds/bootanimation/iot/BootAction.h
@@ -18,7 +18,9 @@
 #define _BOOTANIMATION_BOOTACTION_H
 
 #include <string>
+#include <vector>
 
+#include <boot_action/boot_action.h>  // libandroidthings native API.
 #include <utils/RefBase.h>
 
 namespace android {
@@ -28,7 +30,8 @@
     ~BootAction();
 
     // libraryPath is a fully qualified path to the target .so library.
-    bool init(const std::string& libraryPath);
+    bool init(const std::string& libraryPath,
+              const std::vector<ABootActionParameter>& parameters);
 
     // The animation is going to start playing partNumber for the playCount'th
     // time, update the action as needed.
@@ -41,7 +44,8 @@
     void shutdown();
 
 private:
-    typedef bool (*libInit)();
+    typedef bool (*libInit)(const ABootActionParameter* parameters,
+                            size_t num_parameters);
     typedef void (*libStartPart)(int partNumber, int playNumber);
     typedef void (*libShutdown)();
 
diff --git a/cmds/bootanimation/iot/BootParameters.cpp b/cmds/bootanimation/iot/BootParameters.cpp
new file mode 100644
index 0000000..da6ad0d
--- /dev/null
+++ b/cmds/bootanimation/iot/BootParameters.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 "BootParameters.h"
+
+#define LOG_TAG "BootParameters"
+
+#include <fcntl.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <base/json/json_parser.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_value_converter.h>
+#include <utils/Log.h>
+
+using android::base::RemoveFileIfExists;
+using android::base::ReadFileToString;
+using base::JSONReader;
+using base::JSONValueConverter;
+using base::Value;
+
+namespace android {
+
+namespace {
+
+// Brightness and volume are stored as integer strings in next_boot.json.
+// They are divided by this constant to produce the actual float values in
+// range [0.0, 1.0]. This constant must match its counterpart in
+// DeviceManager.
+constexpr const float kFloatScaleFactor = 1000.0f;
+
+constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json";
+constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json";
+
+void swapBootConfigs() {
+    // rename() will fail if next_boot.json doesn't exist, so delete
+    // last_boot.json manually first.
+    std::string err;
+    if (!RemoveFileIfExists(kLastBootFile, &err))
+        ALOGE("Unable to delete last boot file: %s", err.c_str());
+
+    if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT)
+        ALOGE("Unable to swap boot files: %s", strerror(errno));
+
+    int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE);
+    if (fd == -1) {
+        ALOGE("Unable to create next boot file: %s", strerror(errno));
+    } else {
+        // Make next_boot.json writable to everyone so DeviceManagementService
+        // can save saved_parameters there.
+        if (fchmod(fd, DEFFILEMODE))
+            ALOGE("Unable to set next boot file permissions: %s", strerror(errno));
+        close(fd);
+    }
+}
+
+}  // namespace
+
+BootParameters::SavedBootParameters::SavedBootParameters()
+    : brightness(-kFloatScaleFactor), volume(-kFloatScaleFactor) {}
+
+void BootParameters::SavedBootParameters::RegisterJSONConverter(
+        JSONValueConverter<SavedBootParameters>* converter) {
+    converter->RegisterIntField("brightness", &SavedBootParameters::brightness);
+    converter->RegisterIntField("volume", &SavedBootParameters::volume);
+    converter->RegisterRepeatedString("param_names",
+                                      &SavedBootParameters::param_names);
+    converter->RegisterRepeatedString("param_values",
+                                      &SavedBootParameters::param_values);
+}
+
+BootParameters::BootParameters() {
+    swapBootConfigs();
+    loadParameters();
+}
+
+void BootParameters::loadParameters() {
+    std::string contents;
+    if (!ReadFileToString(kLastBootFile, &contents)) {
+        if (errno != ENOENT)
+            ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno));
+
+        return;
+    }
+
+    std::unique_ptr<Value> json = JSONReader::Read(contents);
+    if (json.get() == nullptr) {
+        return;
+    }
+
+    JSONValueConverter<SavedBootParameters> converter;
+    if (converter.Convert(*(json.get()), &mRawParameters)) {
+        mBrightness = mRawParameters.brightness / kFloatScaleFactor;
+        mVolume = mRawParameters.volume / kFloatScaleFactor;
+
+        if (mRawParameters.param_names.size() == mRawParameters.param_values.size()) {
+            for (size_t i = 0; i < mRawParameters.param_names.size(); i++) {
+                mParameters.push_back({
+                        .key = mRawParameters.param_names[i]->c_str(),
+                        .value = mRawParameters.param_values[i]->c_str()
+                });
+            }
+        } else {
+            ALOGW("Parameter names and values size mismatch");
+        }
+    }
+}
+
+}  // namespace android
diff --git a/cmds/bootanimation/iot/BootParameters.h b/cmds/bootanimation/iot/BootParameters.h
new file mode 100644
index 0000000..ff3b018
--- /dev/null
+++ b/cmds/bootanimation/iot/BootParameters.h
@@ -0,0 +1,73 @@
+/*
+ * 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 _BOOTANIMATION_BOOT_PARAMETERS_H_
+#define _BOOTANIMATION_BOOT_PARAMETERS_H_
+
+#include <list>
+#include <vector>
+
+#include <base/json/json_value_converter.h>
+#include <boot_action/boot_action.h>  // libandroidthings native API.
+
+namespace android {
+
+// Provides access to the parameters set by DeviceManager.reboot().
+class BootParameters {
+public:
+    // Constructor loads the parameters for this boot and swaps the param files
+    // to clear the parameters for next boot.
+    BootParameters();
+
+    // Returns true if volume/brightness were explicitly set on reboot.
+    bool hasVolume() const { return mVolume >= 0; }
+    bool hasBrightness() const { return mBrightness >= 0; }
+
+    // Returns volume/brightness in [0,1], or -1 if unset.
+    float getVolume() const { return mVolume; }
+    float getBrightness() const { return mBrightness; }
+
+    // Returns the additional boot parameters that were set on reboot.
+    const std::vector<ABootActionParameter>& getParameters() const { return mParameters; }
+
+private:
+    // Raw boot saved_parameters loaded from .json.
+    struct SavedBootParameters {
+        int brightness;
+        int volume;
+        ScopedVector<std::string> param_names;
+        ScopedVector<std::string> param_values;
+
+        SavedBootParameters();
+        static void RegisterJSONConverter(
+                ::base::JSONValueConverter<SavedBootParameters>* converter);
+    };
+
+    void loadParameters();
+
+    float mVolume = -1.f;
+    float mBrightness = -1.f;
+    std::vector<ABootActionParameter> mParameters;
+
+    // ABootActionParameter is just a raw pointer so we need to keep the
+    // original strings around to avoid losing them.
+    SavedBootParameters mRawParameters;
+};
+
+}  // namespace android
+
+
+#endif  // _BOOTANIMATION_BOOT_PARAMETERS_H_
diff --git a/cmds/bootanimation/iot/bootanim_iot.rc b/cmds/bootanimation/iot/bootanim_iot.rc
new file mode 100644
index 0000000..2fc1336
--- /dev/null
+++ b/cmds/bootanimation/iot/bootanim_iot.rc
@@ -0,0 +1,2 @@
+on post-fs-data
+    mkdir /data/misc/bootanimation 0777 root root
diff --git a/cmds/bootanimation/iot/iotbootanimation_main.cpp b/cmds/bootanimation/iot/iotbootanimation_main.cpp
index d62478b..00cef43 100644
--- a/cmds/bootanimation/iot/iotbootanimation_main.cpp
+++ b/cmds/bootanimation/iot/iotbootanimation_main.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "IotBootAnimation"
 
-#include <android-base/file.h>
+#include <base/files/file_util.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
@@ -28,30 +28,38 @@
 
 #include "BootAction.h"
 #include "BootAnimationUtil.h"
+#include "BootParameters.h"
 
 using namespace android;
-using android::base::ReadFileToString;
 
 // Create a typedef for readability.
 typedef android::BootAnimation::Animation Animation;
 
 namespace {
 
-class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {public:
+constexpr const char* kDefaultLibName = "libbootaction.so";
+
+class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {
+public:
+    BootActionAnimationCallbacks(std::unique_ptr<BootParameters> bootParameters)
+        : mBootParameters(std::move(bootParameters)) {}
+
     void init(const Vector<Animation::Part>&) override {
         std::string library_path("/oem/lib/");
 
         // This value is optionally provided by the user and will be written to
         // /oem/oem.prop.
         char property[PROP_VALUE_MAX] = {0};
-        if (property_get("ro.oem.bootactions.lib", property, "") < 1) {
-            ALOGI("No bootaction specified");
-            return;
-        }
+        property_get("ro.oem.bootactions.lib", property, kDefaultLibName);
         library_path += property;
 
+        if (!::base::PathExists(::base::FilePath(library_path))) {
+            ALOGI("Skipping boot actions: %s does not exist", library_path.c_str());
+            return;
+        }
+
         mBootAction = new BootAction();
-        if (!mBootAction->init(library_path)) {
+        if (!mBootAction->init(library_path, mBootParameters->getParameters())) {
             mBootAction = NULL;
         }
     };
@@ -64,6 +72,20 @@
 
     void shutdown() override {
         if (mBootAction != nullptr) {
+            // If we have a bootaction we want to wait until we are actually
+            // told to shut down. If the animation exits early keep the action
+            // running.
+            char value[PROPERTY_VALUE_MAX] = {0};
+            for (int exitRequested = 0; exitRequested == 0; ) {
+                property_get("service.bootanim.exit", value, "0");
+                exitRequested = atoi(value);
+
+                // Poll value at 10hz.
+                if (exitRequested == 0) {
+                  usleep(100000);
+                }
+            }
+
             mBootAction->shutdown();
             // Give it two seconds to shut down.
             sleep(2);
@@ -72,6 +94,7 @@
     };
 
 private:
+    std::unique_ptr<BootParameters> mBootParameters;
     sp<BootAction> mBootAction = nullptr;
 };
 
@@ -80,6 +103,9 @@
 int main() {
     setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
 
+    // Clear our params for next boot no matter what.
+    std::unique_ptr<BootParameters> bootParameters(new BootParameters());
+
     if (bootAnimationDisabled()) {
         ALOGI("boot animation disabled");
         return 0;
@@ -90,7 +116,8 @@
     sp<ProcessState> proc(ProcessState::self());
     ProcessState::self()->startThreadPool();
 
-    sp<BootAnimation> boot = new BootAnimation(new BootActionAnimationCallbacks());
+    sp<BootAnimation> boot = new BootAnimation(
+            new BootActionAnimationCallbacks(std::move(bootParameters)));
 
     IPCThreadState::self()->joinThreadPool();
     return 0;
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
index 830bf9e..cb5fd02 100644
--- a/cmds/incidentd/Android.mk
+++ b/cmds/incidentd/Android.mk
@@ -23,7 +23,7 @@
 LOCAL_MODULE := incidentd
 
 LOCAL_SRC_FILES := \
-        src/EncodedBuffer.cpp \
+        src/PrivacyBuffer.cpp \
         src/FdBuffer.cpp \
         src/IncidentService.cpp \
         src/Privacy.cpp \
@@ -31,7 +31,6 @@
         src/Section.cpp \
         src/io_util.cpp \
         src/main.cpp \
-        src/protobuf.cpp \
         src/report_directory.cpp
 
 LOCAL_CFLAGS += \
@@ -54,6 +53,7 @@
         libcutils \
         libincident \
         liblog \
+        libprotoutil \
         libselinux \
         libservices \
         libutils
@@ -93,16 +93,15 @@
 LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
 
 LOCAL_SRC_FILES := \
-    src/EncodedBuffer.cpp \
+    src/PrivacyBuffer.cpp \
     src/FdBuffer.cpp \
     src/Privacy.cpp \
     src/Reporter.cpp \
     src/Section.cpp \
     src/io_util.cpp \
-    src/protobuf.cpp \
     src/report_directory.cpp \
     tests/section_list.cpp \
-    tests/EncodedBuffer_test.cpp \
+    tests/PrivacyBuffer_test.cpp \
     tests/FdBuffer_test.cpp \
     tests/Reporter_test.cpp \
     tests/Section_test.cpp \
@@ -116,6 +115,7 @@
     libcutils \
     libincident \
     liblog \
+    libprotoutil \
     libselinux \
     libservices \
     libutils \
diff --git a/cmds/incidentd/src/EncodedBuffer.cpp b/cmds/incidentd/src/EncodedBuffer.cpp
deleted file mode 100644
index e8f2c11..0000000
--- a/cmds/incidentd/src/EncodedBuffer.cpp
+++ /dev/null
@@ -1,195 +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.
- */
-
-#include "EncodedBuffer.h"
-#include "io_util.h"
-#include "protobuf.h"
-
-#include <deque>
-
-const size_t BUFFER_SIZE = 4 * 1024; // 4 KB
-
-/**
- * Read varint from iterator, the iterator will point to next available byte.
- * Return the number of bytes of the varint.
- */
-static uint32_t
-read_raw_varint(FdBuffer::iterator* it)
-{
-    uint32_t val = 0;
-    int i = 0;
-    bool hasNext = true;
-    while (hasNext) {
-        hasNext = ((**it & 0x80) != 0);
-        val += (**it & 0x7F) << (7*i);
-        (*it)++;
-        i++;
-    }
-    return val;
-}
-
-/**
- * Write the field to buf based on the wire type, iterator will point to next field.
- * If skip is set to true, no data will be written to buf. Return number of bytes written.
- */
-static size_t
-write_field_or_skip(FdBuffer::iterator* iter, vector<uint8_t>* buf, uint8_t wireType, bool skip)
-{
-    FdBuffer::iterator snapshot = iter->snapshot();
-    size_t bytesToWrite = 0;
-    uint32_t varint = 0;
-    switch (wireType) {
-        case WIRE_TYPE_VARINT:
-            varint = read_raw_varint(iter);
-            if(!skip) return write_raw_varint(buf, varint);
-            break;
-        case WIRE_TYPE_FIXED64:
-            bytesToWrite = 8;
-            break;
-        case WIRE_TYPE_LENGTH_DELIMITED:
-            bytesToWrite = read_raw_varint(iter);
-            if(!skip) write_raw_varint(buf, bytesToWrite);
-            break;
-        case WIRE_TYPE_FIXED32:
-            bytesToWrite = 4;
-            break;
-    }
-    if (skip) {
-        *iter += bytesToWrite;
-    } else {
-        for (size_t i=0; i<bytesToWrite; i++) {
-            buf->push_back(**iter);
-            (*iter)++;
-        }
-    }
-    return skip ? 0 : *iter - snapshot;
-}
-
-/**
- * Strip next field based on its private policy and request spec, then stores data in buf.
- * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer.
- *
- * The iterator must point to the head of a protobuf formatted field for successful operation.
- * After exit with NO_ERROR, iterator points to the next protobuf field's head.
- */
-static status_t
-stripField(FdBuffer::iterator* iter, vector<uint8_t>* buf, const Privacy* parentPolicy, const PrivacySpec& spec)
-{
-    if (iter->outOfBound() || parentPolicy == NULL) return BAD_VALUE;
-
-    uint32_t varint = read_raw_varint(iter);
-    uint8_t wireType = read_wire_type(varint);
-    uint32_t fieldId = read_field_id(varint);
-    const Privacy* policy = parentPolicy->lookup(fieldId);
-
-    if (policy == NULL || !policy->IsMessageType() || !policy->HasChildren()) {
-        bool skip = !spec.CheckPremission(policy);
-        size_t amt = buf->size();
-        if (!skip) amt += write_header(buf, fieldId, wireType);
-        amt += write_field_or_skip(iter, buf, wireType, skip); // point to head of next field
-        return buf->size() != amt ? BAD_VALUE : NO_ERROR;
-    }
-    // current field is message type and its sub-fields have extra privacy policies
-    deque<vector<uint8_t>> q;
-    uint32_t msgSize = read_raw_varint(iter);
-    size_t finalSize = 0;
-    FdBuffer::iterator start = iter->snapshot();
-    while ((*iter - start) != (int)msgSize) {
-        vector<uint8_t> v;
-        status_t err = stripField(iter, &v, policy, spec);
-        if (err != NO_ERROR) return err;
-        if (v.empty()) continue;
-        q.push_back(v);
-        finalSize += v.size();
-    }
-
-    write_header(buf, fieldId, wireType);
-    write_raw_varint(buf, finalSize);
-    buf->reserve(finalSize); // reserve the size of the field
-    while (!q.empty()) {
-        vector<uint8_t> subField = q.front();
-        for (vector<uint8_t>::iterator it = subField.begin(); it != subField.end(); it++) {
-            buf->push_back(*it);
-        }
-        q.pop_front();
-    }
-    return NO_ERROR;
-}
-
-// ================================================================================
-EncodedBuffer::EncodedBuffer(const FdBuffer& buffer, const Privacy* policy)
-        : mFdBuffer(buffer),
-          mPolicy(policy),
-          mBuffers(),
-          mSize(0)
-{
-}
-
-EncodedBuffer::~EncodedBuffer()
-{
-}
-
-status_t
-EncodedBuffer::strip(const PrivacySpec& spec)
-{
-    // optimization when no strip happens
-    if (mPolicy == NULL || !mPolicy->HasChildren() || spec.RequireAll()) {
-        if (spec.CheckPremission(mPolicy)) mSize = mFdBuffer.size();
-        return NO_ERROR;
-    }
-
-    FdBuffer::iterator it = mFdBuffer.begin();
-    vector<uint8_t> field;
-    field.reserve(BUFFER_SIZE);
-
-    while (it != mFdBuffer.end()) {
-        status_t err = stripField(&it, &field, mPolicy, spec);
-        if (err != NO_ERROR) return err;
-        if (field.size() > BUFFER_SIZE) { // rotate to another chunk if buffer size exceeds
-            mBuffers.push_back(field);
-            mSize += field.size();
-            field.clear();
-        }
-    }
-    if (!field.empty()) {
-        mBuffers.push_back(field);
-        mSize += field.size();
-    }
-    return NO_ERROR;
-}
-
-void
-EncodedBuffer::clear()
-{
-    mSize = 0;
-    mBuffers.clear();
-}
-
-size_t
-EncodedBuffer::size() const { return mSize; }
-
-status_t
-EncodedBuffer::flush(int fd)
-{
-    if (size() == mFdBuffer.size()) return mFdBuffer.flush(fd);
-
-    for (vector<vector<uint8_t>>::iterator it = mBuffers.begin(); it != mBuffers.end(); it++) {
-        status_t err = write_all(fd, it->data(), it->size());
-        if (err != NO_ERROR) return err;
-    }
-    return NO_ERROR;
-}
-
diff --git a/cmds/incidentd/src/EncodedBuffer.h b/cmds/incidentd/src/EncodedBuffer.h
deleted file mode 100644
index ea8603a..0000000
--- a/cmds/incidentd/src/EncodedBuffer.h
+++ /dev/null
@@ -1,64 +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.
- */
-
-#ifndef ENCODED_BUFFER_H
-#define ENCODED_BUFFER_H
-
-#include "FdBuffer.h"
-#include "Privacy.h"
-
-#include <stdint.h>
-#include <vector>
-
-/**
- * EncodedBuffer is constructed from FdBuffer which holds original protobuf formatted data and
- * its privacy policy in its tagged proto message. The class strips PII-sensitive fields
- * based on the request and holds stripped data in its buffer for output.
- */
-class EncodedBuffer
-{
-public:
-    EncodedBuffer(const FdBuffer& buffer, const Privacy* policy);
-    ~EncodedBuffer();
-
-    /**
-     * Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip succeeds.
-     */
-    status_t strip(const PrivacySpec& spec);
-
-    /**
-     * Clear encoded buffer so it can be reused by another request.
-     */
-    void clear();
-
-    /**
-     * Return the size of the stripped data.
-     */
-    size_t size() const;
-
-    /**
-     * Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds.
-     */
-    status_t flush(int fd);
-
-private:
-    const FdBuffer& mFdBuffer;
-    const Privacy* mPolicy;
-    vector<vector<uint8_t>> mBuffers;
-    size_t mSize;
-};
-
-#endif // ENCODED_BUFFER_H
\ No newline at end of file
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index bb399b5..b7633a4 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "incidentd"
 
 #include "FdBuffer.h"
-#include "io_util.h"
 
 #include <cutils/log.h>
 #include <utils/SystemClock.h>
@@ -31,10 +30,9 @@
 const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max
 
 FdBuffer::FdBuffer()
-    :mBuffers(),
+    :mBuffer(BUFFER_SIZE),
      mStartTime(-1),
      mFinishTime(-1),
-     mCurrentWritten(-1),
      mTimedOut(false),
      mTruncated(false)
 {
@@ -42,11 +40,6 @@
 
 FdBuffer::~FdBuffer()
 {
-    const int N = mBuffers.size();
-    for (int i=0; i<N; i++) {
-        uint8_t* buf = mBuffers[i];
-        free(buf);
-    }
 }
 
 status_t
@@ -60,20 +53,12 @@
 
     fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
 
-    uint8_t* buf = NULL;
     while (true) {
-        if (mCurrentWritten >= BUFFER_SIZE || mCurrentWritten < 0) {
-            if (mBuffers.size() == MAX_BUFFER_COUNT) {
-                mTruncated = true;
-                break;
-            }
-            buf = (uint8_t*)malloc(BUFFER_SIZE);
-            if (buf == NULL) {
-                return NO_MEMORY;
-            }
-            mBuffers.push_back(buf);
-            mCurrentWritten = 0;
+        if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
+            mTruncated = true;
+            break;
         }
+        if (mBuffer.writeBuffer() == NULL) return NO_MEMORY;
 
         int64_t remainingTime = (mStartTime + timeout) - uptimeMillis();
         if (remainingTime <= 0) {
@@ -91,7 +76,7 @@
             if ((pfds.revents & POLLERR) != 0) {
                 return errno != 0 ? -errno : UNKNOWN_ERROR;
             } else {
-                ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten);
+                ssize_t amt = ::read(fd, mBuffer.writeBuffer(), mBuffer.currentToWrite());
                 if (amt < 0) {
                     if (errno == EAGAIN || errno == EWOULDBLOCK) {
                         continue;
@@ -101,11 +86,10 @@
                 } else if (amt == 0) {
                     break;
                 }
-                mCurrentWritten += amt;
+                mBuffer.wp()->move(amt);
             }
         }
     }
-
     mFinishTime = uptimeMillis();
     return NO_ERROR;
 }
@@ -132,20 +116,12 @@
     int rpos = 0, wpos = 0;
 
     // This is the buffer used to store processed data
-    uint8_t* buf = NULL;
     while (true) {
-        if (mCurrentWritten >= BUFFER_SIZE || mCurrentWritten < 0) {
-            if (mBuffers.size() == MAX_BUFFER_COUNT) {
-                mTruncated = true;
-                break;
-            }
-            buf = (uint8_t*)malloc(BUFFER_SIZE);
-            if (buf == NULL) {
-                return NO_MEMORY;
-            }
-            mBuffers.push_back(buf);
-            mCurrentWritten = 0;
+        if (mBuffer.size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) {
+            mTruncated = true;
+            break;
         }
+        if (mBuffer.writeBuffer() == NULL) return NO_MEMORY;
 
         int64_t remainingTime = (mStartTime + timeoutMs) - uptimeMillis();
         if (remainingTime <= 0) {
@@ -223,7 +199,7 @@
         }
 
         // read from parsing process
-        ssize_t amt = ::read(fromFd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten);
+        ssize_t amt = ::read(fromFd, mBuffer.writeBuffer(), mBuffer.currentToWrite());
         if (amt < 0) {
             if (!(errno == EAGAIN || errno == EWOULDBLOCK)) {
                 return -errno;
@@ -231,7 +207,7 @@
         } else if (amt == 0) {
             break;
         } else {
-            mCurrentWritten += amt;
+            mBuffer.wp()->move(amt);
         }
     }
 
@@ -242,105 +218,11 @@
 size_t
 FdBuffer::size() const
 {
-    if (mBuffers.empty()) return 0;
-    return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten;
+    return mBuffer.size();
 }
 
-status_t
-FdBuffer::flush(int fd) const
+EncodedBuffer::iterator
+FdBuffer::data() const
 {
-    size_t i=0;
-    status_t err = NO_ERROR;
-    for (i=0; i<mBuffers.size()-1; i++) {
-        err = write_all(fd, mBuffers[i], BUFFER_SIZE);
-        if (err != NO_ERROR) return err;
-    }
-    return write_all(fd, mBuffers[i], mCurrentWritten);
-}
-
-FdBuffer::iterator
-FdBuffer::begin() const
-{
-    return iterator(*this, 0, 0);
-}
-
-FdBuffer::iterator
-FdBuffer::end() const
-{
-    if (mBuffers.empty() || mCurrentWritten < 0) return begin();
-    if (mCurrentWritten == BUFFER_SIZE)
-        // FdBuffer doesn't allocate another buf since no more bytes to read.
-        return FdBuffer::iterator(*this, mBuffers.size(), 0);
-    return FdBuffer::iterator(*this, mBuffers.size() - 1, mCurrentWritten);
-}
-
-// ===============================================================================
-FdBuffer::iterator::iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset)
-        : mFdBuffer(buffer),
-          mIndex(index),
-          mOffset(offset)
-{
-}
-
-FdBuffer::iterator&
-FdBuffer::iterator::operator=(iterator& other) const { return other; }
-
-FdBuffer::iterator&
-FdBuffer::iterator::operator+(size_t offset)
-{
-    size_t newOffset = mOffset + offset;
-    while (newOffset >= BUFFER_SIZE) {
-        mIndex++;
-        newOffset -= BUFFER_SIZE;
-    }
-    mOffset = newOffset;
-    return *this;
-}
-
-FdBuffer::iterator&
-FdBuffer::iterator::operator+=(size_t offset) { return *this + offset; }
-
-FdBuffer::iterator&
-FdBuffer::iterator::operator++() { return *this + 1; }
-
-FdBuffer::iterator
-FdBuffer::iterator::operator++(int) { return *this + 1; }
-
-bool
-FdBuffer::iterator::operator==(iterator other) const
-{
-    return mIndex == other.mIndex && mOffset == other.mOffset;
-}
-
-bool
-FdBuffer::iterator::operator!=(iterator other) const { return !(*this == other); }
-
-int
-FdBuffer::iterator::operator-(iterator other) const
-{
-    return (int)bytesRead() - (int)other.bytesRead();
-}
-
-FdBuffer::iterator::reference
-FdBuffer::iterator::operator*() const
-{
-    return mFdBuffer.mBuffers[mIndex][mOffset];
-}
-
-FdBuffer::iterator
-FdBuffer::iterator::snapshot() const
-{
-    return FdBuffer::iterator(mFdBuffer, mIndex, mOffset);
-}
-
-size_t
-FdBuffer::iterator::bytesRead() const
-{
-    return mIndex * BUFFER_SIZE + mOffset;
-}
-
-bool
-FdBuffer::iterator::outOfBound() const
-{
-    return bytesRead() > mFdBuffer.size();
+    return mBuffer.begin();
 }
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
index dfe39c6..8857ae7 100644
--- a/cmds/incidentd/src/FdBuffer.h
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -17,11 +17,11 @@
 #ifndef FD_BUFFER_H
 #define FD_BUFFER_H
 
+#include <android/util/EncodedBuffer.h>
 #include <utils/Errors.h>
 
-#include <vector>
-
 using namespace android;
+using namespace android::util;
 using namespace std;
 
 /**
@@ -71,52 +71,19 @@
     size_t size() const;
 
     /**
-     * Flush all the data to given file descriptor;
-     */
-    status_t flush(int fd) const;
-
-    /**
      * How long the read took in milliseconds.
      */
     int64_t durationMs() const { return mFinishTime - mStartTime; }
 
     /**
-     * Read data stored in FdBuffer
+     * Reader API for data stored in FdBuffer
      */
-    class iterator;
-    friend class iterator;
-    class iterator : public std::iterator<std::random_access_iterator_tag, uint8_t> {
-    public:
-        iterator(const FdBuffer& buffer, ssize_t index, ssize_t offset);
-        iterator& operator=(iterator& other) const;
-        iterator& operator+(size_t offset);
-        iterator& operator+=(size_t offset);
-        iterator& operator++();
-        iterator operator++(int);
-        bool operator==(iterator other) const;
-        bool operator!=(iterator other) const;
-        int operator-(iterator other) const;
-        reference operator*() const;
-
-        // return the snapshot of the current iterator
-        iterator snapshot() const;
-        // how many bytes are read
-        size_t bytesRead() const;
-        // random access could make the iterator out of bound
-        bool outOfBound() const;
-    private:
-        const FdBuffer& mFdBuffer;
-        size_t mIndex;
-        size_t mOffset;
-    };
-    iterator begin() const;
-    iterator end() const;
+    EncodedBuffer::iterator data() const;
 
 private:
-    vector<uint8_t*> mBuffers;
+    EncodedBuffer mBuffer;
     int64_t mStartTime;
     int64_t mFinishTime;
-    ssize_t mCurrentWritten;
     bool mTimedOut;
     bool mTruncated;
 };
diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp
index e7969e7..140b12c 100644
--- a/cmds/incidentd/src/Privacy.cpp
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -30,6 +30,9 @@
 bool
 Privacy::IsMessageType() const { return type == TYPE_MESSAGE; }
 
+uint64_t
+Privacy::EncodedFieldId() const { return (uint64_t)type << 32 | field_id; }
+
 bool
 Privacy::IsStringType() const { return type == TYPE_STRING; }
 
diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h
index 7f1977e..f514f19 100644
--- a/cmds/incidentd/src/Privacy.h
+++ b/cmds/incidentd/src/Privacy.h
@@ -40,6 +40,8 @@
     bool IsMessageType() const;
     bool IsStringType() const;
     bool HasChildren() const;
+    uint64_t EncodedFieldId() const;
+
     const Privacy* lookup(uint32_t fieldId) const;
 };
 
diff --git a/cmds/incidentd/src/PrivacyBuffer.cpp b/cmds/incidentd/src/PrivacyBuffer.cpp
new file mode 100644
index 0000000..d926ea7
--- /dev/null
+++ b/cmds/incidentd/src/PrivacyBuffer.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "PrivacyBuffer.h"
+#include "io_util.h"
+
+#include <android/util/protobuf.h>
+
+using namespace android::util;
+
+/**
+ * Write the field to buf based on the wire type, iterator will point to next field.
+ * If skip is set to true, no data will be written to buf. Return number of bytes written.
+ */
+void
+PrivacyBuffer::writeFieldOrSkip(uint32_t fieldTag, bool skip)
+{
+    uint8_t wireType = read_wire_type(fieldTag);
+    size_t bytesToWrite = 0;
+    uint32_t varint = 0;
+
+    switch (wireType) {
+        case WIRE_TYPE_VARINT:
+            varint = mData.readRawVarint();
+            if (!skip) {
+                mProto.writeRawVarint(fieldTag);
+                mProto.writeRawVarint(varint);
+            }
+            return;
+        case WIRE_TYPE_FIXED64:
+            if (!skip) mProto.writeRawVarint(fieldTag);
+            bytesToWrite = 8;
+            break;
+        case WIRE_TYPE_LENGTH_DELIMITED:
+            bytesToWrite = mData.readRawVarint();
+            if(!skip) mProto.writeLengthDelimitedHeader(read_field_id(fieldTag), bytesToWrite);
+            break;
+        case WIRE_TYPE_FIXED32:
+            if (!skip) mProto.writeRawVarint(fieldTag);
+            bytesToWrite = 4;
+            break;
+    }
+    if (skip) {
+        mData.rp()->move(bytesToWrite);
+    } else {
+        for (size_t i=0; i<bytesToWrite; i++) {
+            mProto.writeRawByte(mData.next());
+        }
+    }
+}
+
+/**
+ * Strip next field based on its private policy and request spec, then stores data in buf.
+ * Return NO_ERROR if succeeds, otherwise BAD_VALUE is returned to indicate bad data in FdBuffer.
+ *
+ * The iterator must point to the head of a protobuf formatted field for successful operation.
+ * After exit with NO_ERROR, iterator points to the next protobuf field's head.
+ */
+status_t
+PrivacyBuffer::stripField(const Privacy* parentPolicy, const PrivacySpec& spec)
+{
+    if (!mData.hasNext() || parentPolicy == NULL) return BAD_VALUE;
+    uint32_t fieldTag = mData.readRawVarint();
+    const Privacy* policy = parentPolicy->lookup(read_field_id(fieldTag));
+
+    if (policy == NULL || !policy->IsMessageType() || !policy->HasChildren()) {
+        bool skip = !spec.CheckPremission(policy);
+        // iterator will point to head of next field
+        writeFieldOrSkip(fieldTag, skip);
+        return NO_ERROR;
+    }
+    // current field is message type and its sub-fields have extra privacy policies
+    uint32_t msgSize = mData.readRawVarint();
+    EncodedBuffer::Pointer start = mData.rp()->copy();
+    while (mData.rp()->pos() - start.pos() != msgSize) {
+        long long token = mProto.start(policy->EncodedFieldId());
+        status_t err = stripField(policy, spec);
+        if (err != NO_ERROR) return err;
+        mProto.end(token);
+    }
+    return NO_ERROR;
+}
+
+// ================================================================================
+PrivacyBuffer::PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator& data)
+        :mPolicy(policy),
+         mData(data),
+         mProto(),
+         mSize(0)
+{
+}
+
+PrivacyBuffer::~PrivacyBuffer()
+{
+}
+
+status_t
+PrivacyBuffer::strip(const PrivacySpec& spec)
+{
+    // optimization when no strip happens
+    if (mPolicy == NULL || !mPolicy->HasChildren() || spec.RequireAll()) {
+        if (spec.CheckPremission(mPolicy)) mSize = mData.size();
+        return NO_ERROR;
+    }
+    while (mData.hasNext()) {
+        status_t err = stripField(mPolicy, spec);
+        if (err != NO_ERROR) return err;
+    }
+    if (mData.bytesRead() != mData.size()) return BAD_VALUE;
+    mSize = mProto.size();
+    mData.rp()->rewind(); // rewind the read pointer back to beginning after the strip.
+    return NO_ERROR;
+}
+
+void
+PrivacyBuffer::clear()
+{
+    mSize = 0;
+    mProto = ProtoOutputStream();
+}
+
+size_t
+PrivacyBuffer::size() const { return mSize; }
+
+status_t
+PrivacyBuffer::flush(int fd)
+{
+    status_t err = NO_ERROR;
+    EncodedBuffer::iterator iter = size() == mData.size() ? mData : mProto.data();
+    while (iter.readBuffer() != NULL) {
+        err = write_all(fd, iter.readBuffer(), iter.currentToRead());
+        iter.rp()->move(iter.currentToRead());
+        if (err != NO_ERROR) return err;
+    }
+    return NO_ERROR;
+}
diff --git a/cmds/incidentd/src/PrivacyBuffer.h b/cmds/incidentd/src/PrivacyBuffer.h
new file mode 100644
index 0000000..c9ca9a7
--- /dev/null
+++ b/cmds/incidentd/src/PrivacyBuffer.h
@@ -0,0 +1,71 @@
+/*
+ * 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 PRIVACY_BUFFER_H
+#define PRIVACY_BUFFER_H
+
+#include "Privacy.h"
+
+#include <android/util/EncodedBuffer.h>
+#include <android/util/ProtoOutputStream.h>
+#include <stdint.h>
+#include <utils/Errors.h>
+
+using namespace android;
+using namespace android::util;
+
+/**
+ * PrivacyBuffer holds the original protobuf data and strips PII-sensitive fields
+ * based on the request and holds stripped data in its own buffer for output.
+ */
+class PrivacyBuffer
+{
+public:
+    PrivacyBuffer(const Privacy* policy, EncodedBuffer::iterator& data);
+    ~PrivacyBuffer();
+
+    /**
+     * Strip based on the request and hold data in its own buffer. Return NO_ERROR if strip succeeds.
+     */
+    status_t strip(const PrivacySpec& spec);
+
+    /**
+     * Clear encoded buffer so it can be reused by another request.
+     */
+    void clear();
+
+    /**
+     * Return the size of the stripped data.
+     */
+    size_t size() const;
+
+    /**
+     * Flush buffer to the given fd. NO_ERROR is returned if the flush succeeds.
+     */
+    status_t flush(int fd);
+
+private:
+    const Privacy* mPolicy;
+    EncodedBuffer::iterator& mData;
+
+    ProtoOutputStream mProto;
+    size_t mSize;
+
+    status_t stripField(const Privacy* parentPolicy, const PrivacySpec& spec);
+    void writeFieldOrSkip(uint32_t fieldTag, bool skip);
+};
+
+#endif // PRIVACY_BUFFER_H
\ No newline at end of file
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 11347e2..34930aa 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -48,6 +48,10 @@
 
 ReportRequest::~ReportRequest()
 {
+    if (fd >= 0) {
+        // clean up the opened file descriptor
+        close(fd);
+    }
 }
 
 bool
@@ -179,8 +183,8 @@
             // Execute - go get the data and write it into the file descriptors.
             err = (*section)->Execute(&batch);
             if (err != NO_ERROR) {
-                ALOGW("Incident section %s (%d) failed. Stopping report.",
-                        (*section)->name.string(), id);
+                ALOGW("Incident section %s (%d) failed: %s. Stopping report.",
+                        (*section)->name.string(), id, strerror(-err));
                 goto DONE;
             }
 
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index 166fef0..892bcca 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -16,15 +16,15 @@
 
 #define LOG_TAG "incidentd"
 
-#include "EncodedBuffer.h"
 #include "FdBuffer.h"
 #include "Privacy.h"
+#include "PrivacyBuffer.h"
 #include "Section.h"
 
 #include "io_util.h"
-#include "protobuf.h"
 #include "section_list.h"
 
+#include <android/util/protobuf.h>
 #include <private/android_filesystem_config.h>
 #include <binder/IServiceManager.h>
 #include <map>
@@ -32,8 +32,13 @@
 #include <wait.h>
 #include <unistd.h>
 
+using namespace android::util;
 using namespace std;
 
+// special section ids
+const int FIELD_ID_INCIDENT_HEADER = 1;
+
+// incident section parameters
 const int   WAIT_MAX = 5;
 const struct timespec WAIT_INTERVAL_NS = {0, 200 * 1000 * 1000};
 const char* INCIDENT_HELPER = "/system/bin/incident_helper";
@@ -127,7 +132,8 @@
 write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* requests)
 {
     status_t err = -EBADF;
-    EncodedBuffer encodedBuffer(buffer, get_privacy_of_section(id));
+    EncodedBuffer::iterator data = buffer.data();
+    PrivacyBuffer privacyBuffer(get_privacy_of_section(id), data);
     int writeable = 0;
 
     // The streaming ones, group requests by spec in order to save unnecessary strip operations
@@ -143,34 +149,34 @@
 
     for (map<PrivacySpec, vector<sp<ReportRequest>>>::iterator mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) {
         PrivacySpec spec = mit->first;
-        err = encodedBuffer.strip(spec);
-        if (err != NO_ERROR) return err; // it means the encodedBuffer data is corrupted.
-        if (encodedBuffer.size() == 0) continue;
+        err = privacyBuffer.strip(spec);
+        if (err != NO_ERROR) return err; // it means the privacyBuffer data is corrupted.
+        if (privacyBuffer.size() == 0) continue;
 
         for (vector<sp<ReportRequest>>::iterator it = mit->second.begin(); it != mit->second.end(); it++) {
             sp<ReportRequest> request = *it;
-            err = write_section_header(request->fd, id, encodedBuffer.size());
+            err = write_section_header(request->fd, id, privacyBuffer.size());
             if (err != NO_ERROR) { request->err = err; continue; }
-            err = encodedBuffer.flush(request->fd);
+            err = privacyBuffer.flush(request->fd);
             if (err != NO_ERROR) { request->err = err; continue; }
             writeable++;
-            ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, encodedBuffer.size(), request->fd, spec.dest);
+            ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, privacyBuffer.size(), request->fd, spec.dest);
         }
-        encodedBuffer.clear();
+        privacyBuffer.clear();
     }
 
     // The dropbox file
     if (requests->mainFd() >= 0) {
-        err = encodedBuffer.strip(get_default_dropbox_spec());
+        err = privacyBuffer.strip(get_default_dropbox_spec());
         if (err != NO_ERROR) return err; // the buffer data is corrupted.
-        if (encodedBuffer.size() == 0) goto DONE;
+        if (privacyBuffer.size() == 0) goto DONE;
 
-        err = write_section_header(requests->mainFd(), id, encodedBuffer.size());
+        err = write_section_header(requests->mainFd(), id, privacyBuffer.size());
         if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; }
-        err = encodedBuffer.flush(requests->mainFd());
+        err = privacyBuffer.flush(requests->mainFd());
         if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; }
         writeable++;
-        ALOGD("Section %d flushed %zu bytes to dropbox %d", id, encodedBuffer.size(), requests->mainFd());
+        ALOGD("Section %d flushed %zu bytes to dropbox %d", id, privacyBuffer.size(), requests->mainFd());
     }
 
 DONE:
diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp
deleted file mode 100644
index 4fffec1..0000000
--- a/cmds/incidentd/src/protobuf.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2016 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 "protobuf.h"
-
-uint8_t read_wire_type(uint32_t varint)
-{
-    return (uint8_t) (varint & 0x07);
-}
-
-uint32_t read_field_id(uint32_t varint)
-{
-    return varint >> 3;
-}
-
-uint8_t*
-write_raw_varint(uint8_t* buf, uint32_t val)
-{
-    uint8_t* p = buf;
-    while (true) {
-        if ((val & ~0x7F) == 0) {
-            *p++ = (uint8_t)val;
-            return p;
-        } else {
-            *p++ = (uint8_t)((val & 0x7F) | 0x80);
-            val >>= 7;
-        }
-    }
-}
-
-uint8_t*
-write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
-{
-    buf = write_raw_varint(buf, (fieldId << 3) | 2);
-    buf = write_raw_varint(buf, size);
-    return buf;
-}
-
-size_t
-write_raw_varint(vector<uint8_t>* buf, uint32_t val)
-{
-    size_t size = 0;
-    while (true) {
-        size++;
-        if ((val & ~0x7F) == 0) {
-            buf->push_back((uint8_t) val);
-            return size;
-        } else {
-            buf->push_back((uint8_t)((val & 0x7F) | 0x80));
-            val >>= 7;
-        }
-    }
-}
-
-size_t
-write_header(vector<uint8_t>* buf, uint32_t fieldId, uint8_t wireType)
-{
-    return write_raw_varint(buf, (fieldId << 3) | wireType);
-}
\ No newline at end of file
diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h
deleted file mode 100644
index 263c864..0000000
--- a/cmds/incidentd/src/protobuf.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2016 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 PROTOBUF_H
-#define PROTOBUF_H
-
-#include <stdint.h>
-#include <vector>
-
-using namespace std;
-
-const uint8_t WIRE_TYPE_VARINT = 0;
-const uint8_t WIRE_TYPE_FIXED64 = 1;
-const uint8_t WIRE_TYPE_LENGTH_DELIMITED = 2;
-const uint8_t WIRE_TYPE_FIXED32 = 5;
-
-/**
- * Read the wire type from varint, it is the smallest 3 bits.
- */
-uint8_t read_wire_type(uint32_t varint);
-
-/**
- * read field id from varint, it is varint >> 3;
- */
-uint32_t read_field_id(uint32_t varint);
-
-/**
- * Write a varint into the buffer. Return the next position to write at.
- * There must be 10 bytes in the buffer. The same as
- * EncodedBuffer.writeRawVarint32
- */
-uint8_t* write_raw_varint(uint8_t* buf, uint32_t val);
-
-/**
- * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position
- * to write at. There must be 20 bytes in the buffer.
- */
-uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size);
-
-/**
- * Write a varint into a vector. Return the size of the varint.
- */
-size_t write_raw_varint(vector<uint8_t>* buf, uint32_t val);
-
-/**
- * Write a protobuf header. Return the size of the header.
- */
-size_t write_header(vector<uint8_t>* buf, uint32_t fieldId, uint8_t wireType);
-
-enum {
-    // IncidentProto.header
-    FIELD_ID_INCIDENT_HEADER = 1
-};
-
-#endif  // PROTOBUF_H
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
index 110902c..65030b3 100644
--- a/cmds/incidentd/src/report_directory.cpp
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -97,7 +97,8 @@
         err = BAD_VALUE;
         goto done;
     }
-    if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) {
+    if ((st.st_uid != AID_SYSTEM && st.st_uid != AID_ROOT) ||
+        (st.st_gid != AID_SYSTEM && st.st_gid != AID_ROOT)) {
         ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s",
                 st.st_uid, st.st_gid, directory);
         err = BAD_VALUE;
diff --git a/cmds/incidentd/tests/EncodedBuffer_test.cpp b/cmds/incidentd/tests/EncodedBuffer_test.cpp
deleted file mode 100644
index 37a938a..0000000
--- a/cmds/incidentd/tests/EncodedBuffer_test.cpp
+++ /dev/null
@@ -1,255 +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.
-
-#include "EncodedBuffer.h"
-
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <string.h>
-
-using namespace android;
-using namespace android::base;
-using namespace std;
-using ::testing::StrEq;
-using ::testing::Test;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStdout;
-
-const uint8_t LOCAL = 0;
-const uint8_t EXPLICIT = 1;
-const uint8_t AUTOMATIC = 2;
-
-const uint8_t OTHER_TYPE = 1;
-const uint8_t STRING_TYPE = 9;
-const uint8_t MESSAGE_TYPE = 11;
-const string STRING_FIELD_0 = "\x02\viamtestdata";
-const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
-const string STRING_FIELD_2 = "\x12\vwhatthefuck";
-const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
-const string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1
-const string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2;
-
-class EncodedBufferTest : public Test {
-public:
-    virtual ~EncodedBufferTest() {
-        // Delete in reverse order of construction, to be consistent with
-        // regular allocation/deallocation.
-        while (!privacies.empty()) {
-            delete privacies.back();
-            privacies.pop_back();
-        }
-    }
-
-    virtual void SetUp() override {
-        ASSERT_NE(tf.fd, -1);
-    }
-
-    void writeToFdBuffer(string str) {
-        ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
-        ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
-    }
-
-    void assertBuffer(EncodedBuffer& buf, string expected) {
-        ASSERT_EQ(buf.size(), expected.size());
-        CaptureStdout();
-        ASSERT_EQ(buf.flush(STDOUT_FILENO), NO_ERROR);
-        ASSERT_THAT(GetCapturedStdout(), StrEq(expected));
-    }
-
-    void assertStrip(uint8_t dest, string expected, Privacy* policy) {
-        PrivacySpec spec(dest);
-        EncodedBuffer encodedBuf(buffer, policy);
-        ASSERT_EQ(encodedBuf.strip(spec), NO_ERROR);
-        assertBuffer(encodedBuf, expected);
-    }
-
-    void assertStripByFields(uint8_t dest, string expected, int size, Privacy* privacy, ...) {
-        Privacy* list[size+1];
-        list[0] = privacy;
-        va_list args;
-        va_start(args, privacy);
-        for (int i=1; i<size; i++) {
-            Privacy* p = va_arg(args, Privacy*);
-            list[i] = p;
-        }
-        va_end(args);
-        list[size] = NULL;
-        assertStrip(dest, expected, create_message_privacy(300, list));
-    }
-
-    Privacy* create_privacy(uint32_t field_id, uint8_t type, uint8_t dest) {
-        Privacy* p = new_uninit_privacy();
-        p->field_id = field_id;
-        p->type = type;
-        p->children = NULL;
-        p->dest = dest;
-        p->patterns = NULL;
-        return p;
-    }
-
-    Privacy* create_message_privacy(uint32_t field_id, Privacy** children) {
-        Privacy* p = new_uninit_privacy();
-        p->field_id = field_id;
-        p->type = MESSAGE_TYPE;
-        p->children = children;
-        p->dest = EXPLICIT;
-        p->patterns = NULL;
-        return p;
-    }
-
-    Privacy* create_string_privacy(uint32_t field_id, uint8_t dest, const char** patterns) {
-        Privacy* p = new_uninit_privacy();
-        p->field_id = field_id;
-        p->type = STRING_TYPE;
-        p->children = NULL;
-        p->dest = dest;
-        p->patterns = patterns;
-        return p;
-    }
-
-    FdBuffer buffer;
-private:
-    TemporaryFile tf;
-    // Littering this code with unique_ptr (or similar) is ugly, so we just
-    // mass-free everything after the test completes.
-    std::vector<Privacy *> privacies;
-
-    Privacy *new_uninit_privacy() {
-        Privacy* p = new Privacy;
-        privacies.push_back(p);
-        return p;
-    }
-};
-
-TEST_F(EncodedBufferTest, NullFieldPolicy) {
-    writeToFdBuffer(STRING_FIELD_0);
-    assertStrip(EXPLICIT, STRING_FIELD_0, create_string_privacy(300, AUTOMATIC, NULL));
-}
-
-TEST_F(EncodedBufferTest, StripSpecNotAllowed) {
-    writeToFdBuffer(STRING_FIELD_0);
-    assertStripByFields(AUTOMATIC, "", 1, create_privacy(0, STRING_TYPE, EXPLICIT));
-}
-
-TEST_F(EncodedBufferTest, StripVarintField) {
-    writeToFdBuffer(VARINT_FIELD_1);
-    assertStripByFields(EXPLICIT, "", 1, create_privacy(1, OTHER_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, StripLengthDelimitedField_String) {
-    writeToFdBuffer(STRING_FIELD_2);
-    assertStripByFields(EXPLICIT, "", 1, create_privacy(2, STRING_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, StripFixed64Field) {
-    writeToFdBuffer(FIX64_FIELD_3);
-    assertStripByFields(EXPLICIT, "", 1, create_privacy(3, OTHER_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, StripFixed32Field) {
-    writeToFdBuffer(FIX32_FIELD_4);
-    assertStripByFields(EXPLICIT, "", 1, create_privacy(4, OTHER_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, StripLengthDelimitedField_Message) {
-    writeToFdBuffer(MESSAGE_FIELD_5);
-    assertStripByFields(EXPLICIT, "", 1, create_privacy(5, MESSAGE_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, NoStripVarintField) {
-    writeToFdBuffer(VARINT_FIELD_1);
-    assertStripByFields(EXPLICIT, VARINT_FIELD_1, 1, create_privacy(1, OTHER_TYPE, AUTOMATIC));
-}
-
-TEST_F(EncodedBufferTest, NoStripLengthDelimitedField_String) {
-    writeToFdBuffer(STRING_FIELD_2);
-    assertStripByFields(EXPLICIT, STRING_FIELD_2, 1, create_privacy(2, STRING_TYPE, AUTOMATIC));
-}
-
-TEST_F(EncodedBufferTest, NoStripFixed64Field) {
-    writeToFdBuffer(FIX64_FIELD_3);
-    assertStripByFields(EXPLICIT, FIX64_FIELD_3, 1, create_privacy(3, OTHER_TYPE, AUTOMATIC));
-}
-
-TEST_F(EncodedBufferTest, NoStripFixed32Field) {
-    writeToFdBuffer(FIX32_FIELD_4);
-    assertStripByFields(EXPLICIT, FIX32_FIELD_4, 1, create_privacy(4, OTHER_TYPE, AUTOMATIC));
-}
-
-TEST_F(EncodedBufferTest, NoStripLengthDelimitedField_Message) {
-    writeToFdBuffer(MESSAGE_FIELD_5);
-    assertStripByFields(EXPLICIT, MESSAGE_FIELD_5, 1, create_privacy(5, MESSAGE_TYPE, AUTOMATIC));
-}
-
-TEST_F(EncodedBufferTest, StripVarintAndString) {
-    writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
-            + FIX64_FIELD_3 + FIX32_FIELD_4);
-    string expected = STRING_FIELD_0 + FIX64_FIELD_3 + FIX32_FIELD_4;
-    assertStripByFields(EXPLICIT, expected, 2,
-            create_privacy(1, OTHER_TYPE, LOCAL), create_privacy(2, STRING_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, StripVarintAndFixed64) {
-    writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
-            + FIX64_FIELD_3 + FIX32_FIELD_4);
-    string expected = STRING_FIELD_0 + STRING_FIELD_2 + FIX32_FIELD_4;
-    assertStripByFields(EXPLICIT, expected, 2,
-            create_privacy(1, OTHER_TYPE, LOCAL), create_privacy(3, OTHER_TYPE, LOCAL));
-}
-
-TEST_F(EncodedBufferTest, StripVarintInNestedMessage) {
-    writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5);
-    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
-    string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
-    assertStripByFields(EXPLICIT, expected, 1, create_message_privacy(5, list));
-}
-
-TEST_F(EncodedBufferTest, StripFix64AndVarintInNestedMessage) {
-    writeToFdBuffer(STRING_FIELD_0 + FIX64_FIELD_3 + MESSAGE_FIELD_5);
-    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
-    string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
-    assertStripByFields(EXPLICIT, expected, 2, create_privacy(3, OTHER_TYPE, LOCAL), create_message_privacy(5, list));
-}
-
-TEST_F(EncodedBufferTest, ClearAndStrip) {
-    string data = STRING_FIELD_0 + VARINT_FIELD_1;
-    writeToFdBuffer(data);
-    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
-    EncodedBuffer encodedBuf(buffer, create_message_privacy(300, list));
-    PrivacySpec spec1(EXPLICIT), spec2(LOCAL);
-
-    ASSERT_EQ(encodedBuf.strip(spec1), NO_ERROR);
-    assertBuffer(encodedBuf, STRING_FIELD_0);
-    ASSERT_EQ(encodedBuf.strip(spec2), NO_ERROR);
-    assertBuffer(encodedBuf, data);
-}
-
-TEST_F(EncodedBufferTest, BadDataInFdBuffer) {
-    writeToFdBuffer("iambaddata");
-    Privacy* list[] = { create_privacy(4, OTHER_TYPE, AUTOMATIC), NULL };
-    EncodedBuffer encodedBuf(buffer, create_message_privacy(300, list));
-    PrivacySpec spec;
-    ASSERT_EQ(encodedBuf.strip(spec), BAD_VALUE);
-}
-
-TEST_F(EncodedBufferTest, BadDataInNestedMessage) {
-    writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5 + "aoeoe");
-    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
-    Privacy* field5[] = { create_message_privacy(5, list), NULL };
-    EncodedBuffer encodedBuf(buffer, create_message_privacy(300, field5));
-    PrivacySpec spec;
-    ASSERT_EQ(encodedBuf.strip(spec), BAD_VALUE);
-}
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index d1436b2..2afa778 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -49,12 +49,11 @@
 
     void AssertBufferContent(const char* expected) {
         int i=0;
-        FdBuffer::iterator it = buffer.begin();
-        while (expected[i] != '\0') {
-            ASSERT_EQ(*it, expected[i++]);
-            it++;
+        EncodedBuffer::iterator it = buffer.data();
+        while (it.hasNext()) {
+            ASSERT_EQ(it.next(), expected[i++]);
         }
-        ASSERT_EQ(it, buffer.end());
+        EXPECT_EQ(expected[i], '\0');
     }
 
     bool DoDataStream(int rFd, int wFd) {
@@ -92,20 +91,8 @@
 }
 
 TEST_F(FdBufferTest, IterateEmpty) {
-    FdBuffer::iterator it = buffer.begin();
-    EXPECT_EQ(it, buffer.end());
-    it += 1;
-    EXPECT_TRUE(it.outOfBound());
-}
-
-TEST_F(FdBufferTest, IteratorSnapshot) {
-    FdBuffer::iterator it = buffer.begin();
-    it += 4;
-    FdBuffer::iterator snapshot = it.snapshot();
-    it += 5;
-    EXPECT_TRUE(snapshot != it);
-    EXPECT_EQ(it - snapshot, 5);
-    EXPECT_EQ(snapshot - it, -5);
+    EncodedBuffer::iterator it = buffer.data();
+    EXPECT_FALSE(it.hasNext());
 }
 
 TEST_F(FdBufferTest, ReadAndIterate) {
@@ -114,15 +101,15 @@
     ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
 
     int i=0;
-    for (FdBuffer::iterator it = buffer.begin(); it != buffer.end(); ++it) {
-        EXPECT_EQ(*it, (uint8_t)testdata[i++]);
+    EncodedBuffer::iterator it = buffer.data();
+    while (it.hasNext()) {
+        EXPECT_EQ(it.next(), (uint8_t)testdata[i++]);
     }
 
-    FdBuffer::iterator it = buffer.begin();
-    it += buffer.size();
-    EXPECT_EQ(it, buffer.end());
+    it.rp()->rewind();
+    it.rp()->move(buffer.size());
     EXPECT_EQ(it.bytesRead(), testdata.size());
-    EXPECT_FALSE(it.outOfBound());
+    EXPECT_FALSE(it.hasNext());
 }
 
 TEST_F(FdBufferTest, ReadTimeout) {
@@ -258,13 +245,15 @@
         EXPECT_FALSE(buffer.timedOut());
         EXPECT_TRUE(buffer.truncated());
         wait(&pid);
-        FdBuffer::iterator it = buffer.begin();
-        it += fourMB;
+        EncodedBuffer::iterator it = buffer.data();
+        it.rp()->move(fourMB);
         EXPECT_EQ(it.bytesRead(), fourMB);
-        EXPECT_EQ(it, buffer.end());
-        for (FdBuffer::iterator it = buffer.begin(); it != buffer.end(); it++) {
+        EXPECT_FALSE(it.hasNext());
+
+        it.rp()->rewind();
+        while (it.hasNext()) {
             char c = 'A' + (it.bytesRead() % 64 / 8);
-            ASSERT_TRUE(*it == c);
+            ASSERT_TRUE(it.next() == c);
         }
     }
 }
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
new file mode 100644
index 0000000..8f6e355
--- /dev/null
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -0,0 +1,262 @@
+// 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 "FdBuffer.h"
+#include "PrivacyBuffer.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <string.h>
+
+using namespace android;
+using namespace android::base;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStdout;
+
+const uint8_t LOCAL = 0;
+const uint8_t EXPLICIT = 1;
+const uint8_t AUTOMATIC = 2;
+
+const uint8_t OTHER_TYPE = 1;
+const uint8_t STRING_TYPE = 9;
+const uint8_t MESSAGE_TYPE = 11;
+const string STRING_FIELD_0 = "\x02\viamtestdata";
+const string VARINT_FIELD_1 = "\x08\x96\x01"; // 150
+const string STRING_FIELD_2 = "\x12\vwhatthefuck";
+const string FIX64_FIELD_3 = "\x19\xff\xff\xff\xff\xff\xff\xff\xff"; // -1
+const string FIX32_FIELD_4 = "\x25\xff\xff\xff\xff"; // -1
+const string MESSAGE_FIELD_5 = "\x2a\x10" + VARINT_FIELD_1 + STRING_FIELD_2;
+
+
+class PrivacyBufferTest : public Test {
+public:
+    virtual ~PrivacyBufferTest() {
+        // Delete in reverse order of construction, to be consistent with
+        // regular allocation/deallocation.
+        while (!privacies.empty()) {
+            delete privacies.back();
+            privacies.pop_back();
+        }
+    }
+
+    virtual void SetUp() override {
+        ASSERT_NE(tf.fd, -1);
+    }
+
+    void writeToFdBuffer(string str) {
+        ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
+        ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
+        ASSERT_EQ(str.size(), buffer.size());
+    }
+
+    void assertBuffer(PrivacyBuffer& buf, string expected) {
+        ASSERT_EQ(buf.size(), expected.size());
+        CaptureStdout();
+        ASSERT_EQ(buf.flush(STDOUT_FILENO), NO_ERROR);
+        ASSERT_THAT(GetCapturedStdout(), StrEq(expected));
+    }
+
+    void assertStrip(uint8_t dest, string expected, Privacy* policy) {
+        PrivacySpec spec(dest);
+        EncodedBuffer::iterator bufData = buffer.data();
+        PrivacyBuffer privacyBuf(policy, bufData);
+        ASSERT_EQ(privacyBuf.strip(spec), NO_ERROR);
+        assertBuffer(privacyBuf, expected);
+    }
+
+    void assertStripByFields(uint8_t dest, string expected, int size, Privacy* privacy, ...) {
+        Privacy* list[size+1];
+        list[0] = privacy;
+        va_list args;
+        va_start(args, privacy);
+        for (int i=1; i<size; i++) {
+            Privacy* p = va_arg(args, Privacy*);
+            list[i] = p;
+        }
+        va_end(args);
+        list[size] = NULL;
+        assertStrip(dest, expected, create_message_privacy(300, list));
+    }
+
+    Privacy* create_privacy(uint32_t field_id, uint8_t type, uint8_t dest) {
+        Privacy* p = new_uninit_privacy();
+        p->field_id = field_id;
+        p->type = type;
+        p->children = NULL;
+        p->dest = dest;
+        p->patterns = NULL;
+        return p;
+    }
+
+    Privacy* create_message_privacy(uint32_t field_id, Privacy** children) {
+        Privacy* p = new_uninit_privacy();
+        p->field_id = field_id;
+        p->type = MESSAGE_TYPE;
+        p->children = children;
+        p->dest = EXPLICIT;
+        p->patterns = NULL;
+        return p;
+    }
+
+    Privacy* create_string_privacy(uint32_t field_id, uint8_t dest, const char** patterns) {
+        Privacy* p = new_uninit_privacy();
+        p->field_id = field_id;
+        p->type = STRING_TYPE;
+        p->children = NULL;
+        p->dest = dest;
+        p->patterns = patterns;
+        return p;
+    }
+
+    FdBuffer buffer;
+private:
+    TemporaryFile tf;
+    // Littering this code with unique_ptr (or similar) is ugly, so we just
+    // mass-free everything after the test completes.
+    std::vector<Privacy *> privacies;
+
+    Privacy *new_uninit_privacy() {
+        Privacy* p = new Privacy;
+        privacies.push_back(p);
+        return p;
+    }
+};
+
+TEST_F(PrivacyBufferTest, NullFieldPolicy) {
+    writeToFdBuffer(STRING_FIELD_0);
+    assertStrip(EXPLICIT, STRING_FIELD_0, create_string_privacy(300, AUTOMATIC, NULL));
+}
+
+TEST_F(PrivacyBufferTest, StripSpecNotAllowed) {
+    writeToFdBuffer(STRING_FIELD_0);
+    assertStripByFields(AUTOMATIC, "", 1, create_privacy(0, STRING_TYPE, EXPLICIT));
+}
+
+TEST_F(PrivacyBufferTest, StripVarintField) {
+    writeToFdBuffer(VARINT_FIELD_1);
+    assertStripByFields(EXPLICIT, "", 1, create_privacy(1, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, StripLengthDelimitedField_String) {
+    writeToFdBuffer(STRING_FIELD_2);
+    assertStripByFields(EXPLICIT, "", 1, create_privacy(2, STRING_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, StripFixed64Field) {
+    writeToFdBuffer(FIX64_FIELD_3);
+    assertStripByFields(EXPLICIT, "", 1, create_privacy(3, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, StripFixed32Field) {
+    writeToFdBuffer(FIX32_FIELD_4);
+    assertStripByFields(EXPLICIT, "", 1, create_privacy(4, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, StripLengthDelimitedField_Message) {
+    writeToFdBuffer(MESSAGE_FIELD_5);
+    assertStripByFields(EXPLICIT, "", 1, create_privacy(5, MESSAGE_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, NoStripVarintField) {
+    writeToFdBuffer(VARINT_FIELD_1);
+    assertStripByFields(EXPLICIT, VARINT_FIELD_1, 1, create_privacy(1, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(PrivacyBufferTest, NoStripLengthDelimitedField_String) {
+    writeToFdBuffer(STRING_FIELD_2);
+    assertStripByFields(EXPLICIT, STRING_FIELD_2, 1, create_privacy(2, STRING_TYPE, AUTOMATIC));
+}
+
+TEST_F(PrivacyBufferTest, NoStripFixed64Field) {
+    writeToFdBuffer(FIX64_FIELD_3);
+    assertStripByFields(EXPLICIT, FIX64_FIELD_3, 1, create_privacy(3, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(PrivacyBufferTest, NoStripFixed32Field) {
+    writeToFdBuffer(FIX32_FIELD_4);
+    assertStripByFields(EXPLICIT, FIX32_FIELD_4, 1, create_privacy(4, OTHER_TYPE, AUTOMATIC));
+}
+
+TEST_F(PrivacyBufferTest, NoStripLengthDelimitedField_Message) {
+    writeToFdBuffer(MESSAGE_FIELD_5);
+    assertStripByFields(EXPLICIT, MESSAGE_FIELD_5, 1, create_privacy(5, MESSAGE_TYPE, AUTOMATIC));
+}
+
+TEST_F(PrivacyBufferTest, StripVarintAndString) {
+    writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
+            + FIX64_FIELD_3 + FIX32_FIELD_4);
+    string expected = STRING_FIELD_0 + FIX64_FIELD_3 + FIX32_FIELD_4;
+    assertStripByFields(EXPLICIT, expected, 2,
+            create_privacy(1, OTHER_TYPE, LOCAL), create_privacy(2, STRING_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, StripVarintAndFixed64) {
+    writeToFdBuffer(STRING_FIELD_0 + VARINT_FIELD_1 + STRING_FIELD_2
+            + FIX64_FIELD_3 + FIX32_FIELD_4);
+    string expected = STRING_FIELD_0 + STRING_FIELD_2 + FIX32_FIELD_4;
+    assertStripByFields(EXPLICIT, expected, 2,
+            create_privacy(1, OTHER_TYPE, LOCAL), create_privacy(3, OTHER_TYPE, LOCAL));
+}
+
+TEST_F(PrivacyBufferTest, StripVarintInNestedMessage) {
+    writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5);
+    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+    string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
+    assertStripByFields(EXPLICIT, expected, 1, create_message_privacy(5, list));
+}
+
+TEST_F(PrivacyBufferTest, StripFix64AndVarintInNestedMessage) {
+    writeToFdBuffer(STRING_FIELD_0 + FIX64_FIELD_3 + MESSAGE_FIELD_5);
+    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+    string expected = STRING_FIELD_0 + "\x2a\xd" + STRING_FIELD_2;
+    assertStripByFields(EXPLICIT, expected, 2, create_privacy(3, OTHER_TYPE, LOCAL), create_message_privacy(5, list));
+}
+
+TEST_F(PrivacyBufferTest, ClearAndStrip) {
+    string data = STRING_FIELD_0 + VARINT_FIELD_1;
+    writeToFdBuffer(data);
+    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+    EncodedBuffer::iterator bufData = buffer.data();
+    PrivacyBuffer privacyBuf(create_message_privacy(300, list), bufData);
+    PrivacySpec spec1(EXPLICIT), spec2(LOCAL);
+
+    ASSERT_EQ(privacyBuf.strip(spec1), NO_ERROR);
+    assertBuffer(privacyBuf, STRING_FIELD_0);
+    ASSERT_EQ(privacyBuf.strip(spec2), NO_ERROR);
+    assertBuffer(privacyBuf, data);
+}
+
+TEST_F(PrivacyBufferTest, BadDataInFdBuffer) {
+    writeToFdBuffer("iambaddata");
+    Privacy* list[] = { create_privacy(4, OTHER_TYPE, AUTOMATIC), NULL };
+    EncodedBuffer::iterator bufData = buffer.data();
+    PrivacyBuffer privacyBuf(create_message_privacy(300, list), bufData);
+    PrivacySpec spec;
+    ASSERT_EQ(privacyBuf.strip(spec), BAD_VALUE);
+}
+
+TEST_F(PrivacyBufferTest, BadDataInNestedMessage) {
+    writeToFdBuffer(STRING_FIELD_0 + MESSAGE_FIELD_5 + "aoeoe");
+    Privacy* list[] = { create_privacy(1, OTHER_TYPE, LOCAL), NULL };
+    Privacy* field5[] = { create_message_privacy(5, list), NULL };
+    EncodedBuffer::iterator bufData = buffer.data();
+    PrivacyBuffer privacyBuf(create_message_privacy(300, field5), bufData);
+    PrivacySpec spec;
+    ASSERT_EQ(privacyBuf.strip(spec), BAD_VALUE);
+}
diff --git a/cmds/incidentd/tests/Reporter_test.cpp b/cmds/incidentd/tests/Reporter_test.cpp
index 5d074bc..8d99fc7 100644
--- a/cmds/incidentd/tests/Reporter_test.cpp
+++ b/cmds/incidentd/tests/Reporter_test.cpp
@@ -32,8 +32,6 @@
 using namespace std;
 using ::testing::StrEq;
 using ::testing::Test;
-using ::testing::internal::CaptureStdout;
-using ::testing::internal::GetCapturedStdout;
 
 class TestListener : public IIncidentReportStatusListener
 {
@@ -139,20 +137,24 @@
 }
 
 TEST_F(ReporterTest, RunReportWithHeaders) {
+    TemporaryFile tf;
     IncidentReportArgs args1, args2;
     args1.addSection(1);
     args2.addSection(2);
     std::vector<int8_t> header {'a', 'b', 'c', 'd', 'e'};
     args2.addHeader(header);
-    sp<ReportRequest> r1 = new ReportRequest(args1, l, STDOUT_FILENO);
-    sp<ReportRequest> r2 = new ReportRequest(args2, l, STDOUT_FILENO);
+    sp<ReportRequest> r1 = new ReportRequest(args1, l, tf.fd);
+    sp<ReportRequest> r2 = new ReportRequest(args2, l, tf.fd);
 
     reporter->batch.add(r1);
     reporter->batch.add(r2);
 
-    CaptureStdout();
     ASSERT_EQ(Reporter::REPORT_FINISHED, reporter->runReport());
-    EXPECT_THAT(GetCapturedStdout(), StrEq("\n\x5" "abcde"));
+
+    string result;
+    ReadFileToString(tf.path, &result);
+    EXPECT_THAT(result, StrEq("\n\x5" "abcde"));
+
     EXPECT_EQ(l->startInvoked, 2);
     EXPECT_EQ(l->finishInvoked, 2);
     EXPECT_TRUE(l->startSections.empty());
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 79faa1b..9490880 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -116,9 +116,8 @@
     }
 
     public int run(String[] args) throws RemoteException {
-        boolean validCommand = false;
         if (args.length < 1) {
-            return showUsage();
+            return runShellCommand("package", mArgs);
         }
         mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
         mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
@@ -134,18 +133,6 @@
         String op = args[0];
         mNextArg = 1;
 
-        if ("list".equals(op)) {
-            return runList();
-        }
-
-        if ("path".equals(op)) {
-            return runPath();
-        }
-
-        if ("dump".equals(op)) {
-            return runDump();
-        }
-
         if ("install".equals(op)) {
             return runInstall();
         }
@@ -166,138 +153,12 @@
             return runInstallAbandon();
         }
 
-        if ("set-installer".equals(op)) {
-            return runSetInstaller();
-        }
-
-        if ("uninstall".equals(op)) {
-            return runUninstall();
-        }
-
-        if ("clear".equals(op)) {
-            return runClear();
-        }
-
-        if ("enable".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-        }
-
-        if ("disable".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
-        }
-
-        if ("disable-user".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
-        }
-
-        if ("disable-until-used".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
-        }
-
-        if ("default-state".equals(op)) {
-            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
-        }
-
-        if ("hide".equals(op)) {
-            return runSetHiddenSetting(true);
-        }
-
-        if ("unhide".equals(op)) {
-            return runSetHiddenSetting(false);
-        }
-
-        if ("grant".equals(op)) {
-            return runGrantRevokePermission(true);
-        }
-
-        if ("revoke".equals(op)) {
-            return runGrantRevokePermission(false);
-        }
-
-        if ("reset-permissions".equals(op)) {
-            return runResetPermissions();
-        }
-
-        if ("set-permission-enforced".equals(op)) {
-            return runSetPermissionEnforced();
-        }
-
-        if ("set-app-link".equals(op)) {
-            return runSetAppLink();
-        }
-
-        if ("get-app-link".equals(op)) {
-            return runGetAppLink();
-        }
-
-        if ("set-install-location".equals(op)) {
-            return runSetInstallLocation();
-        }
-
-        if ("get-install-location".equals(op)) {
-            return runGetInstallLocation();
-        }
-
-        if ("trim-caches".equals(op)) {
-            return runTrimCaches();
-        }
-
-        if ("create-user".equals(op)) {
-            return runCreateUser();
-        }
-
-        if ("remove-user".equals(op)) {
-            return runRemoveUser();
-        }
-
-        if ("get-max-users".equals(op)) {
-            return runGetMaxUsers();
-        }
-
-        if ("force-dex-opt".equals(op)) {
-            return runForceDexOpt();
-        }
-
-        if ("move-package".equals(op)) {
-            return runMovePackage();
-        }
-
-        if ("move-primary-storage".equals(op)) {
-            return runMovePrimaryStorage();
-        }
-
-        if ("set-user-restriction".equals(op)) {
-            return runSetUserRestriction();
-        }
-
-        try {
-            if (args.length == 1) {
-                if (args[0].equalsIgnoreCase("-l")) {
-                    validCommand = true;
-                    return runShellCommand("package", new String[] { "list", "package" });
-                } else if (args[0].equalsIgnoreCase("-lf")) {
-                    validCommand = true;
-                    return runShellCommand("package", new String[] { "list", "package", "-f" });
-                }
-            } else if (args.length == 2) {
-                if (args[0].equalsIgnoreCase("-p")) {
-                    validCommand = true;
-                    return displayPackageFilePath(args[1], UserHandle.USER_SYSTEM);
-                }
-            }
-            return 1;
-        } finally {
-            if (validCommand == false) {
-                if (op != null) {
-                    System.err.println("Error: unknown command '" + op + "'");
-                }
-                showUsage();
-            }
-        }
+        return runShellCommand("package", mArgs);
     }
 
     static final class MyShellCallback extends ShellCallback {
-        @Override public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+        @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
+                String mode) {
             File file = new File(path);
             final ParcelFileDescriptor fd;
             try {
@@ -416,7 +277,7 @@
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                             null, null);
                     params.sessionParams.setSize(
-                            PackageHelper.calculateInstalledSize(pkgLite, false,
+                            PackageHelper.calculateInstalledSize(pkgLite,
                             params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
                     System.err.println("Error: Failed to parse APK file: " + e);
@@ -704,59 +565,6 @@
         }
     }
 
-    /**
-     * Execute the list sub-command.
-     *
-     * pm list [package | packages]
-     * pm list permission-groups
-     * pm list permissions
-     * pm list features
-     * pm list libraries
-     * pm list instrumentation
-     */
-    private int runList() {
-        final String type = nextArg();
-        if ("users".equals(type)) {
-            return runShellCommand("user", new String[] { "list" });
-        }
-        return runShellCommand("package", mArgs);
-    }
-
-    private int runUninstall() {
-        return runShellCommand("package", mArgs);
-    }
-
-    private int runPath() {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-        return displayPackageFilePath(pkg, userId);
-    }
-
-    private int runDump() {
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return 1;
-        }
-        ActivityManager.dumpPackageStateStatic(FileDescriptor.out, pkg);
-        return 0;
-    }
-
     class LocalPackageInstallObserver extends PackageInstallObserver {
         boolean finished;
         int result;
@@ -779,477 +587,6 @@
         }
     }
 
-    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
-    private int runSetAppLink() {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-                if (userId < 0) {
-                    System.err.println("Error: user must be >= 0");
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: unknown option: " + opt);
-                return showUsage();
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return showUsage();
-        }
-
-        // State to apply; {always|ask|never|undefined}, required
-        final String modeString = nextArg();
-        if (modeString == null) {
-            System.err.println("Error: no app link state specified.");
-            return showUsage();
-        }
-
-        final int newMode;
-        switch (modeString.toLowerCase()) {
-            case "undefined":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-                break;
-
-            case "always":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-                break;
-
-            case "ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-                break;
-
-            case "always-ask":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-                break;
-
-            case "never":
-                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-                break;
-
-            default:
-                System.err.println("Error: unknown app link state '" + modeString + "'");
-                return 1;
-        }
-
-        try {
-            final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
-            if (info == null) {
-                System.err.println("Error: package " + pkg + " not found.");
-                return 1;
-            }
-
-            if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-                System.err.println("Error: package " + pkg + " does not handle web links.");
-                return 1;
-            }
-
-            if (!mPm.updateIntentVerificationStatus(pkg, newMode, userId)) {
-                System.err.println("Error: unable to update app link status for " + pkg);
-                return 1;
-            }
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    // pm get-app-link [--user USER_ID] PACKAGE
-    private int runGetAppLink() {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextOptionData());
-                if (userId < 0) {
-                    System.err.println("Error: user must be >= 0");
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: unknown option: " + opt);
-                return showUsage();
-            }
-        }
-
-        // Package name to act on; required
-        final String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return showUsage();
-        }
-
-        try {
-            final PackageInfo info = mPm.getPackageInfo(pkg, 0, userId);
-            if (info == null) {
-                System.err.println("Error: package " + pkg + " not found.");
-                return 1;
-            }
-
-            if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
-                System.err.println("Error: package " + pkg + " does not handle web links.");
-                return 1;
-            }
-
-            System.out.println(linkStateToString(mPm.getIntentVerificationStatus(pkg, userId)));
-        } catch (Exception e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-
-        return 0;
-    }
-
-    private String linkStateToString(int state) {
-        switch (state) {
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
-            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
-        }
-        return "Unknown link state: " + state;
-    }
-
-    private int runSetInstallLocation() {
-        int loc;
-
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no install location specified.");
-            return 1;
-        }
-        try {
-            loc = Integer.parseInt(arg);
-        } catch (NumberFormatException e) {
-            System.err.println("Error: install location has to be a number.");
-            return 1;
-        }
-        try {
-            if (!mPm.setInstallLocation(loc)) {
-                System.err.println("Error: install location has to be a number.");
-                return 1;
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runGetInstallLocation() {
-        try {
-            int loc = mPm.getInstallLocation();
-            String locStr = "invalid";
-            if (loc == PackageHelper.APP_INSTALL_AUTO) {
-                locStr = "auto";
-            } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
-                locStr = "internal";
-            } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
-                locStr = "external";
-            }
-            System.out.println(loc + "[" + locStr + "]");
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runSetInstaller() throws RemoteException {
-        final String targetPackage = nextArg();
-        final String installerPackageName = nextArg();
-
-        if (targetPackage == null || installerPackageName == null) {
-            throw new IllegalArgumentException(
-                    "must provide both target and installer package names");
-        }
-
-        mPm.setInstallerPackageName(targetPackage, installerPackageName);
-        System.out.println("Success");
-        return 0;
-    }
-
-    public int runCreateUser() {
-        String name;
-        int userId = -1;
-        int flags = 0;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if ("--profileOf".equals(opt)) {
-                String optionData = nextOptionData();
-                if (optionData == null || !isNumber(optionData)) {
-                    System.err.println("Error: no USER_ID specified");
-                    return showUsage();
-                } else {
-                    userId = Integer.parseInt(optionData);
-                }
-            } else if ("--managed".equals(opt)) {
-                flags |= UserInfo.FLAG_MANAGED_PROFILE;
-            } else if ("--restricted".equals(opt)) {
-                flags |= UserInfo.FLAG_RESTRICTED;
-            } else if ("--ephemeral".equals(opt)) {
-                flags |= UserInfo.FLAG_EPHEMERAL;
-            } else if ("--guest".equals(opt)) {
-                flags |= UserInfo.FLAG_GUEST;
-            } else if ("--demo".equals(opt)) {
-                flags |= UserInfo.FLAG_DEMO;
-            } else {
-                System.err.println("Error: unknown option " + opt);
-                return showUsage();
-            }
-        }
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no user name specified.");
-            return 1;
-        }
-        name = arg;
-        try {
-            UserInfo info;
-            if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
-                // In non-split user mode, userId can only be SYSTEM
-                int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
-                info = mUm.createRestrictedProfile(name, parentUserId);
-                mAm.addSharedAccountsFromParentUser(parentUserId, userId,
-                        (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
-            } else if (userId < 0) {
-                info = mUm.createUser(name, flags);
-            } else {
-                info = mUm.createProfileForUser(name, flags, userId, null);
-            }
-
-            if (info != null) {
-                System.out.println("Success: created user id " + info.id);
-                return 0;
-            } else {
-                System.err.println("Error: couldn't create User.");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    public int runRemoveUser() {
-        int userId;
-        String arg = nextArg();
-        if (arg == null) {
-            System.err.println("Error: no user id specified.");
-            return 1;
-        }
-        try {
-            userId = Integer.parseInt(arg);
-        } catch (NumberFormatException e) {
-            System.err.println("Error: user id '" + arg + "' is not a number.");
-            return 1;
-        }
-        try {
-            if (mUm.removeUser(userId)) {
-                System.out.println("Success: removed user");
-                return 0;
-            } else {
-                System.err.println("Error: couldn't remove user id " + userId);
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    public int runGetMaxUsers() {
-        System.out.println("Maximum supported users: " + UserManager.getMaxSupportedUsers());
-        return 0;
-    }
-
-    public int runForceDexOpt() {
-        final String packageName = nextArg();
-        try {
-            mPm.forceDexOpt(packageName);
-            return 0;
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runMovePackage() {
-        final String packageName = nextArg();
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-
-        try {
-            final int moveId = mPm.movePackage(packageName, volumeUuid);
-
-            int status = mPm.getMoveStatus(moveId);
-            while (!PackageManager.isMoveStatusFinished(status)) {
-                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-                status = mPm.getMoveStatus(moveId);
-            }
-
-            if (status == PackageManager.MOVE_SUCCEEDED) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failure [" + status + "]");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runMovePrimaryStorage() {
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-
-        try {
-            final int moveId = mPm.movePrimaryStorage(volumeUuid);
-
-            int status = mPm.getMoveStatus(moveId);
-            while (!PackageManager.isMoveStatusFinished(status)) {
-                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
-                status = mPm.getMoveStatus(moveId);
-            }
-
-            if (status == PackageManager.MOVE_SUCCEEDED) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failure [" + status + "]");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
-    }
-
-    public int runSetUserRestriction() {
-        int userId = UserHandle.USER_SYSTEM;
-        String opt = nextOption();
-        if (opt != null && "--user".equals(opt)) {
-            String arg = nextArg();
-            if (arg == null || !isNumber(arg)) {
-                System.err.println("Error: valid userId not specified");
-                return 1;
-            }
-            userId = Integer.parseInt(arg);
-        }
-
-        String restriction = nextArg();
-        String arg = nextArg();
-        boolean value;
-        if ("1".equals(arg)) {
-            value = true;
-        } else if ("0".equals(arg)) {
-            value = false;
-        } else {
-            System.err.println("Error: valid value not specified");
-            return 1;
-        }
-        try {
-            mUm.setUserRestriction(restriction, value, userId);
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            return 1;
-        }
-    }
-
-    static class ClearDataObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-    }
-
-    private int runClear() {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-
-        ClearDataObserver obs = new ClearDataObserver();
-        try {
-            ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-
-            if (obs.result) {
-                System.out.println("Success");
-                return 0;
-            } else {
-                System.err.println("Failed");
-                return 1;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private static String enabledSettingToString(int state) {
-        switch (state) {
-            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
-                return "default";
-            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
-                return "enabled";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
-                return "disabled";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
-                return "disabled-user";
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                return "disabled-until-used";
-        }
-        return "unknown";
-    }
-
     private static boolean isNumber(String s) {
         try {
             Integer.parseInt(s);
@@ -1259,169 +596,6 @@
         return true;
     }
 
-    private int runSetEnabledSetting(int state) {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package or component specified");
-            return showUsage();
-        }
-        ComponentName cn = ComponentName.unflattenFromString(pkg);
-        if (cn == null) {
-            try {
-                mPm.setApplicationEnabledSetting(pkg, state, 0, userId,
-                        "shell:" + android.os.Process.myUid());
-                System.out.println("Package " + pkg + " new state: "
-                        + enabledSettingToString(
-                        mPm.getApplicationEnabledSetting(pkg, userId)));
-                return 0;
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-        } else {
-            try {
-                mPm.setComponentEnabledSetting(cn, state, 0, userId);
-                System.out.println("Component " + cn.toShortString() + " new state: "
-                        + enabledSettingToString(
-                        mPm.getComponentEnabledSetting(cn, userId)));
-                return 0;
-            } catch (RemoteException e) {
-                System.err.println(e.toString());
-                System.err.println(PM_NOT_RUNNING_ERR);
-                return 1;
-            }
-        }
-    }
-
-    private int runSetHiddenSetting(boolean state) {
-        int userId = UserHandle.USER_SYSTEM;
-        String option = nextOption();
-        if (option != null && option.equals("--user")) {
-            String optionData = nextOptionData();
-            if (optionData == null || !isNumber(optionData)) {
-                System.err.println("Error: no USER_ID specified");
-                return showUsage();
-            } else {
-                userId = Integer.parseInt(optionData);
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package or component specified");
-            return showUsage();
-        }
-        try {
-            mPm.setApplicationHiddenSettingAsUser(pkg, state, userId);
-            System.out.println("Package " + pkg + " new hidden state: "
-                    + mPm.getApplicationHiddenSettingAsUser(pkg, userId));
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-    }
-
-    private int runGrantRevokePermission(boolean grant) {
-        int userId = UserHandle.USER_SYSTEM;
-
-        String opt = null;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("--user")) {
-                userId = Integer.parseInt(nextArg());
-            }
-        }
-
-        String pkg = nextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified");
-            return showUsage();
-        }
-        String perm = nextArg();
-        if (perm == null) {
-            System.err.println("Error: no permission specified");
-            return showUsage();
-        }
-
-        try {
-            if (grant) {
-                mPm.grantRuntimePermission(pkg, perm, userId);
-            } else {
-                mPm.revokeRuntimePermission(pkg, perm, userId);
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
-    private int runResetPermissions() {
-        try {
-            mPm.resetRuntimePermissions();
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
-    private int runSetPermissionEnforced() {
-        final String permission = nextArg();
-        if (permission == null) {
-            System.err.println("Error: no permission specified");
-            return showUsage();
-        }
-        final String enforcedRaw = nextArg();
-        if (enforcedRaw == null) {
-            System.err.println("Error: no enforcement specified");
-            return showUsage();
-        }
-        final boolean enforced = Boolean.parseBoolean(enforcedRaw);
-        try {
-            mPm.setPermissionEnforced(permission, enforced);
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
-        }
-    }
-
     static class ClearCacheObserver extends IPackageDataObserver.Stub {
         boolean finished;
         boolean result;
@@ -1437,62 +611,17 @@
 
     }
 
-    private int runTrimCaches() {
-        String size = nextArg();
-        if (size == null) {
-            System.err.println("Error: no size specified");
-            return showUsage();
-        }
-        long multiplier = 1;
-        int len = size.length();
-        char c = size.charAt(len - 1);
-        if (c < '0' || c > '9') {
-            if (c == 'K' || c == 'k') {
-                multiplier = 1024L;
-            } else if (c == 'M' || c == 'm') {
-                multiplier = 1024L*1024L;
-            } else if (c == 'G' || c == 'g') {
-                multiplier = 1024L*1024L*1024L;
-            } else {
-                System.err.println("Invalid suffix: " + c);
-                return showUsage();
+    static class ClearDataObserver extends IPackageDataObserver.Stub {
+        boolean finished;
+        boolean result;
+
+        @Override
+        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+            synchronized (this) {
+                finished = true;
+                result = succeeded;
+                notifyAll();
             }
-            size = size.substring(0, len-1);
-        }
-        long sizeVal;
-        try {
-            sizeVal = Long.parseLong(size) * multiplier;
-        } catch (NumberFormatException e) {
-            System.err.println("Error: expected number at: " + size);
-            return showUsage();
-        }
-        String volumeUuid = nextArg();
-        if ("internal".equals(volumeUuid)) {
-            volumeUuid = null;
-        }
-        ClearDataObserver obs = new ClearDataObserver();
-        try {
-            mPm.freeStorageAndNotify(volumeUuid, sizeVal,
-                    StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
-            synchronized (obs) {
-                while (!obs.finished) {
-                    try {
-                        obs.wait();
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return 0;
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        } catch (IllegalArgumentException e) {
-            System.err.println("Bad argument: " + e.toString());
-            return showUsage();
-        } catch (SecurityException e) {
-            System.err.println("Operation not allowed: " + e.toString());
-            return 1;
         }
     }
 
@@ -1570,11 +699,19 @@
     private static int showUsage() {
         System.err.println("usage: pm path [--user USER_ID] PACKAGE");
         System.err.println("       pm dump PACKAGE");
-        System.err.println("       pm install [-lrtsfd] [-i PACKAGE] [--user USER_ID] [PATH]");
-        System.err.println("       pm install-create [-lrtsfdp] [-i PACKAGE] [-S BYTES]");
-        System.err.println("               [--install-location 0/1/2]");
-        System.err.println("               [--force-uuid internal|UUID]");
-        System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH]");
+        System.err.println("       pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
+        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        System.err.println("               [--originating-uri URI] [---referrer URI]");
+        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
+        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
+        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+        System.err.println("       pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
+        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        System.err.println("               [--originating-uri URI] [---referrer URI]");
+        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
+        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
+        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+        System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
         System.err.println("       pm install-commit SESSION_ID");
         System.err.println("       pm install-abandon SESSION_ID");
         System.err.println("       pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
@@ -1613,15 +750,27 @@
         System.err.println("pm install: install a single legacy package");
         System.err.println("pm install-create: create an install session");
         System.err.println("    -l: forward lock application");
-        System.err.println("    -r: replace existing application");
+        System.err.println("    -r: allow replacement of existing application");
         System.err.println("    -t: allow test packages");
-        System.err.println("    -i: specify the installer package name");
+        System.err.println("    -i: specify package name of installer owning the app");
         System.err.println("    -s: install application on sdcard");
         System.err.println("    -f: install application on internal flash");
         System.err.println("    -d: allow version code downgrade (debuggable packages only)");
-        System.err.println("    -p: partial application install");
+        System.err.println("    -p: partial application install (new split on top of existing pkg)");
         System.err.println("    -g: grant all runtime permissions");
         System.err.println("    -S: size in bytes of entire session");
+        System.err.println("    --dont-kill: installing a new feature split, don't kill running app");
+        System.err.println("    --originating-uri: set URI where app was downloaded from");
+        System.err.println("    --referrer: set URI that instigated the install of the app");
+        System.err.println("    --pkg: specify expected package name of app being installed");
+        System.err.println("    --abi: override the default ABI of the platform");
+        System.err.println("    --instantapp: cause the app to be installed as an ephemeral install app");
+        System.err.println("    --full: cause the app to be installed as a non-ephemeral full app");
+        System.err.println("    --install-location: force the install location:");
+        System.err.println("        0=auto, 1=internal only, 2=prefer external");
+        System.err.println("    --force-uuid: force install on to disk volume with given UUID");
+        System.err.println("    --force-sdk: allow install even when existing app targets platform");
+        System.err.println("        codename but new one targets a final API level");
         System.err.println("");
         System.err.println("pm install-write: write a package into existing session; path may");
         System.err.println("  be '-' to read from stdin");
diff --git a/cmds/screencap/Android.mk b/cmds/screencap/Android.mk
index b0dc422..72e3c56 100644
--- a/cmds/screencap/Android.mk
+++ b/cmds/screencap/Android.mk
@@ -8,7 +8,7 @@
     libcutils \
     libutils \
     libbinder \
-    libskia \
+    libhwui \
     libui \
     libgui
 
diff --git a/cmds/statsd/.clang-format b/cmds/statsd/.clang-format
new file mode 100644
index 0000000..3d64bee
--- /dev/null
+++ b/cmds/statsd/.clang-format
@@ -0,0 +1,14 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortLoopsOnASingleLine: true
+BinPackArguments: true
+BinPackParameters: true
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ContinuationIndentWidth: 8
+DerivePointerAlignment: false
+IndentWidth: 4
+PointerAlignment: Left
+TabWidth: 4
+AccessModifierOffset: -4
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
new file mode 100644
index 0000000..4ebca84
--- /dev/null
+++ b/cmds/statsd/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2015 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.
+//
+
+// ==========================================================
+// Build the library for use on the host
+// ==========================================================
+cc_library_host_shared {
+    name: "libstats_proto_host",
+    srcs: [
+        "src/stats_events.proto",
+    ],
+
+    shared_libs: [
+        "libplatformprotos",
+    ],
+
+    proto: {
+        type: "full",
+        export_proto_headers: true,
+    },
+}
+
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index b9ee7ff..db634d4 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -14,22 +14,61 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-# ================
-# proto static lib
-# ================
-include $(CLEAR_VARS)
+statsd_common_src := \
+    ../../core/java/android/os/IStatsCallbacks.aidl \
+    ../../core/java/android/os/IStatsCompanionService.aidl \
+    ../../core/java/android/os/IStatsManager.aidl \
+    src/stats_log.proto \
+    src/statsd_config.proto \
+    src/stats_events_copy.proto \
+    src/anomaly/AnomalyMonitor.cpp \
+    src/condition/CombinationConditionTracker.cpp \
+    src/condition/condition_util.cpp \
+    src/condition/SimpleConditionTracker.cpp \
+    src/condition/ConditionWizard.cpp \
+    src/config/ConfigKey.cpp \
+    src/config/ConfigListener.cpp \
+    src/config/ConfigManager.cpp \
+    src/external/KernelWakelockPuller.cpp \
+    src/external/StatsPullerManager.cpp \
+    src/logd/LogEvent.cpp \
+    src/logd/LogListener.cpp \
+    src/logd/LogReader.cpp \
+    src/matchers/CombinationLogMatchingTracker.cpp \
+    src/matchers/matcher_util.cpp \
+    src/matchers/SimpleLogMatchingTracker.cpp \
+    src/metrics/CountAnomalyTracker.cpp \
+    src/metrics/MetricProducer.cpp \
+    src/metrics/EventMetricProducer.cpp \
+    src/metrics/CountMetricProducer.cpp \
+    src/metrics/DurationMetricProducer.cpp \
+    src/metrics/MetricsManager.cpp \
+    src/metrics/metrics_manager_util.cpp \
+    src/packages/UidMap.cpp \
+    src/storage/DropboxReader.cpp \
+    src/storage/DropboxWriter.cpp \
+    src/StatsLogProcessor.cpp \
+    src/StatsService.cpp \
+    src/stats_util.cpp
 
-LOCAL_MODULE := statsd_proto
-LOCAL_MODULE_TAGS := optional
+statsd_common_c_includes := \
+    $(LOCAL_PATH)/src \
+    $(LOCAL_PATH)/../../libs/services/include
 
-LOCAL_SRC_FILES := $(call all-proto-files-under, src)
+statsd_common_aidl_includes := \
+    $(LOCAL_PATH)/../../core/java
 
-LOCAL_PROTOC_FLAGS :=
-LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
-
-include $(BUILD_STATIC_LIBRARY)
-
-STATSD_PROTO_INCLUDES := $(local-generated-sources-dir)/src/$(LOCAL_PATH)
+statsd_common_shared_libraries := \
+    libbase \
+    libbinder \
+    libcutils \
+    libincident \
+    liblog \
+    libselinux \
+    libutils \
+    libservices \
+    libandroidfw \
+    libprotoutil
 
 # =========
 # statsd
@@ -40,21 +79,8 @@
 LOCAL_MODULE := statsd
 
 LOCAL_SRC_FILES := \
-    ../../core/java/android/os/IStatsCompanionService.aidl \
-    ../../core/java/android/os/IStatsManager.aidl \
-    src/StatsService.cpp \
-    src/AnomalyMonitor.cpp \
-    src/LogEntryPrinter.cpp \
-    src/LogReader.cpp \
-    src/main.cpp \
-    src/DropboxWriter.cpp \
-    src/parse_util.cpp \
-    src/StatsLogProcessor.cpp \
-    src/stats_log.proto \
-    src/statsd_config.proto \
-    src/stats_constants.proto \
-    src/DropboxReader.cpp \
-
+    $(statsd_common_src) \
+    src/main.cpp
 
 LOCAL_CFLAGS += \
     -Wall \
@@ -72,24 +98,12 @@
     LOCAL_CFLAGS += \
             -Os
 endif
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
 
-LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/src \
-	STATSD_PROTO_INCLUDES
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
+LOCAL_C_INCLUDES += $(statsd_common_c_includes)
 
-LOCAL_STATIC_LIBRARIES := statsd_proto
-
-LOCAL_SHARED_LIBRARIES := \
-        libbase \
-        libbinder \
-        libcutils \
-        libincident \
-        liblog \
-        libselinux \
-        libutils \
-        libservices \
-        libandroidfw \
-        libprotobuf-cpp-lite \
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
 
 LOCAL_MODULE_CLASS := EXECUTABLES
 
@@ -97,6 +111,7 @@
 
 include $(BUILD_EXECUTABLE)
 
+
 # ==============
 # statsd_test
 # ==============
@@ -107,6 +122,9 @@
 LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
+LOCAL_C_INCLUDES += $(statsd_common_c_includes)
+
 LOCAL_CFLAGS += \
     -Wall \
     -Werror \
@@ -115,29 +133,28 @@
     -Wno-unused-function \
     -Wno-unused-parameter
 
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/src \
-	STATSD_PROTO_INCLUDES
-
 LOCAL_SRC_FILES := \
-    ../../core/java/android/os/IStatsCompanionService.aidl \
-    ../../core/java/android/os/IStatsManager.aidl \
-    src/StatsService.cpp \
+    $(statsd_common_src) \
+    tests/AnomalyMonitor_test.cpp \
+    tests/ConditionTracker_test.cpp \
+    tests/ConfigManager_test.cpp \
     tests/indexed_priority_queue_test.cpp \
-    src/LogEntryPrinter.cpp \
-    src/LogReader.cpp \
+    tests/LogEntryMatcher_test.cpp \
     tests/LogReader_test.cpp \
+    tests/MetricsManager_test.cpp \
+    tests/UidMap_test.cpp
+
 
 LOCAL_STATIC_LIBRARIES := \
-    libgmock \
-    statsd_proto
+    libgmock
 
-LOCAL_SHARED_LIBRARIES := \
-    libbase \
-    libbinder \
-    libcutils \
-    liblog \
-    libselinux \
-    libutils \
-    libprotobuf-cpp-lite \
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+
+statsd_common_src:=
+statsd_common_aidl_includes:=
+statsd_common_c_includes:=
 
 include $(BUILD_NATIVE_TEST)
+
diff --git a/cmds/statsd/src/AnomalyMonitor.cpp b/cmds/statsd/src/AnomalyMonitor.cpp
deleted file mode 100644
index 2d3454a..0000000
--- a/cmds/statsd/src/AnomalyMonitor.cpp
+++ /dev/null
@@ -1,108 +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.
- */
-
-#define LOG_TAG "AnomalyMonitor"
-#define DEBUG true
-
-#include "AnomalyMonitor.h"
-
-#include <cutils/log.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-AnomalyMonitor::AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec)
-        : mRegisteredAlarmTimeSec(0),
-          mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec) {
-}
-
-AnomalyMonitor::~AnomalyMonitor() {
-}
-
-void AnomalyMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
-    std::lock_guard<std::mutex> lock(mLock);
-    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
-    mStatsCompanionService = statsCompanionService;
-    if (statsCompanionService == nullptr) {
-        if (DEBUG) ALOGD("Erasing link to statsCompanionService");
-        return;
-    }
-    if (DEBUG) ALOGD("Creating link to statsCompanionService");
-    const sp<const AnomalyAlarm> top = mPq.top();
-    if (top != nullptr) {
-        updateRegisteredAlarmTime_l(top->timestampSec);
-    }
-}
-
-void AnomalyMonitor::add(sp<const AnomalyAlarm> alarm) {
-    std::lock_guard<std::mutex> lock(mLock);
-    if (alarm == nullptr) {
-        ALOGW("Asked to add a null alarm.");
-        return;
-    }
-    if (alarm->timestampSec < 1) {
-        // forbidden since a timestamp 0 is used to indicate no alarm registered
-        ALOGW("Asked to add a 0-time alarm.");
-        return;
-    }
-    // TODO: Ensure that refractory period is respected.
-    if (DEBUG) ALOGD("Adding alarm with time %u", alarm->timestampSec);
-    mPq.push(alarm);
-    if (mRegisteredAlarmTimeSec < 1 ||
-            alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) {
-        updateRegisteredAlarmTime_l(alarm->timestampSec);
-    }
-}
-
-void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) {
-    std::lock_guard<std::mutex> lock(mLock);
-    if (alarm == nullptr) {
-        ALOGW("Asked to remove a null alarm.");
-        return;
-    }
-    if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec);
-    mPq.remove(alarm);
-    if (mPq.empty()) {
-        if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
-        mRegisteredAlarmTimeSec = 0;
-        if (mStatsCompanionService != nullptr) {
-            mStatsCompanionService->cancelAnomalyAlarm();
-        }
-        return;
-    }
-    uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec;
-    if (DEBUG) ALOGD("Soonest alarm is %u", soonestAlarmTimeSec);
-    if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) {
-        updateRegisteredAlarmTime_l(soonestAlarmTimeSec);
-    }
-}
-
-void AnomalyMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
-    if (DEBUG) ALOGD("Updating reg alarm time to %u", timestampSec);
-    mRegisteredAlarmTimeSec = timestampSec;
-    if (mStatsCompanionService != nullptr) {
-        mStatsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec));
-    }
-}
-
-int64_t AnomalyMonitor::secToMs(uint32_t timeSec) {
-    return ((int64_t) timeSec) * 1000;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/AnomalyMonitor.h b/cmds/statsd/src/AnomalyMonitor.h
deleted file mode 100644
index e89afa8..0000000
--- a/cmds/statsd/src/AnomalyMonitor.h
+++ /dev/null
@@ -1,138 +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.
- */
-
-#ifndef ANOMALY_MONITOR_H
-#define ANOMALY_MONITOR_H
-
-#include <indexed_priority_queue.h>
-#include <android/os/IStatsCompanionService.h>
-#include <utils/RefBase.h>
-
-#include <queue>
-#include <vector>
-
-using namespace android;
-
-using android::os::IStatsCompanionService;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Represents an alarm, associated with some aggregate metric, holding a
- * projected time at which the metric is expected to exceed its anomaly
- * threshold.
- * Timestamps are in seconds since epoch in a uint32, so will fail in year 2106.
- */
-struct AnomalyAlarm : public RefBase {
-    AnomalyAlarm(uint32_t timestampSec) : timestampSec(timestampSec) {
-    }
-
-    const uint32_t timestampSec;
-
-    /** AnomalyAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
-    struct SmallerTimestamp {
-        bool operator()(sp<const AnomalyAlarm> a, sp<const AnomalyAlarm> b) const {
-            return (a->timestampSec < b->timestampSec);
-        }
-    };
-};
-
-/**
- * Manages alarms for Anomaly Detection.
- */
-class AnomalyMonitor : public RefBase {
- public:
-    /**
-     * @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs
-     * from the registered alarm by more than this amount, update the registered
-     * alarm.
-     */
-    AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec);
-    ~AnomalyMonitor();
-
-    /**
-     * Tells AnomalyMonitor what IStatsCompanionService to use and, if
-     * applicable, immediately registers an existing alarm with it.
-     * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
-     * update IStatsCompanionService (until such time as it is set non-null).
-     */
-    void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
-
-    /**
-     * Adds the given alarm (reference) to the queue.
-     */
-    void add(sp<const AnomalyAlarm> alarm);
-
-    /**
-     * Removes the given alarm (reference) from the queue.
-     * Note that alarm comparison is reference-based; if another alarm exists
-     * with the same timestampSec, that alarm will still remain in the queue.
-     */
-    void remove(sp<const AnomalyAlarm> alarm);
-
-    /**
-     * Returns the projected alarm timestamp that is registered with
-     * StatsCompanionService. This may not be equal to the soonest alarm,
-     * but should be within minDiffToUpdateRegisteredAlarmTimeSec of it.
-     */
-    uint32_t getRegisteredAlarmTimeSec() const {
-        return mRegisteredAlarmTimeSec;
-    }
-
- private:
-    std::mutex mLock;
-
-    /**
-     * Timestamp (seconds since epoch) of the alarm registered with
-     * StatsCompanionService. This, in general, may not be equal to the soonest
-     * alarm stored in mPq, but should be within minUpdateTimeSec of it.
-     * A value of 0 indicates that no alarm is currently registered.
-     */
-    uint32_t mRegisteredAlarmTimeSec;
-
-    /**
-     * Priority queue of alarms, prioritized by soonest alarm.timestampSec.
-     */
-    indexed_priority_queue<AnomalyAlarm, AnomalyAlarm::SmallerTimestamp> mPq;
-
-    /**
-     * Binder interface for communicating with StatsCompanionService.
-     */
-    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
-
-    /**
-     * Amount by which the soonest projected alarm must differ from
-     * mRegisteredAlarmTimeSec before updateRegisteredAlarmTime_l is called.
-     */
-    uint32_t mMinUpdateTimeSec;
-
-    /**
-     * Updates the alarm registered with StatsCompanionService to the given time.
-     * Also correspondingly updates mRegisteredAlarmTimeSec.
-     */
-    void updateRegisteredAlarmTime_l(uint32_t timestampSec);
-
-    /** Converts uint32 timestamp in seconds to a Java long in msec. */
-    int64_t secToMs(uint32_t timeSec);
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
-#endif // ANOMALY_MONITOR_H
\ No newline at end of file
diff --git a/cmds/statsd/src/DropboxReader.cpp b/cmds/statsd/src/DropboxReader.cpp
deleted file mode 100644
index 307e771..0000000
--- a/cmds/statsd/src/DropboxReader.cpp
+++ /dev/null
@@ -1,126 +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.
- */
-#include <android/os/DropBoxManager.h>
-#include <android-base/file.h>
-#include <androidfw/ZipUtils.h>
-
-#include "DropboxReader.h"
-
-using android::sp;
-using android::String16;
-using android::binder::Status;
-using android::base::unique_fd;
-using android::os::DropBoxManager;
-using android::ZipUtils;
-using std::vector;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-status_t DropboxReader::readStatsLogs(FILE* out, const string& tag, long msec) {
-    sp<DropBoxManager> dropbox = new DropBoxManager();
-    StatsLogReport logReport;
-
-    long timestamp = msec;
-    // instead of while(true), put a hard limit 1000. Dropbox won't have more than 1000 files.
-    for(int i = 0; i < 1000; i++ ) {
-        DropBoxManager::Entry entry;
-        Status status = dropbox->getNextEntry(String16(tag.c_str()),
-                timestamp, &entry);
-        if (!status.isOk()) {
-            ALOGD("No more entries, or failed to read. We can't tell unfortunately.");
-            return android::OK;
-        }
-
-        const unique_fd& fd = entry.getFd();
-
-        // use this timestamp for next query.
-        timestamp = entry.getTimestamp();
-
-        if (entry.getFlags() & DropBoxManager::IS_GZIPPED) {
-            if (!parseFromGzipFile(fd, logReport)) {
-                // Failed to parse from the file. Continue to fetch the next entry.
-                continue;
-            }
-        } else {
-            if (!parseFromFile(fd, logReport)) {
-                // Failed to parse from the file. Continue to fetch the next entry.
-                continue;
-            }
-        }
-
-        printLog(out, logReport);
-    }
-    return android::OK;
-}
-
-bool DropboxReader::parseFromGzipFile(const unique_fd& fd, StatsLogReport& logReport) {
-    FILE *file = fdopen(fd, "r");
-    bool result = false;
-    bool scanResult;
-    int method;
-    long compressedLen;
-    long uncompressedLen;
-    unsigned long crc32;
-    scanResult = ZipUtils::examineGzip(file, &method, &uncompressedLen,
-            &compressedLen, &crc32);
-    if (scanResult && method == kCompressDeflated) {
-        vector<uint8_t> buf(uncompressedLen);
-        if (ZipUtils::inflateToBuffer(file, &buf[0], uncompressedLen, compressedLen)) {
-            if (logReport.ParseFromArray(&buf[0], uncompressedLen)) {
-                result = true;
-            }
-        }
-    } else {
-        ALOGE("This isn't a valid deflated gzip file");
-    }
-    fclose(file);
-    return result;
-}
-
-// parse a non zipped file.
-bool DropboxReader::parseFromFile(const unique_fd& fd, StatsLogReport& logReport) {
-    string content;
-    if (!android::base::ReadFdToString(fd, &content)) {
-        ALOGE("Failed to read file");
-        return false;
-    }
-    if (!logReport.ParseFromString(content)) {
-        ALOGE("failed to parse log entry from data");
-        return false;
-    }
-    return true;
-}
-
-void DropboxReader::printLog(FILE* out, const StatsLogReport& logReport) {
-    fprintf(out, "start_time_msec=%lld, end_time_msec=%lld, ",
-            logReport.start_report_millis(), logReport.end_report_millis());
-    for (int i = 0; i < logReport.event_metrics().data_size(); i++) {
-        EventMetricData eventMetricData = logReport.event_metrics().data(i);
-        for (int j = 0; j < eventMetricData.key_value_pair_size(); j++) {
-            fprintf(out, "key=%d, ", eventMetricData.key_value_pair(j).key());
-            fprintf(out, "value_str=%s ", eventMetricData.key_value_pair(j).value_str().c_str());
-            fprintf(out, "value_int=%lld ", eventMetricData.key_value_pair(j).value_int());
-            fprintf(out, "value_float=%f ", eventMetricData.key_value_pair(j).value_float());
-        }
-    }
-    fprintf(out, "\n");
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/DropboxReader.h b/cmds/statsd/src/DropboxReader.h
deleted file mode 100644
index b7f8739..0000000
--- a/cmds/statsd/src/DropboxReader.h
+++ /dev/null
@@ -1,52 +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.
- */
-
-#ifndef DROPBOX_READER_H
-#define DROPBOX_READER_H
-
-#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
-
-#include <stdint.h>
-#include <stdio.h>
-
-using android::base::unique_fd;
-using android::status_t;
-using std::string;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class DropboxReader {
-public:
-    // msec is the start timestamp.
-    static status_t readStatsLogs(FILE* out, const string& tag, long msec);
-
-private:
-    static bool parseFromFile(const unique_fd& fd, StatsLogReport& logReport);
-    static bool parseFromGzipFile(const unique_fd& fd, StatsLogReport& logReport);
-    static void printLog(FILE* out, const StatsLogReport& logReport);
-    enum {
-      kCompressStored = 0,    // no compression
-      kCompressDeflated = 8,  // standard deflate
-    };
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
-#endif //DROPBOX_READER_H
diff --git a/cmds/statsd/src/DropboxWriter.cpp b/cmds/statsd/src/DropboxWriter.cpp
deleted file mode 100644
index b9d48fa..0000000
--- a/cmds/statsd/src/DropboxWriter.cpp
+++ /dev/null
@@ -1,67 +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.
- */
-
-#include <android/os/DropBoxManager.h>
-
-#include "DropboxWriter.h"
-
-using android::binder::Status;
-using android::os::DropBoxManager;
-using android::sp;
-using android::String16;
-using std::vector;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-DropboxWriter::DropboxWriter(const string& tag)
-    : mTag(tag), mLogReport(), mBufferSize(0) {
-}
-
-void DropboxWriter::addStatsLogReport(const StatsLogReport& log) {
-    mLogReport = log;
-    flushIfNecessary(log);
-    mBufferSize += log.ByteSize();
-}
-
-void DropboxWriter::flushIfNecessary(const StatsLogReport& log) {
-    // TODO: Decide to flush depending on the serialized size of the StatsLogReport.
-    // if (entry.ByteSize() + mBufferSize > kMaxSerializedBytes) {
-    //     flush();
-    // }
-    flush();
-}
-
-void DropboxWriter::flush() {
-    // now we get an exact byte size of the output
-    const int numBytes = mLogReport.ByteSize();
-    vector<uint8_t> buffer(numBytes);
-    sp<DropBoxManager> dropbox = new DropBoxManager();
-    mLogReport.SerializeToArray(&buffer[0], numBytes);
-    Status status = dropbox->addData(String16(mTag.c_str()), &buffer[0],
-            numBytes, 0 /* no flag */);
-    if (!status.isOk()) {
-        ALOGE("failed to write to dropbox");
-        //TODO: What to do if flush fails??
-    }
-    mLogReport.Clear();
-    mBufferSize = 0;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/DropboxWriter.h b/cmds/statsd/src/DropboxWriter.h
deleted file mode 100644
index 59629fb..0000000
--- a/cmds/statsd/src/DropboxWriter.h
+++ /dev/null
@@ -1,72 +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.
- */
-
-#ifndef DROPBOX_WRITER_H
-#define DROPBOX_WRITER_H
-
-#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
-
-using std::string;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class DropboxWriter {
-public:
-    /* tag will be part of the file name, and used as the key to build the file index inside
-       DropBoxManagerService.
-     */
-    DropboxWriter(const string& tag);
-
-    void addStatsLogReport(const StatsLogReport& log);
-
-    /* Request a flush to dropbox. */
-    void flush();
-
-private:
-    /* Max *serialized* size of the logs kept in memory before flushing to dropbox.
-       Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
-       So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
-       is higher than its serialized size. DropboxManager will compress the file when the data is
-       larger than 4KB. So the final file size is less than this number.
-     */
-    static const size_t kMaxSerializedBytes = 16 * 1024;
-
-    const string mTag;
-
-    /* Data that was captured for a single metric over a given interval of time. */
-    StatsLogReport mLogReport;
-
-    /* Current *serialized* size of the logs kept in memory.
-       To save computation, we will not calculate the size of the StatsLogReport every time when a new
-       entry is added, which would recursively call ByteSize() on every log entry. Instead, we keep
-       the sum of all individual stats log entry sizes. The size of a proto is approximately the sum
-       of the size of all member protos.
-     */
-    size_t mBufferSize = 0;
-
-    /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush
-       the logs to dropbox if true. */
-    void flushIfNecessary(const StatsLogReport& log);
-
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
-#endif //DROPBOX_WRITER_H
diff --git a/cmds/statsd/src/Log.h b/cmds/statsd/src/Log.h
new file mode 100644
index 0000000..7852709
--- /dev/null
+++ b/cmds/statsd/src/Log.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/*
+ * This file must be included at the top of the file. Other header files
+ * occasionally include log.h, and if LOG_TAG isn't set when that happens
+ * we'll get a preprocesser error when we try to define it here.
+ */
+
+#pragma once
+
+#define LOG_TAG "statsd"
+
+#include <log/log.h>
+
+#define VLOG(...) \
+    if (DEBUG) ALOGD(__VA_ARGS__);
diff --git a/cmds/statsd/src/LogEntryPrinter.cpp b/cmds/statsd/src/LogEntryPrinter.cpp
deleted file mode 100644
index c877b05..0000000
--- a/cmds/statsd/src/LogEntryPrinter.cpp
+++ /dev/null
@@ -1,69 +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.
- */
-
-#include <LogEntryPrinter.h>
-
-#include <log/event_tag_map.h>
-#include <log/logprint.h>
-#include <utils/Errors.h>
-
-using namespace android;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-LogEntryPrinter::LogEntryPrinter(int out)
-    :m_out(out)
-{
-    // Initialize the EventTagMap, which is how we know the names of the numeric event tags.
-    // If this fails, we can't print well, but something will print.
-    m_tags = android_openEventTagMap(NULL);
-
-    // Printing format
-    m_format = android_log_format_new();
-    android_log_setPrintFormat(m_format, FORMAT_THREADTIME);
-}
-
-LogEntryPrinter::~LogEntryPrinter()
-{
-    if (m_tags != NULL) {
-        android_closeEventTagMap(m_tags);
-    }
-    android_log_format_free(m_format);
-}
-
-void
-LogEntryPrinter::OnLogEvent(const log_msg& msg)
-{
-    status_t err;
-    AndroidLogEntry entry;
-    char buf[1024];
-
-    err = android_log_processBinaryLogBuffer(&(const_cast<log_msg*>(&msg)->entry_v1),
-                &entry, m_tags, buf, sizeof(buf));
-    if (err == NO_ERROR) {
-        android_log_printLogLine(m_format, m_out, &entry);
-    } else {
-        printf("log entry: %s\n", buf);
-        fflush(stdout);
-    }
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
diff --git a/cmds/statsd/src/LogEntryPrinter.h b/cmds/statsd/src/LogEntryPrinter.h
deleted file mode 100644
index ed720dc..0000000
--- a/cmds/statsd/src/LogEntryPrinter.h
+++ /dev/null
@@ -1,62 +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.
- */
-
-#ifndef LOG_ENTRY_PRINTER_H
-#define LOG_ENTRY_PRINTER_H
-
-#include "LogReader.h"
-
-#include <log/logprint.h>
-
-#include <stdio.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Decodes the log entry and prints it to the supplied file descriptor.
- */
-class LogEntryPrinter : public LogListener
-{
-public:
-    LogEntryPrinter(int out);
-    virtual ~LogEntryPrinter();
-
-    virtual void OnLogEvent(const log_msg& msg);
-
-private:
-    /**
-     * Where to write to.
-     */
-    int m_out;
-
-    /**
-     * Numeric to string tag name mapping.
-     */
-    EventTagMap* m_tags;
-
-    /**
-     * Pretty printing format.
-     */
-    AndroidLogFormat* m_format;
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
-#endif // LOG_ENTRY_PRINTER_H
diff --git a/cmds/statsd/src/LogReader.cpp b/cmds/statsd/src/LogReader.cpp
deleted file mode 100644
index c9164f9..0000000
--- a/cmds/statsd/src/LogReader.cpp
+++ /dev/null
@@ -1,150 +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.
- */
-
-#include "LogReader.h"
-
-#include <log/log_read.h>
-
-#include <utils/Errors.h>
-
-#include <time.h>
-#include <unistd.h>
-
-using namespace android;
-using namespace std;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-#define SNOOZE_INITIAL_MS 100
-#define SNOOZE_MAX_MS (10 * 60 * 1000) // Ten minutes
-
-
-// ================================================================================
-LogListener::LogListener()
-{
-}
-
-LogListener::~LogListener()
-{
-}
-
-
-// ================================================================================
-LogReader::LogReader()
-{
-}
-
-LogReader::~LogReader()
-{
-}
-
-void
-LogReader::AddListener(const sp<LogListener>& listener)
-{
-    m_listeners.push_back(listener);
-}
-
-void
-LogReader::Run()
-{
-    int nextSnoozeMs = SNOOZE_INITIAL_MS;
-
-    // In an ideal world, this outer loop will only ever run one iteration, but it
-    // exists to handle crashes in logd.  The inner loop inside connect_and_read()
-    // reads from logd forever, but if that read fails, we fall out to the outer
-    // loop, do the backoff (resetting the backoff timeout if we successfully read
-    // something), and then try again.
-    while (true) {
-        // Connect and read
-        int lineCount = connect_and_read();
-
-        // Figure out how long to sleep.
-        if (lineCount > 0) {
-            // If we managed to read at least one line, reset the backoff
-            nextSnoozeMs = SNOOZE_INITIAL_MS;
-        } else {
-            // Otherwise, expontial backoff
-            nextSnoozeMs *= 1.5f;
-            if (nextSnoozeMs > 10 * 60 * 1000) {
-                // Don't wait for toooo long.
-                nextSnoozeMs = SNOOZE_MAX_MS;
-            }
-        }
-
-        // Sleep
-        timespec ts;
-        timespec rem;
-        ts.tv_sec = nextSnoozeMs / 1000;
-        ts.tv_nsec = (nextSnoozeMs % 1000) * 1000000L;
-        while (nanosleep(&ts, &rem) == -1) {
-            if (errno == EINTR) {
-                ts = rem;
-            }
-            // other errors are basically impossible
-        }
-    }
-}
-
-int
-LogReader::connect_and_read()
-{
-    int lineCount = 0;
-    status_t err;
-    logger_list* loggers;
-    logger* eventLogger;
-
-    // Prepare the logging context
-    loggers = android_logger_list_alloc(ANDROID_LOG_RDONLY,
-            /* don't stop after N lines */ 0,
-            /* no pid restriction */ 0);
-
-    // Open the buffer(s)
-    eventLogger = android_logger_open(loggers, LOG_ID_STATS);
-
-    // Read forever
-    if (eventLogger) {
-        while (true) {
-            log_msg msg;
-
-            // Read a message
-            err = android_logger_list_read(loggers, &msg);
-            if (err < 0) {
-                fprintf(stderr, "logcat read failure: %s\n", strerror(err));
-                break;
-            }
-
-            // Record that we read one (used above to know how to snooze).
-            lineCount++;
-
-            // Call the listeners
-            for (vector<sp<LogListener> >::iterator it = m_listeners.begin();
-                    it != m_listeners.end(); it++) {
-                (*it)->OnLogEvent(msg);
-            }
-        }
-    }
-
-    // Free the logger list and close the individual loggers
-    android_logger_list_free(loggers);
-
-    return lineCount;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/LogReader.h b/cmds/statsd/src/LogReader.h
deleted file mode 100644
index 4c2afe8..0000000
--- a/cmds/statsd/src/LogReader.h
+++ /dev/null
@@ -1,88 +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.
- */
-
-#ifndef LOGREADER_H
-#define LOGREADER_H
-
-#include <log/log_read.h>
-#include <utils/RefBase.h>
-
-#include <vector>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Callback for LogReader 
- */
-class LogListener : public virtual android::RefBase
-{
-public:
-    LogListener();
-    virtual ~LogListener();
-
-    // TODO: Rather than using log_msg, which doesn't have any real internal structure
-    // here, we should pull this out into our own LogEntry class.
-    virtual void OnLogEvent(const log_msg& msg) = 0;
-};
-
-/**
- * Class to read logs from logd.
- */
-class LogReader : public virtual android::RefBase
-{
-public:
-    /**
-     * Construct the LogReader with a pointer back to the StatsService
-     */
-    LogReader();
-
-    /**
-     * Destructor.
-     */
-    virtual ~LogReader();
-
-    /**
-     * Add a LogListener class.
-     */
-    void AddListener(const android::sp<LogListener>& listener);
-
-   /**
-    * Run the main LogReader loop
-    */
-    void Run();
-
-private:
-    /**
-     * List of listeners to call back on when we do get an event.
-     */
-    std::vector<android::sp<LogListener> > m_listeners;
-
-    /**
-     * Connect to a single instance of logd, and read until there's a read error.
-     * Logd can crash, exit, be killed etc.
-     *
-     * Returns the number of lines that were read.
-     */
-    int connect_and_read();
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
-#endif // LOGREADER_H
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 1ae23ef..cdaca1b 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -14,66 +14,113 @@
  * limitations under the License.
  */
 
-#include <StatsLogProcessor.h>
+#include "Log.h"
+
+#include "StatsLogProcessor.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "metrics/CountMetricProducer.h"
+#include "stats_util.h"
 
 #include <log/log_event_list.h>
 #include <utils/Errors.h>
-#include <parse_util.h>
 
 using namespace android;
+using std::make_unique;
+using std::unique_ptr;
+using std::vector;
 
 namespace android {
 namespace os {
 namespace statsd {
 
-StatsLogProcessor::StatsLogProcessor() : m_dropbox_writer("all-logs")
-{
-    // Initialize the EventTagMap, which is how we know the names of the numeric event tags.
-    // If this fails, we can't print well, but something will print.
-    m_tags = android_openEventTagMap(NULL);
-
-    // Printing format
-    m_format = android_log_format_new();
-    android_log_setPrintFormat(m_format, FORMAT_THREADTIME);
+StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
+                                     const std::function<void(const vector<uint8_t>&)>& pushLog)
+    : mUidMap(uidMap), mPushLog(pushLog) {
 }
 
-StatsLogProcessor::~StatsLogProcessor()
-{
-    if (m_tags != NULL) {
-        android_closeEventTagMap(m_tags);
-    }
-    android_log_format_free(m_format);
+StatsLogProcessor::~StatsLogProcessor() {
 }
 
-void
-StatsLogProcessor::OnLogEvent(const log_msg& msg)
-{
-    status_t err;
-    AndroidLogEntry entry;
-    char buf[1024];
-
-    err = android_log_processBinaryLogBuffer(&(const_cast<log_msg*>(&msg)->entry_v1),
-                &entry, m_tags, buf, sizeof(buf));
-
-    // dump all statsd logs to dropbox for now.
-    // TODO: Add filtering, aggregation, etc.
-    if (err == NO_ERROR) {
-        StatsLogReport logReport;
-        logReport.set_start_report_millis(entry.tv_sec / 1000 + entry.tv_nsec / 1000 / 1000);
-        EventMetricData *eventMetricData = logReport.mutable_event_metrics()->add_data();
-        *eventMetricData = parse(msg);
-
-        m_dropbox_writer.addStatsLogReport(logReport);
+// TODO: what if statsd service restarts? How do we know what logs are already processed before?
+void StatsLogProcessor::OnLogEvent(const LogEvent& msg) {
+    // pass the event to metrics managers.
+    for (auto& pair : mMetricsManagers) {
+        pair.second->onLogEvent(msg);
+        flushIfNecessary(msg.GetTimestampNs(), pair.first, pair.second);
     }
 }
 
-void
-StatsLogProcessor::UpdateConfig(const int config_source, StatsdConfig config)
-{
-    m_configs[config_source] = config;
-    ALOGD("Updated configuration for source %i", config_source);
+void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
+    auto it = mMetricsManagers.find(key);
+    if (it != mMetricsManagers.end()) {
+        it->second->finish();
+    }
+
+    ALOGD("Updated configuration for key %s", key.ToString().c_str());
+
+    unique_ptr<MetricsManager> newMetricsManager = std::make_unique<MetricsManager>(config);
+    if (newMetricsManager->isConfigValid()) {
+        mMetricsManagers[key] = std::move(newMetricsManager);
+        // Why doesn't this work? mMetricsManagers.insert({key, std::move(newMetricsManager)});
+        ALOGD("StatsdConfig valid");
+    } else {
+        // If there is any error in the config, don't use it.
+        ALOGD("StatsdConfig NOT valid");
+    }
 }
 
-} // namespace statsd
-} // namespace os
-} // namespace android
+vector<StatsLogReport> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
+    auto it = mMetricsManagers.find(key);
+    if (it == mMetricsManagers.end()) {
+        ALOGW("Config source %s does not exist", key.ToString().c_str());
+        return vector<StatsLogReport>();
+    }
+
+    return it->second->onDumpReport();
+}
+
+void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
+    auto it = mMetricsManagers.find(key);
+    if (it != mMetricsManagers.end()) {
+        it->second->finish();
+        mMetricsManagers.erase(it);
+    }
+    auto flushTime = mLastFlushTimes.find(key);
+    if (flushTime != mLastFlushTimes.end()) {
+        mLastFlushTimes.erase(flushTime);
+    }
+}
+
+void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs,
+                                         const ConfigKey& key,
+                                         const unique_ptr<MetricsManager>& metricsManager) {
+    auto lastFlushNs = mLastFlushTimes.find(key);
+    if (lastFlushNs != mLastFlushTimes.end()) {
+        if (timestampNs - lastFlushNs->second < kMinFlushPeriod) {
+            return;
+        }
+    }
+
+    size_t totalBytes = metricsManager->byteSize();
+    if (totalBytes > kMaxSerializedBytes) {
+        flush();
+        mLastFlushTimes[key] = std::move(timestampNs);
+    }
+}
+
+void StatsLogProcessor::flush() {
+    // TODO: Take ConfigKey as an argument and flush metrics related to the
+    // ConfigKey. Also, create a wrapper that holds a repeated field of
+    // StatsLogReport's.
+    /*
+    StatsLogReport logReport;
+    const int numBytes = logReport.ByteSize();
+    vector<uint8_t> logReportBuffer(numBytes);
+    logReport.SerializeToArray(&logReportBuffer[0], numBytes);
+    mPushLog(logReportBuffer);
+    */
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index a6d182c..6463441 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -16,46 +16,66 @@
 #ifndef STATS_LOG_PROCESSOR_H
 #define STATS_LOG_PROCESSOR_H
 
-#include "parse_util.h"
+#include "config/ConfigListener.h"
+#include "logd/LogReader.h"
+#include "metrics/MetricsManager.h"
+#include "packages/UidMap.h"
 
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+#include <stdio.h>
 #include <unordered_map>
 
 namespace android {
 namespace os {
 namespace statsd {
 
-class StatsLogProcessor : public LogListener
-{
+class StatsLogProcessor : public ConfigListener {
 public:
-    StatsLogProcessor();
+    StatsLogProcessor(const sp<UidMap>& uidMap,
+                      const std::function<void(const vector<uint8_t>&)>& pushLog);
     virtual ~StatsLogProcessor();
 
-    virtual void OnLogEvent(const log_msg& msg);
+    virtual void OnLogEvent(const LogEvent& event);
 
-    virtual void UpdateConfig(const int config_source, StatsdConfig config);
+    void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
+    void OnConfigRemoved(const ConfigKey& key);
+
+    // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
+    std::vector<StatsLogReport> onDumpReport(const ConfigKey& key);
+
+    /* Request a flush through a binder call. */
+    void flush();
 
 private:
-    /**
-     * Numeric to string tag name mapping.
-     */
-    EventTagMap* m_tags;
+    std::unordered_map<ConfigKey, std::unique_ptr<MetricsManager>> mMetricsManagers;
 
-    /**
-     * Pretty printing format.
-     */
-    AndroidLogFormat* m_format;
+    std::unordered_map<ConfigKey, long> mLastFlushTimes;
 
-    DropboxWriter m_dropbox_writer;
+    sp<UidMap> mUidMap;  // Reference to the UidMap to lookup app name and version for each uid.
 
-    /**
-     * Configs that have been specified, keyed by the source. This allows us to over-ride the config
-     * from a source later.
+    /* Max *serialized* size of the logs kept in memory before flushing through binder call.
+       Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
+       So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
+       is higher than its serialized size.
      */
-    std::unordered_map<int, StatsdConfig> m_configs;
+    static const size_t kMaxSerializedBytes = 16 * 1024;
+
+    /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush
+       the logs to callback clients if true. */
+    void flushIfNecessary(uint64_t timestampNs,
+                          const ConfigKey& key,
+                          const unique_ptr<MetricsManager>& metricsManager);
+
+    std::function<void(const vector<uint8_t>&)> mPushLog;
+
+    /* Minimum period between two flushes in nanoseconds. Currently set to 10
+     * minutes. */
+    static const unsigned long long kMinFlushPeriod = 600 * NS_PER_SEC;
 };
 
-} // namespace statsd
-} // namespace os
-} // namespace android
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
 
-#endif //STATS_LOG_PROCESSOR_H
+#endif  // STATS_LOG_PROCESSOR_H
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 965c9b7..604753e 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -14,24 +14,24 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "statsd"
 #define DEBUG true
+#include "Log.h"
 
 #include "StatsService.h"
-#include "DropboxReader.h"
+#include "storage/DropboxReader.h"
 
 #include <android-base/file.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-#include <cutils/log.h>
 #include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
 #include <private/android_filesystem_config.h>
 #include <utils/Looper.h>
 #include <utils/String16.h>
 
-#include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/system_properties.h>
+#include <unistd.h>
 
 using namespace android;
 
@@ -39,28 +39,68 @@
 namespace os {
 namespace statsd {
 
+// ======================================================================
+/**
+ * Watches for the death of the stats companion (system process).
+ */
+class CompanionDeathRecipient : public IBinder::DeathRecipient {
+public:
+    CompanionDeathRecipient(const sp<AnomalyMonitor>& anomalyMonitor);
+    virtual void binderDied(const wp<IBinder>& who);
+
+private:
+    const sp<AnomalyMonitor> mAnomalyMonitor;
+};
+
+CompanionDeathRecipient::CompanionDeathRecipient(const sp<AnomalyMonitor>& anomalyMonitor)
+    : mAnomalyMonitor(anomalyMonitor) {
+}
+
+void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) {
+    ALOGW("statscompanion service died");
+    mAnomalyMonitor->setStatsCompanionService(nullptr);
+}
+
+// ======================================================================
 StatsService::StatsService(const sp<Looper>& handlerLooper)
-        : mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Change this based on the config
+    : mAnomalyMonitor(new AnomalyMonitor(2))  // TODO: Put this comment somewhere better
 {
-    ALOGD("stats service constructed");
+    mStatsPullerManager = new StatsPullerManager();
+    mUidMap = new UidMap();
+    mConfigManager = new ConfigManager();
+    mProcessor = new StatsLogProcessor(mUidMap, [this](const vector<uint8_t>& log) {
+      pushLog(log);
+    });
+
+    mConfigManager->AddListener(mProcessor);
+
+    init_system_properties();
 }
 
-StatsService::~StatsService()
-{
+StatsService::~StatsService() {
 }
 
-status_t
-StatsService::setProcessor(const sp<StatsLogProcessor>& main_processor) {
-    m_processor = main_processor;
-    ALOGD("stats service set to processor %p", m_processor.get());
-    return NO_ERROR;
+void StatsService::init_system_properties() {
+    mEngBuild = false;
+    const prop_info* buildType = __system_property_find("ro.build.type");
+    if (buildType != NULL) {
+        __system_property_read_callback(buildType, init_build_type_callback, this);
+    }
 }
 
-// Implement our own because the default binder implementation isn't
-// properly handling SHELL_COMMAND_TRANSACTION
-status_t
-StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
+void StatsService::init_build_type_callback(void* cookie, const char* /*name*/, const char* value,
+                                            uint32_t serial) {
+    if (0 == strcmp("eng", value) || 0 == strcmp("userdebug", value)) {
+        reinterpret_cast<StatsService*>(cookie)->mEngBuild = true;
+    }
+}
+
+/**
+ * Implement our own because the default binder implementation isn't
+ * properly handling SHELL_COMMAND_TRANSACTION.
+ */
+status_t StatsService::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+                                  uint32_t flags) {
     status_t err;
 
     switch (code) {
@@ -73,10 +113,9 @@
             for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
                 args.add(String8(data.readString16()));
             }
-            sp<IShellCallback> shellCallback = IShellCallback::asInterface(
-                    data.readStrongBinder());
-            sp<IResultReceiver> resultReceiver = IResultReceiver::asInterface(
-                    data.readStrongBinder());
+            sp<IShellCallback> shellCallback = IShellCallback::asInterface(data.readStrongBinder());
+            sp<IResultReceiver> resultReceiver =
+                    IResultReceiver::asInterface(data.readStrongBinder());
 
             FILE* fin = fdopen(in, "r");
             FILE* fout = fdopen(out, "w");
@@ -104,75 +143,280 @@
 
             return NO_ERROR;
         }
-        default: {
-            return BnStatsManager::onTransact(code, data, reply, flags);
-        }
+        default: { return BnStatsManager::onTransact(code, data, reply, flags); }
     }
 }
 
-status_t
-StatsService::dump(int fd, const Vector<String16>& args)
-{
+/**
+ * Write debugging data about statsd.
+ */
+status_t StatsService::dump(int fd, const Vector<String16>& args) {
     FILE* out = fdopen(fd, "w");
     if (out == NULL) {
         return NO_MEMORY;  // the fd is already open
     }
 
-    fprintf(out, "StatsService::dump:");
-    ALOGD("StatsService::dump:");
-    const int N = args.size();
-    for (int i=0; i<N; i++) {
-        fprintf(out, " %s", String8(args[i]).string());
-        ALOGD("   %s", String8(args[i]).string());
-    }
-    fprintf(out, "\n");
+    // TODO: Proto format for incident reports
+    dump_impl(out);
 
     fclose(out);
     return NO_ERROR;
 }
 
-status_t
-StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args)
-{
-    if (args.size() > 0) {
-        if (!args[0].compare(String8("print-stats-log")) && args.size() > 1) {
-            return doPrintStatsLog(out, args);
-        }
+/**
+ * Write debugging data about statsd in text format.
+ */
+void StatsService::dump_impl(FILE* out) {
+    mConfigManager->Dump(out);
+}
+
+/**
+ * Implementation of the adb shell cmd stats command.
+ */
+status_t StatsService::command(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
+    // TODO: Permission check
+
+    const int argCount = args.size();
+    if (argCount >= 1) {
+        // adb shell cmd stats config ...
         if (!args[0].compare(String8("config"))) {
-            return doLoadConfig(in);
+            return cmd_config(in, out, err, args);
+        }
+
+        // adb shell cmd stats print-stats-log
+        if (!args[0].compare(String8("print-stats-log")) && args.size() > 1) {
+            return cmd_print_stats_log(out, args);
+        }
+
+        if (!args[0].compare(String8("print-uid-map"))) {
+            return cmd_print_uid_map(out);
+        }
+
+        if (!args[0].compare(String8("dump-report"))) {
+            return cmd_dump_report(out, err, args);
+        }
+
+        if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
+            return cmd_print_pulled_metrics(out, args);
         }
     }
 
-    printCmdHelp(out);
+    print_cmd_help(out);
     return NO_ERROR;
 }
 
-status_t
-StatsService::doLoadConfig(FILE* in)
-{
-    string content;
-    if (!android::base::ReadFdToString(fileno(in), &content)) {
-        return UNKNOWN_ERROR;
+void StatsService::print_cmd_help(FILE* out) {
+    fprintf(out,
+            "usage: adb shell cmd stats print-stats-log [tag_required] "
+            "[timestamp_nsec_optional]\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats print-uid-map \n");
+    fprintf(out, "\n");
+    fprintf(out, "  Prints the UID, app name, version mapping.\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmds stats pull-source [int] \n");
+    fprintf(out, "\n");
+    fprintf(out, "  Prints the output of a pulled metrics source (int indicates source)\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n");
+    fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
+    fprintf(out, "\n");
+    fprintf(out, "  Adds, updates or removes a configuration. The proto should be in\n");
+    fprintf(out, "  wire-encoded protobuf format and passed via stdin.\n");
+    fprintf(out, "\n");
+    fprintf(out, "  UID           The uid to use. It is only possible to pass the UID\n");
+    fprintf(out, "                parameter on eng builds.  If UID is omitted the calling\n");
+    fprintf(out, "                uid is used.\n");
+    fprintf(out, "  NAME          The per-uid name to use\n");
+}
+
+status_t StatsService::cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args) {
+    const int argCount = args.size();
+    if (argCount >= 2) {
+        if (args[1] == "update" || args[1] == "remove") {
+            bool good = false;
+            int uid = -1;
+            string name;
+
+            if (argCount == 3) {
+                // Automatically pick the UID
+                uid = IPCThreadState::self()->getCallingUid();
+                // TODO: What if this isn't a binder call? Should we fail?
+                name.assign(args[2].c_str(), args[2].size());
+                good = true;
+            } else if (argCount == 4) {
+                // If it's a userdebug or eng build, then the shell user can
+                // impersonate other uids.
+                if (mEngBuild) {
+                    const char* s = args[2].c_str();
+                    if (*s != '\0') {
+                        char* end = NULL;
+                        uid = strtol(s, &end, 0);
+                        if (*end == '\0') {
+                            name.assign(args[3].c_str(), args[3].size());
+                            good = true;
+                        }
+                    }
+                } else {
+                    fprintf(err,
+                            "The config can only be set for other UIDs on eng or userdebug "
+                            "builds.\n");
+                }
+            }
+
+            if (!good) {
+                // If arg parsing failed, print the help text and return an error.
+                print_cmd_help(out);
+                return UNKNOWN_ERROR;
+            }
+
+            if (args[1] == "update") {
+                // Read stream into buffer.
+                string buffer;
+                if (!android::base::ReadFdToString(fileno(in), &buffer)) {
+                    fprintf(err, "Error reading stream for StatsConfig.\n");
+                    return UNKNOWN_ERROR;
+                }
+
+                // Parse buffer.
+                StatsdConfig config;
+                if (!config.ParseFromString(buffer)) {
+                    fprintf(err, "Error parsing proto stream for StatsConfig.\n");
+                    return UNKNOWN_ERROR;
+                }
+
+                // Add / update the config.
+                mConfigManager->UpdateConfig(ConfigKey(uid, name), config);
+            } else {
+                // Remove the config.
+                mConfigManager->RemoveConfig(ConfigKey(uid, name));
+            }
+
+            return NO_ERROR;
+        }
     }
-    StatsdConfig config;
-    if (config.ParseFromString(content)) {
-        ALOGD("Config parsed from command line: %s", config.SerializeAsString().c_str());
-        m_processor->UpdateConfig(0, config);
-        return NO_ERROR;
+    print_cmd_help(out);
+    return UNKNOWN_ERROR;
+}
+
+status_t StatsService::cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args) {
+    if (mProcessor != nullptr) {
+        const int argCount = args.size();
+        bool good = false;
+        int uid;
+        string name;
+        if (argCount == 2) {
+            // Automatically pick the UID
+            uid = IPCThreadState::self()->getCallingUid();
+            // TODO: What if this isn't a binder call? Should we fail?
+            name.assign(args[2].c_str(), args[2].size());
+            good = true;
+        } else if (argCount == 3) {
+            // If it's a userdebug or eng build, then the shell user can
+            // impersonate other uids.
+            if (mEngBuild) {
+                const char* s = args[1].c_str();
+                if (*s != '\0') {
+                    char* end = NULL;
+                    uid = strtol(s, &end, 0);
+                    if (*end == '\0') {
+                        name.assign(args[2].c_str(), args[2].size());
+                        good = true;
+                    }
+                }
+            } else {
+                fprintf(out,
+                        "The metrics can only be dumped for other UIDs on eng or userdebug "
+                        "builds.\n");
+            }
+        }
+        if (good) {
+            mProcessor->onDumpReport(ConfigKey(uid, name));
+            // TODO: print the returned StatsLogReport to file instead of printing to logcat.
+            fprintf(out, "Dump report for Config [%d,%s]\n", uid, name.c_str());
+            fprintf(out, "See the StatsLogReport in logcat...\n");
+            return android::OK;
+        } else {
+            // If arg parsing failed, print the help text and return an error.
+            print_cmd_help(out);
+            return UNKNOWN_ERROR;
+        }
     } else {
-        ALOGD("Config failed to be parsed");
+        fprintf(out, "Log processor does not exist...\n");
         return UNKNOWN_ERROR;
     }
 }
 
-Status
-StatsService::informAnomalyAlarmFired()
-{
+status_t StatsService::cmd_print_stats_log(FILE* out, const Vector<String8>& args) {
+    long msec = 0;
+
+    if (args.size() > 2) {
+        msec = strtol(args[2].string(), NULL, 10);
+    }
+    return DropboxReader::readStatsLogs(out, args[1].string(), msec);
+}
+
+status_t StatsService::cmd_print_uid_map(FILE* out) {
+    mUidMap->printUidMap(out);
+    return NO_ERROR;
+}
+
+status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) {
+    int s = atoi(args[1].c_str());
+    auto stats = mStatsPullerManager->Pull(s);
+    for (const auto& it : stats) {
+        fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
+    }
+    fprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size());
+    return NO_ERROR;
+}
+
+Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
+                                      const vector<String16>& app) {
+    if (DEBUG) ALOGD("StatsService::informAllUidData was called");
+
+    if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                                         "Only system uid can call informAllUidData");
+    }
+
+    mUidMap->updateMap(uid, version, app);
+    if (DEBUG) ALOGD("StatsService::informAllUidData succeeded");
+
+    return Status::ok();
+}
+
+Status StatsService::informOnePackage(const String16& app, int32_t uid, int32_t version) {
+    if (DEBUG) ALOGD("StatsService::informOnePackage was called");
+
+    if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                                         "Only system uid can call informOnePackage");
+    }
+    mUidMap->updateApp(app, uid, version);
+    return Status::ok();
+}
+
+Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
+    if (DEBUG) ALOGD("StatsService::informOnePackageRemoved was called");
+
+    if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                                         "Only system uid can call informOnePackageRemoved");
+    }
+    mUidMap->removeApp(app, uid);
+    return Status::ok();
+}
+
+Status StatsService::informAnomalyAlarmFired() {
     if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
-                "Only system uid can call informAnomalyAlarmFired");
+                                         "Only system uid can call informAnomalyAlarmFired");
     }
 
     if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
@@ -181,14 +425,12 @@
     return Status::ok();
 }
 
-Status
-StatsService::informPollAlarmFired()
-{
+Status StatsService::informPollAlarmFired() {
     if (DEBUG) ALOGD("StatsService::informPollAlarmFired was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
-                "Only system uid can call informPollAlarmFired");
+                                         "Only system uid can call informPollAlarmFired");
     }
 
     if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
@@ -197,12 +439,10 @@
     return Status::ok();
 }
 
-Status
-StatsService::systemRunning()
-{
+Status StatsService::systemRunning() {
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
         return Status::fromExceptionCode(Status::EX_SECURITY,
-                "Only system uid can call systemRunning");
+                                         "Only system uid can call systemRunning");
     }
 
     // When system_server is up and running, schedule the dropbox task to run.
@@ -213,10 +453,9 @@
     return Status::ok();
 }
 
-void
-StatsService::sayHiToStatsCompanion()
-{
-    // TODO: This method needs to be private. It is temporarily public and unsecured for testing purposes.
+void StatsService::sayHiToStatsCompanion() {
+    // TODO: This method needs to be private. It is temporarily public and unsecured for testing
+    // purposes.
     sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion != nullptr) {
         if (DEBUG) ALOGD("Telling statsCompanion that statsd is ready");
@@ -226,8 +465,7 @@
     }
 }
 
-sp<IStatsCompanionService>
-StatsService::getStatsCompanionService() {
+sp<IStatsCompanionService> StatsService::getStatsCompanionService() {
     sp<IStatsCompanionService> statsCompanion = nullptr;
     // Get statscompanion service from service manager
     const sp<IServiceManager> sm(defaultServiceManager());
@@ -242,9 +480,7 @@
     return statsCompanion;
 }
 
-Status
-StatsService::statsCompanionReady()
-{
+Status StatsService::statsCompanionReady() {
     if (DEBUG) ALOGD("StatsService::statsCompanionReady was called");
 
     if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
@@ -254,39 +490,59 @@
 
     sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
     if (statsCompanion == nullptr) {
-        return Status::fromExceptionCode(Status::EX_NULL_POINTER,
-                                         "statscompanion unavailable despite it contacting statsd!");
+        return Status::fromExceptionCode(
+                Status::EX_NULL_POINTER,
+                "statscompanion unavailable despite it contacting statsd!");
     }
     if (DEBUG) ALOGD("StatsService::statsCompanionReady linking to statsCompanion.");
-    IInterface::asBinder(statsCompanion)->linkToDeath(new StatsdDeathRecipient(mAnomalyMonitor));
+    IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
     mAnomalyMonitor->setStatsCompanionService(statsCompanion);
 
     return Status::ok();
 }
 
-void
-StatsdDeathRecipient::binderDied(const wp<IBinder>& who) {
-    ALOGW("statscompanion service died");
-    mAnmlyMntr->setStatsCompanionService(nullptr);
+void StatsService::Startup() {
+    mConfigManager->Startup();
 }
 
-status_t
-StatsService::doPrintStatsLog(FILE* out, const Vector<String8>& args) {
-    long msec = 0;
+void StatsService::OnLogEvent(const LogEvent& event) {
+    mProcessor->OnLogEvent(event);
+}
 
-    if (args.size() > 2) {
-        msec = strtol(args[2].string(), NULL, 10);
+Status StatsService::requestPush() {
+    mProcessor->flush();
+    return Status::ok();
+}
+
+Status StatsService::pushLog(const vector<uint8_t>& log) {
+    std::lock_guard<std::mutex> lock(mLock);
+    for (size_t i = 0; i < mCallbacks.size(); i++) {
+        mCallbacks[i]->onReceiveLogs((vector<uint8_t>*)&log);
     }
-    return DropboxReader::readStatsLogs(out, args[1].string(), msec);
+    return Status::ok();
 }
 
-void
-StatsService::printCmdHelp(FILE* out) {
-    fprintf(out, "Usage:\n");
-    fprintf(out, "\t print-stats-log [tag_required] [timestamp_nsec_optional]\n");
-    fprintf(out, "\t config\t Loads a new config from command-line (must be proto in wire-encoded format).\n");
+Status StatsService::subscribeStatsLog(const sp<IStatsCallbacks>& callback) {
+    std::lock_guard<std::mutex> lock(mLock);
+    for (size_t i = 0; i < mCallbacks.size(); i++) {
+        if (mCallbacks[i] == callback) {
+           return Status::fromStatusT(-errno);
+        }
+    }
+    mCallbacks.add(callback);
+    IInterface::asBinder(callback)->linkToDeath(this);
+    return Status::ok();
 }
 
-} // namespace statsd
-} // namespace os
-} // namespace android
+void StatsService::binderDied(const wp<IBinder>& who) {
+    for (size_t i = 0; i < mCallbacks.size(); i++) {
+        if (IInterface::asBinder(mCallbacks[i]) == who) {
+            mCallbacks.removeAt(i);
+            break;
+        }
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 57276d2..7f04658 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -17,14 +17,17 @@
 #ifndef STATS_SERVICE_H
 #define STATS_SERVICE_H
 
-#include "AnomalyMonitor.h"
 #include "StatsLogProcessor.h"
+#include "anomaly/AnomalyMonitor.h"
+#include "config/ConfigManager.h"
+#include "external/StatsPullerManager.h"
+#include "packages/UidMap.h"
 
 #include <android/os/BnStatsManager.h>
+#include <android/os/IStatsCallbacks.h>
 #include <android/os/IStatsCompanionService.h>
 #include <binder/IResultReceiver.h>
 #include <binder/IShellCallback.h>
-#include <frameworks/base/cmds/statsd/src/statsd_config.pb.h>
 #include <utils/Looper.h>
 
 #include <deque>
@@ -40,62 +43,156 @@
 namespace os {
 namespace statsd {
 
-class StatsService : public BnStatsManager {
+class StatsService : public BnStatsManager, public LogListener, public IBinder::DeathRecipient {
 public:
     StatsService(const sp<Looper>& handlerLooper);
     virtual ~StatsService();
 
     virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
-
     virtual status_t dump(int fd, const Vector<String16>& args);
-
     virtual status_t command(FILE* in, FILE* out, FILE* err, Vector<String8>& args);
 
     virtual Status systemRunning();
-
-    // Inform statsd that statsCompanion is ready.
     virtual Status statsCompanionReady();
-
     virtual Status informAnomalyAlarmFired();
-
     virtual Status informPollAlarmFired();
+    virtual Status informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
+                                    const vector<String16>& app);
+    virtual Status informOnePackage(const String16& app, int32_t uid, int32_t version);
+    virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
 
-    virtual status_t setProcessor(const sp<StatsLogProcessor>& main_processor);
+    /**
+     * Called right before we start processing events.
+     */
+    void Startup();
 
-    // TODO: public for testing since statsd doesn't run when system starts. Change to private later.
+    /**
+     * Called by LogReader when there's a log event to process.
+     */
+    virtual void OnLogEvent(const LogEvent& event);
+
+    /**
+     * Binder call to force trigger pushLog. This would be called by callback
+     * clients.
+     */
+    virtual Status requestPush() override;
+
+    /**
+     * Pushes stats log entries from statsd to callback clients.
+     */
+    Status pushLog(const vector<uint8_t>& log);
+
+    /**
+     * Binder call to listen to statsd to send stats log entries.
+     */
+    virtual Status subscribeStatsLog(const sp<IStatsCallbacks>& callbacks) override;
+
+    // TODO: public for testing since statsd doesn't run when system starts. Change to private
+    // later.
     /** Inform statsCompanion that statsd is ready. */
     virtual void sayHiToStatsCompanion();
 
-private:
-    sp<StatsLogProcessor> m_processor; // Reference to the processor for updating configs.
+    /** Fetches and returns the StatsCompanionService. */
+    static sp<IStatsCompanionService> getStatsCompanionService();
 
-    const sp<AnomalyMonitor> mAnomalyMonitor;  // TODO: Move this to a more logical file/class
-
-    status_t doPrintStatsLog(FILE* out, const Vector<String8>& args);
-
-    void printCmdHelp(FILE* out);
-
-    status_t doLoadConfig(FILE* in);
-
-    /** Fetches the StatsCompanionService. */
-    sp<IStatsCompanionService> getStatsCompanionService();
-};
-
-// --- StatsdDeathRecipient ---
-class StatsdDeathRecipient : public IBinder::DeathRecipient {
-public:
-    StatsdDeathRecipient(sp<AnomalyMonitor> anomalyMonitor)
-            : mAnmlyMntr(anomalyMonitor) {
-    }
-
-    virtual void binderDied(const wp<IBinder>& who);
+    /** IBinder::DeathRecipient */
+    virtual void binderDied(const wp<IBinder>& who) override;
 
 private:
-    const sp<AnomalyMonitor> mAnmlyMntr;
+    /**
+     * Load system properties at init.
+     */
+    void init_system_properties();
+
+    /**
+     * Helper for loading system properties.
+     */
+    static void init_build_type_callback(void* cookie, const char* name, const char* value,
+                                         uint32_t serial);
+
+    /**
+     * Text output of dumpsys.
+     */
+    void dump_impl(FILE* out);
+
+    /**
+     * Print usage information for the commands
+     */
+    void print_cmd_help(FILE* out);
+
+    /**
+     * Handle the config sub-command.
+     */
+    status_t cmd_config(FILE* in, FILE* out, FILE* err, Vector<String8>& args);
+
+    /**
+     * Print the event log.
+     */
+    status_t cmd_print_stats_log(FILE* out, const Vector<String8>& args);
+
+    /**
+     * Print the event log.
+     */
+    status_t cmd_dump_report(FILE* out, FILE* err, const Vector<String8>& args);
+
+    /**
+     * Print the mapping of uids to package names.
+     */
+    status_t cmd_print_uid_map(FILE* out);
+
+    /**
+     * Print contents of a pulled metrics source.
+     */
+    status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
+
+    /**
+     * Update a configuration.
+     */
+    void set_config(int uid, const string& name, const StatsdConfig& config);
+
+    /**
+     * Tracks the uid <--> package name mapping.
+     */
+    sp<UidMap> mUidMap;
+
+    /**
+     * Fetches external metrics.
+     */
+    sp<StatsPullerManager> mStatsPullerManager;
+
+    /**
+     * Tracks the configurations that have been passed to statsd.
+     */
+    sp<ConfigManager> mConfigManager;
+
+    /**
+     * The metrics recorder.
+     */
+    sp<StatsLogProcessor> mProcessor;
+
+    /**
+     * The anomaly detector.
+     */
+    const sp<AnomalyMonitor> mAnomalyMonitor;
+
+    /**
+     * Whether this is an eng build.
+     */
+    bool mEngBuild;
+
+    /**
+     * Lock for callback handling.
+     */
+    std::mutex mLock;
+
+    /**
+     * Vector maintaining the list of callbacks for clients.
+     */
+    Vector< sp<IStatsCallbacks> > mCallbacks;
 };
 
-} // namespace statsd
-} // namespace os
-} // namespace android
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
 
-#endif // STATS_SERVICE_H
+#endif  // STATS_SERVICE_H
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.cpp b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
new file mode 100644
index 0000000..7a46410
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 DEBUG true
+#include "Log.h"
+
+#include "anomaly/AnomalyMonitor.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+AnomalyMonitor::AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec)
+    : mRegisteredAlarmTimeSec(0), mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec) {
+}
+
+AnomalyMonitor::~AnomalyMonitor() {
+}
+
+void AnomalyMonitor::setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+    std::lock_guard<std::mutex> lock(mLock);
+    sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+    mStatsCompanionService = statsCompanionService;
+    if (statsCompanionService == nullptr) {
+        if (DEBUG) ALOGD("Erasing link to statsCompanionService");
+        return;
+    }
+    if (DEBUG) ALOGD("Creating link to statsCompanionService");
+    const sp<const AnomalyAlarm> top = mPq.top();
+    if (top != nullptr) {
+        updateRegisteredAlarmTime_l(top->timestampSec);
+    }
+}
+
+void AnomalyMonitor::add(sp<const AnomalyAlarm> alarm) {
+    std::lock_guard<std::mutex> lock(mLock);
+    if (alarm == nullptr) {
+        ALOGW("Asked to add a null alarm.");
+        return;
+    }
+    if (alarm->timestampSec < 1) {
+        // forbidden since a timestamp 0 is used to indicate no alarm registered
+        ALOGW("Asked to add a 0-time alarm.");
+        return;
+    }
+    // TODO: Ensure that refractory period is respected.
+    if (DEBUG) ALOGD("Adding alarm with time %u", alarm->timestampSec);
+    mPq.push(alarm);
+    if (mRegisteredAlarmTimeSec < 1 ||
+        alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) {
+        updateRegisteredAlarmTime_l(alarm->timestampSec);
+    }
+}
+
+void AnomalyMonitor::remove(sp<const AnomalyAlarm> alarm) {
+    std::lock_guard<std::mutex> lock(mLock);
+    if (alarm == nullptr) {
+        ALOGW("Asked to remove a null alarm.");
+        return;
+    }
+    if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec);
+    mPq.remove(alarm);
+    if (mPq.empty()) {
+        if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
+        mRegisteredAlarmTimeSec = 0;
+        if (mStatsCompanionService != nullptr) {
+            mStatsCompanionService->cancelAnomalyAlarm();
+        }
+        return;
+    }
+    uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec;
+    if (DEBUG) ALOGD("Soonest alarm is %u", soonestAlarmTimeSec);
+    if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) {
+        updateRegisteredAlarmTime_l(soonestAlarmTimeSec);
+    }
+}
+
+// More efficient than repeatedly calling remove(mPq.top()) since it batches the
+// updates to the registered alarm.
+unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> AnomalyMonitor::popSoonerThan(
+        uint32_t timestampSec) {
+    if (DEBUG) ALOGD("Removing alarms with time <= %u", timestampSec);
+    unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> oldAlarms;
+    std::lock_guard<std::mutex> lock(mLock);
+
+    for (sp<const AnomalyAlarm> t = mPq.top(); t != nullptr && t->timestampSec <= timestampSec;
+         t = mPq.top()) {
+        oldAlarms.insert(t);
+        mPq.pop();  // remove t
+    }
+    // Always update registered alarm time (if anything has changed).
+    if (!oldAlarms.empty()) {
+        if (mPq.empty()) {
+            if (DEBUG) ALOGD("Queue is empty. Cancel any alarm.");
+            mRegisteredAlarmTimeSec = 0;
+            if (mStatsCompanionService != nullptr) {
+                mStatsCompanionService->cancelAnomalyAlarm();
+            }
+        } else {
+            // Always update the registered alarm in this case (unlike remove()).
+            updateRegisteredAlarmTime_l(mPq.top()->timestampSec);
+        }
+    }
+    return oldAlarms;
+}
+
+void AnomalyMonitor::updateRegisteredAlarmTime_l(uint32_t timestampSec) {
+    if (DEBUG) ALOGD("Updating reg alarm time to %u", timestampSec);
+    mRegisteredAlarmTimeSec = timestampSec;
+    if (mStatsCompanionService != nullptr) {
+        mStatsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec));
+    }
+}
+
+int64_t AnomalyMonitor::secToMs(uint32_t timeSec) {
+    return ((int64_t)timeSec) * 1000;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
new file mode 100644
index 0000000..e2ac623
--- /dev/null
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -0,0 +1,148 @@
+/*
+ * 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 ANOMALY_MONITOR_H
+#define ANOMALY_MONITOR_H
+
+#include "anomaly/indexed_priority_queue.h"
+
+#include <android/os/IStatsCompanionService.h>
+#include <utils/RefBase.h>
+
+#include <queue>
+#include <unordered_set>
+#include <vector>
+
+using namespace android;
+
+using android::os::IStatsCompanionService;
+using std::unordered_set;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Represents an alarm, associated with some aggregate metric, holding a
+ * projected time at which the metric is expected to exceed its anomaly
+ * threshold.
+ * Timestamps are in seconds since epoch in a uint32, so will fail in year 2106.
+ */
+struct AnomalyAlarm : public RefBase {
+    AnomalyAlarm(uint32_t timestampSec) : timestampSec(timestampSec) {
+    }
+
+    const uint32_t timestampSec;
+
+    /** AnomalyAlarm a is smaller (higher priority) than b if its timestamp is sooner. */
+    struct SmallerTimestamp {
+        bool operator()(sp<const AnomalyAlarm> a, sp<const AnomalyAlarm> b) const {
+            return (a->timestampSec < b->timestampSec);
+        }
+    };
+};
+
+/**
+ * Manages alarms for Anomaly Detection.
+ */
+class AnomalyMonitor : public RefBase {
+public:
+    /**
+     * @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs
+     * from the registered alarm by more than this amount, update the registered
+     * alarm.
+     */
+    AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec);
+    ~AnomalyMonitor();
+
+    /**
+     * Tells AnomalyMonitor what IStatsCompanionService to use and, if
+     * applicable, immediately registers an existing alarm with it.
+     * If nullptr, AnomalyMonitor will continue to add/remove alarms, but won't
+     * update IStatsCompanionService (until such time as it is set non-null).
+     */
+    void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService);
+
+    /**
+     * Adds the given alarm (reference) to the queue.
+     */
+    void add(sp<const AnomalyAlarm> alarm);
+
+    /**
+     * Removes the given alarm (reference) from the queue.
+     * Note that alarm comparison is reference-based; if another alarm exists
+     * with the same timestampSec, that alarm will still remain in the queue.
+     */
+    void remove(sp<const AnomalyAlarm> alarm);
+
+    /**
+     * Returns and removes all alarms whose timestamp <= the given timestampSec.
+     * Always updates the registered alarm if return is non-empty.
+     */
+    unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> popSoonerThan(
+            uint32_t timestampSec);
+
+    /**
+     * Returns the projected alarm timestamp that is registered with
+     * StatsCompanionService. This may not be equal to the soonest alarm,
+     * but should be within minDiffToUpdateRegisteredAlarmTimeSec of it.
+     */
+    uint32_t getRegisteredAlarmTimeSec() const {
+        return mRegisteredAlarmTimeSec;
+    }
+
+private:
+    std::mutex mLock;
+
+    /**
+     * Timestamp (seconds since epoch) of the alarm registered with
+     * StatsCompanionService. This, in general, may not be equal to the soonest
+     * alarm stored in mPq, but should be within minUpdateTimeSec of it.
+     * A value of 0 indicates that no alarm is currently registered.
+     */
+    uint32_t mRegisteredAlarmTimeSec;
+
+    /**
+     * Priority queue of alarms, prioritized by soonest alarm.timestampSec.
+     */
+    indexed_priority_queue<AnomalyAlarm, AnomalyAlarm::SmallerTimestamp> mPq;
+
+    /**
+     * Binder interface for communicating with StatsCompanionService.
+     */
+    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+    /**
+     * Amount by which the soonest projected alarm must differ from
+     * mRegisteredAlarmTimeSec before updateRegisteredAlarmTime_l is called.
+     */
+    uint32_t mMinUpdateTimeSec;
+
+    /**
+     * Updates the alarm registered with StatsCompanionService to the given time.
+     * Also correspondingly updates mRegisteredAlarmTimeSec.
+     */
+    void updateRegisteredAlarmTime_l(uint32_t timestampSec);
+
+    /** Converts uint32 timestamp in seconds to a Java long in msec. */
+    int64_t secToMs(uint32_t timeSec);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // ANOMALY_MONITOR_H
diff --git a/cmds/statsd/src/anomaly/indexed_priority_queue.h b/cmds/statsd/src/anomaly/indexed_priority_queue.h
new file mode 100644
index 0000000..1a2e9c2
--- /dev/null
+++ b/cmds/statsd/src/anomaly/indexed_priority_queue.h
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Log.h"
+
+#include <utils/RefBase.h>
+#include <unordered_map>
+#include <vector>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
+template <class AA>
+struct SpHash {
+    size_t operator()(const sp<const AA>& k) const {
+        return std::hash<const AA*>()(k.get());
+    }
+};
+
+/**
+ * Min priority queue for generic type AA.
+ * Unlike a regular priority queue, this class is also capable of removing interior elements.
+ * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
+ *    whether a should be closer to the top of the queue than b.
+ */
+template <class AA, class Comparator>
+class indexed_priority_queue {
+public:
+    indexed_priority_queue();
+    /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */
+    void push(sp<const AA> a);
+    /** Removes a from the priority queue. If not present or a==nullptr, does nothing. */
+    void remove(sp<const AA> a);
+    /** Removes the top element, if there is one. */
+    void pop();
+    /** Removes all elements. */
+    void clear();
+    /** Returns whether priority queue contains a (not just a copy of a, but a itself). */
+    bool contains(sp<const AA> a) const;
+    /** Returns min element. Returns nullptr iff empty(). */
+    sp<const AA> top() const;
+    /** Returns number of elements in priority queue. */
+    size_t size() const {
+        return pq.size() - 1;
+    }  // pq is 1-indexed
+    /** Returns true iff priority queue is empty. */
+    bool empty() const {
+        return size() < 1;
+    }
+
+private:
+    /** Vector representing a min-heap (1-indexed, with nullptr at 0). */
+    std::vector<sp<const AA>> pq;
+    /** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */
+    std::unordered_map<sp<const AA>, size_t, SpHash<AA>> indices;
+
+    void init();
+    void sift_up(size_t idx);
+    void sift_down(size_t idx);
+    /** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */
+    bool higher(size_t idx1, size_t idx2) const;
+    void swap_indices(size_t i, size_t j);
+};
+
+// Implementation must be done in this file due to use of template.
+
+template <class AA, class Comparator>
+indexed_priority_queue<AA, Comparator>::indexed_priority_queue() {
+    init();
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::push(sp<const AA> a) {
+    if (a == nullptr) return;
+    if (contains(a)) return;
+    pq.push_back(a);
+    size_t idx = size();  // index of last element since 1-indexed
+    indices.insert({a, idx});
+    sift_up(idx);  // get the pq back in order
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::remove(sp<const AA> a) {
+    if (a == nullptr) return;
+    if (!contains(a)) return;
+    size_t idx = indices[a];
+    if (idx >= pq.size()) {
+        ALOGE("indexed_priority_queue: Invalid index in map of indices.");
+        return;
+    }
+    if (idx == size()) {  // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
+        pq.pop_back();
+        indices.erase(a);
+        return;
+    }
+    // move last element (guaranteed not to be at idx) to idx, then delete a
+    sp<const AA> last_a = pq.back();
+    pq[idx] = last_a;
+    pq.pop_back();
+    indices[last_a] = idx;
+    indices.erase(a);
+
+    // get the heap back in order (since the element at idx is not in order)
+    sift_up(idx);
+    sift_down(idx);
+}
+
+// The same as, but slightly more efficient than, remove(top()).
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::pop() {
+    sp<const AA> a = top();
+    if (a == nullptr) return;
+    const size_t idx = 1;
+    if (idx == size()) {  // if a is the last element
+        pq.pop_back();
+        indices.erase(a);
+        return;
+    }
+    // move last element (guaranteed not to be at idx) to idx, then delete a
+    sp<const AA> last_a = pq.back();
+    pq[idx] = last_a;
+    pq.pop_back();
+    indices[last_a] = idx;
+    indices.erase(a);
+
+    // get the heap back in order (since the element at idx is not in order)
+    sift_down(idx);
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::clear() {
+    pq.clear();
+    indices.clear();
+    init();
+}
+
+template <class AA, class Comparator>
+sp<const AA> indexed_priority_queue<AA, Comparator>::top() const {
+    if (empty()) return nullptr;
+    return pq[1];
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::init() {
+    pq.push_back(nullptr);         // so that pq is 1-indexed.
+    indices.insert({nullptr, 0});  // just to be consistent with pq.
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::sift_up(size_t idx) {
+    while (idx > 1) {
+        size_t parent = idx / 2;
+        if (higher(idx, parent))
+            swap_indices(idx, parent);
+        else
+            break;
+        idx = parent;
+    }
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::sift_down(size_t idx) {
+    while (2 * idx <= size()) {
+        size_t child = 2 * idx;
+        if (child < size() && higher(child + 1, child)) child++;
+        if (higher(child, idx))
+            swap_indices(child, idx);
+        else
+            break;
+        idx = child;
+    }
+}
+
+template <class AA, class Comparator>
+bool indexed_priority_queue<AA, Comparator>::higher(size_t idx1, size_t idx2) const {
+    if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
+        ALOGE("indexed_priority_queue: Attempting to access invalid index");
+        return false;  // got to do something.
+    }
+    return Comparator()(pq[idx1], pq[idx2]);
+}
+
+template <class AA, class Comparator>
+bool indexed_priority_queue<AA, Comparator>::contains(sp<const AA> a) const {
+    if (a == nullptr) return false;  // publicly, we pretend that nullptr is not actually in pq.
+    return indices.count(a) > 0;
+}
+
+template <class AA, class Comparator>
+void indexed_priority_queue<AA, Comparator>::swap_indices(size_t i, size_t j) {
+    if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
+        ALOGE("indexed_priority_queue: Attempting to swap invalid index");
+        return;
+    }
+    sp<const AA> val_i = pq[i];
+    sp<const AA> val_j = pq[j];
+    pq[i] = val_j;
+    pq[j] = val_i;
+    indices[val_i] = j;
+    indices[val_j] = i;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
new file mode 100644
index 0000000..f56c15a
--- /dev/null
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "CombinationConditionTracker.h"
+
+#include <log/logprint.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::unordered_map;
+using std::vector;
+
+CombinationConditionTracker::CombinationConditionTracker(const string& name, const int index)
+    : ConditionTracker(name, index) {
+    VLOG("creating CombinationConditionTracker %s", mName.c_str());
+}
+
+CombinationConditionTracker::~CombinationConditionTracker() {
+    VLOG("~CombinationConditionTracker() %s", mName.c_str());
+}
+
+bool CombinationConditionTracker::init(const vector<Condition>& allConditionConfig,
+                                       const vector<sp<ConditionTracker>>& allConditionTrackers,
+                                       const unordered_map<string, int>& conditionNameIndexMap,
+                                       vector<bool>& stack) {
+    VLOG("Combiniation condition init() %s", mName.c_str());
+    if (mInitialized) {
+        return true;
+    }
+
+    // mark this node as visited in the recursion stack.
+    stack[mIndex] = true;
+
+    Condition_Combination combinationCondition = allConditionConfig[mIndex].combination();
+
+    if (!combinationCondition.has_operation()) {
+        return false;
+    }
+    mLogicalOperation = combinationCondition.operation();
+
+    if (mLogicalOperation == LogicalOperation::NOT && combinationCondition.condition_size() != 1) {
+        return false;
+    }
+
+    for (string child : combinationCondition.condition()) {
+        auto it = conditionNameIndexMap.find(child);
+
+        if (it == conditionNameIndexMap.end()) {
+            ALOGW("Condition %s not found in the config", child.c_str());
+            return false;
+        }
+
+        int childIndex = it->second;
+        const auto& childTracker = allConditionTrackers[childIndex];
+        // if the child is a visited node in the recursion -> circle detected.
+        if (stack[childIndex]) {
+            ALOGW("Circle detected!!!");
+            return false;
+        }
+
+        bool initChildSucceeded = childTracker->init(allConditionConfig, allConditionTrackers,
+                                                     conditionNameIndexMap, stack);
+
+        if (!initChildSucceeded) {
+            ALOGW("Child initialization failed %s ", child.c_str());
+            return false;
+        } else {
+            ALOGW("Child initialization success %s ", child.c_str());
+        }
+
+        mChildren.push_back(childIndex);
+
+        mTrackerIndex.insert(childTracker->getLogTrackerIndex().begin(),
+                             childTracker->getLogTrackerIndex().end());
+    }
+
+    // unmark this node in the recursion stack.
+    stack[mIndex] = false;
+
+    mInitialized = true;
+
+    return true;
+}
+
+void CombinationConditionTracker::isConditionMet(
+        const map<string, HashableDimensionKey>& conditionParameters,
+        const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+    for (const int childIndex : mChildren) {
+        if (conditionCache[childIndex] == ConditionState::kNotEvaluated) {
+            allConditions[childIndex]->isConditionMet(conditionParameters, allConditions,
+                                                      conditionCache);
+        }
+    }
+    conditionCache[mIndex] =
+            evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
+}
+
+bool CombinationConditionTracker::evaluateCondition(
+        const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
+        const std::vector<sp<ConditionTracker>>& mAllConditions,
+        std::vector<ConditionState>& nonSlicedConditionCache,
+        std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
+    // value is up to date.
+    if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
+        return false;
+    }
+
+    for (const int childIndex : mChildren) {
+        if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
+            const sp<ConditionTracker>& child = mAllConditions[childIndex];
+            child->evaluateCondition(event, eventMatcherValues, mAllConditions,
+                                     nonSlicedConditionCache, nonSlicedChangedCache,
+                                     slicedConditionChanged);
+        }
+    }
+
+    ConditionState newCondition =
+            evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
+
+    bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+    mNonSlicedConditionState = newCondition;
+
+    nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
+
+    nonSlicedChangedCache[mIndex] = nonSlicedChanged;
+
+    if (mSliced) {
+        for (const int childIndex : mChildren) {
+            // If any of the sliced condition in children condition changes, the combination
+            // condition may be changed too.
+            if (slicedConditionChanged[childIndex]) {
+                slicedConditionChanged[mIndex] = true;
+                break;
+            }
+        }
+        ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
+              slicedConditionChanged[mIndex] == true);
+    }
+
+    return nonSlicedChanged;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
new file mode 100644
index 0000000..fc88a88
--- /dev/null
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -0,0 +1,64 @@
+/*
+ * 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 COMBINATION_CONDITION_TRACKER_H
+#define COMBINATION_CONDITION_TRACKER_H
+
+#include "ConditionTracker.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class CombinationConditionTracker : public virtual ConditionTracker {
+public:
+    CombinationConditionTracker(const std::string& name, const int index);
+
+    ~CombinationConditionTracker();
+
+    bool init(const std::vector<Condition>& allConditionConfig,
+              const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+              const std::unordered_map<std::string, int>& conditionNameIndexMap,
+              std::vector<bool>& stack) override;
+
+    bool evaluateCondition(const LogEvent& event,
+                           const std::vector<MatchingState>& eventMatcherValues,
+                           const std::vector<sp<ConditionTracker>>& mAllConditions,
+                           std::vector<ConditionState>& conditionCache,
+                           std::vector<bool>& changedCache,
+                           std::vector<bool>& slicedConditionMayChanged) override;
+
+    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+                        const std::vector<sp<ConditionTracker>>& allConditions,
+                        std::vector<ConditionState>& conditionCache) override;
+
+    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override{};
+
+private:
+    LogicalOperation mLogicalOperation;
+    // Store index of the children Conditions.
+    // We don't store string name of the Children, because we want to get rid of the hash map to
+    // map the name to object. We don't want to store smart pointers to children, because it
+    // increases the risk of circular dependency and memory leak.
+    std::vector<int> mChildren;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // COMBINATION_CONDITION_TRACKER_H
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
new file mode 100644
index 0000000..055b478
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Log.h"
+
+#include "condition/condition_util.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/LogMatchingTracker.h"
+#include "matchers/matcher_util.h"
+
+#include <log/logprint.h>
+#include <utils/RefBase.h>
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class ConditionTracker : public virtual RefBase {
+public:
+    ConditionTracker(const std::string& name, const int index)
+        : mName(name),
+          mIndex(index),
+          mInitialized(false),
+          mTrackerIndex(),
+          mNonSlicedConditionState(ConditionState::kUnknown),
+          mSliced(false){};
+
+    virtual ~ConditionTracker(){};
+
+    // Initialize this ConditionTracker. This initialization is done recursively (DFS). It can also
+    // be done in the constructor, but we do it separately because (1) easy to return a bool to
+    // indicate whether the initialization is successful. (2) makes unit test easier.
+    // allConditionConfig: the list of all Condition config from statsd_config.
+    // allConditionTrackers: the list of all ConditionTrackers (this is needed because we may also
+    //                       need to call init() on children conditions)
+    // conditionNameIndexMap: the mapping from condition name to its index.
+    // stack: a bit map to keep track which nodes have been visited on the stack in the recursion.
+    virtual bool init(const std::vector<Condition>& allConditionConfig,
+                      const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                      const std::unordered_map<std::string, int>& conditionNameIndexMap,
+                      std::vector<bool>& stack) = 0;
+
+    // evaluate current condition given the new event.
+    // return true if the condition state changed, false if the condition state is not changed.
+    // event: the new log event
+    // eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
+    //                     event before ConditionTrackers, because ConditionTracker depends on
+    //                     LogMatchingTrackers.
+    // mAllConditions: the list of all ConditionTracker
+    // conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
+    // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
+    // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
+    //      Because sliced condition needs parameters to determine the value. So the sliced
+    //      condition is not pushed to metrics. We only inform the relevant metrics that the sliced
+    //      condition may have changed, and metrics should pull the conditions that they are
+    //      interested in.
+    virtual bool evaluateCondition(const LogEvent& event,
+                                   const std::vector<MatchingState>& eventMatcherValues,
+                                   const std::vector<sp<ConditionTracker>>& mAllConditions,
+                                   std::vector<ConditionState>& conditionCache,
+                                   std::vector<bool>& nonSlicedConditionChanged,
+                                   std::vector<bool>& slicedConditionMayChanged) = 0;
+
+    // Return the current condition state.
+    virtual ConditionState isConditionMet() {
+        return mNonSlicedConditionState;
+    };
+
+    // Query the condition with parameters.
+    // [conditionParameters]: a map from condition name to the HashableDimensionKey to query the
+    //                       condition.
+    // [allConditions]: all condition trackers. This is needed because the condition evaluation is
+    //                  done recursively
+    // [conditionCache]: the cache holding the condition evaluation values.
+    virtual void isConditionMet(
+            const std::map<std::string, HashableDimensionKey>& conditionParameters,
+            const std::vector<sp<ConditionTracker>>& allConditions,
+            std::vector<ConditionState>& conditionCache) = 0;
+
+    // return the list of LogMatchingTracker index that this ConditionTracker uses.
+    virtual const std::set<int>& getLogTrackerIndex() const {
+        return mTrackerIndex;
+    }
+
+    virtual void setSliced(bool sliced) {
+        mSliced = mSliced | sliced;
+    }
+
+    virtual void addDimensions(const std::vector<KeyMatcher>& keyMatchers) = 0;
+
+protected:
+    // We don't really need the string name, but having a name here makes log messages
+    // easy to debug.
+    const std::string mName;
+
+    // the index of this condition in the manager's condition list.
+    const int mIndex;
+
+    // if it's properly initialized.
+    bool mInitialized;
+
+    // the list of LogMatchingTracker index that this ConditionTracker uses.
+    std::set<int> mTrackerIndex;
+
+    ConditionState mNonSlicedConditionState;
+
+    bool mSliced;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
diff --git a/cmds/statsd/src/condition/ConditionWizard.cpp b/cmds/statsd/src/condition/ConditionWizard.cpp
new file mode 100644
index 0000000..411f7e5
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.cpp
@@ -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.
+ */
+#include "ConditionWizard.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::string;
+using std::vector;
+
+ConditionState ConditionWizard::query(const int index,
+                                      const map<string, HashableDimensionKey>& parameters) {
+    vector<ConditionState> cache(mAllConditions.size(), ConditionState::kNotEvaluated);
+
+    mAllConditions[index]->isConditionMet(parameters, mAllConditions, cache);
+    return cache[index];
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/condition/ConditionWizard.h b/cmds/statsd/src/condition/ConditionWizard.h
new file mode 100644
index 0000000..4889b64
--- /dev/null
+++ b/cmds/statsd/src/condition/ConditionWizard.h
@@ -0,0 +1,48 @@
+/*
+ * 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 CONDITION_WIZARD_H
+#define CONDITION_WIZARD_H
+
+#include "ConditionTracker.h"
+#include "condition_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Held by MetricProducer, to query a condition state with input defined in EventConditionLink.
+class ConditionWizard : public virtual android::RefBase {
+public:
+    ConditionWizard(std::vector<sp<ConditionTracker>>& conditionTrackers)
+        : mAllConditions(conditionTrackers){};
+
+    // Query condition state, for a ConditionTracker at [conditionIndex], with [conditionParameters]
+    // [conditionParameters] mapping from condition name to the HashableDimensionKey to query the
+    //                       condition.
+    // The ConditionTracker at [conditionIndex] can be a CombinationConditionTracker. In this case,
+    // the conditionParameters contains the parameters for it's children SimpleConditionTrackers.
+    ConditionState query(const int conditionIndex,
+                         const std::map<std::string, HashableDimensionKey>& conditionParameters);
+
+private:
+    std::vector<sp<ConditionTracker>>& mAllConditions;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // CONDITION_WIZARD_H
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
new file mode 100644
index 0000000..aff4768
--- /dev/null
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -0,0 +1,227 @@
+/*
+ * 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 DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "SimpleConditionTracker.h"
+
+#include <log/logprint.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+using std::string;
+using std::unique_ptr;
+using std::unordered_map;
+using std::vector;
+
+SimpleConditionTracker::SimpleConditionTracker(
+        const string& name, const int index, const SimpleCondition& simpleCondition,
+        const unordered_map<string, int>& trackerNameIndexMap)
+    : ConditionTracker(name, index) {
+    VLOG("creating SimpleConditionTracker %s", mName.c_str());
+    mCountNesting = simpleCondition.count_nesting();
+
+    if (simpleCondition.has_start()) {
+        auto pair = trackerNameIndexMap.find(simpleCondition.start());
+        if (pair == trackerNameIndexMap.end()) {
+            ALOGW("Start matcher %s not found in the config", simpleCondition.start().c_str());
+            return;
+        }
+        mStartLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStartLogMatcherIndex);
+    } else {
+        mStartLogMatcherIndex = -1;
+    }
+
+    if (simpleCondition.has_stop()) {
+        auto pair = trackerNameIndexMap.find(simpleCondition.stop());
+        if (pair == trackerNameIndexMap.end()) {
+            ALOGW("Stop matcher %s not found in the config", simpleCondition.stop().c_str());
+            return;
+        }
+        mStopLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStopLogMatcherIndex);
+    } else {
+        mStopLogMatcherIndex = -1;
+    }
+
+    if (simpleCondition.has_stop_all()) {
+        auto pair = trackerNameIndexMap.find(simpleCondition.stop_all());
+        if (pair == trackerNameIndexMap.end()) {
+            ALOGW("Stop all matcher %s not found in the config", simpleCondition.stop().c_str());
+            return;
+        }
+        mStopAllLogMatcherIndex = pair->second;
+        mTrackerIndex.insert(mStopAllLogMatcherIndex);
+    } else {
+        mStopAllLogMatcherIndex = -1;
+    }
+
+    mInitialized = true;
+}
+
+SimpleConditionTracker::~SimpleConditionTracker() {
+    VLOG("~SimpleConditionTracker()");
+}
+
+bool SimpleConditionTracker::init(const vector<Condition>& allConditionConfig,
+                                  const vector<sp<ConditionTracker>>& allConditionTrackers,
+                                  const unordered_map<string, int>& conditionNameIndexMap,
+                                  vector<bool>& stack) {
+    // SimpleConditionTracker does not have dependency on other conditions, thus we just return
+    // if the initialization was successful.
+    return mInitialized;
+}
+
+void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+    VLOG("%s DUMP:", name.c_str());
+
+    for (const auto& pair : conditions) {
+        VLOG("\t%s %d", pair.first.c_str(), pair.second);
+    }
+}
+
+void SimpleConditionTracker::addDimensions(const std::vector<KeyMatcher>& keyMatchers) {
+    VLOG("Added dimensions size %lu", (unsigned long)keyMatchers.size());
+    mDimensionsList.push_back(keyMatchers);
+    mSliced = true;
+}
+
+bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
+                                               const vector<MatchingState>& eventMatcherValues,
+                                               const vector<sp<ConditionTracker>>& mAllConditions,
+                                               vector<ConditionState>& conditionCache,
+                                               vector<bool>& nonSlicedConditionChanged,
+                                               std::vector<bool>& slicedConditionChanged) {
+    if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
+        // it has been evaluated.
+        VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
+        return false;
+    }
+
+    // Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
+
+    ConditionState newCondition = mNonSlicedConditionState;
+    bool matched = false;
+    // Note: The order to evaluate the following start, stop, stop_all matters.
+    // The priority of overwrite is stop_all > stop > start.
+    if (mStartLogMatcherIndex >= 0 &&
+        eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
+        matched = true;
+        newCondition = ConditionState::kTrue;
+    }
+
+    if (mStopLogMatcherIndex >= 0 &&
+        eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
+        matched = true;
+        newCondition = ConditionState::kFalse;
+    }
+
+    bool stopAll = false;
+    if (mStopAllLogMatcherIndex >= 0 &&
+        eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+        matched = true;
+        newCondition = ConditionState::kFalse;
+        stopAll = true;
+    }
+
+    if (matched == false) {
+        slicedConditionChanged[mIndex] = false;
+        nonSlicedConditionChanged[mIndex] = false;
+        conditionCache[mIndex] = mNonSlicedConditionState;
+        return false;
+    }
+
+    bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
+
+    bool slicedChanged = false;
+
+    if (stopAll) {
+        // TODO: handle stop all; all dimension should be cleared.
+    }
+
+    if (mDimensionsList.size() > 0) {
+        for (size_t i = 0; i < mDimensionsList.size(); i++) {
+            const auto& dim = mDimensionsList[i];
+            vector<KeyValuePair> key = getDimensionKey(event, dim);
+            HashableDimensionKey hashableKey = getHashableKey(key);
+            if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
+                mSlicedConditionState[hashableKey] != newCondition) {
+                slicedChanged = true;
+                mSlicedConditionState[hashableKey] = newCondition;
+            }
+            VLOG("key: %s %d", hashableKey.c_str(), newCondition);
+        }
+        // dump all dimensions for debugging
+        if (DEBUG) {
+            print(mSlicedConditionState, mName);
+        }
+    }
+
+    // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
+    // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
+    // so mark the slicedConditionChanged to be true.
+    // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
+    //     APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
+    //     SCREEN_OFF is not sliced, and it changes from False -> True;
+    //     We need to populate this change to parent condition. Because for App_B,
+    //     the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
+    slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
+    nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
+
+    VLOG("SimpleCondition %s nonSlicedChange? %d  SlicedChanged? %d", mName.c_str(),
+         nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
+    mNonSlicedConditionState = newCondition;
+    conditionCache[mIndex] = mNonSlicedConditionState;
+
+    return nonSlicedConditionChanged[mIndex];
+}
+
+void SimpleConditionTracker::isConditionMet(
+        const map<string, HashableDimensionKey>& conditionParameters,
+        const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
+    const auto pair = conditionParameters.find(mName);
+    if (pair == conditionParameters.end()) {
+        // the query does not need my sliced condition. just return the non sliced condition.
+        conditionCache[mIndex] = mNonSlicedConditionState;
+        VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+        return;
+    }
+
+    const HashableDimensionKey& key = pair->second;
+    VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
+
+    if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
+        // never seen this key before. the condition is unknown to us.
+        conditionCache[mIndex] = ConditionState::kUnknown;
+    } else {
+        conditionCache[mIndex] = mSlicedConditionState[key];
+    }
+
+    VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
+
+    if (DEBUG) {
+        print(mSlicedConditionState, mName);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
new file mode 100644
index 0000000..1f357f0
--- /dev/null
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -0,0 +1,81 @@
+/*
+ * 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 SIMPLE_CONDITION_TRACKER_H
+#define SIMPLE_CONDITION_TRACKER_H
+
+#include "ConditionTracker.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class SimpleConditionTracker : public virtual ConditionTracker {
+public:
+    // dimensions is a vector of vector because for one single condition, different metrics may be
+    // interested in slicing in different ways. one vector<KeyMatcher> defines one type of slicing.
+    SimpleConditionTracker(const std::string& name, const int index,
+                           const SimpleCondition& simpleCondition,
+                           const std::unordered_map<std::string, int>& trackerNameIndexMap);
+
+    ~SimpleConditionTracker();
+
+    bool init(const std::vector<Condition>& allConditionConfig,
+              const std::vector<sp<ConditionTracker>>& allConditionTrackers,
+              const std::unordered_map<std::string, int>& conditionNameIndexMap,
+              std::vector<bool>& stack) override;
+
+    bool evaluateCondition(const LogEvent& event,
+                           const std::vector<MatchingState>& eventMatcherValues,
+                           const std::vector<sp<ConditionTracker>>& mAllConditions,
+                           std::vector<ConditionState>& conditionCache,
+                           std::vector<bool>& changedCache,
+                           std::vector<bool>& slicedChangedCache) override;
+
+    void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
+                        const std::vector<sp<ConditionTracker>>& allConditions,
+                        std::vector<ConditionState>& conditionCache) override;
+
+    void addDimensions(const std::vector<KeyMatcher>& keyMatchers) override;
+
+private:
+    // The index of the LogEventMatcher which defines the start.
+    int mStartLogMatcherIndex;
+
+    // The index of the LogEventMatcher which defines the end.
+    int mStopLogMatcherIndex;
+
+    // if the start end needs to be nested.
+    bool mCountNesting;
+
+    // The index of the LogEventMatcher which defines the stop all.
+    int mStopAllLogMatcherIndex;
+
+    // Different metrics may subscribe to different types of slicings. So it's a vector of vector.
+    std::vector<std::vector<KeyMatcher>> mDimensionsList;
+
+    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+    // that StatsLogReport wants.
+    std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // SIMPLE_CONDITION_TRACKER_H
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
new file mode 100644
index 0000000..40d41be
--- /dev/null
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -0,0 +1,115 @@
+/*
+ * 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 "Log.h"
+
+#include "condition_util.h"
+
+#include <log/event_tag_map.h>
+#include <log/log_event_list.h>
+#include <log/logprint.h>
+#include <utils/Errors.h>
+#include <unordered_map>
+#include "../matchers/matcher_util.h"
+#include "ConditionTracker.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::set;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+
+ConditionState evaluateCombinationCondition(const std::vector<int>& children,
+                                            const LogicalOperation& operation,
+                                            const std::vector<ConditionState>& conditionCache) {
+    ConditionState newCondition;
+
+    bool hasUnknown = false;
+    bool hasFalse = false;
+    bool hasTrue = false;
+
+    for (auto childIndex : children) {
+        ConditionState childState = conditionCache[childIndex];
+        if (childState == ConditionState::kUnknown) {
+            hasUnknown = true;
+            break;
+        }
+        if (childState == ConditionState::kFalse) {
+            hasFalse = true;
+        }
+        if (childState == ConditionState::kTrue) {
+            hasTrue = true;
+        }
+    }
+
+    // If any child condition is in unknown state, the condition is unknown too.
+    if (hasUnknown) {
+        return ConditionState::kUnknown;
+    }
+
+    switch (operation) {
+        case LogicalOperation::AND: {
+            newCondition = hasFalse ? ConditionState::kFalse : ConditionState::kTrue;
+            break;
+        }
+        case LogicalOperation::OR: {
+            newCondition = hasTrue ? ConditionState::kTrue : ConditionState::kFalse;
+            break;
+        }
+        case LogicalOperation::NOT:
+            newCondition = (conditionCache[children[0]] == ConditionState::kFalse)
+                                   ? ConditionState::kTrue
+                                   : ConditionState::kFalse;
+            break;
+        case LogicalOperation::NAND:
+            newCondition = hasFalse ? ConditionState::kTrue : ConditionState::kFalse;
+            break;
+        case LogicalOperation::NOR:
+            newCondition = hasTrue ? ConditionState::kFalse : ConditionState::kTrue;
+            break;
+    }
+    return newCondition;
+}
+
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+                                                 const EventConditionLink& link) {
+    vector<KeyMatcher> eventKey;
+    eventKey.reserve(link.key_in_main().size());
+
+    for (const auto& key : link.key_in_main()) {
+        eventKey.push_back(key);
+    }
+
+    vector<KeyValuePair> dimensionKey = getDimensionKey(event, eventKey);
+
+    for (int i = 0; i < link.key_in_main_size(); i++) {
+        auto& kv = dimensionKey[i];
+        kv.set_key(link.key_in_condition(i).key());
+    }
+
+    return getHashableKey(dimensionKey);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
new file mode 100644
index 0000000..47e245e
--- /dev/null
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -0,0 +1,45 @@
+/*
+ * 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 CONDITION_UTIL_H
+#define CONDITION_UTIL_H
+
+#include <vector>
+#include "../matchers/matcher_util.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum ConditionState {
+    kNotEvaluated = -2,
+    kUnknown = -1,
+    kFalse = 0,
+    kTrue = 1,
+};
+
+ConditionState evaluateCombinationCondition(const std::vector<int>& children,
+                                            const LogicalOperation& operation,
+                                            const std::vector<ConditionState>& conditionCache);
+
+HashableDimensionKey getDimensionKeyForCondition(const LogEvent& event,
+                                                 const EventConditionLink& link);
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // CONDITION_UTIL_H
diff --git a/cmds/statsd/src/config/ConfigKey.cpp b/cmds/statsd/src/config/ConfigKey.cpp
new file mode 100644
index 0000000..a365dc0
--- /dev/null
+++ b/cmds/statsd/src/config/ConfigKey.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "config/ConfigKey.h"
+
+#include <sstream>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::ostringstream;
+
+ConfigKey::ConfigKey() {
+}
+
+ConfigKey::ConfigKey(const ConfigKey& that) : mName(that.mName), mUid(that.mUid) {
+}
+
+ConfigKey::ConfigKey(int uid, const string& name) : mName(name), mUid(uid) {
+}
+
+ConfigKey::~ConfigKey() {
+}
+
+string ConfigKey::ToString() const {
+    ostringstream out;
+    out << '(' << mUid << ',' << mName << ')';
+    return out.str();
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/config/ConfigKey.h b/cmds/statsd/src/config/ConfigKey.h
new file mode 100644
index 0000000..bbf20fd
--- /dev/null
+++ b/cmds/statsd/src/config/ConfigKey.h
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+#include <functional>
+#include <iostream>
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::hash;
+using std::ostream;
+using std::string;
+
+/**
+ * Uniquely identifies a configuration.
+ */
+class ConfigKey {
+public:
+    ConfigKey();
+    explicit ConfigKey(const ConfigKey& that);
+    ConfigKey(int uid, const string& name);
+    ~ConfigKey();
+
+    inline int GetUid() const {
+        return mUid;
+    }
+    inline const string& GetName() const {
+        return mName;
+    }
+
+    inline bool operator<(const ConfigKey& that) const {
+        if (mUid < that.mUid) {
+            return true;
+        }
+        if (mUid > that.mUid) {
+            return false;
+        }
+        return mName < that.mName;
+    };
+
+    inline bool operator==(const ConfigKey& that) const {
+        return mUid == that.mUid && mName == that.mName;
+    };
+
+    string ToString() const;
+
+private:
+    string mName;
+    int mUid;
+};
+
+inline ostream& operator<<(ostream& os, const ConfigKey& config) {
+    return os << config.ToString();
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+/**
+ * A hash function for ConfigKey so it can be used for unordered_map/set.
+ * Unfortunately this hast to go in std namespace because C++ is fun!
+ */
+namespace std {
+
+using android::os::statsd::ConfigKey;
+
+template <>
+struct hash<ConfigKey> {
+    std::size_t operator()(const ConfigKey& key) const {
+        return (7 * key.GetUid()) ^ ((hash<string>()(key.GetName())));
+    }
+};
+
+}  // namespace std
diff --git a/cmds/statsd/src/config/ConfigListener.cpp b/cmds/statsd/src/config/ConfigListener.cpp
new file mode 100644
index 0000000..21a3f16
--- /dev/null
+++ b/cmds/statsd/src/config/ConfigListener.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 "config/ConfigListener.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+ConfigListener::ConfigListener() {
+}
+
+ConfigListener::~ConfigListener() {
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/config/ConfigListener.h b/cmds/statsd/src/config/ConfigListener.h
new file mode 100644
index 0000000..a58766d
--- /dev/null
+++ b/cmds/statsd/src/config/ConfigListener.h
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
+#include "config/ConfigKey.h"
+
+#include <utils/RefBase.h>
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::RefBase;
+using std::string;
+
+/**
+ * Callback for different subsystems inside statsd to implement to find out
+ * when a configuration has been added, updated or removed.
+ */
+class ConfigListener : public virtual RefBase {
+public:
+    ConfigListener();
+    virtual ~ConfigListener();
+
+    /**
+     * A configuration was added or updated.
+     */
+    virtual void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) = 0;
+
+    /**
+     * A configuration was removed.
+     */
+    virtual void OnConfigRemoved(const ConfigKey& key) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
new file mode 100644
index 0000000..c16971a
--- /dev/null
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -0,0 +1,288 @@
+/*
+ * 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 "config/ConfigManager.h"
+
+#include "stats_util.h"
+
+#include <vector>
+
+#include <stdio.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+static StatsdConfig build_fake_config();
+
+ConfigManager::ConfigManager() {
+}
+
+ConfigManager::~ConfigManager() {
+}
+
+void ConfigManager::Startup() {
+    // TODO: Implement me -- read from storage and call onto all of the listeners.
+    // Instead, we'll just make a fake one.
+
+    // this should be called from StatsService when it receives a statsd_config
+    UpdateConfig(ConfigKey(0, "fake"), build_fake_config());
+}
+
+void ConfigManager::AddListener(const sp<ConfigListener>& listener) {
+    mListeners.push_back(listener);
+}
+
+void ConfigManager::UpdateConfig(const ConfigKey& key, const StatsdConfig& config) {
+    // Add to map
+    mConfigs[key] = config;
+    // Why doesn't this work? mConfigs.insert({key, config});
+
+    // Save to disk
+    update_saved_configs();
+
+    // Tell everyone
+    for (auto& listener : mListeners) {
+        listener->OnConfigUpdated(key, config);
+    }
+}
+
+void ConfigManager::RemoveConfig(const ConfigKey& key) {
+    unordered_map<ConfigKey, StatsdConfig>::iterator it = mConfigs.find(key);
+    if (it != mConfigs.end()) {
+        // Remove from map
+        mConfigs.erase(it);
+
+        // Save to disk
+        update_saved_configs();
+
+        // Tell everyone
+        for (auto& listener : mListeners) {
+            listener->OnConfigRemoved(key);
+        }
+    }
+    // If we didn't find it, just quietly ignore it.
+}
+
+void ConfigManager::RemoveConfigs(int uid) {
+    vector<ConfigKey> removed;
+
+    for (auto it = mConfigs.begin(); it != mConfigs.end();) {
+        // Remove from map
+        if (it->first.GetUid() == uid) {
+            removed.push_back(it->first);
+            it = mConfigs.erase(it);
+        } else {
+            it++;
+        }
+    }
+
+    // Remove separately so if they do anything in the callback they can't mess up our iteration.
+    for (auto& key : removed) {
+        // Tell everyone
+        for (auto& listener : mListeners) {
+            listener->OnConfigRemoved(key);
+        }
+    }
+}
+
+void ConfigManager::Dump(FILE* out) {
+    fprintf(out, "CONFIGURATIONS (%d)\n", (int)mConfigs.size());
+    fprintf(out, "     uid name\n");
+    for (unordered_map<ConfigKey, StatsdConfig>::const_iterator it = mConfigs.begin();
+         it != mConfigs.end(); it++) {
+        fprintf(out, "  %6d %s\n", it->first.GetUid(), it->first.GetName().c_str());
+        // TODO: Print the contents of the config too.
+    }
+}
+
+void ConfigManager::update_saved_configs() {
+    // TODO: Implement me -- write to disk.
+}
+
+static StatsdConfig build_fake_config() {
+    // HACK: Hard code a test metric for counting screen on events...
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    int WAKE_LOCK_TAG_ID = 11;
+    int WAKE_LOCK_UID_KEY_ID = 1;
+    int WAKE_LOCK_STATE_KEY = 3;
+    int WAKE_LOCK_ACQUIRE_VALUE = 1;
+    int WAKE_LOCK_RELEASE_VALUE = 0;
+
+    int APP_USAGE_ID = 12345;
+    int APP_USAGE_UID_KEY_ID = 1;
+    int APP_USAGE_STATE_KEY = 2;
+    int APP_USAGE_FOREGROUND = 1;
+    int APP_USAGE_BACKGROUND = 0;
+
+    int SCREEN_EVENT_TAG_ID = 29;
+    int SCREEN_EVENT_STATE_KEY = 1;
+    int SCREEN_EVENT_ON_VALUE = 2;
+    int SCREEN_EVENT_OFF_VALUE = 1;
+
+    int UID_PROCESS_STATE_TAG_ID = 27;
+    int UID_PROCESS_STATE_UID_KEY = 1;
+
+    // Count Screen ON events.
+    CountMetric* metric = config.add_count_metric();
+    metric->set_metric_id(1);
+    metric->set_what("SCREEN_TURNED_ON");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+
+    // Anomaly threshold for screen-on count.
+    Alert* alert = metric->add_alerts();
+    alert->set_number_of_buckets(6);
+    alert->set_trigger_if_sum_gt(10);
+    alert->set_refractory_period_secs(30);
+
+    // Count process state changes, slice by uid.
+    metric = config.add_count_metric();
+    metric->set_metric_id(2);
+    metric->set_what("PROCESS_STATE_CHANGE");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    KeyMatcher* keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+
+    // Count process state changes, slice by uid, while SCREEN_IS_OFF
+    metric = config.add_count_metric();
+    metric->set_metric_id(3);
+    metric->set_what("PROCESS_STATE_CHANGE");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+    metric->set_condition("SCREEN_IS_OFF");
+
+    // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
+    metric = config.add_count_metric();
+    metric->set_metric_id(4);
+    metric->set_what("APP_GET_WL");
+    metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    keyMatcher = metric->add_dimension();
+    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+    metric->set_condition("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    EventConditionLink* link = metric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Duration of an app holding wl, while screen on and app in background
+    DurationMetric* durationMetric = config.add_duration_metric();
+    durationMetric->set_metric_id(5);
+    durationMetric->set_start("APP_GET_WL");
+    durationMetric->set_stop("APP_RELEASE_WL");
+    durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
+    durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
+    keyMatcher = durationMetric->add_dimension();
+    keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
+    durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    link = durationMetric->add_links();
+    link->set_condition("APP_IS_BACKGROUND");
+    link->add_key_in_main()->set_key(WAKE_LOCK_UID_KEY_ID);
+    link->add_key_in_condition()->set_key(APP_USAGE_UID_KEY_ID);
+
+    // Add an EventMetric to log process state change events.
+    EventMetric* eventMetric = config.add_event_metric();
+    eventMetric->set_metric_id(6);
+    eventMetric->set_what("SCREEN_TURNED_ON");
+
+    // Event matchers............
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_TURNED_ON");
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+    KeyValueMatcher* keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+    keyValueMatcher->set_eq_int(SCREEN_EVENT_ON_VALUE);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_TURNED_OFF");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(SCREEN_EVENT_TAG_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(SCREEN_EVENT_STATE_KEY);
+    keyValueMatcher->set_eq_int(SCREEN_EVENT_OFF_VALUE);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("PROCESS_STATE_CHANGE");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(UID_PROCESS_STATE_TAG_ID);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_GOES_BACKGROUND");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+    keyValueMatcher->set_eq_int(APP_USAGE_BACKGROUND);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_GOES_FOREGROUND");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(APP_USAGE_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(APP_USAGE_STATE_KEY);
+    keyValueMatcher->set_eq_int(APP_USAGE_FOREGROUND);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_GET_WL");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+    keyValueMatcher->set_eq_int(WAKE_LOCK_ACQUIRE_VALUE);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("APP_RELEASE_WL");
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(WAKE_LOCK_TAG_ID);
+    keyValueMatcher = simpleLogEntryMatcher->add_key_value_matcher();
+    keyValueMatcher->mutable_key_matcher()->set_key(WAKE_LOCK_STATE_KEY);
+    keyValueMatcher->set_eq_int(WAKE_LOCK_RELEASE_VALUE);
+
+    // Conditions.............
+    Condition* condition = config.add_condition();
+    condition->set_name("SCREEN_IS_ON");
+    SimpleCondition* simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("SCREEN_TURNED_ON");
+    simpleCondition->set_stop("SCREEN_TURNED_OFF");
+
+    condition = config.add_condition();
+    condition->set_name("SCREEN_IS_OFF");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("SCREEN_TURNED_OFF");
+    simpleCondition->set_stop("SCREEN_TURNED_ON");
+
+    condition = config.add_condition();
+    condition->set_name("APP_IS_BACKGROUND");
+    simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("APP_GOES_BACKGROUND");
+    simpleCondition->set_stop("APP_GOES_FOREGROUND");
+
+    condition = config.add_condition();
+    condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
+    Condition_Combination* combination_condition = condition->mutable_combination();
+    combination_condition->set_operation(LogicalOperation::AND);
+    combination_condition->add_condition("APP_IS_BACKGROUND");
+    combination_condition->add_condition("SCREEN_IS_ON");
+
+    return config;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
new file mode 100644
index 0000000..5d73eaf
--- /dev/null
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "config/ConfigKey.h"
+#include "config/ConfigListener.h"
+
+#include <string>
+#include <unordered_map>
+
+#include <stdio.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using android::RefBase;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+/**
+ * Keeps track of which configurations have been set from various sources.
+ *
+ * TODO: Store the configs persistently too.
+ * TODO: Dump method for debugging.
+ */
+class ConfigManager : public virtual RefBase {
+public:
+    ConfigManager();
+    virtual ~ConfigManager();
+
+    /**
+     * Call to load the saved configs from disk.
+     *
+     * TODO: Implement me
+     */
+    void Startup();
+
+    /**
+     * Someone else wants to know about the configs.
+     */
+    void AddListener(const sp<ConfigListener>& listener);
+
+    /**
+     * A configuration was added or updated.
+     *
+     * Reports this to listeners.
+     */
+    void UpdateConfig(const ConfigKey& key, const StatsdConfig& data);
+
+    /**
+     * A configuration was removed.
+     *
+     * Reports this to listeners.
+     */
+    void RemoveConfig(const ConfigKey& key);
+
+    /**
+     * Remove all of the configs for the given uid.
+     */
+    void RemoveConfigs(int uid);
+
+    /**
+     * Text dump of our state for debugging.
+     */
+    void Dump(FILE* out);
+
+private:
+    /**
+     * Save the configs to disk.
+     */
+    void update_saved_configs();
+
+    /**
+     * The Configs that have been set
+     */
+    unordered_map<ConfigKey, StatsdConfig> mConfigs;
+
+    /**
+     * The ConfigListeners that will be told about changes.
+     */
+    vector<sp<ConfigListener>> mListeners;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.cpp b/cmds/statsd/src/external/KernelWakelockPuller.cpp
new file mode 100644
index 0000000..ee072f8
--- /dev/null
+++ b/cmds/statsd/src/external/KernelWakelockPuller.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "Log.h"
+
+#include <android/os/IStatsCompanionService.h>
+#include <binder/IPCThreadState.h>
+#include <private/android_filesystem_config.h>
+#include "StatsService.h"
+#include "external/KernelWakelockPuller.h"
+#include "external/StatsPuller.h"
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace android::os;
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const int KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+// The reading and parsing are implemented in Java. It is not difficult to port over. But for now
+// let StatsCompanionService handle that and send the data back.
+vector<StatsLogEventWrapper> KernelWakelockPuller::pull() {
+    sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
+    vector<StatsLogEventWrapper> returned_value;
+    if (statsCompanion != NULL) {
+        Status status = statsCompanion->pullData(KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS,
+                                                 &returned_value);
+        if (!status.isOk()) {
+            ALOGW("error pulling kernel wakelock");
+        }
+        ALOGD("KernelWakelockPuller::pull succeeded!");
+        return returned_value;
+    } else {
+        ALOGW("statsCompanion not found!");
+        return returned_value;
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.h b/cmds/statsd/src/external/KernelWakelockPuller.h
new file mode 100644
index 0000000..c12806c
--- /dev/null
+++ b/cmds/statsd/src/external/KernelWakelockPuller.h
@@ -0,0 +1,39 @@
+/*
+ * 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 STATSD_KERNELWAKELOCKPULLER_H
+#define STATSD_KERNELWAKELOCKPULLER_H
+
+#include <utils/String16.h>
+#include "external/StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class KernelWakelockPuller : public StatsPuller {
+public:
+    // a number of stats need to be pulled from StatsCompanionService
+    //
+    const static int PULL_CODE_KERNEL_WAKELOCKS;
+    vector<StatsLogEventWrapper> pull() override;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // STATSD_KERNELWAKELOCKPULLER_H
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
new file mode 100644
index 0000000..6655629
--- /dev/null
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -0,0 +1,42 @@
+/*
+ * 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 STATSD_STATSPULLER_H
+#define STATSD_STATSPULLER_H
+
+#include <android/os/StatsLogEventWrapper.h>
+#include <utils/String16.h>
+#include <vector>
+
+using android::os::StatsLogEventWrapper;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StatsPuller {
+public:
+    virtual ~StatsPuller(){};
+
+    virtual vector<StatsLogEventWrapper> pull() = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // STATSD_STATSPULLER_H
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
new file mode 100644
index 0000000..7f554d3
--- /dev/null
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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 DEBUG true
+#include "Log.h"
+
+#include <android/os/IStatsCompanionService.h>
+#include "KernelWakelockPuller.h"
+#include "StatsService.h"
+#include "external/StatsPullerManager.h"
+#include "logd/LogEvent.h"
+#include <cutils/log.h>
+#include <algorithm>
+
+#include <iostream>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const int StatsPullerManager::KERNEL_WAKELOCKS = 1;
+
+StatsPullerManager::StatsPullerManager() {
+    mStatsPullers.insert(
+            {static_cast<int>(KERNEL_WAKELOCKS), std::make_unique<KernelWakelockPuller>()});
+}
+
+vector<std::shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode) {
+    if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
+
+    vector<std::shared_ptr<LogEvent>> ret;
+    if (mStatsPullers.find(pullCode) != mStatsPullers.end()) {
+        vector<StatsLogEventWrapper> outputs = (mStatsPullers.find(pullCode)->second)->pull();
+        for (const StatsLogEventWrapper& it : outputs) {
+            log_msg tmp;
+            tmp.entry_v1.len = it.bytes.size();
+            // Manually set the header size to 28 bytes to match the pushed log events.
+            tmp.entry.hdr_size = 28;
+            // And set the received bytes starting after the 28 bytes reserved for header.
+            std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + 28);
+            std::shared_ptr<LogEvent> evt = std::make_shared<LogEvent>(tmp);
+            ret.push_back(evt);
+            // ret.emplace_back(tmp);
+        }
+        return ret;
+    } else {
+        ALOGD("Unknown pull code %d", pullCode);
+        return ret;  // Return early since we don't know what to pull.
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
new file mode 100644
index 0000000..e46aec1
--- /dev/null
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -0,0 +1,51 @@
+/*
+ * 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 STATSD_STATSPULLERMANAGER_H
+#define STATSD_STATSPULLERMANAGER_H
+
+#include <utils/String16.h>
+#include <unordered_map>
+#include "external/StatsPuller.h"
+#include "logd/LogEvent.h"
+#include "matchers/matcher_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const static int KERNEL_WAKELOCKS = 1;
+
+class StatsPullerManager : public virtual RefBase {
+public:
+    // Enums of pulled data types (pullCodes)
+    // These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
+    // TODO: pull the constant from stats_events.proto instead
+    const static int KERNEL_WAKELOCKS;
+    StatsPullerManager();
+
+    // We return a vector of shared_ptr since LogEvent's copy constructor is not available.
+    vector<std::shared_ptr<LogEvent>> Pull(const int pullCode);
+
+private:
+    std::unordered_map<int, std::unique_ptr<StatsPuller>> mStatsPullers;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // STATSD_STATSPULLERMANAGER_H
diff --git a/cmds/statsd/src/indexed_priority_queue.h b/cmds/statsd/src/indexed_priority_queue.h
deleted file mode 100644
index 76409c07..0000000
--- a/cmds/statsd/src/indexed_priority_queue.h
+++ /dev/null
@@ -1,199 +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.
- */
-
-#ifndef STATSD_INDEXED_PRIORITY_QUEUE_H
-#define STATSD_INDEXED_PRIORITY_QUEUE_H
-
-// ALOGE can be called from this file. If header loaded by another class, use their LOG_TAG instead.
-#ifndef LOG_TAG
-#define LOG_TAG "statsd(indexed_priority_queue)"
-#endif //LOG_TAG
-
-#include <cutils/log.h>
-#include <unordered_map>
-#include <utils/RefBase.h>
-#include <vector>
-
-using namespace android;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/** Defines a hash function for sp<AA>, returning the hash of the underlying pointer. */
-template <class AA>
-struct SpHash {
-    size_t operator()(const sp<const AA>& k) const {
-        return std::hash<const AA*>()(k.get());
-    }
-};
-
-/**
- * Min priority queue for generic type AA.
- * Unlike a regular priority queue, this class is also capable of removing interior elements.
- * @tparam Comparator must implement [bool operator()(sp<const AA> a, sp<const AA> b)], returning
- *    whether a should be closer to the top of the queue than b.
- */
-template <class AA, class Comparator>
-class indexed_priority_queue {
- public:
-    indexed_priority_queue();
-    /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */
-    void push(sp<const AA> a);
-    /** Removes a from the priority queue. If not present or a==nullptr, does nothing. */
-    void remove(sp<const AA> a);
-    /** Removes all elements. */
-    void clear();
-    /** Returns whether priority queue contains a (not just a copy of a, but a itself). */
-    bool contains(sp<const AA> a) const;
-    /** Returns min element. Returns nullptr iff empty(). */
-    sp<const AA> top() const;
-    /** Returns number of elements in priority queue. */
-    size_t size() const { return pq.size() - 1; } // pq is 1-indexed
-    /** Returns true iff priority queue is empty. */
-    bool empty() const { return size() < 1; }
-
- private:
-    /** Vector representing a min-heap (1-indexed, with nullptr at 0). */
-    std::vector<sp<const AA>> pq;
-    /** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */
-    std::unordered_map<sp<const AA>, size_t, SpHash<AA>> indices;
-
-    void init();
-    void sift_up(size_t idx);
-    void sift_down(size_t idx);
-    /** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */
-    bool higher(size_t idx1, size_t idx2) const;
-    void swap_indices(size_t i, size_t j);
-};
-
-// Implementation must be done in this file due to use of template.
-
-template <class AA, class Comparator>
-indexed_priority_queue<AA,Comparator>::indexed_priority_queue() {
-    init();
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::push(sp<const AA> a) {
-    if (a == nullptr) return;
-    if (contains(a)) return;
-    pq.push_back(a);
-    size_t idx = size(); // index of last element since 1-indexed
-    indices.insert({a, idx});
-    sift_up(idx); // get the pq back in order
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::remove(sp<const AA> a) {
-    if (a == nullptr) return;
-    if (!contains(a)) return;
-    size_t idx = indices[a];
-    if (idx >= pq.size()) {
-        ALOGE("indexed_priority_queue: Invalid index in map of indices.");
-        return;
-    }
-    if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1)
-        pq.pop_back();
-        indices.erase(a);
-        return;
-    }
-    // move last element (guaranteed not to be at idx) to idx, then delete a
-    sp<const AA> last_a = pq.back();
-    pq[idx] = last_a;
-    pq.pop_back();
-    indices[last_a] = idx;
-    indices.erase(a);
-
-    // get the heap back in order (since the element at idx is not in order)
-    sift_up(idx);
-    sift_down(idx);
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::clear() {
-    pq.clear();
-    indices.clear();
-    init();
-}
-
-template <class AA, class Comparator>
-sp<const AA> indexed_priority_queue<AA,Comparator>::top() const {
-    if (empty()) return nullptr;
-    return pq[1];
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::init() {
-    pq.push_back(nullptr); // so that pq is 1-indexed.
-    indices.insert({nullptr, 0}); // just to be consistent with pq.
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::sift_up(size_t idx) {
-    while (idx > 1) {
-        size_t parent = idx/2;
-        if (higher(idx, parent)) swap_indices(idx, parent);
-        else break;
-        idx = parent;
-    }
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::sift_down(size_t idx) {
-    while (2*idx <= size()) {
-        size_t child = 2 * idx;
-        if (child < size() && higher(child+1, child)) child++;
-        if (higher(child, idx)) swap_indices(child, idx);
-        else break;
-        idx = child;
-    }
-}
-
-template <class AA, class Comparator>
-bool indexed_priority_queue<AA,Comparator>::higher(size_t idx1, size_t idx2) const {
-    if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) {
-        ALOGE("indexed_priority_queue: Attempting to access invalid index");
-        return false; // got to do something.
-    }
-    return Comparator()(pq[idx1], pq[idx2]);
-}
-
-template <class AA, class Comparator>
-bool indexed_priority_queue<AA,Comparator>::contains(sp<const AA> a) const {
-    if (a == nullptr) return false; // publicly, we pretend that nullptr is not actually in pq.
-    return indices.count(a) > 0;
-}
-
-template <class AA, class Comparator>
-void indexed_priority_queue<AA,Comparator>::swap_indices(size_t i, size_t j) {
-    if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) {
-        ALOGE("indexed_priority_queue: Attempting to swap invalid index");
-        return;
-    }
-    sp<const AA> val_i = pq[i];
-    sp<const AA> val_j = pq[j];
-    pq[i] = val_j;
-    pq[j] = val_i;
-    indices[val_i] = j;
-    indices[val_j] = i;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-
-#endif //STATSD_INDEXED_PRIORITY_QUEUE_H
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
new file mode 100644
index 0000000..1a039f6
--- /dev/null
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -0,0 +1,230 @@
+/*
+ * 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 "logd/LogEvent.h"
+
+#include <sstream>
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::ostringstream;
+using std::string;
+using android::util::ProtoOutputStream;
+
+// We need to keep a copy of the android_log_event_list owned by this instance so that the char*
+// for strings is not cleared before we can read them.
+LogEvent::LogEvent(log_msg msg) : mList(msg) {
+    init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
+}
+
+LogEvent::LogEvent(int tag) : mList(tag) {
+}
+
+LogEvent::~LogEvent() {
+}
+
+void LogEvent::init() {
+    mList.convert_to_reader();
+    init(mTimestampNs, &mList);
+}
+
+/**
+ * The elements of each log event are stored as a vector of android_log_list_elements.
+ * The goal is to do as little preprocessing as possible, because we read a tiny fraction
+ * of the elements that are written to the log.
+ */
+void LogEvent::init(int64_t timestampNs, android_log_event_list* reader) {
+    mTimestampNs = timestampNs;
+    mTagId = reader->tag();
+
+    mElements.clear();
+    android_log_list_element elem;
+
+    // TODO: The log is actually structured inside one list.  This is convenient
+    // because we'll be able to use it to put the attribution (WorkSource) block first
+    // without doing our own tagging scheme.  Until that change is in, just drop the
+    // list-related log elements and the order we get there is our index-keyed data
+    // structure.
+    do {
+        elem = android_log_read_next(reader->context());
+        switch ((int)elem.type) {
+            case EVENT_TYPE_INT:
+            case EVENT_TYPE_FLOAT:
+            case EVENT_TYPE_STRING:
+            case EVENT_TYPE_LONG:
+                mElements.push_back(elem);
+                break;
+            case EVENT_TYPE_LIST:
+                break;
+            case EVENT_TYPE_LIST_STOP:
+                break;
+            case EVENT_TYPE_UNKNOWN:
+                break;
+            default:
+                break;
+        }
+    } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
+}
+
+android_log_event_list* LogEvent::GetAndroidLogEventList() {
+    return &mList;
+}
+
+int64_t LogEvent::GetLong(size_t key, status_t* err) const {
+    if (key < 1 || (key - 1)  >= mElements.size()) {
+        *err = BAD_INDEX;
+        return 0;
+    }
+    key--;
+    const android_log_list_element& elem = mElements[key];
+    if (elem.type == EVENT_TYPE_INT) {
+        return elem.data.int32;
+    } else if (elem.type == EVENT_TYPE_LONG) {
+        return elem.data.int64;
+    } else if (elem.type == EVENT_TYPE_FLOAT) {
+        return (int64_t)elem.data.float32;
+    } else {
+        *err = BAD_TYPE;
+        return 0;
+    }
+}
+
+const char* LogEvent::GetString(size_t key, status_t* err) const {
+    if (key < 1 || (key - 1)  >= mElements.size()) {
+        *err = BAD_INDEX;
+        return NULL;
+    }
+    key--;
+    const android_log_list_element& elem = mElements[key];
+    if (elem.type != EVENT_TYPE_STRING) {
+        *err = BAD_TYPE;
+        return NULL;
+    }
+    // Need to add the '/0' at the end by specifying the length of the string.
+    return string(elem.data.string, elem.len).c_str();
+}
+
+bool LogEvent::GetBool(size_t key, status_t* err) const {
+    if (key < 1 || (key - 1)  >= mElements.size()) {
+        *err = BAD_INDEX;
+        return 0;
+    }
+    key--;
+    const android_log_list_element& elem = mElements[key];
+    if (elem.type == EVENT_TYPE_INT) {
+        return elem.data.int32 != 0;
+    } else if (elem.type == EVENT_TYPE_LONG) {
+        return elem.data.int64 != 0;
+    } else if (elem.type == EVENT_TYPE_FLOAT) {
+        return elem.data.float32 != 0;
+    } else {
+        *err = BAD_TYPE;
+        return 0;
+    }
+}
+
+float LogEvent::GetFloat(size_t key, status_t* err) const {
+    if (key < 1 || (key - 1)  >= mElements.size()) {
+        *err = BAD_INDEX;
+        return 0;
+    }
+    key--;
+    const android_log_list_element& elem = mElements[key];
+    if (elem.type == EVENT_TYPE_INT) {
+        return (float)elem.data.int32;
+    } else if (elem.type == EVENT_TYPE_LONG) {
+        return (float)elem.data.int64;
+    } else if (elem.type == EVENT_TYPE_FLOAT) {
+        return elem.data.float32;
+    } else {
+        *err = BAD_TYPE;
+        return 0;
+    }
+}
+
+KeyValuePair LogEvent::GetKeyValueProto(size_t key) const {
+    KeyValuePair pair;
+    pair.set_key(key);
+    // If the value is not valid, return the KeyValuePair without assigning the value.
+    // Caller can detect the error by checking the enum for "one of" proto type.
+    if (key < 1 || (key - 1) >= mElements.size()) {
+        return pair;
+    }
+    key--;
+
+    const android_log_list_element& elem = mElements[key];
+    if (elem.type == EVENT_TYPE_INT) {
+        pair.set_value_int(elem.data.int32);
+    } else if (elem.type == EVENT_TYPE_LONG) {
+        pair.set_value_int(elem.data.int64);
+    } else if (elem.type == EVENT_TYPE_STRING) {
+        pair.set_value_str(elem.data.string);
+    } else if (elem.type == EVENT_TYPE_FLOAT) {
+        pair.set_value_float(elem.data.float32);
+    }
+    return pair;
+}
+
+string LogEvent::ToString() const {
+    ostringstream result;
+    result << "{ " << mTimestampNs << " (" << mTagId << ")";
+    const size_t N = mElements.size();
+    for (size_t i=0; i<N; i++) {
+        result << " ";
+        result << (i + 1);
+        result << "->";
+        const android_log_list_element& elem = mElements[i];
+        if (elem.type == EVENT_TYPE_INT) {
+            result << elem.data.int32;
+        } else if (elem.type == EVENT_TYPE_LONG) {
+            result << elem.data.int64;
+        } else if (elem.type == EVENT_TYPE_FLOAT) {
+            result << elem.data.float32;
+        } else if (elem.type == EVENT_TYPE_STRING) {
+            // Need to add the '/0' at the end by specifying the length of the string.
+            result << string(elem.data.string, elem.len).c_str();
+        }
+    }
+    result << " }";
+    return result.str();
+}
+
+void LogEvent::ToProto(ProtoOutputStream& proto) const {
+    long long atomToken = proto.start(TYPE_MESSAGE + mTagId);
+    const size_t N = mElements.size();
+    for (size_t i=0; i<N; i++) {
+        const int key = i + 1;
+
+        const android_log_list_element& elem = mElements[i];
+        if (elem.type == EVENT_TYPE_INT) {
+            proto.write(TYPE_INT32 + key, elem.data.int32);
+        } else if (elem.type == EVENT_TYPE_LONG) {
+            proto.write(TYPE_INT64 + key, (long long)elem.data.int64);
+        } else if (elem.type == EVENT_TYPE_FLOAT) {
+            proto.write(TYPE_FLOAT + key, elem.data.float32);
+        } else if (elem.type == EVENT_TYPE_STRING) {
+            proto.write(TYPE_STRING + key, elem.data.string);
+        }
+    }
+    proto.end(atomToken);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
new file mode 100644
index 0000000..9ef20ea
--- /dev/null
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+#include <android/util/ProtoOutputStream.h>
+#include <log/log_event_list.h>
+#include <log/log_read.h>
+#include <utils/Errors.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::string;
+using std::vector;
+
+/**
+ * Wrapper for the log_msg structure.
+ */
+class LogEvent {
+public:
+    /**
+     * Read a LogEvent from a log_msg.
+     */
+    explicit LogEvent(log_msg msg);
+
+    /**
+     * Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
+     * mode. Obtain this list with the getter. Make sure to call init() before attempting to read
+     * any of the values. This constructor is useful for unit-testing since we can't pass in an
+     * android_log_event_list since there is no copy constructor or assignment operator available.
+     */
+    explicit LogEvent(int tag);
+
+    ~LogEvent();
+
+    /**
+     * Get the timestamp associated with this event.
+     */
+    uint64_t GetTimestampNs() const { return mTimestampNs; }
+
+    /**
+     * Get the tag for this event.
+     */
+    int GetTagId() const { return mTagId; }
+
+    /**
+     * Get the nth value, starting at 1.
+     *
+     * Returns BAD_INDEX if the index is larger than the number of elements.
+     * Returns BAD_TYPE if the index is available but the data is the wrong type.
+     */
+    int64_t GetLong(size_t key, status_t* err) const;
+    const char* GetString(size_t key, status_t* err) const;
+    bool GetBool(size_t key, status_t* err) const;
+    float GetFloat(size_t key, status_t* err) const;
+
+    /**
+     * Return a string representation of this event.
+     */
+    string ToString() const;
+
+    /**
+     * Write this object to a ProtoOutputStream.
+     */
+    void ToProto(android::util::ProtoOutputStream& out) const;
+
+    /*
+     * Get a KeyValuePair proto object.
+     */
+    KeyValuePair GetKeyValueProto(size_t key) const;
+
+    /**
+     * A pointer to the contained log_event_list.
+     *
+     * @return The android_log_event_list contained within.
+     */
+    android_log_event_list* GetAndroidLogEventList();
+
+    /**
+     * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
+     * and prepares the list for reading.
+     */
+    void init();
+
+private:
+    /**
+     * Don't copy, it's slower. If we really need this we can add it but let's try to
+     * avoid it.
+     */
+    explicit LogEvent(const LogEvent&);
+
+    /**
+     * Parses a log_msg into a LogEvent object.
+     */
+    void init(const log_msg& msg);
+
+    /**
+     * Parses a log_msg into a LogEvent object.
+     */
+    void init(int64_t timestampNs, android_log_event_list* reader);
+
+    vector<android_log_list_element> mElements;
+    // Need a copy of the android_log_event_list so the strings are not cleared.
+    android_log_event_list mList;
+    long mTimestampNs;
+    int mTagId;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
diff --git a/cmds/statsd/src/logd/LogListener.cpp b/cmds/statsd/src/logd/LogListener.cpp
new file mode 100644
index 0000000..6ac7978
--- /dev/null
+++ b/cmds/statsd/src/logd/LogListener.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 "logd/LogReader.h"
+
+#include <log/log_read.h>
+
+#include <utils/Errors.h>
+
+#include <time.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+LogListener::LogListener() {
+}
+
+LogListener::~LogListener() {
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/logd/LogListener.h b/cmds/statsd/src/logd/LogListener.h
new file mode 100644
index 0000000..9641226
--- /dev/null
+++ b/cmds/statsd/src/logd/LogListener.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "logd/LogEvent.h"
+
+#include <utils/RefBase.h>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Callback for LogReader
+ */
+class LogListener : public virtual android::RefBase {
+public:
+    LogListener();
+    virtual ~LogListener();
+
+    virtual void OnLogEvent(const LogEvent& msg) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/logd/LogReader.cpp b/cmds/statsd/src/logd/LogReader.cpp
new file mode 100644
index 0000000..c441a5e
--- /dev/null
+++ b/cmds/statsd/src/logd/LogReader.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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 "logd/LogReader.h"
+
+#include <utils/Errors.h>
+
+#include <time.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define SNOOZE_INITIAL_MS 100
+#define SNOOZE_MAX_MS (10 * 60 * 1000)  // Ten minutes
+
+LogReader::LogReader(const sp<LogListener>& listener) : mListener(listener) {
+}
+
+LogReader::~LogReader() {
+}
+
+void LogReader::Run() {
+    int nextSnoozeMs = SNOOZE_INITIAL_MS;
+
+    // In an ideal world, this outer loop will only ever run one iteration, but it
+    // exists to handle crashes in logd.  The inner loop inside connect_and_read()
+    // reads from logd forever, but if that read fails, we fall out to the outer
+    // loop, do the backoff (resetting the backoff timeout if we successfully read
+    // something), and then try again.
+    while (true) {
+        // Connect and read
+        int lineCount = connect_and_read();
+
+        // Figure out how long to sleep.
+        if (lineCount > 0) {
+            // If we managed to read at least one line, reset the backoff
+            nextSnoozeMs = SNOOZE_INITIAL_MS;
+        } else {
+            // Otherwise, expontial backoff
+            nextSnoozeMs *= 1.5f;
+            if (nextSnoozeMs > 10 * 60 * 1000) {
+                // Don't wait for toooo long.
+                nextSnoozeMs = SNOOZE_MAX_MS;
+            }
+        }
+
+        // Sleep
+        timespec ts;
+        timespec rem;
+        ts.tv_sec = nextSnoozeMs / 1000;
+        ts.tv_nsec = (nextSnoozeMs % 1000) * 1000000L;
+        while (nanosleep(&ts, &rem) == -1) {
+            if (errno == EINTR) {
+                ts = rem;
+            }
+            // other errors are basically impossible
+        }
+    }
+}
+
+int LogReader::connect_and_read() {
+    int lineCount = 0;
+    status_t err;
+    logger_list* loggers;
+    logger* eventLogger;
+
+    // Prepare the logging context
+    loggers = android_logger_list_alloc(ANDROID_LOG_RDONLY,
+                                        /* don't stop after N lines */ 0,
+                                        /* no pid restriction */ 0);
+
+    // Open the buffer(s)
+    eventLogger = android_logger_open(loggers, LOG_ID_STATS);
+
+    // Read forever
+    if (eventLogger) {
+
+        while (true) {
+            log_msg msg;
+
+            // Read a message
+            err = android_logger_list_read(loggers, &msg);
+            if (err < 0) {
+                fprintf(stderr, "logcat read failure: %s\n", strerror(err));
+                break;
+            }
+
+            // Record that we read one (used above to know how to snooze).
+            lineCount++;
+
+            // Wrap it in a LogEvent object
+            LogEvent event(msg);
+
+            // Call the listener
+            mListener->OnLogEvent(event);
+        }
+    }
+
+    // Free the logger list and close the individual loggers
+    android_logger_list_free(loggers);
+
+    return lineCount;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/logd/LogReader.h b/cmds/statsd/src/logd/LogReader.h
new file mode 100644
index 0000000..c51074c
--- /dev/null
+++ b/cmds/statsd/src/logd/LogReader.h
@@ -0,0 +1,69 @@
+/*
+ * 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 LOGREADER_H
+#define LOGREADER_H
+
+#include "logd/LogListener.h"
+
+#include <utils/RefBase.h>
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Class to read logs from logd.
+ */
+class LogReader : public virtual android::RefBase {
+public:
+    /**
+     * Construct the LogReader with the event listener. (Which is StatsService)
+     */
+    LogReader(const sp<LogListener>& listener);
+
+    /**
+     * Destructor.
+     */
+    virtual ~LogReader();
+
+    /**
+     * Run the main LogReader loop
+     */
+    void Run();
+
+private:
+    /**
+     * Who is going to get the events when they're read.
+     */
+    sp<LogListener> mListener;
+
+    /**
+     * Connect to a single instance of logd, and read until there's a read error.
+     * Logd can crash, exit, be killed etc.
+     *
+     * Returns the number of lines that were read.
+     */
+    int connect_and_read();
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // LOGREADER_H
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index c1dad4f..a740280 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -14,25 +14,22 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "statsd"
+#include "Log.h"
 
-#include "LogEntryPrinter.h"
-#include "LogReader.h"
 #include "StatsService.h"
-#include "StatsLogProcessor.h"
+#include "logd/LogReader.h"
 
 #include <binder/IInterface.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 #include <binder/Status.h>
-#include <cutils/log.h>
 #include <utils/Looper.h>
 #include <utils/StrongPointer.h>
 
 #include <stdio.h>
-#include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 
 using namespace android;
@@ -49,21 +46,15 @@
 /**
  * Thread func for where the log reader runs.
  */
-static void*
-log_reader_thread_func(void* cookie)
-{
+static void* log_reader_thread_func(void* cookie) {
     log_reader_thread_data* data = static_cast<log_reader_thread_data*>(cookie);
 
-    sp<LogReader> reader = new LogReader();
+    sp<LogReader> reader = new LogReader(data->service);
 
-    // Put the printer one first, so it will print before the real ones.
-    reader->AddListener(new LogEntryPrinter(STDOUT_FILENO));
-    sp<StatsLogProcessor> main_processor = new StatsLogProcessor();
-    data->service->setProcessor(main_processor);
-    reader->AddListener(main_processor);
+    // Tell StatsService that we're ready to go.
+    data->service->Startup();
 
-    // TODO: Construct and add real LogListners here.
-
+    // Run the read loop. Never returns.
     reader->Run();
 
     ALOGW("statsd LogReader.Run() is not supposed to return.");
@@ -75,9 +66,7 @@
 /**
  * Creates and starts the thread to own the LogReader.
  */
-static status_t
-start_log_reader_thread(const sp<StatsService>& service)
-{
+static status_t start_log_reader_thread(const sp<StatsService>& service) {
     status_t err;
     pthread_attr_t attr;
     pthread_t thread;
@@ -108,9 +97,7 @@
 }
 
 // ================================================================================
-int
-main(int /*argc*/, char** /*argv*/)
-{
+int main(int /*argc*/, char** /*argv*/) {
     status_t err;
 
     // Set up the looper
@@ -118,7 +105,7 @@
 
     // Set up the binder
     sp<ProcessState> ps(ProcessState::self());
-    ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram
+    ps->setThreadPoolMaxThreadCount(1);  // everything is oneway, let it queue and save ram
     ps->startThreadPool();
     ps->giveThreadPoolName();
     IPCThreadState::self()->disableBackgroundScheduling(true);
@@ -132,6 +119,8 @@
 
     // TODO: This line is temporary, since statsd doesn't start up automatically (and therefore
     // the call in StatsService::SystemRunning() won't ever be called right now).
+    // TODO: Are you sure? Don't we need to reconnect to the system process if we get restarted?
+    //  --joeo
     service->sayHiToStatsCompanion();
 
     // Start the log reader thread
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
new file mode 100644
index 0000000..78ba762
--- /dev/null
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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 "Log.h"
+
+#include "CombinationLogMatchingTracker.h"
+#include "matchers/matcher_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::unordered_map;
+using std::vector;
+
+CombinationLogMatchingTracker::CombinationLogMatchingTracker(const string& name, const int index)
+    : LogMatchingTracker(name, index) {
+}
+
+CombinationLogMatchingTracker::~CombinationLogMatchingTracker() {
+}
+
+bool CombinationLogMatchingTracker::init(const vector<LogEntryMatcher>& allLogMatchers,
+                                         const vector<sp<LogMatchingTracker>>& allTrackers,
+                                         const unordered_map<string, int>& matcherMap,
+                                         vector<bool>& stack) {
+    if (mInitialized) {
+        return true;
+    }
+
+    // mark this node as visited in the recursion stack.
+    stack[mIndex] = true;
+
+    LogEntryMatcher_Combination matcher = allLogMatchers[mIndex].combination();
+
+    // LogicalOperation is missing in the config
+    if (!matcher.has_operation()) {
+        return false;
+    }
+
+    mLogicalOperation = matcher.operation();
+
+    if (mLogicalOperation == LogicalOperation::NOT && matcher.matcher_size() != 1) {
+        return false;
+    }
+
+    for (const string& child : matcher.matcher()) {
+        auto pair = matcherMap.find(child);
+        if (pair == matcherMap.end()) {
+            ALOGW("Matcher %s not found in the config", child.c_str());
+            return false;
+        }
+
+        int childIndex = pair->second;
+
+        // if the child is a visited node in the recursion -> circle detected.
+        if (stack[childIndex]) {
+            ALOGE("Circle detected in matcher config");
+            return false;
+        }
+
+        if (!allTrackers[childIndex]->init(allLogMatchers, allTrackers, matcherMap, stack)) {
+            ALOGW("child matcher init failed %s", child.c_str());
+            return false;
+        }
+
+        mChildren.push_back(childIndex);
+
+        const set<int>& childTagIds = allTrackers[childIndex]->getTagIds();
+        mTagIds.insert(childTagIds.begin(), childTagIds.end());
+    }
+
+    mInitialized = true;
+    // unmark this node in the recursion stack.
+    stack[mIndex] = false;
+    return true;
+}
+
+void CombinationLogMatchingTracker::onLogEvent(const LogEvent& event,
+                                               const vector<sp<LogMatchingTracker>>& allTrackers,
+                                               vector<MatchingState>& matcherResults) {
+    // this event has been processed.
+    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+        return;
+    }
+
+    if (mTagIds.find(event.GetTagId()) == mTagIds.end()) {
+        matcherResults[mIndex] = MatchingState::kNotMatched;
+        return;
+    }
+
+    // evaluate children matchers if they haven't been evaluated.
+    for (const int childIndex : mChildren) {
+        if (matcherResults[childIndex] == MatchingState::kNotComputed) {
+            const sp<LogMatchingTracker>& child = allTrackers[childIndex];
+            child->onLogEvent(event, allTrackers, matcherResults);
+        }
+    }
+
+    bool matched = combinationMatch(mChildren, mLogicalOperation, matcherResults);
+    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
new file mode 100644
index 0000000..006d74c
--- /dev/null
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -0,0 +1,57 @@
+/*
+ * 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 COMBINATION_LOG_MATCHING_TRACKER_H
+#define COMBINATION_LOG_MATCHING_TRACKER_H
+
+#include <log/log_read.h>
+#include <log/logprint.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+#include "LogMatchingTracker.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Represents a LogEntryMatcher_Combination in the StatsdConfig.
+class CombinationLogMatchingTracker : public virtual LogMatchingTracker {
+public:
+    CombinationLogMatchingTracker(const std::string& name, const int index);
+
+    bool init(const std::vector<LogEntryMatcher>& allLogMatchers,
+              const std::vector<sp<LogMatchingTracker>>& allTrackers,
+              const std::unordered_map<std::string, int>& matcherMap,
+              std::vector<bool>& stack);
+
+    ~CombinationLogMatchingTracker();
+
+    void onLogEvent(const LogEvent& event,
+                    const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                    std::vector<MatchingState>& matcherResults) override;
+
+private:
+    LogicalOperation mLogicalOperation;
+
+    std::vector<int> mChildren;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // COMBINATION_LOG_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
new file mode 100644
index 0000000..d82da3b
--- /dev/null
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -0,0 +1,95 @@
+/*
+ * 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 LOG_MATCHING_TRACKER_H
+#define LOG_MATCHING_TRACKER_H
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "logd/LogEvent.h"
+#include "matchers/matcher_util.h"
+
+#include <utils/RefBase.h>
+
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class LogMatchingTracker : public virtual RefBase {
+public:
+    LogMatchingTracker(const std::string& name, const int index)
+        : mName(name), mIndex(index), mInitialized(false){};
+
+    virtual ~LogMatchingTracker(){};
+
+    // Initialize this LogMatchingTracker.
+    // allLogMatchers: the list of the LogEntryMatcher proto config. This is needed because we don't
+    //                 store the proto object in memory. We only need it during initilization.
+    // allTrackers: the list of the LogMatchingTracker objects. It's a one-to-one mapping with
+    //              allLogMatchers. This is needed because the initialization is done recursively
+    //              for CombinationLogMatchingTrackers using DFS.
+    // stack: a bit map to record which matcher has been visited on the stack. This is for detecting
+    //        circle dependency.
+    virtual bool init(const std::vector<LogEntryMatcher>& allLogMatchers,
+                      const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                      const std::unordered_map<std::string, int>& matcherMap,
+                      std::vector<bool>& stack) = 0;
+
+    // Called when a log event comes.
+    // event: the log event.
+    // allTrackers: the list of all LogMatchingTrackers. This is needed because the log processing
+    //              is done recursively.
+    // matcherResults: The cached results for all matchers for this event. Parent matchers can
+    //                 directly access the children's matching results if they have been evaluated.
+    //                 Otherwise, call children matchers' onLogEvent.
+    virtual void onLogEvent(const LogEvent& event,
+                            const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                            std::vector<MatchingState>& matcherResults) = 0;
+
+    // Get the tagIds that this matcher cares about. The combined collection is stored
+    // in MetricMananger, so that we can pass any LogEvents that are not interest of us. It uses
+    // some memory but hopefully it can save us much CPU time when there is flood of events.
+    virtual const std::set<int>& getTagIds() const {
+        return mTagIds;
+    }
+
+protected:
+    // Name of this matching. We don't really need the name, but it makes log message easy to debug.
+    const std::string mName;
+
+    // Index of this LogMatchingTracker in MetricsManager's container.
+    const int mIndex;
+
+    // Whether this LogMatchingTracker has been properly initialized.
+    bool mInitialized;
+
+    // The collection of the event tag ids that this LogMatchingTracker cares. So we can quickly
+    // return kNotMatched when we receive an event with an id not in the list. This is especially
+    // useful when we have a complex CombinationLogMatcherTracker.
+    // TODO: Consider use an array instead of stl set. In reality, the number of the tag ids a
+    // LogMatchingTracker cares is only a few.
+    std::set<int> mTagIds;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // LOG_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
new file mode 100644
index 0000000..b2c88a0
--- /dev/null
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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 DEBUG false  // STOPSHIP if true
+#include "Log.h"
+
+#include "SimpleLogMatchingTracker.h"
+
+#include <log/logprint.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::string;
+using std::unique_ptr;
+using std::unordered_map;
+using std::vector;
+
+
+SimpleLogMatchingTracker::SimpleLogMatchingTracker(const string& name, const int index,
+                                                   const SimpleLogEntryMatcher& matcher)
+    : LogMatchingTracker(name, index), mMatcher(matcher) {
+    if (!matcher.has_tag()) {
+        mInitialized = false;
+    } else {
+        mTagIds.insert(matcher.tag());
+        mInitialized = true;
+    }
+}
+
+SimpleLogMatchingTracker::~SimpleLogMatchingTracker() {
+}
+
+bool SimpleLogMatchingTracker::init(const vector<LogEntryMatcher>& allLogMatchers,
+                                    const vector<sp<LogMatchingTracker>>& allTrackers,
+                                    const unordered_map<string, int>& matcherMap,
+                                    vector<bool>& stack) {
+    // no need to do anything.
+    return mInitialized;
+}
+
+void SimpleLogMatchingTracker::onLogEvent(const LogEvent& event,
+                                          const vector<sp<LogMatchingTracker>>& allTrackers,
+                                          vector<MatchingState>& matcherResults) {
+    if (matcherResults[mIndex] != MatchingState::kNotComputed) {
+        VLOG("Matcher %s already evaluated ", mName.c_str());
+        return;
+    }
+
+    if (mTagIds.find(event.GetTagId()) == mTagIds.end()) {
+        matcherResults[mIndex] = MatchingState::kNotMatched;
+        return;
+    }
+
+    bool matched = matchesSimple(mMatcher, event);
+    matcherResults[mIndex] = matched ? MatchingState::kMatched : MatchingState::kNotMatched;
+    VLOG("Stats SimpleLogMatcher %s matched? %d", mName.c_str(), matched);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
new file mode 100644
index 0000000..e110ec8
--- /dev/null
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -0,0 +1,56 @@
+/*
+ * 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 SIMPLE_LOG_MATCHING_TRACKER_H
+#define SIMPLE_LOG_MATCHING_TRACKER_H
+
+#include <log/log_read.h>
+#include <log/logprint.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+#include "LogMatchingTracker.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class SimpleLogMatchingTracker : public virtual LogMatchingTracker {
+public:
+    SimpleLogMatchingTracker(const std::string& name, const int index,
+                             const SimpleLogEntryMatcher& matcher);
+
+    ~SimpleLogMatchingTracker();
+
+    bool init(const std::vector<LogEntryMatcher>& allLogMatchers,
+              const std::vector<sp<LogMatchingTracker>>& allTrackers,
+              const std::unordered_map<std::string, int>& matcherMap,
+              std::vector<bool>& stack) override;
+
+    void onLogEvent(const LogEvent& event,
+                    const std::vector<sp<LogMatchingTracker>>& allTrackers,
+                    std::vector<MatchingState>& matcherResults) override;
+
+private:
+    const SimpleLogEntryMatcher mMatcher;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // SIMPLE_LOG_MATCHING_TRACKER_H
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
new file mode 100644
index 0000000..6aa2211
--- /dev/null
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -0,0 +1,195 @@
+/*
+ * 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 "Log.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/LogMatchingTracker.h"
+#include "matchers/matcher_util.h"
+#include "stats_util.h"
+
+#include <log/event_tag_map.h>
+#include <log/log_event_list.h>
+#include <log/logprint.h>
+#include <utils/Errors.h>
+
+#include <sstream>
+#include <unordered_map>
+
+using std::ostringstream;
+using std::set;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool combinationMatch(const vector<int>& children, const LogicalOperation& operation,
+                      const vector<MatchingState>& matcherResults) {
+    bool matched;
+    switch (operation) {
+        case LogicalOperation::AND: {
+            matched = true;
+            for (const int childIndex : children) {
+                if (matcherResults[childIndex] != MatchingState::kMatched) {
+                    matched = false;
+                    break;
+                }
+            }
+            break;
+        }
+        case LogicalOperation::OR: {
+            matched = false;
+            for (const int childIndex : children) {
+                if (matcherResults[childIndex] == MatchingState::kMatched) {
+                    matched = true;
+                    break;
+                }
+            }
+            break;
+        }
+        case LogicalOperation::NOT:
+            matched = matcherResults[children[0]] == MatchingState::kNotMatched;
+            break;
+        case LogicalOperation::NAND:
+            matched = false;
+            for (const int childIndex : children) {
+                if (matcherResults[childIndex] != MatchingState::kMatched) {
+                    matched = true;
+                    break;
+                }
+            }
+            break;
+        case LogicalOperation::NOR:
+            matched = true;
+            for (const int childIndex : children) {
+                if (matcherResults[childIndex] == MatchingState::kMatched) {
+                    matched = false;
+                    break;
+                }
+            }
+            break;
+    }
+    return matched;
+}
+
+bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& event) {
+    const int tagId = event.GetTagId();
+
+    if (simpleMatcher.tag() != tagId) {
+        return false;
+    }
+    // now see if this event is interesting to us -- matches ALL the matchers
+    // defined in the metrics.
+    bool allMatched = true;
+    for (int j = 0; allMatched && j < simpleMatcher.key_value_matcher_size(); j++) {
+        auto cur = simpleMatcher.key_value_matcher(j);
+
+        // TODO: Check if this key is a magic key (eg package name).
+        // TODO: Maybe make packages a different type in the config?
+        int key = cur.key_matcher().key();
+
+        const KeyValueMatcher::ValueMatcherCase matcherCase = cur.value_matcher_case();
+        if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqString) {
+            // String fields
+            status_t err = NO_ERROR;
+            const char* val = event.GetString(key, &err);
+            if (err == NO_ERROR && val != NULL) {
+                if (!(cur.eq_string() == val)) {
+                    allMatched = false;
+                }
+            }
+        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+            // Integer fields
+            status_t err = NO_ERROR;
+            int64_t val = event.GetLong(key, &err);
+            if (err == NO_ERROR) {
+                if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqInt) {
+                    if (!(val == cur.eq_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtInt) {
+                    if (!(val < cur.lt_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtInt) {
+                    if (!(val > cur.gt_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLteInt) {
+                    if (!(val <= cur.lte_int())) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGteInt) {
+                    if (!(val >= cur.gte_int())) {
+                        allMatched = false;
+                    }
+                }
+            }
+            break;
+        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kEqBool) {
+            // Boolean fields
+            status_t err = NO_ERROR;
+            bool val = event.GetBool(key, &err);
+            if (err == NO_ERROR) {
+                if (!(cur.eq_bool() == val)) {
+                    allMatched = false;
+                }
+            }
+        } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat ||
+                   matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+            // Float fields
+            status_t err = NO_ERROR;
+            bool val = event.GetFloat(key, &err);
+            if (err == NO_ERROR) {
+                if (matcherCase == KeyValueMatcher::ValueMatcherCase::kLtFloat) {
+                    if (!(cur.lt_float() <= val)) {
+                        allMatched = false;
+                    }
+                } else if (matcherCase == KeyValueMatcher::ValueMatcherCase::kGtFloat) {
+                    if (!(cur.gt_float() >= val)) {
+                        allMatched = false;
+                    }
+                }
+            }
+        } else {
+            // If value matcher is not present, assume that we match.
+        }
+    }
+    return allMatched;
+}
+
+vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+                                     const std::vector<KeyMatcher>& dimensions) {
+    vector<KeyValuePair> key;
+    key.reserve(dimensions.size());
+    for (const KeyMatcher& dimension : dimensions) {
+        KeyValuePair k = event.GetKeyValueProto(dimension.key());
+        key.push_back(k);
+    }
+    return key;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h
new file mode 100644
index 0000000..4ea6f0b
--- /dev/null
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -0,0 +1,53 @@
+/*
+ * 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 MATCHER_UTIL_H
+#define MATCHER_UTIL_H
+
+#include "logd/LogEvent.h"
+
+#include <log/log_read.h>
+#include <log/logprint.h>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum MatchingState {
+    kNotComputed = -1,
+    kNotMatched = 0,
+    kMatched = 1,
+};
+
+bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation,
+                      const std::vector<MatchingState>& matcherResults);
+
+bool matchesSimple(const SimpleLogEntryMatcher& simpleMatcher, const LogEvent& wrapper);
+
+std::vector<KeyValuePair> getDimensionKey(const LogEvent& event,
+                                          const std::vector<KeyMatcher>& dimensions);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // MATCHER_UTIL_H
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
new file mode 100644
index 0000000..7aa748f
--- /dev/null
+++ b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "CountAnomalyTracker.h"
+
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+CountAnomalyTracker::CountAnomalyTracker(const Alert& alert)
+    : mAlert(alert),
+      mNumPastBuckets(alert.number_of_buckets() > 0 ? alert.number_of_buckets() - 1 : 0),
+      mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr) {
+
+    VLOG("CountAnomalyTracker() called");
+    if (alert.number_of_buckets() < 1) {
+        ALOGE("Cannot create CountAnomalyTracker with %d buckets", alert.number_of_buckets());
+    }
+    reset(); // initialization
+}
+
+CountAnomalyTracker::~CountAnomalyTracker() {
+    VLOG("~CountAnomalyTracker() called");
+}
+
+void CountAnomalyTracker::addPastBucket(int pastBucketCount,
+                                        time_t numberOfBucketsAgo) {
+    VLOG("addPastBucket() called.");
+    if (numberOfBucketsAgo < 1) {
+        ALOGE("Cannot add a past bucket %ld units in past", numberOfBucketsAgo);
+        return;
+    }
+    // If past bucket was ancient, just empty out all past info.
+    // This always applies if mNumPastBuckets == 0 (i.e. store no past buckets).
+    if (numberOfBucketsAgo > (time_t) mNumPastBuckets) {
+        reset();
+        return;
+    }
+
+    // Empty out old mPastBuckets[i] values and update mSumPastCounters.
+    for (size_t i = mOldestBucketIndex;
+                        i < mOldestBucketIndex + numberOfBucketsAgo; i++) {
+        mSumPastCounters -= mPastBuckets[index(i)];
+        mPastBuckets[index(i)] = 0;
+    }
+
+    // Replace the oldest bucket with the new bucket we are adding.
+    mPastBuckets[mOldestBucketIndex] = pastBucketCount;
+    mSumPastCounters += pastBucketCount;
+
+    // Advance the oldest bucket index by numberOfBucketsAgo units.
+    mOldestBucketIndex = index(mOldestBucketIndex + numberOfBucketsAgo);
+
+    // TODO: Once dimensions are added to mSumPastCounters:
+    // iterate through mSumPastCounters and remove any entries that are 0.
+}
+
+void CountAnomalyTracker::reset() {
+    VLOG("reset() called.");
+    for (size_t i = 0; i < mNumPastBuckets; i++) {
+        mPastBuckets[i] = 0;
+    }
+    mSumPastCounters = 0;
+    mOldestBucketIndex = 0;
+}
+
+void CountAnomalyTracker::checkAnomaly(int currentCount) {
+    // Skip the check if in refractory period.
+    if (time(nullptr) < mRefractoryPeriodEndsSec) {
+        VLOG("Skipping anomaly check since within refractory period");
+        return;
+    }
+
+    // TODO: Remove these extremely verbose debugging log.
+    VLOG("Checking whether %d + %d > %lld",
+         mSumPastCounters, currentCount, mAlert.trigger_if_sum_gt());
+
+    // Note that this works even if mNumPastBuckets < 1 (since then
+    // mSumPastCounters = 0 so the comparison is based only on currentCount).
+    if (mAlert.has_trigger_if_sum_gt() &&
+            mSumPastCounters + currentCount > mAlert.trigger_if_sum_gt()) {
+        declareAnomaly();
+    }
+}
+
+void CountAnomalyTracker::declareAnomaly() {
+    // TODO(guardrail): Consider guarding against too short refractory periods.
+    time_t currTime = time(nullptr);
+    mRefractoryPeriodEndsSec = currTime + mAlert.refractory_period_secs();
+
+    // TODO: If we had access to the bucket_size_millis, consider calling reset()
+    // if (mAlert.refractory_period_secs() > mNumPastBuckets * bucket_size_millis * 1000).
+
+    if (mAlert.has_incidentd_details()) {
+        const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
+        if (incident.has_alert_name()) {
+            ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
+                  incident.alert_name().c_str());
+        } else {
+            // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+            ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+        }
+        // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+    } else {
+        ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.h b/cmds/statsd/src/metrics/CountAnomalyTracker.h
new file mode 100644
index 0000000..79c47d2a
--- /dev/null
+++ b/cmds/statsd/src/metrics/CountAnomalyTracker.h
@@ -0,0 +1,87 @@
+/*
+ * 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 COUNT_ANOMALY_TRACKER_H
+#define COUNT_ANOMALY_TRACKER_H
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
+
+#include <memory> // unique_ptr
+#include <stdlib.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// TODO: Can probably be used for Count, Value, and Gauge. If so, rename to ValueAnomalyTracker.
+// (caveat: currently, the value cannot be negative. Probably fine for P.)
+class CountAnomalyTracker {
+public:
+    CountAnomalyTracker(const Alert& alert);
+
+    virtual ~CountAnomalyTracker();
+
+
+    // Adds a new past bucket, holding pastBucketCount, and then advances the
+    // present by numberOfBucketsAgo buckets (filling any intervening buckets
+    // with 0s).
+    // Thus, the newly added bucket (which holds pastBucketCount) is stored
+    // numberOfBucketsAgo buckets ago.
+    void addPastBucket(int pastBucketCount, time_t numberOfBucketsAgo);
+
+    // Informs the anomaly tracker of the current bucket's count, so that it can
+    // determine whether an anomaly has occurred. This value is not stored.
+    void checkAnomaly(int currentCount);
+
+private:
+    // statsd_config.proto Alert message that defines this tracker.
+    const Alert mAlert;
+
+    // Number of past buckets. One less than the total number of buckets needed
+    // for the anomaly detection (since the current bucket is not in the past).
+    const size_t mNumPastBuckets;
+
+    // Count values for each of the past mNumPastBuckets buckets.
+    // TODO: Add dimensions. This parallels the type of CountMetricProducer.mCounter.
+    std::unique_ptr<int[]> mPastBuckets;
+
+    // Sum over all of mPastBuckets (cached).
+    // TODO: Add dimensions. This parallels the type of CountMetricProducer.mCounter.
+    //       At that point, mSumPastCounters must never contain entries of 0.
+    int mSumPastCounters;
+
+    // Index of the oldest bucket (i.e. the next bucket to be overwritten).
+    size_t mOldestBucketIndex = 0;
+
+    // Timestamp that the refractory period (if this anomaly was declared) ends, in seconds.
+    // If an anomaly was never declared, set to 0.
+    time_t mRefractoryPeriodEndsSec = 0;
+
+    void declareAnomaly();
+
+    // Calculates the corresponding index within the circular array.
+    size_t index(size_t unsafeIndex) {
+        return unsafeIndex % mNumPastBuckets;
+    }
+
+    // Resets all data. For use when all the data gets stale.
+    void reset();
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // COUNT_ANOMALY_TRACKER_H
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
new file mode 100644
index 0000000..69f336f
--- /dev/null
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -0,0 +1,215 @@
+/*
+ * 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 DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "CountAnomalyTracker.h"
+#include "CountMetricProducer.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::map;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// TODO: add back AnomalyTracker.
+CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard)
+    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+    : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+      mMetric(metric) {
+    // TODO: evaluate initial conditions. and set mConditionMet.
+    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
+    } else {
+        mBucketSizeNs = LLONG_MAX;
+    }
+
+    mAnomalyTrackers.reserve(metric.alerts_size());
+    for (int i = 0; i < metric.alerts_size(); i++) {
+        const Alert& alert = metric.alerts(i);
+        if (alert.trigger_if_sum_gt() > 0 && alert.number_of_buckets() > 0) {
+            mAnomalyTrackers.push_back(std::make_unique<CountAnomalyTracker>(alert));
+        } else {
+            ALOGW("Ignoring invalid count metric alert: threshold=%lld num_buckets= %d",
+                  alert.trigger_if_sum_gt(), alert.number_of_buckets());
+        }
+    }
+
+    // TODO: use UidMap if uid->pkg_name is required
+    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+CountMetricProducer::~CountMetricProducer() {
+    VLOG("~CountMetricProducer() called");
+}
+
+void CountMetricProducer::finish() {
+    // TODO: write the StatsLogReport to dropbox using
+    // DropboxWriter.
+}
+
+static void addSlicedCounterToReport(StatsLogReport_CountMetricDataWrapper& wrapper,
+                                     const vector<KeyValuePair>& key,
+                                     const vector<CountBucketInfo>& buckets) {
+    CountMetricData* data = wrapper.add_data();
+    for (const auto& kv : key) {
+        data->add_dimension()->CopyFrom(kv);
+    }
+    for (const auto& bucket : buckets) {
+        data->add_bucket_info()->CopyFrom(bucket);
+        VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.count());
+    }
+}
+
+void CountMetricProducer::onSlicedConditionMayChange() {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+}
+
+StatsLogReport CountMetricProducer::onDumpReport() {
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
+
+    StatsLogReport report;
+    report.set_metric_id(mMetric.metric_id());
+    report.set_start_report_nanos(mStartTimeNs);
+
+    // Dump current bucket if it's stale.
+    // If current bucket is still on-going, don't force dump current bucket.
+    // In finish(), We can force dump current bucket.
+    flushCounterIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+    StatsLogReport_CountMetricDataWrapper* wrapper = report.mutable_count_metrics();
+
+    for (const auto& pair : mPastBuckets) {
+        const HashableDimensionKey& hashableKey = pair.first;
+        auto it = mDimensionKeyMap.find(hashableKey);
+        if (it == mDimensionKeyMap.end()) {
+            ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+            continue;
+        }
+
+        VLOG("  dimension key %s", hashableKey.c_str());
+        addSlicedCounterToReport(*wrapper, it->second, pair.second);
+    }
+    return report;
+    // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+}
+
+void CountMetricProducer::onConditionChanged(const bool conditionMet) {
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+    mCondition = conditionMet;
+}
+
+void CountMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKey, bool condition,
+        const LogEvent& event) {
+    uint64_t eventTimeNs = event.GetTimestampNs();
+
+    flushCounterIfNeeded(eventTimeNs);
+
+    if (condition == false) {
+        return;
+    }
+
+    auto it = mCurrentSlicedCounter.find(eventKey);
+
+    if (it == mCurrentSlicedCounter.end()) {
+        // create a counter for the new key
+        mCurrentSlicedCounter[eventKey] = 1;
+
+    } else {
+        // increment the existing value
+        auto& count = it->second;
+        count++;
+    }
+
+    // TODO: Re-add anomaly detection (similar to):
+    // for (auto& tracker : mAnomalyTrackers) {
+    //     tracker->checkAnomaly(mCounter);
+    // }
+
+    VLOG("metric %lld %s->%d", mMetric.metric_id(), eventKey.c_str(),
+         mCurrentSlicedCounter[eventKey]);
+}
+
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
+void CountMetricProducer::flushCounterIfNeeded(const uint64_t eventTimeNs) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
+        return;
+    }
+
+    // adjust the bucket start time
+    int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    CountBucketInfo info;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
+
+    for (const auto& counter : mCurrentSlicedCounter) {
+        info.set_count(counter.second);
+        // it will auto create new vector of CountbucketInfo if the key is not found.
+        auto& bucketList = mPastBuckets[counter.first];
+        bucketList.push_back(info);
+
+        VLOG("metric %lld, dump key value: %s -> %d", mMetric.metric_id(), counter.first.c_str(),
+             counter.second);
+    }
+
+    // TODO: Re-add anomaly detection (similar to):
+    // for (auto& tracker : mAnomalyTrackers) {
+    //     tracker->addPastBucket(mCounter, numBucketsForward);
+    //}
+
+    // Reset counters
+    mCurrentSlicedCounter.clear();
+
+    mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+    VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
+}
+
+size_t CountMetricProducer::byteSize() {
+// TODO: return actual proto size when ProtoOutputStream is ready for use for
+// CountMetricsProducer.
+//    return mProto->size();
+    return 0;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
new file mode 100644
index 0000000..be77e47
--- /dev/null
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -0,0 +1,79 @@
+/*
+ * 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 COUNT_METRIC_PRODUCER_H
+#define COUNT_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "CountAnomalyTracker.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class CountMetricProducer : public MetricProducer {
+public:
+    // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+    CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
+                        const sp<ConditionWizard>& wizard);
+
+    virtual ~CountMetricProducer();
+
+    void onConditionChanged(const bool conditionMet) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    void onSlicedConditionMayChange() override;
+
+    size_t byteSize() override;
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+protected:
+    void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+                                   const std::map<std::string, HashableDimensionKey>& conditionKey,
+                                   bool condition, const LogEvent& event) override;
+
+private:
+    const CountMetric mMetric;
+
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<CountBucketInfo>> mPastBuckets;
+
+    // The current bucket.
+    std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+
+    vector<unique_ptr<CountAnomalyTracker>> mAnomalyTrackers;
+
+    void flushCounterIfNeeded(const uint64_t newEventTime);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // COUNT_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
new file mode 100644
index 0000000..a590bc8
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -0,0 +1,361 @@
+/*
+ * 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 DEBUG true
+#include "DurationMetricProducer.h"
+#include "Log.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DurationMetricProducer::DurationMetricProducer(const DurationMetric& metric,
+                                               const int conditionIndex, const size_t startIndex,
+                                               const size_t stopIndex, const size_t stopAllIndex,
+                                               const sp<ConditionWizard>& wizard)
+    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+    : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
+      mMetric(metric),
+      mStartIndex(startIndex),
+      mStopIndex(stopIndex),
+      mStopAllIndex(stopAllIndex) {
+    // TODO: The following boiler plate code appears in all MetricProducers, but we can't abstract
+    // them in the base class, because the proto generated CountMetric, and DurationMetric are
+    // not related. Maybe we should add a template in the future??
+    if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+        mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000000;
+    } else {
+        mBucketSizeNs = LLONG_MAX;
+    }
+
+    // TODO: use UidMap if uid->pkg_name is required
+    mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+DurationMetricProducer::~DurationMetricProducer() {
+    VLOG("~DurationMetric() called");
+}
+
+void DurationMetricProducer::finish() {
+    // TODO: write the StatsLogReport to dropbox using
+    // DropboxWriter.
+}
+
+void DurationMetricProducer::onSlicedConditionMayChange() {
+    VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
+    // Now for each of the on-going event, check if the condition has changed for them.
+    for (auto& pair : mCurrentSlicedDuration) {
+        VLOG("Metric %lld current %s state: %d", mMetric.metric_id(), pair.first.c_str(),
+             pair.second.state);
+        if (pair.second.state == kStopped) {
+            continue;
+        }
+        bool conditionMet = mWizard->query(mConditionTrackerIndex, pair.second.conditionKeys) ==
+                            ConditionState::kTrue;
+        VLOG("key: %s, condition: %d", pair.first.c_str(), conditionMet);
+        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+    }
+}
+
+void DurationMetricProducer::onConditionChanged(const bool conditionMet) {
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+    mCondition = conditionMet;
+    // TODO: need to populate the condition change time from the event which triggers the condition
+    // change, instead of using current time.
+    for (auto& pair : mCurrentSlicedDuration) {
+        noteConditionChanged(pair.first, conditionMet, time(nullptr) * 1000000000);
+    }
+}
+
+static void addDurationBucketsToReport(StatsLogReport_DurationMetricDataWrapper& wrapper,
+                                       const vector<KeyValuePair>& key,
+                                       const vector<DurationBucketInfo>& buckets) {
+    DurationMetricData* data = wrapper.add_data();
+    for (const auto& kv : key) {
+        data->add_dimension()->CopyFrom(kv);
+    }
+    for (const auto& bucket : buckets) {
+        data->add_bucket_info()->CopyFrom(bucket);
+        VLOG("\t bucket [%lld - %lld] count: %lld", bucket.start_bucket_nanos(),
+             bucket.end_bucket_nanos(), bucket.duration_nanos());
+    }
+}
+
+StatsLogReport DurationMetricProducer::onDumpReport() {
+    VLOG("metric %lld dump report now...", mMetric.metric_id());
+    StatsLogReport report;
+    report.set_metric_id(mMetric.metric_id());
+    report.set_start_report_nanos(mStartTimeNs);
+    // Dump current bucket if it's stale.
+    // If current bucket is still on-going, don't force dump current bucket.
+    // In finish(), We can force dump current bucket.
+    flushDurationIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+    report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+
+    StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
+    for (const auto& pair : mPastBuckets) {
+        const HashableDimensionKey& hashableKey = pair.first;
+        auto it = mDimensionKeyMap.find(hashableKey);
+        if (it == mDimensionKeyMap.end()) {
+            ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+            continue;
+        }
+        VLOG("  dimension key %s", hashableKey.c_str());
+        addDurationBucketsToReport(*wrapper, it->second, pair.second);
+    }
+    return report;
+};
+
+void DurationMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+        const LogEvent& event) {
+    flushDurationIfNeeded(event.GetTimestampNs());
+
+    if (matcherIndex == mStopAllIndex) {
+        noteStopAll(event.GetTimestampNs());
+        return;
+    }
+
+    if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end() && mConditionSliced) {
+        // add the durationInfo for the current bucket.
+        auto& durationInfo = mCurrentSlicedDuration[eventKey];
+        durationInfo.conditionKeys = conditionKeys;
+    }
+
+    if (matcherIndex == mStartIndex) {
+        VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), eventKey.c_str(),
+             condition);
+        noteStart(eventKey, condition, event.GetTimestampNs());
+    } else if (matcherIndex == mStopIndex) {
+        VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), eventKey.c_str(),
+             condition);
+        noteStop(eventKey, event.GetTimestampNs());
+    }
+}
+
+void DurationMetricProducer::noteConditionChanged(const HashableDimensionKey& key,
+                                                  const bool conditionMet,
+                                                  const uint64_t eventTime) {
+    flushDurationIfNeeded(eventTime);
+
+    auto it = mCurrentSlicedDuration.find(key);
+    if (it == mCurrentSlicedDuration.end()) {
+        return;
+    }
+
+    switch (it->second.state) {
+        case kStarted:
+            // if condition becomes false, kStarted -> kPaused. Record the current duration.
+            if (!conditionMet) {
+                it->second.state = DurationState::kPaused;
+                it->second.lastDuration =
+                        updateDuration(it->second.lastDuration,
+                                       eventTime - it->second.lastStartTime, mMetric.type());
+                VLOG("Metric %lld Key: %s Paused because condition is false ", mMetric.metric_id(),
+                     key.c_str());
+            }
+            break;
+        case kStopped:
+            // nothing to do if it's stopped.
+            break;
+        case kPaused:
+            // if condition becomes true, kPaused -> kStarted. and the start time is the condition
+            // change time.
+            if (conditionMet) {
+                it->second.state = DurationState::kStarted;
+                it->second.lastStartTime = eventTime;
+                VLOG("Metric %lld Key: %s Paused->Started", mMetric.metric_id(), key.c_str());
+            }
+            break;
+    }
+}
+
+void DurationMetricProducer::noteStart(const HashableDimensionKey& key, const bool conditionMet,
+                                       const uint64_t eventTime) {
+    // this will add an empty bucket for this key if it didn't exist before.
+    DurationInfo& duration = mCurrentSlicedDuration[key];
+
+    switch (duration.state) {
+        case kStarted:
+            // It's safe to do nothing here. even if condition is not true, it means we are about
+            // to receive the condition change event.
+            break;
+        case kPaused:
+            // Safe to do nothing here. kPaused is waiting for the condition change.
+            break;
+        case kStopped:
+            if (!conditionMet) {
+                // event started, but we need to wait for the condition to become true.
+                duration.state = DurationState::kPaused;
+                break;
+            }
+            duration.state = DurationState::kStarted;
+            duration.lastStartTime = eventTime;
+            break;
+    }
+}
+
+void DurationMetricProducer::noteStop(const HashableDimensionKey& key, const uint64_t eventTime) {
+    if (mCurrentSlicedDuration.find(key) == mCurrentSlicedDuration.end()) {
+        // we didn't see a start event before. do nothing.
+        return;
+    }
+    DurationInfo& duration = mCurrentSlicedDuration[key];
+
+    switch (duration.state) {
+        case DurationState::kStopped:
+            // already stopped, do nothing.
+            break;
+        case DurationState::kStarted: {
+            duration.state = DurationState::kStopped;
+            int64_t durationTime = eventTime - duration.lastStartTime;
+            VLOG("Metric %lld, key %s, Stop %lld %lld %lld", mMetric.metric_id(), key.c_str(),
+                 (long long)duration.lastStartTime, (long long)eventTime, (long long)durationTime);
+            duration.lastDuration =
+                    updateDuration(duration.lastDuration, durationTime, mMetric.type());
+            VLOG("  record duration: %lld ", (long long)duration.lastDuration);
+            break;
+        }
+        case DurationState::kPaused: {
+            duration.state = DurationState::kStopped;
+            break;
+        }
+    }
+}
+
+int64_t DurationMetricProducer::updateDuration(const int64_t lastDuration,
+                                               const int64_t durationTime,
+                                               const DurationMetric_AggregationType type) {
+    int64_t result = lastDuration;
+    switch (type) {
+        case DurationMetric_AggregationType_DURATION_SUM:
+            result += durationTime;
+            break;
+        case DurationMetric_AggregationType_DURATION_MAX_SPARSE:
+            if (lastDuration < durationTime) {
+                result = durationTime;
+            }
+            break;
+        case DurationMetric_AggregationType_DURATION_MIN_SPARSE:
+            if (lastDuration > durationTime) {
+                result = durationTime;
+            }
+            break;
+    }
+    return result;
+}
+
+void DurationMetricProducer::noteStopAll(const uint64_t eventTime) {
+    for (auto& duration : mCurrentSlicedDuration) {
+        noteStop(duration.first, eventTime);
+    }
+}
+
+// When a new matched event comes in, we check if event falls into the current
+// bucket. If not, flush the old counter to past buckets and initialize the current buckt.
+void DurationMetricProducer::flushDurationIfNeeded(const uint64_t eventTime) {
+    if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
+        return;
+    }
+
+    // adjust the bucket start time
+    int numBucketsForward = (eventTime - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+
+    DurationBucketInfo info;
+    uint64_t endTime = mCurrentBucketStartTimeNs + mBucketSizeNs;
+    info.set_start_bucket_nanos(mCurrentBucketStartTimeNs);
+    info.set_end_bucket_nanos(endTime);
+
+    uint64_t oldBucketStartTimeNs = mCurrentBucketStartTimeNs;
+    mCurrentBucketStartTimeNs += (numBucketsForward)*mBucketSizeNs;
+    VLOG("Metric %lld: new bucket start time: %lld", mMetric.metric_id(),
+         (long long)mCurrentBucketStartTimeNs);
+
+    for (auto it = mCurrentSlicedDuration.begin(); it != mCurrentSlicedDuration.end(); ++it) {
+        int64_t finalDuration = it->second.lastDuration;
+        if (it->second.state == kStarted) {
+            // the event is still on-going, duration needs to be updated.
+            int64_t durationTime = endTime - it->second.lastStartTime;
+            finalDuration = updateDuration(it->second.lastDuration, durationTime, mMetric.type());
+        }
+
+        VLOG("  final duration for last bucket: %lld", (long long)finalDuration);
+
+        // Don't record empty bucket.
+        if (finalDuration != 0) {
+            info.set_duration_nanos(finalDuration);
+            // it will auto create new vector of CountbucketInfo if the key is not found.
+            auto& bucketList = mPastBuckets[it->first];
+            bucketList.push_back(info);
+        }
+
+        // if the event is still on-going, add the buckets between previous bucket and now. Because
+        // the event has been going on across all the buckets in between.
+        // |prev_bucket|...|..|...|now_bucket|
+        if (it->second.state == kStarted) {
+            for (int i = 1; i < numBucketsForward; i++) {
+                DurationBucketInfo info;
+                info.set_start_bucket_nanos(oldBucketStartTimeNs + mBucketSizeNs * i);
+                info.set_end_bucket_nanos(endTime + mBucketSizeNs * i);
+                info.set_duration_nanos(mBucketSizeNs);
+                auto& bucketList = mPastBuckets[it->first];
+                bucketList.push_back(info);
+                VLOG("  add filling bucket with duration %lld", (long long)mBucketSizeNs);
+            }
+        }
+
+        if (it->second.state == DurationState::kStopped) {
+            // No need to keep buckets for events that were stopped before. If the event starts
+            // again, we will add it back.
+            mCurrentSlicedDuration.erase(it);
+        } else {
+            // for kPaused, and kStarted event, we will keep the buckets, and reset the start time
+            // and duration.
+            it->second.lastStartTime = mCurrentBucketStartTimeNs;
+            it->second.lastDuration = 0;
+        }
+    }
+}
+
+size_t DurationMetricProducer::byteSize() {
+// TODO: return actual proto size when ProtoOutputStream is ready for use for
+// DurationMetricsProducer.
+//    return mProto->size();
+  return 0;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
new file mode 100644
index 0000000..8820403
--- /dev/null
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -0,0 +1,120 @@
+/*
+ * 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 DURATION_METRIC_PRODUCER_H
+#define DURATION_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+enum DurationState {
+    kStopped = 0,  // The event is stopped.
+    kStarted = 1,  // The event is on going.
+    kPaused = 2,   // The event is started, but condition is false, clock is paused. When condition
+                   // turns to true, kPaused will become kStarted.
+};
+
+// Hold duration information for current on-going bucket.
+struct DurationInfo {
+    DurationState state;
+    // most recent start time.
+    int64_t lastStartTime;
+    // existing duration in current bucket. Eventually, the duration will be aggregated in
+    // the way specified in AggregateType (Sum, Max, or Min).
+    int64_t lastDuration;
+    // cache the HashableDimensionKeys we need to query the condition for this duration event.
+    std::map<string, HashableDimensionKey> conditionKeys;
+
+    DurationInfo() : state(kStopped), lastStartTime(0), lastDuration(0){};
+};
+
+class DurationMetricProducer : public MetricProducer {
+public:
+    DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
+                           const size_t startIndex, const size_t stopIndex,
+                           const size_t stopAllIndex, const sp<ConditionWizard>& wizard);
+
+    virtual ~DurationMetricProducer();
+
+    void onConditionChanged(const bool conditionMet) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    void onSlicedConditionMayChange() override;
+
+    size_t byteSize() override;
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+protected:
+    void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+                                   const std::map<std::string, HashableDimensionKey>& conditionKeys,
+                                   bool condition, const LogEvent& event) override;
+
+private:
+    const DurationMetric mMetric;
+
+    // Index of the SimpleLogEntryMatcher which defines the start.
+    const size_t mStartIndex;
+
+    // Index of the SimpleLogEntryMatcher which defines the stop.
+    const size_t mStopIndex;
+
+    // Index of the SimpleLogEntryMatcher which defines the stop all for all dimensions.
+    const size_t mStopAllIndex;
+
+    // Save the past buckets and we can clear when the StatsLogReport is dumped.
+    std::unordered_map<HashableDimensionKey, std::vector<DurationBucketInfo>> mPastBuckets;
+
+    // The current bucket.
+    std::unordered_map<HashableDimensionKey, DurationInfo> mCurrentSlicedDuration;
+
+    void flushDurationIfNeeded(const uint64_t newEventTime);
+
+    void noteStart(const HashableDimensionKey& key, const bool conditionMet,
+                   const uint64_t eventTime);
+
+    void noteStop(const HashableDimensionKey& key, const uint64_t eventTime);
+
+    void noteStopAll(const uint64_t eventTime);
+
+    static int64_t updateDuration(const int64_t lastDuration, const int64_t durationTime,
+                                  const DurationMetric_AggregationType type);
+
+    void noteConditionChanged(const HashableDimensionKey& key, const bool conditionMet,
+                              const uint64_t eventTime);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // DURATION_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
new file mode 100644
index 0000000..7e06105
--- /dev/null
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "EventMetricProducer.h"
+#include "stats_util.h"
+
+#include <cutils/log.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using android::util::ProtoOutputStream;
+using std::map;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// for StatsLogReport
+const int FIELD_ID_METRIC_ID = 1;
+const int FIELD_ID_START_REPORT_NANOS = 2;
+const int FIELD_ID_END_REPORT_NANOS = 2;
+const int FIELD_ID_EVENT_METRICS = 4;
+// for EventMetricData
+const int FIELD_ID_TIMESTAMP_NANOS = 1;
+const int FIELD_ID_STATS_EVENTS = 2;
+// for CountMetricDataWrapper
+const int FIELD_ID_DATA = 1;
+
+EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex,
+                                         const sp<ConditionWizard>& wizard)
+    // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
+    : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+      mMetric(metric) {
+    if (metric.links().size() > 0) {
+        mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+                               metric.links().end());
+        mConditionSliced = true;
+    }
+
+    startNewProtoOutputStream(mStartTimeNs);
+
+    VLOG("metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+         (long long)mBucketSizeNs, (long long)mStartTimeNs);
+}
+
+EventMetricProducer::~EventMetricProducer() {
+    VLOG("~EventMetricProducer() called");
+}
+
+void EventMetricProducer::startNewProtoOutputStream(long long startTime) {
+    mProto = std::make_unique<ProtoOutputStream>();
+    // TODO: We need to auto-generate the field IDs for StatsLogReport, EventMetricData,
+    // and StatsEvent.
+    mProto->write(TYPE_INT32 + FIELD_ID_METRIC_ID, mMetric.metric_id());
+    mProto->write(TYPE_INT64 + FIELD_ID_START_REPORT_NANOS, startTime);
+    mProtoToken = mProto->start(TYPE_MESSAGE + FIELD_ID_EVENT_METRICS);
+}
+
+void EventMetricProducer::finish() {
+}
+
+void EventMetricProducer::onSlicedConditionMayChange() {
+}
+
+StatsLogReport EventMetricProducer::onDumpReport() {
+    long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+    mProto->end(mProtoToken);
+    mProto->write(TYPE_INT64 + FIELD_ID_END_REPORT_NANOS, endTime);
+
+    size_t bufferSize = mProto->size();
+    VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize);
+    std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+    size_t pos = 0;
+    auto it = mProto->data();
+    while (it.readBuffer() != NULL) {
+        size_t toRead = it.currentToRead();
+        std::memcpy(&buffer[pos], it.readBuffer(), toRead);
+        pos += toRead;
+        it.rp()->move(toRead);
+    }
+
+    startNewProtoOutputStream(endTime);
+
+    // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
+    // return std::move(buffer);
+    return StatsLogReport();
+}
+
+void EventMetricProducer::onConditionChanged(const bool conditionMet) {
+    VLOG("Metric %lld onConditionChanged", mMetric.metric_id());
+    mCondition = conditionMet;
+}
+
+void EventMetricProducer::onMatchedLogEventInternal(
+        const size_t matcherIndex, const HashableDimensionKey& eventKey,
+        const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+        const LogEvent& event) {
+
+    if (!condition) {
+        return;
+    }
+
+    long long wrapperToken = mProto->start(TYPE_MESSAGE + FIELD_ID_DATA);
+    mProto->write(TYPE_INT64 + FIELD_ID_TIMESTAMP_NANOS, (long long)event.GetTimestampNs());
+    long long eventToken = mProto->start(TYPE_MESSAGE + FIELD_ID_STATS_EVENTS);
+    event.ToProto(*mProto);
+    mProto->end(eventToken);
+    mProto->end(wrapperToken);
+}
+
+size_t EventMetricProducer::byteSize() {
+  return mProto->size();
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
new file mode 100644
index 0000000..14fa31c
--- /dev/null
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -0,0 +1,72 @@
+/*
+ * 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 EVENT_METRIC_PRODUCER_H
+#define EVENT_METRIC_PRODUCER_H
+
+#include <unordered_map>
+
+#include <android/util/ProtoOutputStream.h>
+#include "../condition/ConditionTracker.h"
+#include "../matchers/matcher_util.h"
+#include "MetricProducer.h"
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "stats_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class EventMetricProducer : public MetricProducer {
+public:
+    // TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
+    EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex,
+                        const sp<ConditionWizard>& wizard);
+
+    virtual ~EventMetricProducer();
+
+    void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+                                   const std::map<std::string, HashableDimensionKey>& conditionKey,
+                                   bool condition, const LogEvent& event) override;
+
+    void onConditionChanged(const bool conditionMet) override;
+
+    void finish() override;
+
+    StatsLogReport onDumpReport() override;
+
+    void onSlicedConditionMayChange() override;
+
+    size_t byteSize() override;
+
+    // TODO: Implement this later.
+    virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+
+private:
+    const EventMetric mMetric;
+
+    std::unique_ptr<android::util::ProtoOutputStream> mProto;
+
+    long long mProtoToken;
+
+    void startNewProtoOutputStream(long long timestamp);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // EVENT_METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
new file mode 100644
index 0000000..3c8ce6e
--- /dev/null
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 "MetricProducer.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+
+void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+    uint64_t eventTimeNs = event.GetTimestampNs();
+    // this is old event, maybe statsd restarted?
+    if (eventTimeNs < mStartTimeNs) {
+        return;
+    }
+
+    HashableDimensionKey eventKey;
+
+    if (mDimension.size() > 0) {
+        vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+        eventKey = getHashableKey(key);
+        // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
+        // expects vector<KeyValuePair>.
+        if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) {
+            mDimensionKeyMap[eventKey] = key;
+        }
+    } else {
+        eventKey = DEFAULT_DIMENSION_KEY;
+    }
+
+    bool condition;
+
+    map<string, HashableDimensionKey> conditionKeys;
+    if (mConditionSliced) {
+        for (const auto& link : mConditionLinks) {
+            HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+            conditionKeys[link.condition()] = conditionKey;
+        }
+        if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+            condition = false;
+        } else {
+            condition = true;
+        }
+    } else {
+        condition = mCondition;
+    }
+
+    onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event);
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
new file mode 100644
index 0000000..80eb527
--- /dev/null
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -0,0 +1,116 @@
+/*
+ * 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 METRIC_PRODUCER_H
+#define METRIC_PRODUCER_H
+
+#include "condition/ConditionWizard.h"
+#include "matchers/matcher_util.h"
+#include "packages/PackageInfoListener.h"
+
+#include <log/logprint.h>
+#include <utils/RefBase.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// A MetricProducer is responsible for compute one single metrics, creating stats log report, and
+// writing the report to dropbox. MetricProducers should respond to package changes as required in
+// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
+// be a no-op.
+class MetricProducer : public virtual PackageInfoListener {
+public:
+    MetricProducer(const int64_t startTimeNs, const int conditionIndex,
+                   const sp<ConditionWizard>& wizard)
+        : mStartTimeNs(startTimeNs),
+          mCurrentBucketStartTimeNs(startTimeNs),
+          mCondition(conditionIndex >= 0 ? false : true),
+          mWizard(wizard),
+          mConditionTrackerIndex(conditionIndex) {
+        // reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
+        mDimensionKeyMap[DEFAULT_DIMENSION_KEY] = std::vector<KeyValuePair>();
+    };
+    virtual ~MetricProducer(){};
+
+    // Consume the parsed stats log entry that already matched the "what" of the metric.
+    void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event);
+
+    virtual void onConditionChanged(const bool condition) = 0;
+
+    virtual void onSlicedConditionMayChange() = 0;
+
+    // This is called when the metric collecting is done, e.g., when there is a new configuration
+    // coming. MetricProducer should do the clean up, and dump existing data to dropbox.
+    virtual void finish() = 0;
+
+    virtual StatsLogReport onDumpReport() = 0;
+
+    virtual bool isConditionSliced() const {
+        return mConditionSliced;
+    };
+
+    virtual size_t byteSize() = 0;
+
+protected:
+    const uint64_t mStartTimeNs;
+
+    uint64_t mCurrentBucketStartTimeNs;
+
+    int64_t mBucketSizeNs;
+
+    bool mCondition;
+
+    bool mConditionSliced;
+
+    sp<ConditionWizard> mWizard;
+
+    int mConditionTrackerIndex;
+
+    std::vector<KeyMatcher> mDimension;  // The dimension defined in statsd_config
+
+    // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
+    // that StatsLogReport wants.
+    std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
+
+    std::vector<EventConditionLink> mConditionLinks;
+
+    /*
+     * Individual metrics can implement their own business logic here. All pre-processing is done.
+     *
+     * [matcherIndex]: the index of the matcher which matched this event. This is interesting to
+     *                 DurationMetric, because it has start/stop/stop_all 3 matchers.
+     * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have
+     *             dimensions, it will be DEFAULT_DIMENSION_KEY
+     * [conditionKey]: the keys of conditions which should be used to query the condition for this
+     *                 target event (from EventConditionLink). This is passed to individual metrics
+     *                 because DurationMetric needs it to be cached.
+     * [condition]: whether condition is met. If condition is sliced, this is the result coming from
+     *              query with ConditionWizard; If condition is not sliced, this is the
+     *              nonSlicedCondition.
+     * [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
+     */
+    virtual void onMatchedLogEventInternal(
+            const size_t matcherIndex, const HashableDimensionKey& eventKey,
+            const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+            const LogEvent& event) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // METRIC_PRODUCER_H
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
new file mode 100644
index 0000000..4fa3965
--- /dev/null
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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 DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include "MetricsManager.h"
+#include <log/logprint.h>
+#include "../condition/CombinationConditionTracker.h"
+#include "../condition/SimpleConditionTracker.h"
+#include "../matchers/CombinationLogMatchingTracker.h"
+#include "../matchers/SimpleLogMatchingTracker.h"
+#include "CountMetricProducer.h"
+#include "metrics_manager_util.h"
+#include "stats_util.h"
+
+using std::make_unique;
+using std::set;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+MetricsManager::MetricsManager(const StatsdConfig& config) {
+    mConfigValid = initStatsdConfig(config, mTagIds, mAllLogEntryMatchers, mAllConditionTrackers,
+                                    mAllMetricProducers, mConditionToMetricMap, mTrackerToMetricMap,
+                                    mTrackerToConditionMap);
+}
+
+MetricsManager::~MetricsManager() {
+    VLOG("~MetricsManager()");
+}
+
+bool MetricsManager::isConfigValid() const {
+    return mConfigValid;
+}
+
+void MetricsManager::finish() {
+    for (auto& metricProducer : mAllMetricProducers) {
+        metricProducer->finish();
+    }
+}
+
+vector<StatsLogReport> MetricsManager::onDumpReport() {
+    VLOG("=========================Metric Reports Start==========================");
+    // one StatsLogReport per MetricProduer
+    vector<StatsLogReport> reportList;
+    for (auto& metric : mAllMetricProducers) {
+        reportList.push_back(metric->onDumpReport());
+    }
+    VLOG("=========================Metric Reports End==========================");
+    return reportList;
+}
+
+// Consume the stats log if it's interesting to this metric.
+void MetricsManager::onLogEvent(const LogEvent& event) {
+    if (!mConfigValid) {
+        return;
+    }
+
+    int tagId = event.GetTagId();
+    if (mTagIds.find(tagId) == mTagIds.end()) {
+        // not interesting...
+        return;
+    }
+
+    // Since at least one of the metrics is interested in this event, we parse it now.
+    ALOGD("%s", event.ToString().c_str());
+    vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
+
+    for (auto& matcher : mAllLogEntryMatchers) {
+        matcher->onLogEvent(event, mAllLogEntryMatchers, matcherCache);
+    }
+
+    // A bitmap to see which ConditionTracker needs to be re-evaluated.
+    vector<bool> conditionToBeEvaluated(mAllConditionTrackers.size(), false);
+
+    for (const auto& pair : mTrackerToConditionMap) {
+        if (matcherCache[pair.first] == MatchingState::kMatched) {
+            const auto& conditionList = pair.second;
+            for (const int conditionIndex : conditionList) {
+                conditionToBeEvaluated[conditionIndex] = true;
+            }
+        }
+    }
+
+    vector<ConditionState> conditionCache(mAllConditionTrackers.size(),
+                                          ConditionState::kNotEvaluated);
+    // A bitmap to track if a condition has changed value.
+    vector<bool> changedCache(mAllConditionTrackers.size(), false);
+    vector<bool> slicedChangedCache(mAllConditionTrackers.size(), false);
+    for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
+        if (conditionToBeEvaluated[i] == false) {
+            continue;
+        }
+        sp<ConditionTracker>& condition = mAllConditionTrackers[i];
+        condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
+                                     changedCache, slicedChangedCache);
+    }
+
+    for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
+        if (changedCache[i] == false && slicedChangedCache[i] == false) {
+            continue;
+        }
+        auto pair = mConditionToMetricMap.find(i);
+        if (pair != mConditionToMetricMap.end()) {
+            auto& metricList = pair->second;
+            for (auto metricIndex : metricList) {
+                // metric cares about non sliced condition, and it's changed.
+                // Push the new condition to it directly.
+                if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
+                    mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i]);
+                    // metric cares about sliced conditions, and it may have changed. Send
+                    // notification, and the metric can query the sliced conditions that are
+                    // interesting to it.
+                } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
+                           slicedChangedCache[i]) {
+                    mAllMetricProducers[metricIndex]->onSlicedConditionMayChange();
+                }
+            }
+        }
+    }
+
+    // For matched LogEntryMatchers, tell relevant metrics that a matched event has come.
+    for (size_t i = 0; i < mAllLogEntryMatchers.size(); i++) {
+        if (matcherCache[i] == MatchingState::kMatched) {
+            auto pair = mTrackerToMetricMap.find(i);
+            if (pair != mTrackerToMetricMap.end()) {
+                auto& metricList = pair->second;
+                for (const int metricIndex : metricList) {
+                    mAllMetricProducers[metricIndex]->onMatchedLogEvent(i, event);
+                }
+            }
+        }
+    }
+}
+
+// Returns the total byte size of all metrics managed by a single config source.
+size_t MetricsManager::byteSize() {
+    size_t totalSize = 0;
+    for (auto metricProducer : mAllMetricProducers) {
+        totalSize += metricProducer->byteSize();
+    }
+    return totalSize;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
new file mode 100644
index 0000000..44cd637
--- /dev/null
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "condition/ConditionTracker.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "logd/LogEvent.h"
+#include "matchers/LogMatchingTracker.h"
+#include "metrics/MetricProducer.h"
+
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// A MetricsManager is responsible for managing metrics from one single config source.
+class MetricsManager {
+public:
+    MetricsManager(const StatsdConfig& config);
+
+    ~MetricsManager();
+
+    // Return whether the configuration is valid.
+    bool isConfigValid() const;
+
+    void onLogEvent(const LogEvent& event);
+
+    // Called when everything should wrap up. We are about to finish (e.g., new config comes).
+    void finish();
+
+    // Config source owner can call onDumpReport() to get all the metrics collected.
+    std::vector<StatsLogReport> onDumpReport();
+
+    size_t byteSize();
+
+private:
+    // All event tags that are interesting to my metrics.
+    std::set<int> mTagIds;
+
+    // We only store the sp of LogMatchingTracker, MetricProducer, and ConditionTracker in
+    // MetricsManager. There are relationships between them, and the relationships are denoted by
+    // index instead of pointers. The reasons for this are: (1) the relationship between them are
+    // complicated, so storing index instead of pointers reduces the risk that A holds B's sp, and B
+    // holds A's sp. (2) When we evaluate matcher results, or condition results, we can quickly get
+    // the related results from a cache using the index.
+
+    // Hold all the log entry matchers from the config.
+    std::vector<sp<LogMatchingTracker>> mAllLogEntryMatchers;
+
+    // Hold all the conditions from the config.
+    std::vector<sp<ConditionTracker>> mAllConditionTrackers;
+
+    // Hold all metrics from the config.
+    std::vector<sp<MetricProducer>> mAllMetricProducers;
+
+    // To make the log processing more efficient, we want to do as much filtering as possible
+    // before we go into individual trackers and conditions to match.
+
+    // 1st filter: check if the event tag id is in mTagIds.
+    // 2nd filter: if it is, we parse the event because there is at least one member is interested.
+    //             then pass to all LogMatchingTrackers (itself also filter events by ids).
+    // 3nd filter: for LogMatchingTrackers that matched this event, we pass this event to the
+    //             ConditionTrackers and MetricProducers that use this matcher.
+    // 4th filter: for ConditionTrackers that changed value due to this event, we pass
+    //             new conditions to  metrics that use this condition.
+
+    // The following map is initialized from the statsd_config.
+
+    // maps from the index of the LogMatchingTracker to index of MetricProducer.
+    std::unordered_map<int, std::vector<int>> mTrackerToMetricMap;
+
+    // maps from LogMatchingTracker to ConditionTracker
+    std::unordered_map<int, std::vector<int>> mTrackerToConditionMap;
+
+    // maps from ConditionTracker to MetricProducer
+    std::unordered_map<int, std::vector<int>> mConditionToMetricMap;
+
+    bool mConfigValid;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
new file mode 100644
index 0000000..e90f998
--- /dev/null
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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 "../condition/CombinationConditionTracker.h"
+#include "../condition/SimpleConditionTracker.h"
+#include "../matchers/CombinationLogMatchingTracker.h"
+#include "../matchers/SimpleLogMatchingTracker.h"
+#include "CountMetricProducer.h"
+#include "DurationMetricProducer.h"
+#include "EventMetricProducer.h"
+#include "stats_util.h"
+
+using std::set;
+using std::string;
+using std::unordered_map;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool handleMetricWithLogTrackers(const string what, const int metricIndex,
+                                 const unordered_map<string, int>& logTrackerMap,
+                                 unordered_map<int, std::vector<int>>& trackerToMetricMap,
+                                 int& logTrackerIndex) {
+    auto logTrackerIt = logTrackerMap.find(what);
+    if (logTrackerIt == logTrackerMap.end()) {
+        ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
+        return false;
+    }
+    logTrackerIndex = logTrackerIt->second;
+    auto& metric_list = trackerToMetricMap[logTrackerIndex];
+    metric_list.push_back(metricIndex);
+    return true;
+}
+
+bool handleMetricWithConditions(
+        const string condition, const int metricIndex,
+        const unordered_map<string, int>& conditionTrackerMap,
+        const ::google::protobuf::RepeatedPtrField<::android::os::statsd::EventConditionLink>&
+                links,
+        vector<sp<ConditionTracker>>& allConditionTrackers, int& conditionIndex,
+        unordered_map<int, std::vector<int>>& conditionToMetricMap) {
+    auto condition_it = conditionTrackerMap.find(condition);
+    if (condition_it == conditionTrackerMap.end()) {
+        ALOGW("cannot find the Condition %s in the config", condition.c_str());
+        return false;
+    }
+
+    for (const auto& link : links) {
+        auto it = conditionTrackerMap.find(link.condition());
+        if (it == conditionTrackerMap.end()) {
+            ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+            return false;
+        }
+        allConditionTrackers[condition_it->second]->setSliced(true);
+        allConditionTrackers[it->second]->setSliced(true);
+        allConditionTrackers[it->second]->addDimensions(
+                vector<KeyMatcher>(link.key_in_condition().begin(), link.key_in_condition().end()));
+    }
+    conditionIndex = condition_it->second;
+
+    // will create new vector if not exist before.
+    auto& metricList = conditionToMetricMap[condition_it->second];
+    metricList.push_back(metricIndex);
+    return true;
+}
+
+bool initLogTrackers(const StatsdConfig& config, unordered_map<string, int>& logTrackerMap,
+                     vector<sp<LogMatchingTracker>>& allLogEntryMatchers, set<int>& allTagIds) {
+    vector<LogEntryMatcher> matcherConfigs;
+    const int logEntryMatcherCount = config.log_entry_matcher_size();
+    matcherConfigs.reserve(logEntryMatcherCount);
+    allLogEntryMatchers.reserve(logEntryMatcherCount);
+
+    for (int i = 0; i < logEntryMatcherCount; i++) {
+        const LogEntryMatcher& logMatcher = config.log_entry_matcher(i);
+
+        int index = allLogEntryMatchers.size();
+        switch (logMatcher.contents_case()) {
+            case LogEntryMatcher::ContentsCase::kSimpleLogEntryMatcher:
+                allLogEntryMatchers.push_back(new SimpleLogMatchingTracker(
+                        logMatcher.name(), index, logMatcher.simple_log_entry_matcher()));
+                break;
+            case LogEntryMatcher::ContentsCase::kCombination:
+                allLogEntryMatchers.push_back(
+                        new CombinationLogMatchingTracker(logMatcher.name(), index));
+                break;
+            default:
+                ALOGE("Matcher %s malformed", logMatcher.name().c_str());
+                return false;
+                // continue;
+        }
+        if (logTrackerMap.find(logMatcher.name()) != logTrackerMap.end()) {
+            ALOGE("Duplicate LogEntryMatcher found!");
+            return false;
+        }
+        logTrackerMap[logMatcher.name()] = index;
+        matcherConfigs.push_back(logMatcher);
+    }
+
+    vector<bool> stackTracker2(allLogEntryMatchers.size(), false);
+    for (auto& matcher : allLogEntryMatchers) {
+        if (!matcher->init(matcherConfigs, allLogEntryMatchers, logTrackerMap, stackTracker2)) {
+            return false;
+        }
+        // Collect all the tag ids that are interesting. TagIds exist in leaf nodes only.
+        const set<int>& tagIds = matcher->getTagIds();
+        allTagIds.insert(tagIds.begin(), tagIds.end());
+    }
+    return true;
+}
+
+bool initConditions(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
+                    unordered_map<string, int>& conditionTrackerMap,
+                    vector<sp<ConditionTracker>>& allConditionTrackers,
+                    unordered_map<int, std::vector<int>>& trackerToConditionMap) {
+    vector<Condition> conditionConfigs;
+    const int conditionTrackerCount = config.condition_size();
+    conditionConfigs.reserve(conditionTrackerCount);
+    allConditionTrackers.reserve(conditionTrackerCount);
+
+    for (int i = 0; i < conditionTrackerCount; i++) {
+        const Condition& condition = config.condition(i);
+        int index = allConditionTrackers.size();
+        switch (condition.contents_case()) {
+            case Condition::ContentsCase::kSimpleCondition: {
+                allConditionTrackers.push_back(new SimpleConditionTracker(
+                        condition.name(), index, condition.simple_condition(), logTrackerMap));
+                break;
+            }
+            case Condition::ContentsCase::kCombination: {
+                allConditionTrackers.push_back(
+                        new CombinationConditionTracker(condition.name(), index));
+                break;
+            }
+            default:
+                ALOGE("Condition %s malformed", condition.name().c_str());
+                return false;
+        }
+        if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) {
+            ALOGE("Duplicate Condition found!");
+            return false;
+        }
+        conditionTrackerMap[condition.name()] = index;
+        conditionConfigs.push_back(condition);
+    }
+
+    vector<bool> stackTracker(allConditionTrackers.size(), false);
+    for (size_t i = 0; i < allConditionTrackers.size(); i++) {
+        auto& conditionTracker = allConditionTrackers[i];
+        if (!conditionTracker->init(conditionConfigs, allConditionTrackers, conditionTrackerMap,
+                                    stackTracker)) {
+            return false;
+        }
+        for (const int trackerIndex : conditionTracker->getLogTrackerIndex()) {
+            auto& conditionList = trackerToConditionMap[trackerIndex];
+            conditionList.push_back(i);
+        }
+    }
+    return true;
+}
+
+bool initMetrics(const StatsdConfig& config, const unordered_map<string, int>& logTrackerMap,
+                 const unordered_map<string, int>& conditionTrackerMap,
+                 vector<sp<ConditionTracker>>& allConditionTrackers,
+                 vector<sp<MetricProducer>>& allMetricProducers,
+                 unordered_map<int, std::vector<int>>& conditionToMetricMap,
+                 unordered_map<int, std::vector<int>>& trackerToMetricMap) {
+    sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
+    const int allMetricsCount =
+            config.count_metric_size() + config.duration_metric_size() + config.event_metric_size();
+    allMetricProducers.reserve(allMetricsCount);
+
+    // Build MetricProducers for each metric defined in config.
+    // (1) build CountMetricProducer
+    for (int i = 0; i < config.count_metric_size(); i++) {
+        const CountMetric& metric = config.count_metric(i);
+        if (!metric.has_what()) {
+            ALOGW("cannot find what in CountMetric %lld", metric.metric_id());
+            return false;
+        }
+
+        int metricIndex = allMetricProducers.size();
+        int trackerIndex;
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
+                                         trackerToMetricMap, trackerIndex)) {
+            return false;
+        }
+
+        int conditionIndex = -1;
+        if (metric.has_condition()) {
+            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                       metric.links(), allConditionTrackers, conditionIndex,
+                                       conditionToMetricMap);
+        }
+
+        sp<MetricProducer> countProducer = new CountMetricProducer(metric, conditionIndex, wizard);
+        allMetricProducers.push_back(countProducer);
+    }
+
+    for (int i = 0; i < config.duration_metric_size(); i++) {
+        int metricIndex = allMetricProducers.size();
+        const DurationMetric& metric = config.duration_metric(i);
+        int trackerIndices[3] = {-1, -1, -1};
+        if (!metric.has_start() ||
+            !handleMetricWithLogTrackers(metric.start(), metricIndex, logTrackerMap,
+                                         trackerToMetricMap, trackerIndices[0])) {
+            ALOGE("Duration metrics must specify a valid the start event matcher");
+            return false;
+        }
+
+        if (metric.has_stop() &&
+            !handleMetricWithLogTrackers(metric.stop(), metricIndex, logTrackerMap,
+                                         trackerToMetricMap, trackerIndices[1])) {
+            return false;
+        }
+
+        if (metric.has_stop_all() &&
+            !handleMetricWithLogTrackers(metric.stop_all(), metricIndex, logTrackerMap,
+                                         trackerToMetricMap, trackerIndices[2])) {
+            return false;
+        }
+
+        int conditionIndex = -1;
+
+        if (metric.has_predicate()) {
+            handleMetricWithConditions(metric.predicate(), metricIndex, conditionTrackerMap,
+                                       metric.links(), allConditionTrackers, conditionIndex,
+                                       conditionToMetricMap);
+        }
+
+        sp<MetricProducer> durationMetric =
+                new DurationMetricProducer(metric, conditionIndex, trackerIndices[0],
+                                           trackerIndices[1], trackerIndices[2], wizard);
+
+        allMetricProducers.push_back(durationMetric);
+    }
+
+    for (int i = 0; i < config.event_metric_size(); i++) {
+        int metricIndex = allMetricProducers.size();
+        const EventMetric& metric = config.event_metric(i);
+        if (!metric.has_metric_id() || !metric.has_what()) {
+            ALOGW("cannot find the metric id or what in config");
+            return false;
+        }
+        int trackerIndex;
+        if (!handleMetricWithLogTrackers(metric.what(), metricIndex, logTrackerMap,
+                                         trackerToMetricMap, trackerIndex)) {
+            return false;
+        }
+
+        int conditionIndex = -1;
+        if (metric.has_condition()) {
+            handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+                                       metric.links(), allConditionTrackers, conditionIndex,
+                                       conditionToMetricMap);
+        }
+
+        sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard);
+
+        allMetricProducers.push_back(eventMetric);
+    }
+
+    return true;
+}
+
+bool initStatsdConfig(const StatsdConfig& config, set<int>& allTagIds,
+                      vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+                      vector<sp<ConditionTracker>>& allConditionTrackers,
+                      vector<sp<MetricProducer>>& allMetricProducers,
+                      unordered_map<int, std::vector<int>>& conditionToMetricMap,
+                      unordered_map<int, std::vector<int>>& trackerToMetricMap,
+                      unordered_map<int, std::vector<int>>& trackerToConditionMap) {
+    unordered_map<string, int> logTrackerMap;
+    unordered_map<string, int> conditionTrackerMap;
+
+    if (!initLogTrackers(config, logTrackerMap, allLogEntryMatchers, allTagIds)) {
+        ALOGE("initLogMatchingTrackers failed");
+        return false;
+    }
+
+    if (!initConditions(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
+                        trackerToConditionMap)) {
+        ALOGE("initConditionTrackers failed");
+        return false;
+    }
+
+    if (!initMetrics(config, logTrackerMap, conditionTrackerMap, allConditionTrackers,
+                     allMetricProducers, conditionToMetricMap, trackerToMetricMap)) {
+        ALOGE("initMetricProducers failed");
+        return false;
+    }
+    return true;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
new file mode 100644
index 0000000..6722eb3
--- /dev/null
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -0,0 +1,96 @@
+/*
+ * 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 METRIC_UTIL_H
+#define METRIC_UTIL_H
+#include <memory>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+#include "../condition/ConditionTracker.h"
+#include "../matchers/LogMatchingTracker.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Helper functions for MetricsManager to initialize from StatsdConfig.
+// *Note*: only initStatsdConfig() should be called from outside.
+// All other functions are intermediate
+// steps, created to make unit tests easier. And most of the parameters in these
+// functions are temporary objects in the initialization phase.
+
+// Initialize the LogMatchingTrackers.
+// input:
+// [config]: the input StatsdConfig
+// output:
+// [logTrackerMap]: this map should contain matcher name to index mapping
+// [allLogEntryMatchers]: should store the sp to all the LogMatchingTracker
+// [allTagIds]: contains the set of all interesting tag ids to this config.
+bool initLogTrackers(const StatsdConfig& config,
+                     std::unordered_map<std::string, int>& logTrackerMap,
+                     std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+                     std::set<int>& allTagIds);
+
+// Initialize ConditionTrackers
+// input:
+// [config]: the input config
+// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
+// output:
+// [conditionTrackerMap]: this map should contain condition name to index mapping
+// [allConditionTrackers]: stores the sp to all the ConditionTrackers
+// [trackerToConditionMap]: contain the mapping from index of
+//                        log tracker to condition trackers that use the log tracker
+bool initConditions(const StatsdConfig& config,
+                    const std::unordered_map<std::string, int>& logTrackerMap,
+                    std::unordered_map<std::string, int>& conditionTrackerMap,
+                    std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                    std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
+                    std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks);
+
+// Initialize MetricProducers.
+// input:
+// [config]: the input config
+// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
+// [conditionTrackerMap]: condition name to index mapping
+// output:
+// [allMetricProducers]: contains the list of sp to the MetricProducers created.
+// [conditionToMetricMap]: contains the mapping from condition tracker index to
+//                          the list of MetricProducer index
+// [trackerToMetricMap]: contains the mapping from log tracker to MetricProducer index.
+bool initMetrics(
+        const StatsdConfig& config, const std::unordered_map<std::string, int>& logTrackerMap,
+        const std::unordered_map<std::string, int>& conditionTrackerMap,
+        const std::unordered_map<int, std::vector<EventConditionLink>>& eventConditionLinks,
+        vector<sp<ConditionTracker>>& allConditionTrackers,
+        std::vector<sp<MetricProducer>>& allMetricProducers,
+        std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+        std::unordered_map<int, std::vector<int>>& trackerToMetricMap);
+
+// Initialize MetricsManager from StatsdConfig.
+// Parameters are the members of MetricsManager. See MetricsManager for declaration.
+bool initStatsdConfig(const StatsdConfig& config, std::set<int>& allTagIds,
+                      std::vector<sp<LogMatchingTracker>>& allLogEntryMatchers,
+                      std::vector<sp<ConditionTracker>>& allConditionTrackers,
+                      std::vector<sp<MetricProducer>>& allMetricProducers,
+                      std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
+                      std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
+                      std::unordered_map<int, std::vector<int>>& trackerToConditionMap);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#endif  // METRIC_UTIL_H
diff --git a/cmds/statsd/src/packages/PackageInfoListener.h b/cmds/statsd/src/packages/PackageInfoListener.h
new file mode 100644
index 0000000..8b948de
--- /dev/null
+++ b/cmds/statsd/src/packages/PackageInfoListener.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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 STATSD_PACKAGE_INFO_LISTENER_H
+#define STATSD_PACKAGE_INFO_LISTENER_H
+
+#include <utils/RefBase.h>
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class PackageInfoListener : public virtual android::RefBase {
+public:
+    // Uid map will notify this listener that the app with apk name and uid has been upgraded to
+    // the specified version.
+    virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int version) = 0;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // STATSD_PACKAGE_INFO_LISTENER_H
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
new file mode 100644
index 0000000..f4621ee
--- /dev/null
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, versionCode 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 "Log.h"
+
+#include "packages/UidMap.h"
+
+#include <utils/Errors.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool UidMap::hasApp(int uid, const string& packageName) const {
+    lock_guard<mutex> lock(mMutex);
+
+    auto range = mMap.equal_range(uid);
+    for (auto it = range.first; it != range.second; ++it) {
+        if (it->second.packageName == packageName) {
+            return true;
+        }
+    }
+    return false;
+}
+
+int UidMap::getAppVersion(int uid, const string& packageName) const {
+    lock_guard<mutex> lock(mMutex);
+
+    auto range = mMap.equal_range(uid);
+    for (auto it = range.first; it != range.second; ++it) {
+        if (it->second.packageName == packageName) {
+            return it->second.versionCode;
+        }
+    }
+    return 0;
+}
+
+void UidMap::updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
+                       const vector<String16>& packageName) {
+    lock_guard<mutex> lock(mMutex);  // Exclusively lock for updates.
+
+    mMap.clear();
+    for (unsigned long j = 0; j < uid.size(); j++) {
+        mMap.insert(make_pair(uid[j],
+                              AppData(string(String8(packageName[j]).string()), versionCode[j])));
+    }
+
+    if (mOutput.initial_size() == 0) {  // Provide the initial states in the mOutput proto
+        for (unsigned long j = 0; j < uid.size(); j++) {
+            auto t = mOutput.add_initial();
+            t->set_app(string(String8(packageName[j]).string()));
+            t->set_version(int(versionCode[j]));
+            t->set_uid(uid[j]);
+        }
+    }
+}
+
+void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
+    lock_guard<mutex> lock(mMutex);
+
+    string app = string(String8(app_16).string());
+
+    // Notify any interested producers that this app has updated
+    for (auto it : mSubscribers) {
+        it->notifyAppUpgrade(app, uid, versionCode);
+    }
+
+    auto log = mOutput.add_changes();
+    log->set_deletion(false);
+    // log.timestamp = TODO: choose how timestamps are computed
+    log->set_app(app);
+    log->set_uid(uid);
+    log->set_version(versionCode);
+
+    auto range = mMap.equal_range(int(uid));
+    for (auto it = range.first; it != range.second; ++it) {
+        if (it->second.packageName == app) {
+            it->second.versionCode = int(versionCode);
+            return;
+        }
+        ALOGD("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
+        return;
+    }
+
+    // Otherwise, we need to add an app at this uid.
+    mMap.insert(make_pair(uid, AppData(app, int(versionCode))));
+}
+
+void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
+    lock_guard<mutex> lock(mMutex);
+
+    string app = string(String8(app_16).string());
+
+    auto log = mOutput.add_changes();
+    log->set_deletion(true);
+    // log.timestamp = TODO: choose how timestamps are computed
+    log->set_app(app);
+    log->set_uid(uid);
+
+    auto range = mMap.equal_range(int(uid));
+    for (auto it = range.first; it != range.second; ++it) {
+        if (it->second.packageName == app) {
+            mMap.erase(it);
+            return;
+        }
+    }
+    ALOGD("removeApp failed to find the app %s with uid %i to remove", app.c_str(), uid);
+    return;
+}
+
+void UidMap::addListener(sp<PackageInfoListener> producer) {
+    lock_guard<mutex> lock(mMutex);  // Lock for updates
+    mSubscribers.insert(producer);
+}
+
+void UidMap::removeListener(sp<PackageInfoListener> producer) {
+    lock_guard<mutex> lock(mMutex);  // Lock for updates
+    mSubscribers.erase(producer);
+}
+
+UidMapping UidMap::getAndClearOutput() {
+    lock_guard<mutex> lock(mMutex);  // Lock for updates
+
+    auto ret = UidMapping(mOutput);  // Copy that will be returned.
+    mOutput.Clear();
+
+    // Re-initialize the initial state for the outputs. This results in extra data being uploaded
+    // but helps ensure we can't re-construct the UID->app name, versionCode mapping in server.
+    for (auto it : mMap) {
+        auto t = mOutput.add_initial();
+        t->set_app(it.second.packageName);
+        t->set_version(it.second.versionCode);
+        t->set_uid(it.first);
+    }
+
+    return ret;
+}
+
+void UidMap::printUidMap(FILE* out) {
+    lock_guard<mutex> lock(mMutex);
+
+    for (auto it : mMap) {
+        fprintf(out, "%s, v%d (%i)\n", it.second.packageName.c_str(), it.second.versionCode,
+                it.first);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
new file mode 100644
index 0000000..d550372
--- /dev/null
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 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 STATSD_UIDMAP_H
+#define STATSD_UIDMAP_H
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "packages/PackageInfoListener.h"
+
+#include <binder/IResultReceiver.h>
+#include <binder/IShellCallback.h>
+#include <log/logprint.h>
+#include <stdio.h>
+#include <utils/RefBase.h>
+#include <mutex>
+#include <set>
+#include <string>
+#include <unordered_map>
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+struct AppData {
+    const string packageName;
+    int versionCode;
+
+    AppData(const string& a, const int v) : packageName(a), versionCode(v){};
+};
+
+// UidMap keeps track of what the corresponding app name (APK name) and version code for every uid
+// at any given moment. This map must be updated by StatsCompanionService.
+class UidMap : public virtual android::RefBase {
+public:
+    /*
+     * All three inputs must be the same size, and the jth element in each array refers to the same
+     * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
+     */
+    void updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
+                   const vector<String16>& packageName);
+
+    // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
+    bool hasApp(int uid, const string& packageName) const;
+
+    int getAppVersion(int uid, const string& packageName) const;
+
+    void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
+    void removeApp(const String16& packageName, const int32_t& uid);
+
+    // Helper for debugging contents of this uid map. Can be triggered with:
+    // adb shell cmd stats print-uid-map
+    void printUidMap(FILE* out);
+
+    // Commands for indicating to the map that a producer should be notified if an app is updated.
+    // This allows the metric producer to distinguish when the same uid or app represents a
+    // different version of an app.
+    void addListener(sp<PackageInfoListener> producer);
+    // Remove the listener from the set of metric producers that subscribe to updates.
+    void removeListener(sp<PackageInfoListener> producer);
+
+    // Grabs the current output contents and then clears it.
+    UidMapping getAndClearOutput();
+
+private:
+    // TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
+    mutable mutex mMutex;
+
+    std::unordered_multimap<int, AppData> mMap;
+
+    // We prepare the output proto as apps are updated, so that we can grab the current output.
+    UidMapping mOutput;
+
+    // Metric producers that should be notified if there's an upgrade in any app.
+    set<sp<PackageInfoListener>> mSubscribers;
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // STATSD_UIDMAP_H
diff --git a/cmds/statsd/src/parse_util.cpp b/cmds/statsd/src/parse_util.cpp
deleted file mode 100644
index ffce884..0000000
--- a/cmds/statsd/src/parse_util.cpp
+++ /dev/null
@@ -1,121 +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.
- */
-
-#include <parse_util.h>
-#include <log/log_event_list.h>
-
-using android::os::statsd::EVENT_TIMESTAMP;
-using android::os::statsd::EventMetricData;
-using android::os::statsd::KeyId;
-using android::os::statsd::KeyId_IsValid;
-using android::os::statsd::KeyValuePair;
-using android::os::statsd::TagId;
-using android::os::statsd::TagId_IsValid;
-
-EventMetricData parse(log_msg msg)
-{
-    // dump all statsd logs to dropbox for now.
-    // TODO: Add filtering, aggregation, etc.
-    EventMetricData eventMetricData;
-
-    // set timestamp of the event.
-    KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
-    keyValuePair->set_key(EVENT_TIMESTAMP);
-    keyValuePair->set_value_int(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec);
-
-    // start iterating k,v pairs.
-    android_log_context context = create_android_log_parser(const_cast<log_msg*>(&msg)->msg()
-                                                            + sizeof(uint32_t),
-                                                            const_cast<log_msg*>(&msg)->len()
-                                                            - sizeof(uint32_t));
-    android_log_list_element elem;
-
-    if (context) {
-        memset(&elem, 0, sizeof(elem));
-        size_t index = 0;
-        int32_t key = -1;
-        int32_t tag = -1;
-
-        do {
-            elem = android_log_read_next(context);
-            switch ((int)elem.type) {
-                case EVENT_TYPE_INT:
-                    if (index == 0) {
-                        tag = elem.data.int32;
-                        if (TagId_IsValid(tag)) {
-                            eventMetricData.set_tag(static_cast<TagId>(tag));
-                        } else {
-                            break;
-                        }
-                    } else if (index % 2 == 1) {
-                        key = elem.data.int32;
-                    } else if (KeyId_IsValid(key)) {
-                        int32_t val = elem.data.int32;
-                        KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
-                        keyValuePair->set_key(static_cast<KeyId>(key));
-                        keyValuePair->set_value_int(val);
-                    } else {
-                    }
-                    index++;
-                    break;
-                case EVENT_TYPE_FLOAT:
-                    if (index % 2 == 0 && KeyId_IsValid(key)) {
-                        float val = elem.data.float32;
-                        KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
-                        keyValuePair->set_key(static_cast<KeyId>(key));
-                        keyValuePair->set_value_float(val);
-                    }
-                    index++;
-                    break;
-                case EVENT_TYPE_STRING:
-                    if (index % 2 == 0 && KeyId_IsValid(key)) {
-                        char* val = elem.data.string;
-                        KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
-                        keyValuePair->set_key(static_cast<KeyId>(key));
-                        keyValuePair->set_value_str(val);
-                    }
-                    index++;
-                    break;
-                case EVENT_TYPE_LONG:
-                    if (index % 2 == 0 && KeyId_IsValid(key)) {
-                        int64_t val = elem.data.int64;
-                        KeyValuePair *keyValuePair = eventMetricData.add_key_value_pair();
-                        keyValuePair->set_key(static_cast<KeyId>(key));
-                        keyValuePair->set_value_int(val);
-                    }
-                    index++;
-                    break;
-                case EVENT_TYPE_LIST:
-                    break;
-                case EVENT_TYPE_LIST_STOP:
-                    break;
-                case EVENT_TYPE_UNKNOWN:
-                    break;
-                default:
-                    elem.complete = true;
-                    break;
-            }
-
-            if (elem.complete) {
-                break;
-            }
-        } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
-
-        android_log_destroy(&context);
-    }
-
-    return eventMetricData;
-}
diff --git a/cmds/statsd/src/parse_util.h b/cmds/statsd/src/parse_util.h
deleted file mode 100644
index 8750f82..0000000
--- a/cmds/statsd/src/parse_util.h
+++ /dev/null
@@ -1,28 +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.
- */
-#ifndef PARSE_UTIL_H
-#define PARSE_UTIL_H
-
-#include "LogReader.h"
-#include "DropboxWriter.h"
-
-#include <log/logprint.h>
-
-using android::os::statsd::EventMetricData;
-
-EventMetricData parse(const log_msg msg);
-
-#endif // PARSE_UTIL_H
diff --git a/cmds/statsd/src/stats_constants.proto b/cmds/statsd/src/stats_constants.proto
deleted file mode 100644
index 9758a2e..0000000
--- a/cmds/statsd/src/stats_constants.proto
+++ /dev/null
@@ -1,39 +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.
- */
-syntax = "proto2";
-
-package android.os.statsd;
-
-option optimize_for = LITE_RUNTIME;
-
-option java_package = "com.android.internal.logging";
-option java_outer_classname = "StatsConstantsProto";
-
-enum TagId {
-  WAKELOCK = 1;
-  SCREEN = 2;
-  PROCESS = 1112; // TODO: Temporary usage only for testing.
-}
-
-enum KeyId {
-  STATE = 1;
-  ANOTHER_STATE = 2;
-  EVENT_TIMESTAMP = 1001;
-  PACKAGE_NAME = 1002;
-  PACKAGE_VERSION = 1003;
-  PACKAGE_VERSION_STRING = 1004;
-  ATTRIBUTION_CHAIN = 1005;
-}
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
new file mode 100644
index 0000000..74ee332
--- /dev/null
+++ b/cmds/statsd/src/stats_events.proto
@@ -0,0 +1,648 @@
+/*
+ * 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";
+
+// TODO: Not the right package and class name
+package android.os.statsd;
+option java_package = "com.android.os";
+option java_outer_classname = "StatsEventProto";
+
+/**
+ * The master event class. This message defines all of the available
+ * raw stats log events from the Android system, also known as "atoms."
+ *
+ * This field contains a single oneof with all of the available messages.
+ * The stats-log-api-gen tool runs as part of the Android build and
+ * generates the android.util.StatsLog class, which contains the constants
+ * and methods that Android uses to log.
+ *
+ * This StatsEvent class is not actually built into the Android system.
+ * Instead, statsd on Android constructs these messages synthetically,
+ * in the format defined here and in stats_log.proto.
+ */
+message StatsEvent {
+    oneof event {
+        // For StatsLog reasons, 1 is illegal and will not work. Must start at 2.
+        BleScanStateChanged ble_scan_state_changed = 2;
+        BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3;
+        BleScanResultReceived ble_scan_result_received = 4;
+        SensorStateChanged sensor_state_changed = 5;
+        GpsScanStateChanged gps_scan_state_changed = 6; // TODO: untested
+        SyncStateChanged sync_state_changed = 7;
+        ScheduledJobStateChanged scheduled_job_state_changed = 8;
+        ScreenBrightnessChanged screen_brightness_changed = 9;
+        // 10-20 are temporarily reserved for wakelocks etc.
+        WakelockStateChanged wakelock_state_changed = 10;
+        UidWakelockStateChanged uid_wakelock_state_changed = 11;
+        LongPartialWakelockStateChanged long_partial_wakelock_state_changed = 12;
+        BatterySaverModeStateChanged battery_saver_mode_state_changed = 21;
+        DeviceIdleModeStateChanged device_idle_mode_state_changed = 22;
+        AudioStateChanged audio_state_changed = 23;
+        MediaCodecActivityChanged media_codec_activity_changed = 24;
+        CameraStateChanged camera_state_changed = 25;
+        FlashlightStateChanged flashlight_state_changed = 26;
+        UidProcessStateChanged uid_process_state_changed = 27;
+        ProcessLifeCycleStateChanged process_life_cycle_state_changed = 28;
+        ScreenStateChanged screen_state_changed = 29;
+        BatteryLevelChanged battery_level_changed = 30;
+        ChargingStateChanged charging_state_changed = 31;
+        PluggedStateChanged plugged_state_changed = 32;
+        DeviceTemperatureReported device_temperature_reported = 33;
+        DeviceOnStatusChanged device_on_status_changed = 34;
+        WakeupAlarmOccurred wakeup_alarm_occurred = 35;
+        KernelWakeupReported kernel_wakeup_reported = 36;
+        WifiLockStateChanged wifi_lock_state_changed = 37;
+        WifiSignalStrengthChanged wifi_signal_strength_changed = 38;
+        WifiScanStateChanged wifi_scan_state_changed = 39;
+        PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
+
+        // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
+    }
+}
+
+/**
+ * A WorkSource represents the chained attribution of applications that
+ * resulted in a particular bit of work being done.
+ */
+message WorkSource {
+    // TODO
+}
+
+/*
+ * *****************************************************************************
+ * Below are all of the individual atoms that are logged by Android via statsd
+ * and Westworld.
+ *
+ * RULES:
+ *   - The field ids for each atom must start at 1, and count upwards by 1.
+ *     Skipping field ids is not allowed.
+ *   - These form an API, so renaming, renumbering or removing fields is
+ *     not allowed between android releases.  (This is not currently enforced,
+ *     but there will be a tool to enforce this restriction).
+ *   - The types must be built-in protocol buffer types, namely, no sub-messages
+ *     are allowed (yet).  The bytes type is also not allowed.
+ *   - The CamelCase name of the message type should match the
+ *     underscore_separated name as defined in StatsEvent.
+ *   - If an atom represents work that can be attributed to an app, there can
+ *     be exactly one WorkSource field. It must be field number 1.
+ *   - A field that is a uid should be a string field, tagged with the [xxx]
+ *     annotation. The generated code on android will be represented by UIDs,
+ *     and those UIDs will be translated in xxx to those strings.
+ *
+ * CONVENTIONS:
+ *   - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange.
+ *   - If there is a UID, it goes first. Think in an object-oriented fashion.
+ * *****************************************************************************
+ */
+
+/**
+ * Logs when the screen state changes.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ScreenStateChanged {
+    // TODO: Use the real screen state.
+    enum State {
+        STATE_UNKNOWN = 0;
+        STATE_OFF = 1;
+        STATE_ON = 2;
+        STATE_DOZE = 3;
+        STATE_DOZE_SUSPEND = 4;
+        STATE_VR = 5;
+    }
+    // New screen state.
+    optional State display_state = 1;
+}
+
+/**
+ * Logs that the state of a process state, as per the activity manager, has changed.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message UidProcessStateChanged {
+    optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+
+    // The state.
+    // TODO: Use the real (mapped) process states.
+    optional int32 state = 2;
+}
+
+/**
+ * Logs that a process started, finished, crashed, or ANRed.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ProcessLifeCycleStateChanged {
+    // TODO: Use the real (mapped) process states.
+    optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+
+    // TODO: What is this?
+    optional string name = 2;
+
+    // The state.
+    // TODO: Use an enum.
+    optional int32 event = 3;
+}
+
+
+
+/**
+ * Logs when the ble scan state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BleScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when an unoptimized ble scan state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleUnoptimizedScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs reporting of a ble scan finding results.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
+message BleScanResultReceived {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Number of ble scan results returned.
+    optional int32 num_of_results = 2;
+}
+
+/**
+ * Logs when a sensor state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SensorStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // TODO: Is there a way to get the actual name of the sensor?
+    // The id (int) of the sensor.
+    optional int32 sensor_id = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+}
+
+
+/**
+ * Logs when GPS state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message GpsScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+
+/**
+ * Logs when a sync manager sync state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message SyncStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Name of the sync (as named in the app)
+    optional string name = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+}
+
+/**
+ * Logs when a job scheduler job state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ScheduledJobStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Name of the job (as named in the app)
+    optional string name = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+
+    // TODO: Consider adding the stopReason (int)
+}
+
+/**
+ * Logs when the audio state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message AudioStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when the video codec state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message MediaCodecActivityChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when the flashlight state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message FlashlightStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs when the camera state changes.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message CameraStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs that the state of a wakelock (per app and per wakelock name) has changed.
+ *
+ * Logged from:
+ *   TODO
+ */
+message WakelockStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Type of wakelock.
+    enum Type {
+        PARTIAL = 0;
+        FULL = 1;
+        WINDOW = 2;
+    }
+    optional int32 type = 2;
+
+    // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
+    optional string tag = 3;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 4;
+}
+
+/**
+ * Logs when an app is holding a wakelock, regardless of the wakelock's name.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message UidWakelockStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // Type of wakelock.
+    enum Type {
+        PARTIAL = 0;
+        FULL = 1;
+        WINDOW = 2;
+    }
+    optional int32 type = 2;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 3;
+}
+
+/**
+ * Logs when a partial wakelock is considered 'long' (over 1 min).
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message LongPartialWakelockStateChanged {
+    // TODO: Add attribution instead of uid?
+    optional int32 uid = 1;
+
+    // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
+    optional string tag = 2;
+
+    // TODO: I have no idea what this is.
+    optional string history_tag = 3;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 4;
+}
+
+/**
+ * Logs Battery Saver state change.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatterySaverModeStateChanged {
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 1;
+}
+
+/**
+ * Logs Doze mode state change.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message DeviceIdleModeStateChanged {
+    // TODO: Use the enum matching BatteryStats.DEVICE_IDLE_MODE_.
+    optional int32 state = 1;
+}
+
+/**
+ * Logs screen brightness level.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ScreenBrightnessChanged {
+    // Screen brightness level. Should be in [-1, 255] according to PowerManager.java.
+    optional int32 level = 1;
+}
+
+/**
+ * Logs battery level (percent full, from 0 to 100).
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message BatteryLevelChanged {
+    // Battery level. Should be in [0, 100].
+    optional int32 battery_level = 1;
+}
+
+/**
+ * Logs change in charging status of the device.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message ChargingStateChanged {
+    // TODO: Link directly to BatteryManager.java's constants (via a proto).
+    enum State {
+        BATTERY_STATUS_UNKNOWN = 1;
+        BATTERY_STATUS_CHARGING = 2;
+        BATTERY_STATUS_DISCHARGING = 3;
+        BATTERY_STATUS_NOT_CHARGING = 4;
+        BATTERY_STATUS_FULL = 5;
+    }
+    optional State charging_state = 1;
+}
+
+/**
+ * Logs whether the device is plugged in, and what power source it is using.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message PluggedStateChanged {
+    // TODO: Link directly to BatteryManager.java's constants (via a proto).
+    enum State {
+        // Note that NONE is not in BatteryManager.java's constants.
+        BATTERY_PLUGGED_NONE = 0;
+        // Power source is an AC charger.
+        BATTERY_PLUGGED_AC = 1;
+        // Power source is a USB port.
+        BATTERY_PLUGGED_USB = 2;
+        // Power source is wireless.
+        BATTERY_PLUGGED_WIRELESS = 4;
+    }
+    optional State plugged_state = 1;
+}
+
+/**
+ * Logs the temperature of the device, in tenths of a degree Celsius.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message DeviceTemperatureReported {
+    // Temperature in tenths of a degree C.
+    optional int32 temperature = 1;
+}
+
+// TODO: Define this more precisely.
+// TODO: Log the ON state somewhere. It isn't currently logged anywhere.
+/**
+ * Logs when the device turns off or on.
+ *
+ * Logged from:
+  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message DeviceOnStatusChanged {
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 1;
+}
+
+/**
+ * Logs when an app's wakeup alarm fires.
+ *
+ * Logged from:
+  *   frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message WakeupAlarmOccurred {
+    // TODO: Add attribution instead of uid?
+    optional int32 uid = 1;
+}
+
+/**
+ * Logs kernel wakeup reasons and aborts.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message KernelWakeupReported {
+    // Name of the kernel wakeup reason (or abort).
+    optional string wakeup_reason_name = 1;
+
+    // Duration (in microseconds) for the wake-up interrupt to be serviced.
+    optional int64 duration_usec = 2;
+}
+
+/**
+ * Logs wifi locks held by an app.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiLockStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs wifi signal strength changes.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiSignalStrengthChanged {
+    // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
+    enum SignalStrength {
+        SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+        SIGNAL_STRENGTH_POOR = 1;
+        SIGNAL_STRENGTH_MODERATE = 2;
+        SIGNAL_STRENGTH_GOOD = 3;
+        SIGNAL_STRENGTH_GREAT = 4;
+    }
+    optional SignalStrength signal_strength = 1;
+}
+
+/**
+ * Logs wifi scans performed by an app.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message WifiScanStateChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    enum State {
+        OFF = 0;
+        ON = 1;
+    }
+    optional State state = 2;
+}
+
+/**
+ * Logs phone signal strength changes.
+ *
+ * Logged from:
+  *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message PhoneSignalStrengthChanged {
+    // TODO: Reference the actual telephony/java/android/telephony/SignalStrength.java states.
+    enum SignalStrength {
+        SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+        SIGNAL_STRENGTH_POOR = 1;
+        SIGNAL_STRENGTH_MODERATE = 2;
+        SIGNAL_STRENGTH_GOOD = 3;
+        SIGNAL_STRENGTH_GREAT = 4;
+    }
+    optional SignalStrength signal_strength = 1;
+}
\ No newline at end of file
diff --git a/cmds/statsd/src/stats_events_copy.proto b/cmds/statsd/src/stats_events_copy.proto
new file mode 100644
index 0000000..5e8ef24
--- /dev/null
+++ b/cmds/statsd/src/stats_events_copy.proto
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+// STOPSHIP: this is a duplicate of stats_event.proto with LITE_RUNTIME added
+// to produce device side lite proto. We should move statsd to soong so that
+// we can generate full and lite library from the same proto file.
+syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
+
+// TODO: Not the right package and class name
+package android.os.statsd;
+option java_package = "com.android.os";
+option java_outer_classname = "StatsEventProto";
+
+/**
+ * The master event class. This message defines all of the available
+ * raw stats log events from the Android system, also known as "atoms."
+ *
+ * This field contains a single oneof with all of the available messages.
+ * The stats-log-api-gen tool runs as part of the Android build and
+ * generates the android.util.StatsLog class, which contains the constants
+ * and methods that Android uses to log.
+ *
+ * This StatsEvent class is not actually built into the Android system.
+ * Instead, statsd on Android constructs these messages synthetically,
+ * in the format defined here and in stats_log.proto.
+ */
+message StatsEvent {
+    oneof event {
+        ScreenStateChanged screen_state_changed = 1;
+        ProcessStateChanged process_state_changed = 2;
+        WakeLockChanged wakelock_changed = 3;
+    }
+}
+
+/**
+ * A WorkSource represents the chained attribution of applications that
+ * resulted in a particular bit of work being done.
+ */
+message WorkSource {
+    // TODO
+}
+
+/*
+ * *****************************************************************************
+ * Below are all of the individual atoms that are logged by Android via statsd
+ * and Westworld.
+ *
+ * RULES:
+ *   - The field ids for each atom must start at 1, and count upwards by 1.
+ *     Skipping field ids is not allowed.
+ *   - These form an API, so renaming, renumbering or removing fields is
+ *     not allowed between android releases.  (This is not currently enforced,
+ *     but there will be a tool to enforce this restriction).
+ *   - The types must be built-in protocol buffer types, namely, no sub-messages
+ *     are allowed (yet).  The bytes type is also not allowed.
+ *   - The CamelCase name of the message type should match the
+ *     underscore_separated name as defined in StatsEvent.
+ *   - If an atom represents work that can be attributed to an app, there can
+ *     be exactly one WorkSource field. It must be field number 1.
+ *   - A field that is a uid should be a string field, tagged with the [xxx]
+ *     annotation. The generated code on android will be represented by UIDs,
+ *     and those UIDs will be translated in xxx to those strings.
+ *
+ * CONVENTIONS:
+ *   - Events are past tense. e.g. ScreenStateChanged, not ScreenStateChange
+ *   - If there is a UID, it goes first. Think in an object-oriented fashion.
+ * *****************************************************************************
+ */
+
+/**
+ * Logs when the screen state changes.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ScreenStateChanged {
+    // TODO: Use the real screen state.
+    enum State {
+        STATE_UNKNOWN = 0;
+        STATE_OFF = 1;
+        STATE_ON = 2;
+        STATE_DOZE = 3;
+        STATE_DOZE_SUSPEND = 4;
+        STATE_VR = 5;
+    }
+    // New screen state.
+    optional State display_state = 1;
+}
+
+/**
+ * Logs that the state of a process state, as per the activity manager has changed.
+ *
+ * Logged from:
+ *   frameworks/base/services/core/java/com/android/server/am/BatteryStatsService.java
+ */
+message ProcessStateChanged {
+    // TODO: Use the real (mapped) process states.
+    optional int32 uid = 1; // TODO: should be a string tagged w/ uid annotation
+
+    // The state.
+    optional int32 state = 2;
+}
+
+/**
+ * Logs that the state of a wakelock has changed.
+ *
+ * Logged from:
+ *   TODO
+ */
+message WakeLockChanged {
+    // TODO: Add attribution instead of uid.
+    optional int32 uid = 1;
+
+    // The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
+    optional string tag = 2;
+
+    // TODO: Use a constant instead of boolean?
+    optional bool state = 3;
+}
+
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 2c66ded..ec91509 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -13,20 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 syntax = "proto2";
+option optimize_for = LITE_RUNTIME;
 
 package android.os.statsd;
 
-option optimize_for = LITE_RUNTIME;
-
 option java_package = "com.android.os";
 option java_outer_classname = "StatsLog";
 
-import "frameworks/base/cmds/statsd/src/statsd_config.proto";
-import "frameworks/base/cmds/statsd/src/stats_constants.proto";
+import "frameworks/base/cmds/statsd/src/stats_events_copy.proto";
 
 message KeyValuePair {
-  optional KeyId key = 1;
+  optional int32 key = 1;
 
   oneof value {
     string value_str = 2;
@@ -37,15 +36,15 @@
 }
 
 message EventMetricData {
-  optional TagId tag = 1;
+  optional int64 timestamp_nanos = 1;
 
-  repeated KeyValuePair key_value_pair = 2;
+  optional StatsEvent stats_events = 2;
 }
 
 message CountBucketInfo {
-  optional int64 start_bucket_millis = 1;
+  optional int64 start_bucket_nanos = 1;
 
-  optional int64 end_bucket_millis = 2;
+  optional int64 end_bucket_nanos = 2;
 
   optional int64 count = 3;
 }
@@ -56,12 +55,77 @@
   repeated CountBucketInfo bucket_info = 2;
 }
 
+message DurationBucketInfo {
+  optional int64 start_bucket_nanos = 1;
+
+  optional int64 end_bucket_nanos = 2;
+
+  optional int64 duration_nanos = 3;
+}
+
+message DurationMetricData {
+  repeated KeyValuePair dimension = 1;
+
+  repeated DurationBucketInfo bucket_info = 2;
+}
+
+message ValueBucketInfo {
+  optional int64 start_bucket_nanos = 1;
+
+  optional int64 end_bucket_nanos = 2;
+
+  optional int64 value = 3;
+}
+
+message ValueMetricData {
+  repeated KeyValuePair dimension = 1;
+
+  repeated ValueBucketInfo bucket_info = 2;
+}
+
+message GaugeBucketInfo {
+  optional int64 start_bucket_nanos = 1;
+
+  optional int64 end_bucket_nanos = 2;
+
+  optional int64 gauge = 3;
+}
+
+message GaugeMetricData {
+  repeated KeyValuePair dimension = 1;
+
+  repeated GaugeBucketInfo bucket_info = 2;
+}
+
+message UidMapping {
+  message AppInfo {
+    optional string app = 1;
+
+    optional int32 version = 2;
+
+    optional int32 uid = 3;
+  }
+
+  repeated AppInfo initial = 1;
+
+  message Change {
+    optional bool deletion = 1;
+
+    optional int64 timestamp_nanos = 2;
+    optional string app = 3;
+    optional int32 uid = 4;
+
+    optional int32 version = 5;
+  }
+  repeated Change changes = 2;
+}
+
 message StatsLogReport {
   optional int32 metric_id = 1;
 
-  optional int64 start_report_millis = 2;
+  optional int64 start_report_nanos = 2;
 
-  optional int64 end_report_millis = 3;
+  optional int64 end_report_nanos = 3;
 
   message EventMetricDataWrapper {
     repeated EventMetricData data = 1;
@@ -69,9 +133,32 @@
   message CountMetricDataWrapper {
     repeated CountMetricData data = 1;
   }
-
+  message DurationMetricDataWrapper {
+    repeated DurationMetricData data = 1;
+  }
+  message ValueMetricDataWrapper {
+    repeated ValueMetricData data = 1;
+  }
+  message GaugeMetricDataWrapper {
+    repeated GaugeMetricData data = 1;
+  }
   oneof data {
     EventMetricDataWrapper event_metrics = 4;
     CountMetricDataWrapper count_metrics = 5;
+    DurationMetricDataWrapper duration_metrics = 6;
+    ValueMetricDataWrapper value_metrics = 7;
+    GaugeMetricDataWrapper gauge_metrics = 8;
   }
 }
+
+message ConfigMetricsReport {
+  message ConfigKey {
+    optional int32 uid = 1;
+    optional string name = 2;
+  }
+  optional ConfigKey config_key = 1;
+
+  repeated StatsLogReport metrics = 2;
+
+  optional UidMapping uid_map = 3;
+}
diff --git a/cmds/statsd/src/stats_util.cpp b/cmds/statsd/src/stats_util.cpp
new file mode 100644
index 0000000..fcce2ff
--- /dev/null
+++ b/cmds/statsd/src/stats_util.cpp
@@ -0,0 +1,162 @@
+/*
+ * 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 "stats_util.h"
+#include <log/log_event_list.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+static inline uint32_t get4LE(const char* src) {
+    return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
+}
+
+int getTagId(log_msg msg) {
+    return get4LE(msg.msg());
+}
+
+EventMetricData parse(log_msg msg) {
+    // dump all statsd logs to dropbox for now.
+    // TODO: Add filtering, aggregation, etc.
+    EventMetricData eventMetricData;
+
+    // set tag.
+    int tag = getTagId(msg);
+    // TODO: Replace the following line when we can serialize on the fly.
+    // eventMetricData.set_tag(tag);
+
+    // set timestamp of the event.
+    eventMetricData.set_timestamp_nanos(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec);
+
+    // start iterating k,v pairs.
+    android_log_context context =
+            create_android_log_parser(const_cast<log_msg*>(&msg)->msg() + sizeof(uint32_t),
+                                      const_cast<log_msg*>(&msg)->len() - sizeof(uint32_t));
+    android_log_list_element elem;
+
+    if (context) {
+        memset(&elem, 0, sizeof(elem));
+        size_t index = 0;
+        int32_t key = -1;
+
+        do {
+            elem = android_log_read_next(context);
+            switch ((int)elem.type) {
+                case EVENT_TYPE_INT:
+                    if (index % 2 == 0) {
+                        key = elem.data.int32;
+                    } else {
+                        // TODO: Fix the following lines when we can serialize on the fly.
+                        /*
+                        int32_t val = elem.data.int32;
+                        KeyValuePair* keyValuePair = eventMetricData.add_key_value_pair();
+                        keyValuePair->set_key(key);
+                        keyValuePair->set_value_int(val);
+                        */
+                    }
+                    index++;
+                    break;
+                case EVENT_TYPE_FLOAT:
+                    if (index % 2 == 1) {
+                        // TODO: Fix the following lines when we can serialize on the fly.
+                        /*
+                        float val = elem.data.float32;
+                        KeyValuePair* keyValuePair = eventMetricData.add_key_value_pair();
+                        keyValuePair->set_key(key);
+                        keyValuePair->set_value_float(val);
+                        */
+                    }
+                    index++;
+                    break;
+                case EVENT_TYPE_STRING:
+                    if (index % 2 == 1) {
+                        // TODO: Fix the following lines when we can serialize on the fly.
+                        /*
+                        char* val = elem.data.string;
+                        KeyValuePair* keyValuePair = eventMetricData.add_key_value_pair();
+                        keyValuePair->set_key(key);
+                        keyValuePair->set_value_str(val);
+                        */
+                    }
+                    index++;
+                    break;
+                case EVENT_TYPE_LONG:
+                    if (index % 2 == 1) {
+                        // TODO: Fix the following lines when we can serialize on the fly.
+                        /*
+                        int64_t val = elem.data.int64;
+                        KeyValuePair* keyValuePair = eventMetricData.add_key_value_pair();
+                        keyValuePair->set_key(key);
+                        keyValuePair->set_value_int(val);
+                        */
+                    }
+                    index++;
+                    break;
+                case EVENT_TYPE_LIST:
+                    break;
+                case EVENT_TYPE_LIST_STOP:
+                    break;
+                case EVENT_TYPE_UNKNOWN:
+                    break;
+                default:
+                    elem.complete = true;
+                    break;
+            }
+
+            if (elem.complete) {
+                break;
+            }
+        } while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
+
+        android_log_destroy(&context);
+    }
+
+    return eventMetricData;
+}
+
+// There is no existing hash function for the dimension key ("repeated KeyValuePair").
+// Temporarily use a string concatenation as the hashable key.
+// TODO: Find a better hash function for std::vector<KeyValuePair>.
+HashableDimensionKey getHashableKey(std::vector<KeyValuePair> keys) {
+    std::string flattened;
+    for (const KeyValuePair& pair : keys) {
+        flattened += std::to_string(pair.key());
+        flattened += ":";
+        switch (pair.value_case()) {
+            case KeyValuePair::ValueCase::kValueStr:
+                flattened += pair.value_str();
+                break;
+            case KeyValuePair::ValueCase::kValueInt:
+                flattened += std::to_string(pair.value_int());
+                break;
+            case KeyValuePair::ValueCase::kValueBool:
+                flattened += std::to_string(pair.value_bool());
+                break;
+            case KeyValuePair::ValueCase::kValueFloat:
+                flattened += std::to_string(pair.value_float());
+                break;
+            default:
+                break;
+        }
+        flattened += "|";
+    }
+    return flattened;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
new file mode 100644
index 0000000..39c1d59
--- /dev/null
+++ b/cmds/statsd/src/stats_util.h
@@ -0,0 +1,52 @@
+/*
+ * 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 STATS_UTIL_H
+#define STATS_UTIL_H
+
+#include "logd/LogReader.h"
+#include "storage/DropboxWriter.h"
+
+#include <log/logprint.h>
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#define DEFAULT_DIMENSION_KEY ""
+#define MATCHER_NOT_FOUND -2
+#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
+
+// TODO: Remove the following constants once they are exposed in ProtOutputStream.h
+const uint64_t FIELD_TYPE_SHIFT = 32;
+const uint64_t TYPE_MESSAGE = 11ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_INT64 = 3ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_INT32 = 5ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_FLOAT = 2ULL << FIELD_TYPE_SHIFT;
+const uint64_t TYPE_STRING = 9ULL << FIELD_TYPE_SHIFT;
+
+typedef std::string HashableDimensionKey;
+
+EventMetricData parse(log_msg msg);
+
+int getTagId(log_msg msg);
+
+std::string getHashableKey(std::vector<KeyValuePair> key);
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // STATS_UTIL_H
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c6119df..a4d2421 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -1,15 +1,30 @@
-syntax = "proto2";
-package android.os.statsd;
+/*
+ * 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";
 option optimize_for = LITE_RUNTIME;
 
+package android.os.statsd;
+
 option java_package = "com.android.internal.os";
 option java_outer_classname = "StatsdConfigProto";
 
-import "frameworks/base/cmds/statsd/src/stats_constants.proto";
-
 message KeyMatcher {
-  optional KeyId key = 1;
+  optional int32 key = 1;
+
   optional bool as_package_name = 2 [ default = false ];
 }
 
@@ -19,14 +34,15 @@
   oneof value_matcher {
     bool eq_bool = 2;
     string eq_string = 3;
-    int32 eq_int32 = 4;
-    int64 eq_int64 = 5;
-    int32 lt_int32 = 6;
-    int32 gt_int32 = 7;
-    int64 lt_int64 = 8;
-    int64 gt_int64 = 9;
-    float lt_float = 10;
-    float gt_float = 11;
+    int32 eq_int = 4;
+
+    int64 lt_int = 5;
+    int64 gt_int = 6;
+    float lt_float = 7;
+    float gt_float = 8;
+
+    int64 lte_int = 9;
+    int64 gte_int = 10;
   }
 }
 
@@ -39,7 +55,7 @@
 }
 
 message SimpleLogEntryMatcher {
-  repeated TagId tag = 1;
+  optional int32 tag = 1;
 
   repeated KeyValueMatcher key_value_matcher = 2;
 }
@@ -49,7 +65,8 @@
 
   message Combination {
     optional LogicalOperation operation = 1;
-    repeated LogEntryMatcher matcher = 2;
+
+    repeated string matcher = 2;
   }
   oneof contents {
     SimpleLogEntryMatcher simple_log_entry_matcher = 2;
@@ -86,12 +103,28 @@
   optional int64 bucket_size_millis = 1;
 }
 
+message Alert {
+  message IncidentdDetails {
+    optional string alert_name = 1;
+    repeated int32 incidentd_sections = 2;
+  }
+  optional IncidentdDetails incidentd_details = 1;
+
+  optional int32 number_of_buckets = 3;
+
+  optional int32 refractory_period_secs = 4;
+
+  optional int64 trigger_if_sum_gt = 5;
+}
+
 message EventMetric {
   optional int64 metric_id = 1;
 
   optional string what = 2;
 
   optional string condition = 3;
+
+  repeated EventConditionLink links = 4;
 }
 
 message CountMetric {
@@ -104,8 +137,98 @@
   repeated KeyMatcher dimension = 4;
 
   optional Bucket bucket = 5;
+
+  repeated Alert alerts = 6;
+
+  optional bool include_in_output = 7;
+
+  repeated EventConditionLink links = 8;
 }
 
+message DurationMetric {
+  optional int64 metric_id = 1;
+
+  optional string start = 2;
+
+  optional string stop = 3;
+
+  optional string stop_all = 4;
+
+  enum AggregationType {
+    DURATION_SUM = 1;
+
+    DURATION_MAX_SPARSE = 2;
+    DURATION_MIN_SPARSE = 3;
+  }
+  optional AggregationType type = 5;
+
+  optional string predicate = 6;
+
+  repeated KeyMatcher dimension = 7;
+
+  optional Bucket bucket = 8;
+
+  repeated Alert alerts = 9;
+
+  repeated EventConditionLink links = 10;
+
+}
+
+message GaugeMetric {
+  optional int64 metric_id = 1;
+
+  optional string what = 2;
+
+  optional int32 gauge_field = 3;
+
+  optional string condition = 4;
+
+  repeated KeyMatcher dimension = 5;
+
+  optional Bucket bucket = 6;
+
+  repeated Alert alerts = 7;
+
+  repeated EventConditionLink links = 8;
+}
+
+message ValueMetric {
+  optional int64 metric_id = 1;
+
+  optional string what = 2;
+
+  optional int32 value_field = 3;
+
+  optional string condition = 4;
+
+  repeated KeyMatcher dimension = 5;
+
+  optional Bucket bucket = 6;
+
+  repeated Alert alerts = 7;
+
+  repeated EventConditionLink links = 8;
+
+  enum Operation {
+    SUM_DIFF = 1;
+    MIN_DIFF = 2;
+    MAX_DIFF = 3;
+    SUM = 4;
+    MIN = 5;
+    MAX = 6;
+    FIRST = 7;
+    LAST = 8;
+  }
+  optional Operation operation = 9 [default = SUM];
+}
+
+message EventConditionLink {
+  optional string condition = 1;
+
+  repeated KeyMatcher key_in_main = 2;
+  repeated KeyMatcher key_in_condition = 3;
+};
+
 message StatsdConfig {
   optional int64 config_id = 1;
 
@@ -113,7 +236,11 @@
 
   repeated CountMetric count_metric = 3;
 
-  repeated LogEntryMatcher log_entry_matcher = 4;
+  repeated ValueMetric value_metric = 4;
 
-  repeated Condition condition = 5;
+  repeated DurationMetric duration_metric = 5;
+
+  repeated LogEntryMatcher log_entry_matcher = 6;
+
+  repeated Condition condition = 7;
 }
diff --git a/cmds/statsd/src/storage/DropboxReader.cpp b/cmds/statsd/src/storage/DropboxReader.cpp
new file mode 100644
index 0000000..c561959
--- /dev/null
+++ b/cmds/statsd/src/storage/DropboxReader.cpp
@@ -0,0 +1,120 @@
+/*
+ * 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 <android-base/file.h>
+#include <android/os/DropBoxManager.h>
+#include <androidfw/ZipUtils.h>
+
+#include "storage/DropboxReader.h"
+
+using android::base::unique_fd;
+using android::binder::Status;
+using android::os::DropBoxManager;
+using android::sp;
+using android::String16;
+using android::ZipUtils;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+status_t DropboxReader::readStatsLogs(FILE* out, const string& tag, long msec) {
+    sp<DropBoxManager> dropbox = new DropBoxManager();
+    StatsLogReport logReport;
+
+    long timestamp = msec;
+    // instead of while(true), put a hard limit 1000. Dropbox won't have more than 1000 files.
+    for (int i = 0; i < 1000; i++) {
+        DropBoxManager::Entry entry;
+        Status status = dropbox->getNextEntry(String16(tag.c_str()), timestamp, &entry);
+        if (!status.isOk()) {
+            ALOGD("No more entries, or failed to read. We can't tell unfortunately.");
+            return android::OK;
+        }
+
+        const unique_fd& fd = entry.getFd();
+
+        // use this timestamp for next query.
+        timestamp = entry.getTimestamp();
+
+        if (entry.getFlags() & DropBoxManager::IS_GZIPPED) {
+            if (!parseFromGzipFile(fd, logReport)) {
+                // Failed to parse from the file. Continue to fetch the next entry.
+                continue;
+            }
+        } else {
+            if (!parseFromFile(fd, logReport)) {
+                // Failed to parse from the file. Continue to fetch the next entry.
+                continue;
+            }
+        }
+
+        printLog(out, logReport);
+    }
+    return android::OK;
+}
+
+bool DropboxReader::parseFromGzipFile(const unique_fd& fd, StatsLogReport& logReport) {
+    FILE* file = fdopen(fd, "r");
+    bool result = false;
+    bool scanResult;
+    int method;
+    long compressedLen;
+    long uncompressedLen;
+    unsigned long crc32;
+    scanResult = ZipUtils::examineGzip(file, &method, &uncompressedLen, &compressedLen, &crc32);
+    if (scanResult && method == kCompressDeflated) {
+        vector<uint8_t> buf(uncompressedLen);
+        if (ZipUtils::inflateToBuffer(file, &buf[0], uncompressedLen, compressedLen)) {
+            if (logReport.ParseFromArray(&buf[0], uncompressedLen)) {
+                result = true;
+            }
+        }
+    } else {
+        ALOGE("This isn't a valid deflated gzip file");
+    }
+    fclose(file);
+    return result;
+}
+
+// parse a non zipped file.
+bool DropboxReader::parseFromFile(const unique_fd& fd, StatsLogReport& logReport) {
+    string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        ALOGE("Failed to read file");
+        return false;
+    }
+    if (!logReport.ParseFromString(content)) {
+        ALOGE("failed to parse log entry from data");
+        return false;
+    }
+    return true;
+}
+
+void DropboxReader::printLog(FILE* out, const StatsLogReport& logReport) {
+    fprintf(out, "start_time_ns=%lld, end_time_ns=%lld, ", logReport.start_report_nanos(),
+            logReport.end_report_nanos());
+    for (int i = 0; i < logReport.event_metrics().data_size(); i++) {
+        EventMetricData eventMetricData = logReport.event_metrics().data(i);
+        // TODO: Pretty-print the proto.
+        // fprintf(out, "EventMetricData=%s", eventMetricData.SerializeAsString().c_str());
+    }
+    fprintf(out, "\n");
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/storage/DropboxReader.h b/cmds/statsd/src/storage/DropboxReader.h
new file mode 100644
index 0000000..a5a28d9
--- /dev/null
+++ b/cmds/statsd/src/storage/DropboxReader.h
@@ -0,0 +1,52 @@
+/*
+ * 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 DROPBOX_READER_H
+#define DROPBOX_READER_H
+
+#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
+
+#include <stdint.h>
+#include <stdio.h>
+
+using android::base::unique_fd;
+using android::status_t;
+using std::string;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class DropboxReader {
+public:
+    // msec is the start timestamp.
+    static status_t readStatsLogs(FILE* out, const string& tag, long msec);
+
+private:
+    static bool parseFromFile(const unique_fd& fd, StatsLogReport& logReport);
+    static bool parseFromGzipFile(const unique_fd& fd, StatsLogReport& logReport);
+    static void printLog(FILE* out, const StatsLogReport& logReport);
+    enum {
+        kCompressStored = 0,    // no compression
+        kCompressDeflated = 8,  // standard deflate
+    };
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // DROPBOX_READER_H
diff --git a/cmds/statsd/src/storage/DropboxWriter.cpp b/cmds/statsd/src/storage/DropboxWriter.cpp
new file mode 100644
index 0000000..e59bdbd
--- /dev/null
+++ b/cmds/statsd/src/storage/DropboxWriter.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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 <android/os/DropBoxManager.h>
+
+#include "storage/DropboxWriter.h"
+
+using android::binder::Status;
+using android::os::DropBoxManager;
+using android::sp;
+using android::String16;
+using std::vector;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DropboxWriter::DropboxWriter(const string& tag) : mTag(tag), mLogReport(), mBufferSize(0) {
+}
+
+void DropboxWriter::addEventMetricData(const EventMetricData& eventMetricData) {
+    flushIfNecessary(eventMetricData);
+    EventMetricData* newEntry = mLogReport.mutable_event_metrics()->add_data();
+    newEntry->CopyFrom(eventMetricData);
+    mBufferSize += eventMetricData.ByteSize();
+}
+
+void DropboxWriter::flushIfNecessary(const EventMetricData& eventMetricData) {
+    if (eventMetricData.ByteSize() + mBufferSize > kMaxSerializedBytes) {
+        flush();
+    }
+}
+
+void DropboxWriter::flush() {
+    // now we get an exact byte size of the output
+    const int numBytes = mLogReport.ByteSize();
+    vector<uint8_t> buffer(numBytes);
+    sp<DropBoxManager> dropbox = new DropBoxManager();
+    mLogReport.SerializeToArray(&buffer[0], numBytes);
+    Status status = dropbox->addData(String16(mTag.c_str()), &buffer[0], numBytes, 0 /* no flag */);
+    if (!status.isOk()) {
+        ALOGE("failed to write to dropbox");
+        // TODO: What to do if flush fails??
+    }
+    mLogReport.Clear();
+    mBufferSize = 0;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/storage/DropboxWriter.h b/cmds/statsd/src/storage/DropboxWriter.h
new file mode 100644
index 0000000..d72f103
--- /dev/null
+++ b/cmds/statsd/src/storage/DropboxWriter.h
@@ -0,0 +1,72 @@
+/*
+ * 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 DROPBOX_WRITER_H
+#define DROPBOX_WRITER_H
+
+#include <utils/RefBase.h>
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+
+using std::string;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class DropboxWriter : public virtual RefBase {
+public:
+    /* tag will be part of the file name, and used as the key to build the file index inside
+       DropBoxManagerService.
+     */
+    DropboxWriter(const string& tag);
+
+    void addEventMetricData(const EventMetricData& eventMetricData);
+
+    /* Request a flush to dropbox. */
+    void flush();
+
+private:
+    /* Max *serialized* size of the logs kept in memory before flushing to dropbox.
+       Proto lite does not implement the SpaceUsed() function which gives the in memory byte size.
+       So we cap memory usage by limiting the serialized size. Note that protobuf's in memory size
+       is higher than its serialized size. DropboxManager will compress the file when the data is
+       larger than 4KB. So the final file size is less than this number.
+     */
+    static const size_t kMaxSerializedBytes = 16 * 1024;
+
+    const string mTag;
+
+    /* Data that was captured for a single metric over a given interval of time. */
+    StatsLogReport mLogReport;
+
+    /* Current *serialized* size of the logs kept in memory.
+       To save computation, we will not calculate the size of the StatsLogReport every time when a
+       new entry is added, which would recursively call ByteSize() on every log entry. Instead, we
+       keep the sum of all individual stats log entry sizes. The size of a proto is approximately
+       the sum of the size of all member protos.
+     */
+    size_t mBufferSize = 0;
+
+    /* Check if the buffer size exceeds the max buffer size when the new entry is added, and flush
+       the logs to dropbox if true. */
+    void flushIfNecessary(const EventMetricData& eventMetricData);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+#endif  // DROPBOX_WRITER_H
diff --git a/cmds/statsd/tests/AnomalyMonitor_test.cpp b/cmds/statsd/tests/AnomalyMonitor_test.cpp
new file mode 100644
index 0000000..59fa160
--- /dev/null
+++ b/cmds/statsd/tests/AnomalyMonitor_test.cpp
@@ -0,0 +1,64 @@
+// 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 "anomaly/AnomalyMonitor.h"
+
+#include <gtest/gtest.h>
+
+using namespace android::os::statsd;
+
+#ifdef __ANDROID__
+TEST(AnomalyMonitor, popSoonerThan) {
+    unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> set;
+    AnomalyMonitor am(2);
+
+    set = am.popSoonerThan(5);
+    EXPECT_TRUE(set.empty());
+
+    sp<const AnomalyAlarm> a = new AnomalyAlarm{10};
+    sp<const AnomalyAlarm> b = new AnomalyAlarm{20};
+    sp<const AnomalyAlarm> c = new AnomalyAlarm{20};
+    sp<const AnomalyAlarm> d = new AnomalyAlarm{30};
+    sp<const AnomalyAlarm> e = new AnomalyAlarm{40};
+    sp<const AnomalyAlarm> f = new AnomalyAlarm{50};
+
+    am.add(a);
+    am.add(b);
+    am.add(c);
+    am.add(d);
+    am.add(e);
+    am.add(f);
+
+    set = am.popSoonerThan(5);
+    EXPECT_TRUE(set.empty());
+
+    set = am.popSoonerThan(30);
+    EXPECT_EQ(4u, set.size());
+    EXPECT_EQ(1u, set.count(a));
+    EXPECT_EQ(1u, set.count(b));
+    EXPECT_EQ(1u, set.count(c));
+    EXPECT_EQ(1u, set.count(d));
+
+    set = am.popSoonerThan(60);
+    EXPECT_EQ(2u, set.size());
+    EXPECT_EQ(1u, set.count(e));
+    EXPECT_EQ(1u, set.count(f));
+
+    set = am.popSoonerThan(80);
+    EXPECT_EQ(0u, set.size());
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/ConditionTracker_test.cpp b/cmds/statsd/tests/ConditionTracker_test.cpp
new file mode 100644
index 0000000..2935ac7
--- /dev/null
+++ b/cmds/statsd/tests/ConditionTracker_test.cpp
@@ -0,0 +1,161 @@
+// 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 "condition/condition_util.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+#include <vector>
+
+using namespace android::os::statsd;
+using std::vector;
+
+
+#ifdef __ANDROID__
+TEST(ConditionTrackerTest, TestUnknownCondition) {
+    LogicalOperation operation = LogicalOperation::AND;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+    children.push_back(2);
+
+    vector<ConditionState> conditionResults;
+    conditionResults.push_back(ConditionState::kUnknown);
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kTrue);
+
+    EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults),
+            ConditionState::kUnknown);
+}
+TEST(ConditionTrackerTest, TestAndCondition) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::AND;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+    children.push_back(2);
+
+    vector<ConditionState> conditionResults;
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kTrue);
+
+    EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kTrue);
+
+    EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults));
+}
+
+TEST(ConditionTrackerTest, TestOrCondition) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::OR;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+    children.push_back(2);
+
+    vector<ConditionState> conditionResults;
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kTrue);
+
+    EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kFalse);
+
+    EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults));
+}
+
+TEST(ConditionTrackerTest, TestNotCondition) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::NOT;
+
+    vector<int> children;
+    children.push_back(0);
+
+    vector<ConditionState> conditionResults;
+    conditionResults.push_back(ConditionState::kTrue);
+
+    EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kFalse);
+    EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults));
+}
+
+TEST(ConditionTrackerTest, TestNandCondition) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::NAND;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+
+    vector<ConditionState> conditionResults;
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kFalse);
+
+    EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kFalse);
+    EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kTrue);
+    EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults));
+}
+
+TEST(ConditionTrackerTest, TestNorCondition) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::NOR;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+
+    vector<ConditionState> conditionResults;
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kFalse);
+
+    EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kFalse);
+    conditionResults.push_back(ConditionState::kFalse);
+    EXPECT_TRUE(evaluateCombinationCondition(children, operation, conditionResults));
+
+    conditionResults.clear();
+    conditionResults.push_back(ConditionState::kTrue);
+    conditionResults.push_back(ConditionState::kTrue);
+    EXPECT_FALSE(evaluateCombinationCondition(children, operation, conditionResults));
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/ConfigManager_test.cpp b/cmds/statsd/tests/ConfigManager_test.cpp
new file mode 100644
index 0000000..aa896ca
--- /dev/null
+++ b/cmds/statsd/tests/ConfigManager_test.cpp
@@ -0,0 +1,156 @@
+// 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 "src/config/ConfigManager.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+#include <iostream>
+
+using namespace android;
+using namespace android::os::statsd;
+using namespace testing;
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+static ostream& operator<<(ostream& os, const StatsdConfig& config) {
+    return os << "StatsdConfig{id=" << config.config_id() << "}";
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+
+/**
+ * Mock ConfigListener
+ */
+class MockListener : public ConfigListener {
+public:
+    MOCK_METHOD2(OnConfigUpdated, void(const ConfigKey& key, const StatsdConfig& config));
+    MOCK_METHOD1(OnConfigRemoved, void(const ConfigKey& key));
+};
+
+/**
+ * Validate that the ConfigKey is the one we wanted.
+ */
+MATCHER_P2(ConfigKeyEq, uid, name, "") {
+    return arg.GetUid() == uid && arg.GetName() == name;
+}
+
+/**
+ * Validate that the StatsdConfig is the one we wanted.
+ */
+MATCHER_P(StatsdConfigEq, configId, "") {
+    return arg.config_id() == configId;
+}
+
+/**
+ * Test the addOrUpdate and remove methods
+ */
+TEST(ConfigManagerTest, TestAddUpdateRemove) {
+    sp<MockListener> listener = new StrictMock<MockListener>();
+
+    sp<ConfigManager> manager = new ConfigManager();
+    manager->AddListener(listener);
+
+    StatsdConfig config91;
+    config91.set_config_id(91);
+    StatsdConfig config92;
+    config92.set_config_id(92);
+    StatsdConfig config93;
+    config93.set_config_id(93);
+    StatsdConfig config94;
+    config94.set_config_id(94);
+
+    {
+        InSequence s;
+
+        // The built-in fake one.
+        // TODO: Remove this when we get rid of the fake one, and make this
+        // test loading one from disk somewhere.
+        EXPECT_CALL(*(listener.get()),
+                    OnConfigUpdated(ConfigKeyEq(0, "fake"), StatsdConfigEq(12345)))
+                .RetiresOnSaturation();
+        manager->Startup();
+
+        // Add another one
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq(91)))
+                .RetiresOnSaturation();
+        manager->UpdateConfig(ConfigKey(1, "zzz"), config91);
+
+        // Update It
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "zzz"), StatsdConfigEq(92)))
+                .RetiresOnSaturation();
+        manager->UpdateConfig(ConfigKey(1, "zzz"), config92);
+
+        // Add one with the same uid but a different name
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(1, "yyy"), StatsdConfigEq(93)))
+                .RetiresOnSaturation();
+        manager->UpdateConfig(ConfigKey(1, "yyy"), config93);
+
+        // Add one with the same name but a different uid
+        EXPECT_CALL(*(listener.get()), OnConfigUpdated(ConfigKeyEq(2, "zzz"), StatsdConfigEq(94)))
+                .RetiresOnSaturation();
+        manager->UpdateConfig(ConfigKey(2, "zzz"), config94);
+
+        // Remove (1,yyy)
+        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "yyy")))
+                .RetiresOnSaturation();
+        manager->RemoveConfig(ConfigKey(1, "yyy"));
+
+        // Remove (2,zzz)
+        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")))
+                .RetiresOnSaturation();
+        manager->RemoveConfig(ConfigKey(2, "zzz"));
+
+        // Remove (1,zzz)
+        EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(1, "zzz")))
+                .RetiresOnSaturation();
+        manager->RemoveConfig(ConfigKey(1, "zzz"));
+
+        // Remove (2,zzz) again and we shouldn't get the callback
+        manager->RemoveConfig(ConfigKey(2, "zzz"));
+    }
+}
+
+/**
+ * Test removing all of the configs for a uid.
+ */
+TEST(ConfigManagerTest, TestRemoveUid) {
+    sp<MockListener> listener = new StrictMock<MockListener>();
+
+    sp<ConfigManager> manager = new ConfigManager();
+    manager->AddListener(listener);
+
+    StatsdConfig config;
+
+    EXPECT_CALL(*(listener.get()), OnConfigUpdated(_, _)).Times(6);
+    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "xxx")));
+    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "yyy")));
+    EXPECT_CALL(*(listener.get()), OnConfigRemoved(ConfigKeyEq(2, "zzz")));
+
+    manager->Startup();
+    manager->UpdateConfig(ConfigKey(1, "aaa"), config);
+    manager->UpdateConfig(ConfigKey(2, "xxx"), config);
+    manager->UpdateConfig(ConfigKey(2, "yyy"), config);
+    manager->UpdateConfig(ConfigKey(2, "zzz"), config);
+    manager->UpdateConfig(ConfigKey(3, "bbb"), config);
+
+    manager->RemoveConfigs(2);
+}
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
new file mode 100644
index 0000000..fdfe8ef
--- /dev/null
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -0,0 +1,324 @@
+// 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 "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/matcher_util.h"
+#include "stats_util.h"
+
+#include <gtest/gtest.h>
+#include <log/log_event_list.h>
+#include <log/log_read.h>
+#include <log/logprint.h>
+
+#include <stdio.h>
+
+using namespace android::os::statsd;
+using std::unordered_map;
+using std::vector;
+
+const int TAG_ID = 123;
+const int FIELD_ID_1 = 1;
+const int FIELD_ID_2 = 2;
+const int FIELD_ID_3 = 2;
+
+// Private API from liblog.
+extern "C" void android_log_rewind(android_log_context ctx);
+
+#ifdef __ANDROID__
+TEST(LogEntryMatcherTest, TestSimpleMatcher) {
+    // Set up the matcher
+    LogEntryMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+    simpleMatcher->set_tag(TAG_ID);
+
+    LogEvent event(TAG_ID);
+
+    // Convert to a LogEvent
+    event.init();
+
+    // Test
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+}
+
+TEST(LogEntryMatcherTest, TestBoolMatcher) {
+    // Set up the matcher
+    LogEntryMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+    simpleMatcher->set_tag(TAG_ID);
+    auto keyValue1 = simpleMatcher->add_key_value_matcher();
+    keyValue1->mutable_key_matcher()->set_key(FIELD_ID_1);
+    auto keyValue2 = simpleMatcher->add_key_value_matcher();
+    keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
+
+    // Set up the event
+    LogEvent event(TAG_ID);
+    auto list = event.GetAndroidLogEventList();
+    *list << true;
+    *list << false;
+
+    // Convert to a LogEvent
+    event.init();
+
+    // Test
+    keyValue1->set_eq_bool(true);
+    keyValue2->set_eq_bool(false);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+
+    keyValue1->set_eq_bool(false);
+    keyValue2->set_eq_bool(false);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+
+    keyValue1->set_eq_bool(true);
+    keyValue2->set_eq_bool(false);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+
+    keyValue1->set_eq_bool(true);
+    keyValue2->set_eq_bool(true);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+}
+
+TEST(LogEntryMatcherTest, TestStringMatcher) {
+    // Set up the matcher
+    LogEntryMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+    simpleMatcher->set_tag(TAG_ID);
+    auto keyValue = simpleMatcher->add_key_value_matcher();
+    keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+    keyValue->set_eq_string("some value");
+
+    // Set up the event
+    LogEvent event(TAG_ID);
+    auto list = event.GetAndroidLogEventList();
+    *list << "some value";
+
+    // Convert to a LogEvent
+    event.init();
+
+    // Test
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+}
+
+TEST(LogEntryMatcherTest, TestIntComparisonMatcher) {
+    // Set up the matcher
+    LogEntryMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+
+    simpleMatcher->set_tag(TAG_ID);
+    auto keyValue = simpleMatcher->add_key_value_matcher();
+    keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+
+    // Set up the event
+    LogEvent event(TAG_ID);
+    auto list = event.GetAndroidLogEventList();
+    *list << 11;
+
+    event.init();
+
+    // Test
+
+    // eq_int
+    keyValue->set_eq_int(10);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_eq_int(11);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_eq_int(12);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+
+    // lt_int
+    keyValue->set_lt_int(10);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_lt_int(11);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_lt_int(12);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+
+    // lte_int
+    keyValue->set_lte_int(10);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_lte_int(11);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_lte_int(12);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+
+    // gt_int
+    keyValue->set_gt_int(10);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_gt_int(11);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_gt_int(12);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+
+    // gte_int
+    keyValue->set_gte_int(10);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_gte_int(11);
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    keyValue->set_gte_int(12);
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+}
+
+#if 0
+
+TEST(LogEntryMatcherTest, TestFloatComparisonMatcher) {
+    // Set up the matcher
+    LogEntryMatcher matcher;
+    auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
+    simpleMatcher->set_tag(TAG_ID);
+
+    auto keyValue = simpleMatcher->add_key_value_matcher();
+    keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
+
+    LogEvent event;
+    event.tagId = TAG_ID;
+
+    keyValue->set_lt_float(10.0);
+    event.floatMap[FIELD_ID_1] = 10.1;
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+    event.floatMap[FIELD_ID_1] = 9.9;
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+
+    keyValue->set_gt_float(10.0);
+    event.floatMap[FIELD_ID_1] = 10.1;
+    EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
+    event.floatMap[FIELD_ID_1] = 9.9;
+    EXPECT_FALSE(matchesSimple(*simpleMatcher, event));
+}
+#endif
+
+// Helper for the composite matchers.
+void addSimpleMatcher(SimpleLogEntryMatcher* simpleMatcher, int tag, int key, int val) {
+    simpleMatcher->set_tag(tag);
+    auto keyValue = simpleMatcher->add_key_value_matcher();
+    keyValue->mutable_key_matcher()->set_key(key);
+    keyValue->set_eq_int(val);
+}
+
+TEST(LogEntryMatcherTest, TestAndMatcher) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::AND;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+    children.push_back(2);
+
+    vector<MatchingState> matcherResults;
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+    matcherResults.push_back(MatchingState::kMatched);
+
+    EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kMatched);
+
+    EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
+}
+
+TEST(LogEntryMatcherTest, TestOrMatcher) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::OR;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+    children.push_back(2);
+
+    vector<MatchingState> matcherResults;
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+    matcherResults.push_back(MatchingState::kMatched);
+
+    EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kNotMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+
+    EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
+}
+
+TEST(LogEntryMatcherTest, TestNotMatcher) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::NOT;
+
+    vector<int> children;
+    children.push_back(0);
+
+    vector<MatchingState> matcherResults;
+    matcherResults.push_back(MatchingState::kMatched);
+
+    EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kNotMatched);
+    EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
+}
+
+TEST(LogEntryMatcherTest, TestNandMatcher) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::NAND;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+
+    vector<MatchingState> matcherResults;
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+
+    EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kNotMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+    EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kMatched);
+    EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
+}
+
+TEST(LogEntryMatcherTest, TestNorMatcher) {
+    // Set up the matcher
+    LogicalOperation operation = LogicalOperation::NOR;
+
+    vector<int> children;
+    children.push_back(0);
+    children.push_back(1);
+
+    vector<MatchingState> matcherResults;
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+
+    EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kNotMatched);
+    matcherResults.push_back(MatchingState::kNotMatched);
+    EXPECT_TRUE(combinationMatch(children, operation, matcherResults));
+
+    matcherResults.clear();
+    matcherResults.push_back(MatchingState::kMatched);
+    matcherResults.push_back(MatchingState::kMatched);
+    EXPECT_FALSE(combinationMatch(children, operation, matcherResults));
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/LogReader_test.cpp b/cmds/statsd/tests/LogReader_test.cpp
index ca538b0..7ce1d6a 100644
--- a/cmds/statsd/tests/LogReader_test.cpp
+++ b/cmds/statsd/tests/LogReader_test.cpp
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#define LOG_TAG "statsd_test"
-
 #include <gtest/gtest.h>
 
 #include <stdio.h>
@@ -21,4 +19,3 @@
 TEST(LogReaderTest, TestNothingAtAll) {
     printf("yay!");
 }
-
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
new file mode 100644
index 0000000..b000e13
--- /dev/null
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -0,0 +1,230 @@
+// 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 <gtest/gtest.h>
+
+#include "src/condition/ConditionTracker.h"
+#include "src/matchers/LogMatchingTracker.h"
+#include "src/metrics/CountMetricProducer.h"
+#include "src/metrics/MetricProducer.h"
+#include "src/metrics/metrics_manager_util.h"
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <vector>
+
+using namespace android::os::statsd;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+// TODO: ADD MORE TEST CASES.
+
+StatsdConfig buildGoodConfig() {
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_ON");
+
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_OFF");
+
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_ON_OR_OFF");
+
+    LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher("SCREEN_IS_ON");
+    combination->add_matcher("SCREEN_IS_OFF");
+
+    return config;
+}
+
+StatsdConfig buildCircleMatchers() {
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_ON");
+
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_ON_OR_OFF");
+
+    LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher("SCREEN_IS_ON");
+    // Circle dependency
+    combination->add_matcher("SCREEN_ON_OR_OFF");
+
+    return config;
+}
+
+StatsdConfig buildMissingMatchers() {
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_ON");
+
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_ON_OR_OFF");
+
+    LogEntryMatcher_Combination* combination = eventMatcher->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_matcher("SCREEN_IS_ON");
+    // undefined matcher
+    combination->add_matcher("ABC");
+
+    return config;
+}
+
+StatsdConfig buildCircleConditions() {
+    StatsdConfig config;
+    config.set_config_id(12345L);
+
+    LogEntryMatcher* eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_ON");
+
+    SimpleLogEntryMatcher* simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+            2 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_ON*/);
+
+    eventMatcher = config.add_log_entry_matcher();
+    eventMatcher->set_name("SCREEN_IS_OFF");
+
+    simpleLogEntryMatcher = eventMatcher->mutable_simple_log_entry_matcher();
+    simpleLogEntryMatcher->set_tag(2 /*SCREEN_STATE_CHANGE*/);
+    simpleLogEntryMatcher->add_key_value_matcher()->mutable_key_matcher()->set_key(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE*/);
+    simpleLogEntryMatcher->mutable_key_value_matcher(0)->set_eq_int(
+            1 /*SCREEN_STATE_CHANGE__DISPLAY_STATE__STATE_OFF*/);
+
+    Condition* condition = config.add_condition();
+    condition->set_name("SCREEN_IS_ON");
+    SimpleCondition* simpleCondition = condition->mutable_simple_condition();
+    simpleCondition->set_start("SCREEN_IS_ON");
+    simpleCondition->set_stop("SCREEN_IS_OFF");
+
+    condition = config.add_condition();
+    condition->set_name("SCREEN_IS_EITHER_ON_OFF");
+
+    Condition_Combination* combination = condition->mutable_combination();
+    combination->set_operation(LogicalOperation::OR);
+    combination->add_condition("SCREEN_IS_ON");
+    combination->add_condition("SCREEN_IS_EITHER_ON_OFF");
+
+    return config;
+}
+
+TEST(MetricsManagerTest, TestGoodConfig) {
+    StatsdConfig config = buildGoodConfig();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_TRUE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                 allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                                 trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
+    StatsdConfig config = buildCircleMatchers();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                                  trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, TestMissingMatchers) {
+    StatsdConfig config = buildMissingMatchers();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                                  trackerToConditionMap));
+}
+
+TEST(MetricsManagerTest, TestCircleConditionDependency) {
+    StatsdConfig config = buildCircleConditions();
+    set<int> allTagIds;
+    vector<sp<LogMatchingTracker>> allLogEntryMatchers;
+    vector<sp<ConditionTracker>> allConditionTrackers;
+    vector<sp<MetricProducer>> allMetricProducers;
+    unordered_map<int, std::vector<int>> conditionToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToMetricMap;
+    unordered_map<int, std::vector<int>> trackerToConditionMap;
+
+    EXPECT_FALSE(initStatsdConfig(config, allTagIds, allLogEntryMatchers, allConditionTrackers,
+                                  allMetricProducers, conditionToMetricMap, trackerToMetricMap,
+                                  trackerToConditionMap));
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
new file mode 100644
index 0000000..f9a90e4
--- /dev/null
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -0,0 +1,69 @@
+// 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 "packages/UidMap.h"
+
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+
+using namespace android;
+using namespace android::os::statsd;
+
+#ifdef __ANDROID__
+const string kApp1 = "app1.sharing.1";
+const string kApp2 = "app2.sharing.1";
+
+TEST(UidMapTest, TestMatching) {
+    UidMap m;
+    vector<int32_t> uids;
+    vector<int32_t> versions;
+    vector<String16> apps;
+
+    uids.push_back(1000);
+    uids.push_back(1000);
+    apps.push_back(String16(kApp1.c_str()));
+    apps.push_back(String16(kApp2.c_str()));
+    versions.push_back(4);
+    versions.push_back(5);
+    m.updateMap(uids, versions, apps);
+    EXPECT_TRUE(m.hasApp(1000, kApp1));
+    EXPECT_TRUE(m.hasApp(1000, kApp2));
+    EXPECT_FALSE(m.hasApp(1000, "not.app"));
+}
+
+TEST(UidMapTest, TestAddAndRemove) {
+    UidMap m;
+    vector<int32_t> uids;
+    vector<int32_t> versions;
+    vector<String16> apps;
+
+    uids.push_back(1000);
+    uids.push_back(1000);
+    apps.push_back(String16(kApp1.c_str()));
+    apps.push_back(String16(kApp2.c_str()));
+    versions.push_back(4);
+    versions.push_back(5);
+    m.updateMap(uids, versions, apps);
+
+    m.updateApp(String16(kApp1.c_str()), 1000, 40);
+    EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
+
+    m.removeApp(String16(kApp1.c_str()), 1000);
+    EXPECT_FALSE(m.hasApp(1000, kApp1));
+    EXPECT_TRUE(m.hasApp(1000, kApp2));
+}
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp
index 1aad089..600b953 100644
--- a/cmds/statsd/tests/indexed_priority_queue_test.cpp
+++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "../src/indexed_priority_queue.h"
+#include "src/anomaly/indexed_priority_queue.h"
 
 #include <gtest/gtest.h>
 
@@ -133,7 +133,6 @@
     EXPECT_TRUE(ipq.contains(aa4_b));
 }
 
-
 TEST(indexed_priority_queue, remove_nonexistant) {
     indexed_priority_queue<AATest, AATest::Smaller> ipq;
     sp<const AATest> aa4 = new AATest{4};
@@ -183,6 +182,40 @@
     EXPECT_FALSE(ipq.contains(nullptr));
 }
 
+TEST(indexed_priority_queue, pop) {
+    indexed_priority_queue<AATest, AATest::Smaller> ipq;
+    sp<const AATest> a = new AATest{1};
+    sp<const AATest> b = new AATest{2};
+    sp<const AATest> c = new AATest{3};
+
+    ipq.push(c);
+    ipq.push(b);
+    ipq.push(a);
+    EXPECT_EQ(3u, ipq.size());
+
+    ipq.pop();
+    EXPECT_EQ(2u, ipq.size());
+    EXPECT_FALSE(ipq.contains(a));
+    EXPECT_TRUE(ipq.contains(b));
+    EXPECT_TRUE(ipq.contains(c));
+
+    ipq.pop();
+    EXPECT_EQ(1u, ipq.size());
+    EXPECT_FALSE(ipq.contains(a));
+    EXPECT_FALSE(ipq.contains(b));
+    EXPECT_TRUE(ipq.contains(c));
+
+    ipq.pop();
+    EXPECT_EQ(0u, ipq.size());
+    EXPECT_FALSE(ipq.contains(a));
+    EXPECT_FALSE(ipq.contains(b));
+    EXPECT_FALSE(ipq.contains(c));
+    EXPECT_TRUE(ipq.empty());
+
+    ipq.pop(); // pop an empty queue
+    EXPECT_TRUE(ipq.empty());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone
index df68f14..47e148d 100644
--- a/config/compiled-classes-phone
+++ b/config/compiled-classes-phone
@@ -709,9 +709,9 @@
 android.bluetooth.BluetoothHeadset$2
 android.bluetooth.BluetoothHeadset$3
 android.bluetooth.BluetoothHealthAppConfiguration
-android.bluetooth.BluetoothInputDevice
-android.bluetooth.BluetoothInputDevice$1
-android.bluetooth.BluetoothInputDevice$2
+android.bluetooth.BluetoothHidHost
+android.bluetooth.BluetoothHidHost$1
+android.bluetooth.BluetoothHidHost$2
 android.bluetooth.BluetoothInputStream
 android.bluetooth.BluetoothManager
 android.bluetooth.BluetoothMap
@@ -754,9 +754,9 @@
 android.bluetooth.IBluetoothHealth
 android.bluetooth.IBluetoothHealth$Stub
 android.bluetooth.IBluetoothHealthCallback
-android.bluetooth.IBluetoothInputDevice
-android.bluetooth.IBluetoothInputDevice$Stub
-android.bluetooth.IBluetoothInputDevice$Stub$Proxy
+android.bluetooth.IBluetoothHidHost
+android.bluetooth.IBluetoothHidHost$Stub
+android.bluetooth.IBluetoothHidHost$Stub$Proxy
 android.bluetooth.IBluetoothManager
 android.bluetooth.IBluetoothManager$Stub
 android.bluetooth.IBluetoothManager$Stub$Proxy
@@ -3269,6 +3269,7 @@
 android.os.Parcel
 android.os.Parcel$1
 android.os.Parcel$2
+android.os.Parcel$ReadWriteHelper
 android.os.ParcelFileDescriptor
 android.os.ParcelFileDescriptor$1
 android.os.ParcelFileDescriptor$2
@@ -5022,8 +5023,6 @@
 android.widget.RemoteViews$BitmapCache
 android.widget.RemoteViews$BitmapReflectionAction
 android.widget.RemoteViews$LayoutParamAction
-android.widget.RemoteViews$MemoryUsageCounter
-android.widget.RemoteViews$MutablePair
 android.widget.RemoteViews$OnClickHandler
 android.widget.RemoteViews$OnViewAppliedListener
 android.widget.RemoteViews$ReflectionAction
@@ -5031,7 +5030,7 @@
 android.widget.RemoteViews$RemoteViewsContextWrapper
 android.widget.RemoteViews$RunnableAction
 android.widget.RemoteViews$RuntimeAction
-android.widget.RemoteViews$SetDrawableParameters
+android.widget.RemoteViews$SetDrawableTint
 android.widget.RemoteViews$SetOnClickPendingIntent
 android.widget.RemoteViews$SetOnClickPendingIntent$1
 android.widget.RemoteViews$ViewGroupAction
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 337d7a0..1b86724 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -2193,6 +2193,7 @@
 android.util.Log
 android.util.Log$1
 android.util.Log$ImmediateLogWriter
+android.util.Log$PreloadHolder
 android.util.Log$TerribleFailureHandler
 android.util.LogPrinter
 android.util.LongArray
@@ -2723,13 +2724,11 @@
 android.widget.RemoteViews$Action
 android.widget.RemoteViews$BitmapCache
 android.widget.RemoteViews$LayoutParamAction
-android.widget.RemoteViews$MemoryUsageCounter
-android.widget.RemoteViews$MutablePair
 android.widget.RemoteViews$OnClickHandler
 android.widget.RemoteViews$ReflectionAction
 android.widget.RemoteViews$RemoteView
 android.widget.RemoteViews$RuntimeAction
-android.widget.RemoteViews$SetDrawableParameters
+android.widget.RemoteViews$SetDrawableTint
 android.widget.RemoteViewsAdapter$RemoteAdapterConnectionCallback
 android.widget.RtlSpacingHelper
 android.widget.ScrollBarDrawable
diff --git a/config/preloaded-classes-extra b/config/preloaded-classes-extra
index 959fff5..09f393a 100644
--- a/config/preloaded-classes-extra
+++ b/config/preloaded-classes-extra
@@ -8,6 +8,7 @@
 android.media.SoundPool
 android.text.format.Formatter
 android.text.Html$HtmlParser
+android.util.Log$PreloadHolder
 com.android.org.conscrypt.TrustedCertificateStore
 org.ccil.cowan.tagsoup.HTMLScanner
 sun.security.jca.Providers
diff --git a/core/java/Android.bp b/core/java/Android.bp
new file mode 100644
index 0000000..42b0f6b
--- /dev/null
+++ b/core/java/Android.bp
@@ -0,0 +1,4 @@
+filegroup {
+    name: "IKeyAttestationApplicationIdProvider.aidl",
+    srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
+}
diff --git a/core/java/android/accessibilityservice/GestureDescription.java b/core/java/android/accessibilityservice/GestureDescription.java
index 92567d7..56f4ae2 100644
--- a/core/java/android/accessibilityservice/GestureDescription.java
+++ b/core/java/android/accessibilityservice/GestureDescription.java
@@ -428,6 +428,18 @@
         }
 
         @Override
+        public String toString() {
+            return "TouchPoint{"
+                    + "mStrokeId=" + mStrokeId
+                    + ", mContinuedStrokeId=" + mContinuedStrokeId
+                    + ", mIsStartOfPath=" + mIsStartOfPath
+                    + ", mIsEndOfPath=" + mIsEndOfPath
+                    + ", mX=" + mX
+                    + ", mY=" + mY
+                    + '}';
+        }
+
+        @Override
         public int describeContents() {
             return 0;
         }
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 00d6657..1a2dc5c 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -843,7 +843,7 @@
         // Assumes forward playing from here on.
         for (int i = 0; i < mEvents.size(); i++) {
             AnimationEvent event = mEvents.get(i);
-            if (event.getTime() > currentPlayTime) {
+            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
                 break;
             }
 
@@ -1264,7 +1264,8 @@
         } else {
             for (int i = mLastEventId + 1; i < size; i++) {
                 AnimationEvent event = mEvents.get(i);
-                if (event.getTime() <= currentPlayTime) {
+                // TODO: need a function that accounts for infinite duration to compare time
+                if (event.getTime() != DURATION_INFINITE && event.getTime() <= currentPlayTime) {
                     latestId = i;
                 }
             }
diff --git a/core/java/android/annotation/NavigationRes.java b/core/java/android/annotation/NavigationRes.java
new file mode 100644
index 0000000..3af5ecf
--- /dev/null
+++ b/core/java/android/annotation/NavigationRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to be a navigation resource reference (e.g. {@code R.navigation.flow}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NavigationRes {
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4e258a3..076c2bc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -114,6 +114,7 @@
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.autofill.AutofillPopupWindow;
 import android.view.autofill.IAutofillWindowPresenter;
 import android.widget.AdapterView;
@@ -541,9 +542,9 @@
  * <ul>
  *     <li> <p>When creating a new document, the backing database entry or file for
  *             it is created immediately.  For example, if the user chooses to write
- *             a new e-mail, a new entry for that e-mail is created as soon as they
+ *             a new email, a new entry for that email is created as soon as they
  *             start entering data, so that if they go to any other activity after
- *             that point this e-mail will now appear in the list of drafts.</p>
+ *             that point this email will now appear in the list of drafts.</p>
  *     <li> <p>When an activity's <code>onPause()</code> method is called, it should
  *             commit to the backing content provider or file any changes the user
  *             has made.  This ensures that those changes will be seen by any other
@@ -947,6 +948,18 @@
         return mAutofillManager;
     }
 
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        newBase.setAutofillClient(this);
+    }
+
+    /** @hide */
+    @Override
+    public final AutofillClient getAutofillClient() {
+        return this;
+    }
+
     /**
      * Called when the activity is starting.  This is where most initialization
      * should go: calling {@link #setContentView(int)} to inflate the
@@ -1866,7 +1879,7 @@
 
         if (isFinishing()) {
             if (mAutoFillResetNeeded) {
-                getAutofillManager().commit();
+                getAutofillManager().onActivityFinished();
             } else if (mIntent != null
                     && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
                 // Activity was launched when user tapped a link in the Autofill Save UI - since
@@ -5854,10 +5867,11 @@
     }
 
     /**
-     * Returns complete component name of this activity.
+     * Returns the complete component name of this activity.
      *
      * @return Returns the complete component name for this activity
      */
+    @Override
     public ComponentName getComponentName()
     {
         return mComponent;
@@ -6246,6 +6260,8 @@
         final AutofillManager afm = getAutofillManager();
         if (afm != null) {
             afm.dump(prefix, writer);
+        } else {
+            writer.print(prefix); writer.println("No AutofillManager");
         }
     }
 
@@ -7288,24 +7304,25 @@
     }
 
     /**
-     * Request to put this Activity in a mode where the user is locked to the
-     * current task.
+     * Request to put this activity in a mode where the user is locked to a restricted set of
+     * applications.
      *
-     * This will prevent the user from launching other apps, going to settings, or reaching the
-     * home screen. This does not include those apps whose {@link android.R.attr#lockTaskMode}
-     * values permit launching while locked.
+     * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+     * for this component, the current task will be launched directly into LockTask mode. Only apps
+     * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+     * be launched while LockTask mode is active. The user will not be able to leave this mode
+     * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+     * already in LockTask mode has no effect.
      *
-     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns true or
-     * lockTaskMode=lockTaskModeAlways for this component then the app will go directly into
-     * Lock Task mode. The user will not be able to exit this mode until
-     * {@link Activity#stopLockTask()} is called.
+     * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+     * system will prompt the user with a dialog requesting permission to use this mode.
+     * The user can exit at any time through instructions shown on the request dialog. Calling
+     * {@link #stopLockTask()} will also terminate this mode.
      *
-     * If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns false
-     * then the system will prompt the user with a dialog requesting permission to enter
-     * this mode.  When entered through this method the user can exit at any time through
-     * an action described by the request dialog.  Calling stopLockTask will also exit the
-     * mode.
+     * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+     * That is, between {@link #onResume()} and {@link #onPause()}.
      *
+     * @see #stopLockTask()
      * @see android.R.attr#lockTaskMode
      */
     public void startLockTask() {
@@ -7316,25 +7333,24 @@
     }
 
     /**
-     * Allow the user to switch away from the current task.
+     * Stop the current task from being locked.
      *
-     * Called to end the mode started by {@link Activity#startLockTask}. This
-     * can only be called by activities that have successfully called
-     * startLockTask previously.
+     * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+     * This can only be called by activities that have called {@link #startLockTask()} previously.
      *
-     * This will allow the user to exit this app and move onto other activities.
-     * <p>Note: This method should only be called when the activity is user-facing. That is,
-     * between onResume() and onPause().
-     * <p>Note: If there are other tasks below this one that are also locked then calling this
-     * method will immediately finish this task and resume the previous locked one, remaining in
-     * lockTask mode.
+     * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+     * by this activity, then calling this method will not terminate the LockTask mode, but only
+     * finish its own task. The device will remain in LockTask mode, until the activity which
+     * started the LockTask mode calls this method, or until its whitelist authorization is revoked
+     * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
      *
+     * @see #startLockTask()
      * @see android.R.attr#lockTaskMode
      * @see ActivityManager#getLockTaskModeState()
      */
     public void stopLockTask() {
         try {
-            ActivityManager.getService().stopLockTaskMode();
+            ActivityManager.getService().stopLockTaskModeByToken(mToken);
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 78d05f5..2305957 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,18 +16,8 @@
 
 package android.app;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
 import android.Manifest;
+import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +36,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -55,6 +46,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.BatteryStats;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
@@ -509,7 +501,7 @@
     public static final int PROCESS_STATE_SERVICE = 11;
 
     /** @hide Process is in the background running a receiver.   Note that from the
-     * perspective of oom_adj receivers run at a higher foreground level, but for our
+     * perspective of oom_adj, receivers run at a higher foreground level, but for our
      * prioritization here that is not necessary and putting them below services means
      * many fewer changes in some process states as they receive broadcasts. */
     public static final int PROCESS_STATE_RECEIVER = 12;
@@ -533,6 +525,20 @@
     /** @hide Process does not exist. */
     public static final int PROCESS_STATE_NONEXISTENT = 18;
 
+    // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
+    // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
+    // be updated to correctly map between them.
+    /**
+     * Maps ActivityManager.PROCESS_STATE_ values to ActivityManagerProto.ProcessState enum.
+     *
+     * @param amInt a process state of the form ActivityManager.PROCESS_STATE_
+     * @return the value of the corresponding android.app.ActivityManagerProto's ProcessState enum.
+     * @hide
+     */
+    public static final int processStateAmToProto(int amInt) {
+        return amInt * 100;
+    }
+
     /** @hide The lowest process state number */
     public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;
 
@@ -673,264 +679,24 @@
         /** Invalid stack ID. */
         public static final int INVALID_STACK_ID = -1;
 
-        /** First static stack ID.
-         * @hide */
-        public static final int FIRST_STATIC_STACK_ID = 0;
-
-        /** Home activity stack ID. */
-        public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID;
-
-        /** ID of stack where fullscreen activities are normally launched into. */
-        public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
-
-        /** ID of stack where freeform/resized activities are normally launched into. */
-        public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
-
-        /** ID of stack that occupies a dedicated region of the screen. */
-        public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
-
-        /** ID of stack that always on top (always visible) when it exist. */
-        public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
-
-        /** ID of stack that contains the Recents activity. */
-        public static final int RECENTS_STACK_ID = PINNED_STACK_ID + 1;
-
-        /** ID of stack that contains activities launched by the assistant. */
-        public static final int ASSISTANT_STACK_ID = RECENTS_STACK_ID + 1;
-
-        /** Last static stack stack ID.
-         * @hide */
-        public static final int LAST_STATIC_STACK_ID = ASSISTANT_STACK_ID;
-
-        /** Start of ID range used by stacks that are created dynamically.
-         * @hide */
-        public static final int FIRST_DYNAMIC_STACK_ID = LAST_STATIC_STACK_ID + 1;
-
-        // TODO: Figure-out a way to remove this.
-        /** @hide */
-        public static boolean isStaticStack(int stackId) {
-            return stackId >= FIRST_STATIC_STACK_ID && stackId <= LAST_STATIC_STACK_ID;
-        }
-
-        // TODO: It seems this mostly means a stack on a secondary display now. Need to see if
-        // there are other meanings. If not why not just use information from the display?
-        /** @hide */
-        public static boolean isDynamicStack(int stackId) {
-            return stackId >= FIRST_DYNAMIC_STACK_ID;
-        }
-
-        /**
-         * Returns true if dynamic stacks are allowed to be visible behind the input stack.
-         * @hide
-         */
-        // TODO: Figure-out a way to remove.
-        public static boolean isDynamicStacksVisibleBehindAllowed(int stackId) {
-            return stackId == PINNED_STACK_ID || stackId == ASSISTANT_STACK_ID;
-        }
-
-        /**
-         * Returns true if we try to maintain focus in the current stack when the top activity
-         * finishes.
-         * @hide
-         */
-        // TODO: Figure-out a way to remove. Probably isn't needed in the new world...
-        public static boolean keepFocusInStackIfPossible(int stackId) {
-            return stackId == FREEFORM_WORKSPACE_STACK_ID
-                    || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the input stack is affected by drag resizing.
-         * @hide
-         */
-        public static boolean isStackAffectedByDragResizing(int stackId) {
-            return isStaticStack(stackId) && stackId != PINNED_STACK_ID
-                    && stackId != ASSISTANT_STACK_ID;
-        }
-
-        /**
-         * Returns true if the windows of tasks being moved to the target stack from the source
-         * stack should be replaced, meaning that window manager will keep the old window around
-         * until the new is ready.
-         * @hide
-         */
-        public static boolean replaceWindowsOnTaskMove(int sourceStackId, int targetStackId) {
-            return sourceStackId == FREEFORM_WORKSPACE_STACK_ID
-                    || targetStackId == FREEFORM_WORKSPACE_STACK_ID;
-        }
-
-        /**
-         * Return whether a stackId is a stack that be a backdrop to a translucent activity.  These
-         * are generally fullscreen stacks.
-         * @hide
-         */
-        public static boolean isBackdropToTranslucentActivity(int stackId) {
-            return stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    || stackId == ASSISTANT_STACK_ID;
-        }
-
-        /**
-         * Returns true if activities from stasks in the given {@param stackId} are allowed to
-         * enter picture-in-picture.
-         * @hide
-         */
-        public static boolean isAllowedToEnterPictureInPicture(int stackId) {
-            return stackId != HOME_STACK_ID && stackId != ASSISTANT_STACK_ID &&
-                    stackId != RECENTS_STACK_ID;
-        }
-
-        /**
-         * Returns true if the top task in the task is allowed to return home when finished and
-         * there are other tasks in the stack.
-         * @hide
-         */
-        public static boolean allowTopTaskToReturnHome(int stackId) {
-            return stackId != PINNED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the stack should be resized to match the bounds specified by
-         * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
-         * @hide
-         */
-        public static boolean resizeStackWithLaunchBounds(int stackId) {
-            return stackId == PINNED_STACK_ID;
-        }
-
-        /**
-         * Returns true if a window from the specified stack with {@param stackId} are normally
-         * fullscreen, i. e. they can become the top opaque fullscreen window, meaning that it
-         * controls system bars, lockscreen occluded/dismissing state, screen rotation animation,
-         * etc.
-         * @hide
-         */
-        // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
-        public static boolean normallyFullscreenWindows(int stackId) {
-            return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID
-                    && stackId != DOCKED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the input stack id should only be present on a device that supports
-         * multi-window mode.
-         * @see android.app.ActivityManager#supportsMultiWindow
-         * @hide
-         */
-        // TODO: What about the other side of docked stack if we move this to WindowConfiguration?
-        public static boolean isMultiWindowStack(int stackId) {
-            return stackId == PINNED_STACK_ID || stackId == FREEFORM_WORKSPACE_STACK_ID
-                    || stackId == DOCKED_STACK_ID;
-        }
-
-        /**
-         * Returns true if the input {@param stackId} is HOME_STACK_ID or RECENTS_STACK_ID
-         * @hide
-         */
-        public static boolean isHomeOrRecentsStack(int stackId) {
-            return stackId == HOME_STACK_ID || stackId == RECENTS_STACK_ID;
-        }
-
-        /** Returns true if the input stack and its content can affect the device orientation.
-         * @hide */
-        public static boolean canSpecifyOrientation(int stackId) {
-            return stackId == HOME_STACK_ID
-                    || stackId == RECENTS_STACK_ID
-                    || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    || stackId == ASSISTANT_STACK_ID
-                    || isDynamicStack(stackId);
-        }
-
-        /** Returns the windowing mode that should be used for this input stack id.
-         * @hide */
-        // TODO: To be removed once we are not using stack id for stuff...
-        public static int getWindowingModeForStackId(int stackId, boolean inSplitScreenMode) {
-            final int windowingMode;
-            switch (stackId) {
-                case FULLSCREEN_WORKSPACE_STACK_ID:
-                case HOME_STACK_ID:
-                case RECENTS_STACK_ID:
-                    windowingMode = inSplitScreenMode
-                            ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_FULLSCREEN;
-                    break;
-                case ASSISTANT_STACK_ID:
-                    windowingMode = WINDOWING_MODE_FULLSCREEN;
-                    break;
-                case PINNED_STACK_ID:
-                    windowingMode = WINDOWING_MODE_PINNED;
-                    break;
-                case DOCKED_STACK_ID:
-                    windowingMode = WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-                    break;
-                case FREEFORM_WORKSPACE_STACK_ID:
-                    windowingMode = WINDOWING_MODE_FREEFORM;
-                    break;
-                default :
-                    windowingMode = WINDOWING_MODE_UNDEFINED;
-            }
-            return windowingMode;
-        }
-
-        /** Returns the stack id for the input windowing mode.
-         * @hide */
-        // TODO: To be removed once we are not using stack id for stuff...
-        public static int getStackIdForWindowingMode(int windowingMode) {
-            switch (windowingMode) {
-                case WINDOWING_MODE_PINNED: return PINNED_STACK_ID;
-                case WINDOWING_MODE_FREEFORM: return FREEFORM_WORKSPACE_STACK_ID;
-                case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return DOCKED_STACK_ID;
-                default: return INVALID_STACK_ID;
-            }
-        }
-
-        /** Returns the activity type that should be used for this input stack id.
-         * @hide */
-        // TODO: To be removed once we are not using stack id for stuff...
-        public static int getActivityTypeForStackId(int stackId) {
-            final int activityType;
-            switch (stackId) {
-                case HOME_STACK_ID:
-                    activityType = ACTIVITY_TYPE_HOME;
-                    break;
-                case RECENTS_STACK_ID:
-                    activityType = ACTIVITY_TYPE_RECENTS;
-                    break;
-                case ASSISTANT_STACK_ID:
-                    activityType = ACTIVITY_TYPE_ASSISTANT;
-                    break;
-                default :
-                    activityType = ACTIVITY_TYPE_STANDARD;
-            }
-            return activityType;
-        }
-
-        /** Returns the stack id for the input activity type.
-         * @hide */
-        // TODO: To be removed once we are not using stack id for stuff...
-        public static int getStackIdForActivityType(int activityType) {
-            switch (activityType) {
-                case ACTIVITY_TYPE_HOME: return HOME_STACK_ID;
-                case ACTIVITY_TYPE_RECENTS: return RECENTS_STACK_ID;
-                case ACTIVITY_TYPE_ASSISTANT: return ASSISTANT_STACK_ID;
-                default: return INVALID_STACK_ID;
-            }
-        }
     }
 
     /**
-     * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
-     * specifies the position of the created docked stack at the top half of the screen if
+     * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+     * which specifies the position of the created docked stack at the top half of the screen if
      * in portrait mode or at the left half of the screen if in landscape mode.
      * @hide
      */
-    public static final int DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT = 0;
+    public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
 
     /**
-     * Input parameter to {@link android.app.IActivityManager#moveTaskToDockedStack} which
+     * Parameter to {@link android.app.IActivityManager#setTaskWindowingModeSplitScreenPrimary}
+     * which
      * specifies the position of the created docked stack at the bottom half of the screen if
      * in portrait mode or at the right half of the screen if in landscape mode.
      * @hide
      */
-    public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
+    public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
 
     /**
      * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
@@ -1083,9 +849,8 @@
     /**
      * Returns true if this is a low-RAM device.  Exactly whether a device is low-RAM
      * is ultimately up to the device configuration, but currently it generally means
-     * something in the class of a 512MB device with about a 800x480 or less screen.
-     * This is mostly intended to be used by apps to determine whether they should turn
-     * off certain features that require more RAM.
+     * something with 1GB or less of RAM.  This is mostly intended to be used by apps
+     * to determine whether they should turn off certain features that require more RAM.
      */
     public boolean isLowRamDevice() {
         return isLowRamDeviceStatic();
@@ -1152,6 +917,7 @@
      * E.g. freeform, split-screen, picture-in-picture.
      * @hide
      */
+    @TestApi
     static public boolean supportsMultiWindow(Context context) {
         // On watches, multi-window is used to present essential system UI, and thus it must be
         // supported regardless of device memory characteristics.
@@ -1166,6 +932,7 @@
      * Returns true if the system supports split screen multi-window.
      * @hide
      */
+    @TestApi
     static public boolean supportsSplitScreenMultiWindow(Context context) {
         return supportsMultiWindow(context)
                 && Resources.getSystem().getBoolean(
@@ -1190,11 +957,14 @@
                 ATTR_TASKDESCRIPTION_PREFIX + "color";
         private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
                 ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
-        private static final String ATTR_TASKDESCRIPTIONICONFILENAME =
+        private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
                 ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+        private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
+                ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
 
         private String mLabel;
         private Bitmap mIcon;
+        private int mIconRes;
         private String mIconFilename;
         private int mColorPrimary;
         private int mColorBackground;
@@ -1208,9 +978,27 @@
          * @param icon An icon that represents the current state of this task.
          * @param colorPrimary A color to override the theme's primary color.  This color must be
          *                     opaque.
+         * @deprecated use TaskDescription constructor with icon resource instead
          */
+        @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
-            this(label, icon, null, colorPrimary, 0, 0, 0);
+            this(label, icon, 0, null, colorPrimary, 0, 0, 0);
+            if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+                throw new RuntimeException("A TaskDescription's primary color should be opaque");
+            }
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this task.
+         * @param iconRes A drawable resource of an icon that represents the current state of this
+         *                activity.
+         * @param colorPrimary A color to override the theme's primary color.  This color must be
+         *                     opaque.
+         */
+        public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
+            this(label, null, iconRes, null, colorPrimary, 0, 0, 0);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1221,9 +1009,22 @@
          *
          * @param label A label and description of the current state of this activity.
          * @param icon An icon that represents the current state of this activity.
+         * @deprecated use TaskDescription constructor with icon resource instead
          */
+        @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon, null, 0, 0, 0, 0);
+            this(label, icon, 0, null, 0, 0, 0, 0);
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this activity.
+         * @param iconRes A drawable resource of an icon that represents the current state of this
+         *                activity.
+         */
+        public TaskDescription(String label, @DrawableRes int iconRes) {
+            this(label, null, iconRes, null, 0, 0, 0, 0);
         }
 
         /**
@@ -1232,21 +1033,22 @@
          * @param label A label and description of the current state of this activity.
          */
         public TaskDescription(String label) {
-            this(label, null, null, 0, 0, 0, 0);
+            this(label, null, 0, null, 0, 0, 0, 0);
         }
 
         /**
          * Creates an empty TaskDescription.
          */
         public TaskDescription() {
-            this(null, null, null, 0, 0, 0, 0);
+            this(null, null, 0, null, 0, 0, 0, 0);
         }
 
         /** @hide */
-        public TaskDescription(String label, Bitmap icon, String iconFilename, int colorPrimary,
-                int colorBackground, int statusBarColor, int navigationBarColor) {
+        public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
+                int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) {
             mLabel = label;
-            mIcon = icon;
+            mIcon = bitmap;
+            mIconRes = iconRes;
             mIconFilename = iconFilename;
             mColorPrimary = colorPrimary;
             mColorBackground = colorBackground;
@@ -1268,6 +1070,7 @@
         public void copyFrom(TaskDescription other) {
             mLabel = other.mLabel;
             mIcon = other.mIcon;
+            mIconRes = other.mIconRes;
             mIconFilename = other.mIconFilename;
             mColorPrimary = other.mColorPrimary;
             mColorBackground = other.mColorBackground;
@@ -1283,6 +1086,7 @@
         public void copyFromPreserveHiddenFields(TaskDescription other) {
             mLabel = other.mLabel;
             mIcon = other.mIcon;
+            mIconRes = other.mIconRes;
             mIconFilename = other.mIconFilename;
             mColorPrimary = other.mColorPrimary;
             if (other.mColorBackground != 0) {
@@ -1355,6 +1159,14 @@
         }
 
         /**
+         * Sets the icon resource for this task description.
+         * @hide
+         */
+        public void setIcon(int iconRes) {
+            mIconRes = iconRes;
+        }
+
+        /**
          * Moves the icon bitmap reference from an actual Bitmap to a file containing the
          * bitmap.
          * @hide
@@ -1382,6 +1194,13 @@
         }
 
         /** @hide */
+        @TestApi
+        public int getIconResource() {
+            return mIconRes;
+        }
+
+        /** @hide */
+        @TestApi
         public String getIconFilename() {
             return mIconFilename;
         }
@@ -1447,7 +1266,10 @@
                         Integer.toHexString(mColorBackground));
             }
             if (mIconFilename != null) {
-                out.attribute(null, ATTR_TASKDESCRIPTIONICONFILENAME, mIconFilename);
+                out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
+            }
+            if (mIconRes != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes));
             }
         }
 
@@ -1459,8 +1281,10 @@
                 setPrimaryColor((int) Long.parseLong(attrValue, 16));
             } else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) {
                 setBackgroundColor((int) Long.parseLong(attrValue, 16));
-            } else if (ATTR_TASKDESCRIPTIONICONFILENAME.equals(attrName)) {
+            } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) {
                 setIconFilename(attrValue);
+            } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) {
+                setIcon(Integer.parseInt(attrValue, 10));
             }
         }
 
@@ -1483,6 +1307,7 @@
                 dest.writeInt(1);
                 mIcon.writeToParcel(dest, 0);
             }
+            dest.writeInt(mIconRes);
             dest.writeInt(mColorPrimary);
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
@@ -1498,6 +1323,7 @@
         public void readFromParcel(Parcel source) {
             mLabel = source.readInt() > 0 ? source.readString() : null;
             mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+            mIconRes = source.readInt();
             mColorPrimary = source.readInt();
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
@@ -1518,8 +1344,8 @@
         @Override
         public String toString() {
             return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
-                    " IconFilename: " + mIconFilename + " colorPrimary: " + mColorPrimary +
-                    " colorBackground: " + mColorBackground +
+                    " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
+                    " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
                     " statusBarColor: " + mColorBackground +
                     " navigationBarColor: " + mNavigationBarColor;
         }
@@ -1645,6 +1471,12 @@
          */
         public int resizeMode;
 
+        /**
+         * The current configuration this task is in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
+
         public RecentTaskInfo() {
         }
 
@@ -1675,7 +1507,6 @@
             }
             dest.writeInt(stackId);
             dest.writeInt(userId);
-            dest.writeLong(firstActiveTime);
             dest.writeLong(lastActiveTime);
             dest.writeInt(affiliatedTaskId);
             dest.writeInt(affiliatedTaskColor);
@@ -1690,6 +1521,7 @@
             }
             dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
             dest.writeInt(resizeMode);
+            configuration.writeToParcel(dest, flags);
         }
 
         public void readFromParcel(Parcel source) {
@@ -1703,7 +1535,6 @@
                     TaskDescription.CREATOR.createFromParcel(source) : null;
             stackId = source.readInt();
             userId = source.readInt();
-            firstActiveTime = source.readLong();
             lastActiveTime = source.readLong();
             affiliatedTaskId = source.readInt();
             affiliatedTaskColor = source.readInt();
@@ -1714,6 +1545,7 @@
                     Rect.CREATOR.createFromParcel(source) : null;
             supportsSplitScreenMultiWindow = source.readInt() == 1;
             resizeMode = source.readInt();
+            configuration.readFromParcel(source);
         }
 
         public static final Creator<RecentTaskInfo> CREATOR
@@ -1745,31 +1577,6 @@
     public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
 
     /**
-     * Provides a list that contains recent tasks for all
-     * profiles of a user.
-     * @hide
-     */
-    public static final int RECENT_INCLUDE_PROFILES = 0x0004;
-
-    /**
-     * Ignores all tasks that are on the home stack.
-     * @hide
-     */
-    public static final int RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS = 0x0008;
-
-    /**
-     * Ignores the top task in the docked stack.
-     * @hide
-     */
-    public static final int RECENT_INGORE_DOCKED_STACK_TOP_TASK = 0x0010;
-
-    /**
-     * Ignores all tasks that are on the pinned stack.
-     * @hide
-     */
-    public static final int RECENT_INGORE_PINNED_STACK_TASKS = 0x0020;
-
-    /**
      * <p></p>Return a list of the tasks that the user has recently launched, with
      * the most recent being first and older ones after in order.
      *
@@ -1804,33 +1611,10 @@
     public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
             throws SecurityException {
         try {
-            return getService().getRecentTasks(maxNum,
-                    flags, UserHandle.myUserId()).getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a
-     * specific user. It requires holding
-     * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
-     * @param maxNum The maximum number of entries to return in the list.  The
-     * actual number returned may be smaller, depending on how many tasks the
-     * user has started and the maximum number the system can remember.
-     * @param flags Information about what to return.  May be any combination
-     * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
-     *
-     * @return Returns a list of RecentTaskInfo records describing each of
-     * the recent tasks. Most recently activated tasks go first.
-     *
-     * @hide
-     */
-    public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId)
-            throws SecurityException {
-        try {
-            return getService().getRecentTasks(maxNum,
-                    flags, userId).getList();
+            if (maxNum < 0) {
+                throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+            }
+            return getService().getRecentTasks(maxNum, flags, UserHandle.myUserId()).getList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1908,6 +1692,12 @@
          */
         public int resizeMode;
 
+        /**
+         * The full configuration the task is currently running in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
+
         public RunningTaskInfo() {
         }
 
@@ -1932,6 +1722,7 @@
             dest.writeInt(numRunning);
             dest.writeInt(supportsSplitScreenMultiWindow ? 1 : 0);
             dest.writeInt(resizeMode);
+            configuration.writeToParcel(dest, flags);
         }
 
         public void readFromParcel(Parcel source) {
@@ -1949,6 +1740,7 @@
             numRunning = source.readInt();
             supportsSplitScreenMultiWindow = source.readInt() != 0;
             resizeMode = source.readInt();
+            configuration.readFromParcel(source);
         }
 
         public static final Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
@@ -2108,23 +1900,74 @@
     public List<RunningTaskInfo> getRunningTasks(int maxNum)
             throws SecurityException {
         try {
-            return getService().getTasks(maxNum, 0);
+            return getService().getTasks(maxNum);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Completely remove the given task.
-     *
-     * @param taskId Identifier of the task to be removed.
-     * @return Returns true if the given task was found and removed.
+     * Sets the windowing mode for a specific task. Only works on tasks of type
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}
+     * @param taskId The id of the task to set the windowing mode for.
+     * @param windowingMode The windowing mode to set for the task.
+     * @param toTop If the task should be moved to the top once the windowing mode changes.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop)
+            throws SecurityException {
+        try {
+            getService().setTaskWindowingMode(taskId, windowingMode, toTop);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resizes the input stack id to the given bounds.
+     * @param stackId Id of the stack to resize.
+     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void resizeStack(int stackId, Rect bounds) throws SecurityException {
+        try {
+            getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */,
+                    false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes stacks in the windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
      *
      * @hide
      */
-    public boolean removeTask(int taskId) throws SecurityException {
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void removeStacksInWindowingModes(int[] windowingModes) throws SecurityException {
         try {
-            return getService().removeTask(taskId);
+            getService().removeStacksInWindowingModes(windowingModes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes stack of the activity types from the system.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void removeStacksWithActivityTypes(int[] activityTypes) throws SecurityException {
+        try {
+            getService().removeStacksWithActivityTypes(activityTypes);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2608,6 +2451,11 @@
         public boolean visible;
         // Index of the stack in the display's stack list, can be used for comparison of stack order
         public int position;
+        /**
+         * The full configuration the stack is currently running in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
 
         @Override
         public int describeContents() {
@@ -2642,6 +2490,7 @@
             } else {
                 dest.writeInt(0);
             }
+            configuration.writeToParcel(dest, flags);
         }
 
         public void readFromParcel(Parcel source) {
@@ -2669,6 +2518,7 @@
             if (source.readInt() > 0) {
                 topActivity = ComponentName.readFromParcel(source);
             }
+            configuration.readFromParcel(source);
         }
 
         public static final Creator<StackInfo> CREATOR = new Creator<StackInfo>() {
@@ -2696,6 +2546,8 @@
                     sb.append(" displayId="); sb.append(displayId);
                     sb.append(" userId="); sb.append(userId);
                     sb.append("\n");
+                    sb.append(" configuration="); sb.append(configuration);
+                    sb.append("\n");
             prefix = prefix + "  ";
             for (int i = 0; i < taskIds.length; ++i) {
                 sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]);
@@ -4048,21 +3900,36 @@
         IBinder service = ServiceManager.checkService(name);
         if (service == null) {
             pw.println("  (Service not found)");
+            pw.flush();
             return;
         }
-        TransferPipe tp = null;
-        try {
-            pw.flush();
-            tp = new TransferPipe();
-            tp.setBufferPrefix("  ");
-            service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
-            tp.go(fd, 10000);
-        } catch (Throwable e) {
-            if (tp != null) {
-                tp.kill();
+        pw.flush();
+        if (service instanceof Binder) {
+            // If this is a local object, it doesn't make sense to do an async dump with it,
+            // just directly dump.
+            try {
+                service.dump(fd, args);
+            } catch (Throwable e) {
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+                pw.flush();
             }
-            pw.println("Failure dumping service:");
-            e.printStackTrace(pw);
+        } else {
+            // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+            TransferPipe tp = null;
+            try {
+                pw.flush();
+                tp = new TransferPipe();
+                tp.setBufferPrefix("  ");
+                service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+                tp.go(fd, 10000);
+            } catch (Throwable e) {
+                if (tp != null) {
+                    tp.kill();
+                }
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+            }
         }
     }
 
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 9d14f61..a46b3c7 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -64,6 +64,27 @@
     public static final int APP_TRANSITION_SNAPSHOT = 4;
 
     /**
+     * The bundle key to extract the assist data.
+     */
+    public static final String ASSIST_KEY_DATA = "data";
+
+    /**
+     * The bundle key to extract the assist structure.
+     */
+    public static final String ASSIST_KEY_STRUCTURE = "structure";
+
+    /**
+     * The bundle key to extract the assist content.
+     */
+    public static final String ASSIST_KEY_CONTENT = "content";
+
+    /**
+     * The bundle key to extract the assist receiver extras.
+     */
+    public static final String ASSIST_KEY_RECEIVER_EXTRAS = "receiverExtras";
+
+
+    /**
      * Grant Uri permissions from one app to another. This method only extends
      * permission grants if {@code callingUid} has permission to them.
      */
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index a68c3a5..4a21f5c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -16,14 +16,14 @@
 
 package android.app;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -159,6 +159,12 @@
     private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
 
     /**
+     * Whether the activity should be launched into LockTask mode.
+     * @see #setLockTaskMode(boolean)
+     */
+    private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
+
+    /**
      * The display id the activity should be launched into.
      * @see #setLaunchDisplayId(int)
      * @hide
@@ -197,10 +203,11 @@
             "android.activity.taskOverlayCanResume";
 
     /**
-     * Where the docked stack should be positioned.
+     * Where the split-screen-primary stack should be positioned.
      * @hide
      */
-    private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
+    private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
+            "android:activity.splitScreenCreateMode";
 
     /**
      * Determines whether to disallow the outgoing activity from entering picture-in-picture as the
@@ -279,13 +286,14 @@
     private int mResultCode;
     private int mExitCoordinatorIndex;
     private PendingIntent mUsageTimeReport;
+    private boolean mLockTaskMode = false;
     private int mLaunchDisplayId = INVALID_DISPLAY;
     @WindowConfiguration.WindowingMode
     private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
     @WindowConfiguration.ActivityType
     private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
     private int mLaunchTaskId = -1;
-    private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+    private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     private boolean mDisallowEnterPictureInPictureWhileLaunching;
     private boolean mTaskOverlay;
     private boolean mTaskOverlayCanResume;
@@ -870,13 +878,15 @@
                 mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
                 break;
         }
+        mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
         mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
         mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
         mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
         mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
         mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
         mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
-        mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+        mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
+                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
         mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
                 KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
         if (opts.containsKey(KEY_ANIM_SPECS)) {
@@ -1056,6 +1066,37 @@
     }
 
     /**
+     * Gets whether the activity is to be launched into LockTask mode.
+     * @return {@code true} if the activity is to be launched into LockTask mode.
+     * @see Activity#startLockTask()
+     * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+     */
+    public boolean getLockTaskMode() {
+        return mLockTaskMode;
+    }
+
+    /**
+     * Sets whether the activity is to be launched into LockTask mode.
+     *
+     * Use this option to start an activity in LockTask mode. Note that only apps permitted by
+     * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if
+     * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns
+     * {@code false} for the package of the target activity, a {@link SecurityException} will be
+     * thrown during {@link Context#startActivity(Intent, Bundle)}.
+     *
+     * Defaults to {@code false} if not set.
+     *
+     * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode.
+     * @return {@code this} {@link ActivityOptions} instance.
+     * @see Activity#startLockTask()
+     * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+     */
+    public ActivityOptions setLockTaskMode(boolean lockTaskMode) {
+        mLockTaskMode = lockTaskMode;
+        return this;
+    }
+
+    /**
      * Gets the id of the display where activity should be launched.
      * @return The id of the display where activity should be launched,
      *         {@link android.view.Display#INVALID_DISPLAY} if not set.
@@ -1155,13 +1196,13 @@
     }
 
     /** @hide */
-    public int getDockCreateMode() {
-        return mDockCreateMode;
+    public int getSplitScreenCreateMode() {
+        return mSplitScreenCreateMode;
     }
 
     /** @hide */
-    public void setDockCreateMode(int dockCreateMode) {
-        mDockCreateMode = dockCreateMode;
+    public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+        mSplitScreenCreateMode = splitScreenCreateMode;
     }
 
     /** @hide */
@@ -1248,6 +1289,7 @@
                 mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
                 break;
         }
+        mLockTaskMode = otherOptions.mLockTaskMode;
         mAnimSpecs = otherOptions.mAnimSpecs;
         mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
         mSpecsFuture = otherOptions.mSpecsFuture;
@@ -1322,13 +1364,14 @@
                 b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
                 break;
         }
+        b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
         b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
         b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
         b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
         b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
         b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
         b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
-        b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+        b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
         b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
                 mDisallowEnterPictureInPictureWhileLaunching);
         if (mAnimSpecs != null) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4e8d240..2516a3e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5281,7 +5281,7 @@
                                 final ApplicationInfo aInfo =
                                         sPackageManager.getApplicationInfo(
                                                 packageName,
-                                                0 /*flags*/,
+                                                PackageManager.GET_SHARED_LIBRARY_FILES,
                                                 UserHandle.myUserId());
 
                                 if (mActivities.size() > 0) {
@@ -5780,7 +5780,7 @@
                 final int preloadedFontsResource = info.metaData.getInt(
                         ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
                 if (preloadedFontsResource != 0) {
-                    data.info.mResources.preloadFonts(preloadedFontsResource);
+                    data.info.getResources().preloadFonts(preloadedFontsResource);
                 }
             }
         } catch (RemoteException e) {
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 2813e8b..55f9e28 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -33,6 +33,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import libcore.util.ZoneInfoDB;
 
@@ -48,7 +49,7 @@
  * if it is not already running.  Registered alarms are retained while the
  * device is asleep (and can optionally wake the device up if they go off
  * during that time), but will be cleared if it is turned off and rebooted.
- * 
+ *
  * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
  * onReceive() method is executing. This guarantees that the phone will not sleep
  * until you have finished handling the broadcast. Once onReceive() returns, the
@@ -296,7 +297,7 @@
      * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
      * how many past alarm events have been accumulated into this intent
      * broadcast.  Recurring alarms that have gone undelivered because the
-     * phone was asleep may have a count greater than one when delivered.  
+     * phone was asleep may have a count greater than one when delivered.
      *
      * <div class="note">
      * <p>
@@ -396,10 +397,10 @@
      * set a recurring alarm for the top of every hour but the phone was asleep
      * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
      * then the next alarm will be sent at 9:00.
-     * 
-     * <p>If your application wants to allow the delivery times to drift in 
+     *
+     * <p>If your application wants to allow the delivery times to drift in
      * order to guarantee that at least a certain time interval always elapses
-     * between alarms, then the approach to take is to use one-time alarms, 
+     * between alarms, then the approach to take is to use one-time alarms,
      * scheduling the next one yourself when handling each alarm delivery.
      *
      * <p class="note">
@@ -1056,7 +1057,7 @@
         /**
          * Creates a new alarm clock description.
          *
-         * @param triggerTime time at which the underlying alarm is triggered in wall time 
+         * @param triggerTime time at which the underlying alarm is triggered in wall time
          *                    milliseconds since the epoch
          * @param showIntent an intent that can be used to show or edit details of
          *                        the alarm clock.
@@ -1089,7 +1090,7 @@
          * Returns an intent that can be used to show or edit details of the alarm clock in
          * the application that scheduled it.
          *
-         * <p class="note">Beware that any application can retrieve and send this intent, 
+         * <p class="note">Beware that any application can retrieve and send this intent,
          * potentially with additional fields filled in. See
          * {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
          * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
@@ -1121,5 +1122,13 @@
                 return new AlarmClockInfo[size];
             }
         };
+
+        /** @hide */
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+            mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT);
+            proto.end(token);
+        }
     }
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4bd85ae..b6fb120 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -254,8 +254,10 @@
     public static final int OP_ANSWER_PHONE_CALLS = 69;
     /** @hide Run jobs when in background */
     public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
+    /** @hide Change Wi-Fi connectivity state */
+    public static final int OP_CHANGE_WIFI_STATE = 71;
     /** @hide */
-    public static final int _NUM_OP = 71;
+    public static final int _NUM_OP = 72;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -496,6 +498,7 @@
             OP_INSTANT_APP_START_FOREGROUND,
             OP_ANSWER_PHONE_CALLS,
             OP_RUN_ANY_IN_BACKGROUND,
+            OP_CHANGE_WIFI_STATE,
     };
 
     /**
@@ -574,6 +577,7 @@
             OPSTR_INSTANT_APP_START_FOREGROUND,
             OPSTR_ANSWER_PHONE_CALLS,
             null, // OP_RUN_ANY_IN_BACKGROUND
+            null, // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -652,6 +656,7 @@
             "INSTANT_APP_START_FOREGROUND",
             "ANSWER_PHONE_CALLS",
             "RUN_ANY_IN_BACKGROUND",
+            "CHANGE_WIFI_STATE",
     };
 
     /**
@@ -730,6 +735,7 @@
             Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
             Manifest.permission.ANSWER_PHONE_CALLS,
             null, // no permission for OP_RUN_ANY_IN_BACKGROUND
+            Manifest.permission.CHANGE_WIFI_STATE,
     };
 
     /**
@@ -809,6 +815,7 @@
             null, // INSTANT_APP_START_FOREGROUND
             null, // ANSWER_PHONE_CALLS
             null, // OP_RUN_ANY_IN_BACKGROUND
+            null, // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -887,6 +894,7 @@
             false, // INSTANT_APP_START_FOREGROUND
             false, // ANSWER_PHONE_CALLS
             false, // OP_RUN_ANY_IN_BACKGROUND
+            false, // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -964,6 +972,7 @@
             AppOpsManager.MODE_DEFAULT,  // OP_INSTANT_APP_START_FOREGROUND
             AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
             AppOpsManager.MODE_ALLOWED,  // OP_RUN_ANY_IN_BACKGROUND
+            AppOpsManager.MODE_ALLOWED,  // OP_CHANGE_WIFI_STATE
     };
 
     /**
@@ -1045,6 +1054,7 @@
             false,
             false, // ANSWER_PHONE_CALLS
             false, // OP_RUN_ANY_IN_BACKGROUND
+            false, // OP_CHANGE_WIFI_STATE
     };
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c48be77..5f34322 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -74,6 +74,7 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -185,6 +186,8 @@
     // The name of the split this Context is representing. May be null.
     private @Nullable String mSplitName = null;
 
+    private AutofillClient mAutofillClient = null;
+
     private final Object mSync = new Object();
 
     @GuardedBy("mSync")
@@ -2225,6 +2228,18 @@
         return mUser.getIdentifier();
     }
 
+    /** @hide */
+    @Override
+    public AutofillClient getAutofillClient() {
+        return mAutofillClient;
+    }
+
+    /** @hide */
+    @Override
+    public void setAutofillClient(AutofillClient client) {
+        mAutofillClient = client;
+    }
+
     static ContextImpl createSystemContext(ActivityThread mainThread) {
         LoadedApk packageInfo = new LoadedApk(mainThread);
         ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index eccb264..4b31b80 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -63,6 +63,7 @@
 import android.os.PersistableBundle;
 import android.os.StrictMode;
 import android.service.voice.IVoiceInteractionSession;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -82,7 +83,7 @@
     // below block of transactions.
 
     // Since these transactions are also called from native code, these must be kept in sync with
-    // the ones in frameworks/native/include/binder/IActivityManager.h
+    // the ones in frameworks/native/libs/binder/include/binder/IActivityManager.h
     // =============== Beginning of transactions used on native side as well ======================
     ParcelFileDescriptor openContentUri(in String uriString);
     // =============== End of transactions used on native side as well ============================
@@ -115,7 +116,9 @@
             in PersistableBundle persistentState, in CharSequence description);
     String getCallingPackage(in IBinder token);
     ComponentName getCallingActivity(in IBinder token);
-    List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags);
+    List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
+    List<ActivityManager.RunningTaskInfo> getFilteredTasks(int maxNum, int ignoreActivityType,
+            int ignoreWindowingMode);
     void moveTaskToFront(int task, int flags, in Bundle options);
     void moveTaskBackwards(int task);
     int getTaskForActivity(in IBinder token, in boolean onlyRoot);
@@ -178,8 +181,8 @@
      * SIGUSR1 is delivered. All others are ignored.
      */
     void signalPersistentProcesses(int signal);
-    ParceledListSlice getRecentTasks(int maxNum,
-            int flags, int userId);
+
+    ParceledListSlice getRecentTasks(int maxNum, int flags, int userId);
     oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res);
     oneway void activityDestroyed(in IBinder token);
     IIntentSender getIntentSender(int type, in String packageName, in IBinder token,
@@ -361,6 +364,15 @@
     void killUid(int appId, int userId, in String reason);
     void setUserIsMonkey(boolean monkey);
     void hang(in IBinder who, boolean allowRestart);
+
+    /**
+     * Sets the windowing mode for a specific task. Only works on tasks of type
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}
+     * @param taskId The id of the task to set the windowing mode for.
+     * @param windowingMode The windowing mode to set for the task.
+     * @param toTop If the task should be moved to the top once the windowing mode changes.
+     */
+    void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop);
     void moveTaskToStack(int taskId, int stackId, boolean toTop);
     /**
      * Resizes the input stack id to the given bounds.
@@ -380,7 +392,8 @@
             boolean preserveWindows, boolean animate, int animationDuration);
     List<ActivityManager.StackInfo> getAllStackInfos();
     void setFocusedStack(int stackId);
-    ActivityManager.StackInfo getStackInfo(int stackId);
+    ActivityManager.StackInfo getFocusedStackInfo();
+    ActivityManager.StackInfo getStackInfo(int windowingMode, int activityType);
     boolean convertFromTranslucent(in IBinder token);
     boolean convertToTranslucent(in IBinder token, in Bundle options);
     void notifyActivityDrawn(in IBinder token);
@@ -400,7 +413,7 @@
     String getTagForIntentSender(in IIntentSender sender, in String prefix);
     boolean startUserInBackground(int userid);
     void startLockTaskModeByToken(in IBinder token);
-    void stopLockTaskMode();
+    void stopLockTaskModeByToken(in IBinder token);
     boolean isInLockTaskMode();
     void setTaskDescription(in IBinder token, in ActivityManager.TaskDescription values);
     int startVoiceActivity(in String callingPackage, int callingPid, int callingUid,
@@ -409,6 +422,9 @@
             in Bundle options, int userId);
     int startAssistantActivity(in String callingPackage, int callingPid, int callingUid,
             in Intent intent, in String resolvedType, in Bundle options, int userId);
+    int startRecentsActivity(in IAssistDataReceiver assistDataReceiver, in Bundle options,
+            int userId);
+    int startActivityFromRecents(int taskId, in Bundle options);
     Bundle getActivityOptions(in IBinder token);
     List<IBinder> getAppTasks(in String callingPackage);
     void startSystemLockTaskMode(int taskId);
@@ -416,7 +432,6 @@
     void finishVoiceTask(in IVoiceInteractionSession session);
     boolean isTopOfTask(in IBinder token);
     void notifyLaunchTaskBehindComplete(in IBinder token);
-    int startActivityFromRecents(int taskId, in Bundle options);
     void notifyEnterAnimationComplete(in IBinder token);
     int startActivityAsCaller(in IApplicationThread caller, in String callingPackage,
             in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
@@ -440,9 +455,8 @@
     // Start of M transactions
     void notifyCleartextNetwork(int uid, in byte[] firstPacket);
     int createStackOnDisplay(int displayId);
-    int getFocusedStackId();
     void setTaskResizeable(int taskId, int resizeableMode);
-    boolean requestAssistContextExtras(int requestType, in IResultReceiver receiver,
+    boolean requestAssistContextExtras(int requestType, in IAssistDataReceiver receiver,
             in Bundle receiverExtras, in IBinder activityToken,
             boolean focused, boolean newSessionId);
     void resizeTask(int taskId, in Rect bounds, int resizeMode);
@@ -488,8 +502,20 @@
     void exitFreeformMode(in IBinder token);
     void reportSizeConfigurations(in IBinder token, in int[] horizontalSizeConfiguration,
             in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations);
-    boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
-            in Rect initialBounds);
+    boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+            boolean animate, in Rect initialBounds);
+    /**
+     * Dismisses split-screen multi-window mode.
+     * {@param toTop} If true the current primary split-screen stack will be placed or left on top.
+     */
+    void dismissSplitScreenMode(boolean toTop);
+    /**
+     * Dismisses PiP
+     * @param animate True if the dismissal should be animated.
+     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
+     *                          default animation duration should be used.
+     */
+    void dismissPip(boolean animate, int animationDuration);
     void suppressResizeConfigChanges(boolean suppress);
     void moveTasksToFullscreenStack(int fromStackId, boolean onTop);
     boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
@@ -540,6 +566,13 @@
     void notifyPinnedStackAnimationStarted();
     void notifyPinnedStackAnimationEnded();
     void removeStack(int stackId);
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(in int[] windowingModes);
+    /** Removes stack of the activity types from the system. */
+    void removeStacksWithActivityTypes(in int[] activityTypes);
     void makePackageIdle(String packageName, int userId);
     int getMemoryTrimLevel();
     /**
@@ -587,7 +620,7 @@
     boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
     void unregisterTaskStackListener(ITaskStackListener listener);
     void moveStackToDisplay(int stackId, int displayId);
-    boolean requestAutofillData(in IResultReceiver receiver, in Bundle receiverExtras,
+    boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
                                 in IBinder activityToken, int flags);
     void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
     int restartUserInBackground(int userId);
@@ -624,7 +657,10 @@
     /**
      * Add a bare uid to the background restrictions whitelist.  Only the system uid may call this.
      */
-     void backgroundWhitelistUid(int uid);
+    void backgroundWhitelistUid(int uid);
+
+    // Start of P transactions
+    void updateLockTaskFeatures(int userId, int flags);
 
     // WARNING: when these transactions are updated, check if they are any callers on the native
     // side. If so, make sure they are using the correct transaction ids and arguments.
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index d4752a7..9e926bd 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -138,6 +138,7 @@
     void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
     boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
     void setNotificationPolicyAccessGranted(String pkg, boolean granted);
+    void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted);
     AutomaticZenRule getAutomaticZenRule(String id);
     List<ZenModeConfig.ZenRule> getZenRules();
     String addAutomaticZenRule(in AutomaticZenRule automaticZenRule);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index a56965b..2e1e988 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -30,7 +30,7 @@
     void onTaskStackChanged();
 
     /** Called whenever an Activity is moved to the pinned stack from another stack. */
-    void onActivityPinned(String packageName, int userId, int taskId);
+    void onActivityPinned(String packageName, int userId, int taskId, int stackId);
 
     /** Called whenever an Activity is moved from the pinned stack to another stack. */
     void onActivityUnpinned();
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index 76643d6..1fe2900 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -174,7 +174,7 @@
      */
     public Intent createConfirmFactoryResetCredentialIntent(
             CharSequence title, CharSequence description, CharSequence alternateButtonLabel) {
-        if (!LockPatternUtils.frpCredentialEnabled()) {
+        if (!LockPatternUtils.frpCredentialEnabled(mContext)) {
             Log.w(TAG, "Factory reset credentials not supported.");
             return null;
         }
@@ -387,8 +387,6 @@
      * such as the Home key and the right soft keys, don't work.
      *
      * @return true if in keyguard restricted input mode.
-     *
-     * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode
      */
     public boolean inKeyguardRestrictedInputMode() {
         try {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 60f4ed0..8226e0f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -858,7 +858,7 @@
      *
      * @hide
      */
-    static public IBinder whitelistToken;
+    private IBinder mWhitelistToken;
 
     /**
      * Must be set by a process to start associating tokens with Notification objects
@@ -1876,12 +1876,12 @@
     {
         int version = parcel.readInt();
 
-        whitelistToken = parcel.readStrongBinder();
-        if (whitelistToken == null) {
-            whitelistToken = processWhitelistToken;
+        mWhitelistToken = parcel.readStrongBinder();
+        if (mWhitelistToken == null) {
+            mWhitelistToken = processWhitelistToken;
         }
         // Propagate this token to all pending intents that are unmarshalled from the parcel.
-        parcel.setClassCookie(PendingIntent.class, whitelistToken);
+        parcel.setClassCookie(PendingIntent.class, mWhitelistToken);
 
         when = parcel.readLong();
         creationTime = parcel.readLong();
@@ -1989,7 +1989,7 @@
      * @hide
      */
     public void cloneInto(Notification that, boolean heavy) {
-        that.whitelistToken = this.whitelistToken;
+        that.mWhitelistToken = this.mWhitelistToken;
         that.when = this.when;
         that.creationTime = this.creationTime;
         that.mSmallIcon = this.mSmallIcon;
@@ -2219,7 +2219,7 @@
     private void writeToParcelImpl(Parcel parcel, int flags) {
         parcel.writeInt(1);
 
-        parcel.writeStrongBinder(whitelistToken);
+        parcel.writeStrongBinder(mWhitelistToken);
         parcel.writeLong(when);
         parcel.writeLong(creationTime);
         if (mSmallIcon == null && icon != 0) {
@@ -3839,8 +3839,8 @@
                 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge);
                 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE);
                 if (isColorized()) {
-                    contentView.setDrawableParameters(R.id.profile_badge, false, -1,
-                            getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP, -1);
+                    contentView.setDrawableTint(R.id.profile_badge, false,
+                            getPrimaryTextColor(), PorterDuff.Mode.SRC_ATOP);
                 }
             }
         }
@@ -4130,18 +4130,14 @@
 
                 if (action != null) {
                     int contrastColor = resolveContrastColor();
-                    contentView.setDrawableParameters(R.id.reply_icon_action,
+                    contentView.setDrawableTint(R.id.reply_icon_action,
                             true /* targetBackground */,
-                            -1,
-                            contrastColor,
-                            PorterDuff.Mode.SRC_ATOP, -1);
+                            contrastColor, PorterDuff.Mode.SRC_ATOP);
                     int iconColor = NotificationColorUtil.isColorLight(contrastColor)
                             ? Color.BLACK : Color.WHITE;
-                    contentView.setDrawableParameters(R.id.reply_icon_action,
+                    contentView.setDrawableTint(R.id.reply_icon_action,
                             false /* targetBackground */,
-                            -1,
-                            iconColor,
-                            PorterDuff.Mode.SRC_ATOP, -1);
+                            iconColor, PorterDuff.Mode.SRC_ATOP);
                     contentView.setOnClickPendingIntent(R.id.right_icon,
                             action.actionIntent);
                     contentView.setOnClickPendingIntent(R.id.reply_icon_action,
@@ -4185,8 +4181,8 @@
 
         private void bindExpandButton(RemoteViews contentView) {
             int color = getPrimaryHighlightColor();
-            contentView.setDrawableParameters(R.id.expand_button, false, -1, color,
-                    PorterDuff.Mode.SRC_ATOP, -1);
+            contentView.setDrawableTint(R.id.expand_button, false, color,
+                    PorterDuff.Mode.SRC_ATOP);
             contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
                     color);
         }
@@ -4293,8 +4289,7 @@
                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
             }
             contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
-            contentView.setDrawableParameters(R.id.icon, false /* targetBackground */,
-                    -1 /* alpha */, -1 /* colorFilter */, null /* mode */, mN.iconLevel);
+            contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
             processSmallIconColor(mN.mSmallIcon, contentView, ambient);
         }
 
@@ -4684,8 +4679,8 @@
                     bgColor = mContext.getColor(oddAction ? R.color.notification_action_list
                             : R.color.notification_action_list_dark);
                 }
-                button.setDrawableParameters(R.id.button_holder, true, -1, bgColor,
-                        PorterDuff.Mode.SRC_ATOP, -1);
+                button.setDrawableTint(R.id.button_holder, true,
+                        bgColor, PorterDuff.Mode.SRC_ATOP);
                 CharSequence title = action.title;
                 ColorStateList[] outResultColor = null;
                 if (isLegacy()) {
@@ -4818,8 +4813,8 @@
             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
             int color = ambient ? resolveAmbientColor() : getPrimaryHighlightColor();
             if (colorable) {
-                contentView.setDrawableParameters(R.id.icon, false, -1, color,
-                        PorterDuff.Mode.SRC_ATOP, -1);
+                contentView.setDrawableTint(R.id.icon, false, color,
+                        PorterDuff.Mode.SRC_ATOP);
 
             }
             contentView.setInt(R.id.notification_header, "setOriginalIconColor",
@@ -4835,8 +4830,8 @@
             if (largeIcon != null && isLegacy()
                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 // resolve color will fall back to the default when legacy
-                contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(),
-                        PorterDuff.Mode.SRC_ATOP, -1);
+                contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(),
+                        PorterDuff.Mode.SRC_ATOP);
             }
         }
 
@@ -4986,6 +4981,8 @@
                 mN.flags |= FLAG_SHOW_LIGHTS;
             }
 
+            mN.allPendingIntents = null;
+
             return mN;
         }
 
@@ -6750,8 +6747,8 @@
                     : NotificationColorUtil.resolveColor(mBuilder.mContext,
                             Notification.COLOR_DEFAULT);
 
-            button.setDrawableParameters(R.id.action0, false, -1, tintColor,
-                    PorterDuff.Mode.SRC_ATOP, -1);
+            button.setDrawableTint(R.id.action0, false, tintColor,
+                    PorterDuff.Mode.SRC_ATOP);
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
             }
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 163a8dc..c06ad3f 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,8 +15,11 @@
  */
 package android.app;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.NotificationManager.Importance;
+import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.media.AudioAttributes;
 import android.net.Uri;
@@ -25,6 +28,11 @@
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.Preconditions;
+
+import com.android.internal.util.Preconditions;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -135,12 +143,15 @@
     private boolean mLights;
     private int mLightColor = DEFAULT_LIGHT_COLOR;
     private long[] mVibration;
+    // Bitwise representation of fields that have been changed by the user, preventing the app from
+    // making changes to these fields.
     private int mUserLockedFields;
     private boolean mVibrationEnabled;
     private boolean mShowBadge = DEFAULT_SHOW_BADGE;
     private boolean mDeleted = DEFAULT_DELETED;
     private String mGroup;
     private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
+    // If this is a blockable system notification channel.
     private boolean mBlockableSystem = false;
 
     /**
@@ -565,14 +576,35 @@
     /**
      * @hide
      */
+    public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
+        populateFromXml(parser, true, context);
+    }
+
+    /**
+     * @hide
+     */
     @SystemApi
     public void populateFromXml(XmlPullParser parser) {
+        populateFromXml(parser, false, null);
+    }
+
+    /**
+     * If {@param forRestore} is true, {@param Context} MUST be non-null.
+     */
+    private void populateFromXml(XmlPullParser parser, boolean forRestore,
+            @Nullable Context context) {
+        Preconditions.checkArgument(!forRestore || context != null,
+                "forRestore is true but got null context");
+
         // Name, id, and importance are set in the constructor.
         setDescription(parser.getAttributeValue(null, ATT_DESC));
         setBypassDnd(Notification.PRIORITY_DEFAULT
                 != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
-        setSound(safeUri(parser, ATT_SOUND), safeAudioAttributes(parser));
+
+        Uri sound = safeUri(parser, ATT_SOUND);
+        setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
+
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
         setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
@@ -584,11 +616,62 @@
         setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
     }
 
+    @Nullable
+    private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
+        if (uri == null) {
+            return null;
+        }
+        ContentResolver contentResolver = context.getContentResolver();
+        // There are backups out there with uncanonical uris (because we fixed this after
+        // shipping). If uncanonical uris are given to MediaProvider.uncanonicalize it won't
+        // verify the uri against device storage and we'll possibly end up with a broken uri.
+        // We then canonicalize the uri to uncanonicalize it back, which means we properly check
+        // the uri and in the case of not having the resource we end up with the default - better
+        // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
+        // according to the docs because canonicalize method has to handle canonical uris as well.
+        Uri canonicalizedUri = contentResolver.canonicalize(uri);
+        if (canonicalizedUri == null) {
+            // We got a null because the uri in the backup does not exist here, so we return default
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
+        return contentResolver.uncanonicalize(canonicalizedUri);
+    }
+
     /**
      * @hide
      */
     @SystemApi
     public void writeXml(XmlSerializer out) throws IOException {
+        writeXml(out, false, null);
+    }
+
+    /**
+     * @hide
+     */
+    public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
+        writeXml(out, true, context);
+    }
+
+    private Uri getSoundForBackup(Context context) {
+        Uri sound = getSound();
+        if (sound == null) {
+            return null;
+        }
+        Uri canonicalSound = context.getContentResolver().canonicalize(sound);
+        if (canonicalSound == null) {
+            // The content provider does not support canonical uris so we backup the default
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
+        return canonicalSound;
+    }
+
+    /**
+     * If {@param forBackup} is true, {@param Context} MUST be non-null.
+     */
+    private void writeXml(XmlSerializer out, boolean forBackup, @Nullable Context context)
+            throws IOException {
+        Preconditions.checkArgument(!forBackup || context != null,
+                "forBackup is true but got null context");
         out.startTag(null, TAG_CHANNEL);
         out.attribute(null, ATT_ID, getId());
         if (getName() != null) {
@@ -609,8 +692,9 @@
             out.attribute(null, ATT_VISIBILITY,
                     Integer.toString(getLockscreenVisibility()));
         }
-        if (getSound() != null) {
-            out.attribute(null, ATT_SOUND, getSound().toString());
+        Uri sound = forBackup ? getSoundForBackup(context) : getSound();
+        if (sound != null) {
+            out.attribute(null, ATT_SOUND, sound.toString());
         }
         if (getAudioAttributes() != null) {
             out.attribute(null, ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
@@ -850,4 +934,35 @@
                 + ", mBlockableSystem=" + mBlockableSystem
                 + '}';
     }
+
+    /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(NotificationChannelProto.ID, mId);
+        proto.write(NotificationChannelProto.NAME, mName);
+        proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
+        proto.write(NotificationChannelProto.IMPORTANCE, mImportance);
+        proto.write(NotificationChannelProto.CAN_BYPASS_DND, mBypassDnd);
+        proto.write(NotificationChannelProto.LOCKSCREEN_VISIBILITY, mLockscreenVisibility);
+        if (mSound != null) {
+            proto.write(NotificationChannelProto.SOUND, mSound.toString());
+        }
+        proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
+        proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
+        if (mVibration != null) {
+            for (long v : mVibration) {
+                proto.write(NotificationChannelProto.VIBRATION, v);
+            }
+        }
+        proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
+        proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
+        proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
+        proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
+        proto.write(NotificationChannelProto.GROUP, mGroup);
+        if (mAudioAttributes != null) {
+            long aToken = proto.start(NotificationChannelProto.AUDIO_ATTRIBUTES);
+            mAudioAttributes.toProto(proto);
+            proto.end(aToken);
+        }
+        proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
+    }
 }
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 5173311..5cb7fb7 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -295,4 +296,15 @@
                 + ", mChannels=" + mChannels
                 + '}';
     }
+
+    /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(NotificationChannelGroupProto.ID, mId);
+        proto.write(NotificationChannelGroupProto.NAME, mName.toString());
+        proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
+        proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
+        for (NotificationChannel channel : mChannels) {
+            channel.toProto(proto);
+        }
+    }
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 8fa7d6c..a52dc1e 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -41,6 +41,7 @@
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -933,8 +934,14 @@
         public static final int PRIORITY_CATEGORY_CALLS = 1 << 3;
         /** Calls from repeat callers are prioritized. */
         public static final int PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 4;
+        /** Alarms are prioritized */
+        public static final int PRIORITY_CATEGORY_ALARMS = 1 << 5;
+        /** Media, system, game (catch-all for non-never suppressible sounds) are prioritized */
+        public static final int PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER = 1 << 6;
 
         private static final int[] ALL_PRIORITY_CATEGORIES = {
+            PRIORITY_CATEGORY_ALARMS,
+            PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER,
             PRIORITY_CATEGORY_REMINDERS,
             PRIORITY_CATEGORY_EVENTS,
             PRIORITY_CATEGORY_MESSAGES,
@@ -1061,6 +1068,27 @@
                     + "]";
         }
 
+        /** @hide */
+        public void toProto(ProtoOutputStream proto, long fieldId) {
+            final long pToken = proto.start(fieldId);
+
+            bitwiseToProtoEnum(proto, PolicyProto.PRIORITY_CATEGORIES, priorityCategories);
+            proto.write(PolicyProto.PRIORITY_CALL_SENDER, priorityCallSenders);
+            proto.write(PolicyProto.PRIORITY_MESSAGE_SENDER, priorityMessageSenders);
+            bitwiseToProtoEnum(
+                    proto, PolicyProto.SUPPRESSED_VISUAL_EFFECTS, suppressedVisualEffects);
+
+            proto.end(pToken);
+        }
+
+        private static void bitwiseToProtoEnum(ProtoOutputStream proto, long fieldId, int data) {
+            for (int i = 1; data > 0; ++i, data >>>= 1) {
+                if ((data & 1) == 1) {
+                    proto.write(fieldId, i);
+                }
+            }
+        }
+
         public static String suppressedEffectsToString(int effects) {
             if (effects <= 0) return "";
             final StringBuilder sb = new StringBuilder();
@@ -1113,6 +1141,9 @@
                 case PRIORITY_CATEGORY_MESSAGES: return "PRIORITY_CATEGORY_MESSAGES";
                 case PRIORITY_CATEGORY_CALLS: return "PRIORITY_CATEGORY_CALLS";
                 case PRIORITY_CATEGORY_REPEAT_CALLERS: return "PRIORITY_CATEGORY_REPEAT_CALLERS";
+                case PRIORITY_CATEGORY_ALARMS: return "PRIORITY_CATEGORY_ALARMS";
+                case PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER:
+                    return "PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER";
                 default: return "PRIORITY_CATEGORY_UNKNOWN_" + priorityCategory;
             }
         }
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index a25c226..8b76cc7 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -33,6 +33,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.AndroidException;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1081,7 +1082,16 @@
         sb.append('}');
         return sb.toString();
     }
-    
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        if (mTarget != null) {
+            proto.write(PendingIntentProto.TARGET, mTarget.asBinder().toString());
+        }
+        proto.end(token);
+    }
+
     public int describeContents() {
         return 0;
     }
@@ -1119,8 +1129,13 @@
      */
     public static void writePendingIntentOrNullToParcel(@Nullable PendingIntent sender,
             @NonNull Parcel out) {
-        out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
-                : null);
+        out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() : null);
+        if (sender != null) {
+            OnMarshaledListener listener = sOnMarshaledListener.get();
+            if (listener != null) {
+                listener.onMarshaled(sender, out, 0 /* flags */);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index af13e69..80fb805 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -27,6 +27,7 @@
 import android.os.Parcelable;
 import android.transition.TransitionUtils;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 
@@ -176,7 +177,7 @@
             Drawable d = imageView.getDrawable();
             Drawable bg = imageView.getBackground();
             if (d != null && (bg == null || bg.getAlpha() == 0)) {
-                Bitmap bitmap = TransitionUtils.createDrawableBitmap(d);
+                Bitmap bitmap = TransitionUtils.createDrawableBitmap(d, imageView);
                 if (bitmap != null) {
                     Bundle bundle = new Bundle();
                     if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
@@ -202,7 +203,8 @@
         } else {
             mTempMatrix.set(viewToGlobalMatrix);
         }
-        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds);
+        ViewGroup parent = (ViewGroup) sharedElement.getParent();
+        return TransitionUtils.createViewBitmap(sharedElement, mTempMatrix, screenBounds, parent);
     }
 
     /**
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index fe7afed..23c4166 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -70,14 +70,19 @@
      * Setting this flag disables quick settings completely, but does not disable expanding the
      * notification shade.
      */
-    public static final int DISABLE2_QUICK_SETTINGS = 0x00000001;
+    public static final int DISABLE2_QUICK_SETTINGS = 1;
+    public static final int DISABLE2_SYSTEM_ICONS = 1 << 1;
+    public static final int DISABLE2_NOTIFICATION_SHADE = 1 << 2;
+    public static final int DISABLE2_GLOBAL_ACTIONS = 1 << 3;
 
     public static final int DISABLE2_NONE = 0x00000000;
 
-    public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS;
+    public static final int DISABLE2_MASK = DISABLE2_QUICK_SETTINGS | DISABLE2_SYSTEM_ICONS
+            | DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS;
 
     @IntDef(flag = true,
-            value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS})
+            value = {DISABLE2_NONE, DISABLE2_MASK, DISABLE2_QUICK_SETTINGS, DISABLE2_SYSTEM_ICONS,
+                    DISABLE2_NOTIFICATION_SHADE, DISABLE2_GLOBAL_ACTIONS})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Disable2Flags {}
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ab70f0e..50f1f36 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -81,10 +81,10 @@
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
 import android.net.wifi.IRttManager;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.IWifiScanner;
@@ -95,6 +95,8 @@
 import android.net.wifi.aware.WifiAwareManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.WifiRttManager;
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
@@ -603,6 +605,16 @@
                         ConnectivityThread.getInstanceLooper());
             }});
 
+        registerService(Context.WIFI_RTT2_SERVICE, WifiRttManager.class,
+                new CachedServiceFetcher<WifiRttManager>() {
+                    @Override
+                    public WifiRttManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(Context.WIFI_RTT2_SERVICE);
+                        IWifiRttManager service = IWifiRttManager.Stub.asInterface(b);
+                        return new WifiRttManager(ctx.getOuterContext(), service);
+                    }});
+
         registerService(Context.ETHERNET_SERVICE, EthernetManager.class,
                 new CachedServiceFetcher<EthernetManager>() {
             @Override
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 4674c9c..895d12a 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -31,7 +31,7 @@
     }
 
     @Override
-    public void onActivityPinned(String packageName, int userId, int taskId)
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
             throws RemoteException {
     }
 
@@ -77,7 +77,7 @@
     }
 
     @Override
-    public void onTaskRemovalStarted(int taskId) {
+    public void onTaskRemovalStarted(int taskId) throws RemoteException {
     }
 
     @Override
@@ -91,11 +91,10 @@
     }
 
     @Override
-    public void onTaskProfileLocked(int taskId, int userId) {
+    public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
     }
 
     @Override
-    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
-            throws RemoteException {
+    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) throws RemoteException {
     }
 }
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 5786238..5c6ffa3 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -4,6 +4,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -181,4 +182,20 @@
             e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Set the component name of the compositor service to bind.
+     *
+     * @param componentName ComponentName of a Service in the application's compositor process to
+     * bind to, or null to clear the current binding.
+     */
+    @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+    public void setAndBindVrCompositor(ComponentName componentName) {
+        try {
+            mService.setAndBindCompositor(
+                    (componentName == null) ? null : componentName.flattenToString());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 942cc99..081bd81 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -388,11 +388,12 @@
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
                 @SetWallpaperFlags int which) {
-            return peekWallpaperBitmap(context, returnDefault, which, context.getUserId());
+            return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
+                    false /* hardware */);
         }
 
         public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
-                @SetWallpaperFlags int which, int userId) {
+                @SetWallpaperFlags int which, int userId, boolean hardware) {
             if (mService != null) {
                 try {
                     if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -409,7 +410,7 @@
                 mCachedWallpaper = null;
                 mCachedWallpaperUserId = 0;
                 try {
-                    mCachedWallpaper = getCurrentWallpaperLocked(context, userId);
+                    mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
                     mCachedWallpaperUserId = userId;
                 } catch (OutOfMemoryError e) {
                     Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -447,7 +448,7 @@
             }
         }
 
-        private Bitmap getCurrentWallpaperLocked(Context context, int userId) {
+        private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
             if (mService == null) {
                 Log.w(TAG, "WallpaperService not running");
                 return null;
@@ -460,6 +461,9 @@
                 if (fd != null) {
                     try {
                         BitmapFactory.Options options = new BitmapFactory.Options();
+                        if (hardware) {
+                            options.inPreferredConfig = Bitmap.Config.HARDWARE;
+                        }
                         return BitmapFactory.decodeFileDescriptor(
                                 fd.getFileDescriptor(), null, options);
                     } catch (OutOfMemoryError e) {
@@ -814,12 +818,23 @@
     }
 
     /**
-     * Like {@link #getDrawable()} but returns a Bitmap.
+     * Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
      *
      * @hide
      */
     public Bitmap getBitmap() {
-        return getBitmapAsUser(mContext.getUserId());
+        return getBitmap(false);
+    }
+
+    /**
+     * Like {@link #getDrawable()} but returns a Bitmap.
+     *
+     * @param hardware Asks for a hardware backed bitmap.
+     * @see Bitmap.Config#HARDWARE
+     * @hide
+     */
+    public Bitmap getBitmap(boolean hardware) {
+        return getBitmapAsUser(mContext.getUserId(), hardware);
     }
 
     /**
@@ -827,8 +842,8 @@
      *
      * @hide
      */
-    public Bitmap getBitmapAsUser(int userId) {
-        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId);
+    public Bitmap getBitmapAsUser(int userId, boolean hardware) {
+        return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
     }
 
     /**
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 0cb3804..de27b4f 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -102,7 +102,7 @@
     public static final int ACTIVITY_TYPE_STANDARD = 1;
     /** Home/Launcher activity type. */
     public static final int ACTIVITY_TYPE_HOME = 2;
-    /** Recents/Overview activity type. */
+    /** Recents/Overview activity type. There is only one activity with this type in the system. */
     public static final int ACTIVITY_TYPE_RECENTS = 3;
     /** Assistant activity type. */
     public static final int ACTIVITY_TYPE_ASSISTANT = 4;
@@ -500,13 +500,19 @@
      * @hide
      */
     public boolean supportSplitScreenWindowingMode() {
-        if (mActivityType == ACTIVITY_TYPE_ASSISTANT) {
-            return false;
-        }
-        return mWindowingMode != WINDOWING_MODE_FREEFORM && mWindowingMode != WINDOWING_MODE_PINNED;
+        return supportSplitScreenWindowingMode(mWindowingMode, mActivityType);
     }
 
-    private static String windowingModeToString(@WindowingMode int windowingMode) {
+    /** @hide */
+    public static boolean supportSplitScreenWindowingMode(int windowingMode, int activityType) {
+        if (activityType == ACTIVITY_TYPE_ASSISTANT) {
+            return false;
+        }
+        return windowingMode != WINDOWING_MODE_FREEFORM && windowingMode != WINDOWING_MODE_PINNED;
+    }
+
+    /** @hide */
+    public static String windowingModeToString(@WindowingMode int windowingMode) {
         switch (windowingMode) {
             case WINDOWING_MODE_UNDEFINED: return "undefined";
             case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6bccad9..772c6d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -63,7 +63,9 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 import com.android.org.conscrypt.TrustedCertificateStore;
 
 import java.io.ByteArrayInputStream;
@@ -1540,6 +1542,92 @@
     public @interface ProvisioningPreCondition {}
 
     /**
+     * Disable all configurable SystemUI features during LockTask mode. This includes,
+     * <ul>
+     *     <li>system info area in the status bar (connectivity icons, clock, etc.)
+     *     <li>notifications (including alerts, icons, and the notification shade)
+     *     <li>Home button
+     *     <li>Recents button and UI
+     *     <li>global actions menu (i.e. power button menu)
+     *     <li>keyguard
+     * </ul>
+     *
+     * This is the default configuration for LockTask.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_NONE = 0;
+
+    /**
+     * Enable the system info area in the status bar during LockTask mode. The system info area
+     * usually occupies the right side of the status bar (although this can differ across OEMs). It
+     * includes all system information indicators, such as date and time, connectivity, battery,
+     * vibration mode, etc.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1;
+
+    /**
+     * Enable notifications during LockTask mode. This includes notification icons on the status
+     * bar, heads-up notifications, and the expandable notification shade. Note that the Quick
+     * Settings panel will still be disabled.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_NOTIFICATIONS = 1 << 1;
+
+    /**
+     * Enable the Home button during LockTask mode. Note that if a custom launcher is used, it has
+     * to be registered as the default launcher with
+     * {@link #addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}, and its
+     * package needs to be whitelisted for LockTask with
+     * {@link #setLockTaskPackages(ComponentName, String[])}.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_HOME = 1 << 2;
+
+    /**
+     * Enable the Recents button and the Recents screen during LockTask mode.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_RECENTS = 1 << 3;
+
+    /**
+     * Enable the global actions dialog during LockTask mode. This is the dialog that shows up when
+     * the user long-presses the power button, for example. Note that the user may not be able to
+     * power off the device if this flag is not set.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_GLOBAL_ACTIONS = 1 << 4;
+
+    /**
+     * Enable the keyguard during LockTask mode. Note that if the keyguard is already disabled with
+     * {@link #setKeyguardDisabled(ComponentName, boolean)}, setting this flag will have no effect.
+     * If this flag is not set, the keyguard will not be shown even if the user has a lock screen
+     * credential.
+     *
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public static final int LOCK_TASK_FEATURE_KEYGUARD = 1 << 5;
+
+    /**
+     * Flags supplied to {@link #setLockTaskFeatures(ComponentName, int)}.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true,
+            value = {LOCK_TASK_FEATURE_NONE, LOCK_TASK_FEATURE_SYSTEM_INFO,
+                    LOCK_TASK_FEATURE_NOTIFICATIONS, LOCK_TASK_FEATURE_HOME,
+                    LOCK_TASK_FEATURE_RECENTS, LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                    LOCK_TASK_FEATURE_KEYGUARD})
+    public @interface LockTaskFeature {}
+
+    /**
      * Service action: Action for a service that device owner and profile owner can optionally
      * own.  If a device owner or a profile owner has such a service, the system tries to keep
      * a bound connection to it, in order to keep their process always running.
@@ -3142,6 +3230,7 @@
      */
     public static final int WIPE_EUICC = 0x0004;
 
+
     /**
      * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
      * other users will remain unaffected. Calling from the primary user will cause the device to
@@ -3157,10 +3246,47 @@
      *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
      */
     public void wipeData(int flags) {
-        throwIfParentInstance("wipeData");
+        final String wipeReasonForUser = mContext.getString(
+                R.string.work_profile_deleted_description_dpm_wipe);
+        wipeDataInternal(flags, wipeReasonForUser);
+    }
+
+    /**
+     * Ask that all user data be wiped. If called as a secondary user, the user will be removed and
+     * other users will remain unaffected, the provided reason for wiping data can be shown to
+     * user. Calling from the primary user will cause the device to reboot, erasing all device data
+     * - including all the secondary users and their data - while booting up. In this case, we don't
+     * show the reason to the user since the device would be factory reset.
+     * <p>
+     * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to
+     * be able to call this method; if it has not, a security exception will be thrown.
+     *
+     * @param flags Bit mask of additional options: currently supported flags are
+     *            {@link #WIPE_EXTERNAL_STORAGE} and {@link #WIPE_RESET_PROTECTION_DATA}.
+     * @param reason a string that contains the reason for wiping data, which can be
+     *                          presented to the user.
+     * @throws SecurityException if the calling application does not own an active administrator
+     *             that uses {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA}
+     * @throws IllegalArgumentException if the input reason string is null or empty.
+     */
+    public void wipeDataWithReason(int flags, @NonNull CharSequence reason) {
+        Preconditions.checkNotNull(reason, "CharSequence is null");
+        wipeDataInternal(flags, reason.toString());
+    }
+
+    /**
+     * Internal function for both {@link #wipeData(int)} and
+     * {@link #wipeDataWithReason(int, CharSequence)} to call.
+     *
+     * @see #wipeData(int)
+     * @see #wipeDataWithReason(int, CharSequence)
+     * @hide
+     */
+    private void wipeDataInternal(int flags, @NonNull String wipeReasonForUser) {
+        throwIfParentInstance("wipeDataWithReason");
         if (mService != null) {
             try {
-                mService.wipeData(flags);
+                mService.wipeDataWithReason(flags, wipeReasonForUser);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -6444,6 +6570,61 @@
     }
 
     /**
+     * Sets which system features to enable for LockTask mode.
+     * <p>
+     * Feature flags set through this method will only take effect for the duration when the device
+     * is in LockTask mode. If this method is not called, none of the features listed here will be
+     * enabled.
+     * <p>
+     * This function can only be called by the device owner or by a profile owner of a user/profile
+     * that is affiliated with the device owner user. See {@link #setAffiliationIds}. Any features
+     * set via this method will be cleared if the user becomes unaffiliated.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param flags Bitfield of feature flags:
+     *              {@link #LOCK_TASK_FEATURE_NONE} (default),
+     *              {@link #LOCK_TASK_FEATURE_SYSTEM_INFO},
+     *              {@link #LOCK_TASK_FEATURE_NOTIFICATIONS},
+     *              {@link #LOCK_TASK_FEATURE_HOME},
+     *              {@link #LOCK_TASK_FEATURE_RECENTS},
+     *              {@link #LOCK_TASK_FEATURE_GLOBAL_ACTIONS},
+     *              {@link #LOCK_TASK_FEATURE_KEYGUARD}
+     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+     * an affiliated user or profile.
+     */
+    public void setLockTaskFeatures(@NonNull ComponentName admin, @LockTaskFeature int flags) {
+        throwIfParentInstance("setLockTaskFeatures");
+        if (mService != null) {
+            try {
+                mService.setLockTaskFeatures(admin, flags);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Gets which system features are enabled for LockTask mode.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return bitfield of flags. See {@link #setLockTaskFeatures(ComponentName, int)} for a list.
+     * @throws SecurityException if {@code admin} is not the device owner, or the profile owner of
+     * an affiliated user or profile.
+     * @see #setLockTaskFeatures(ComponentName, int)
+     */
+    public @LockTaskFeature int getLockTaskFeatures(@NonNull ComponentName admin) {
+        throwIfParentInstance("getLockTaskFeatures");
+        if (mService != null) {
+            try {
+                return mService.getLockTaskFeatures(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return 0;
+    }
+
+    /**
      * Called by device owners to update {@link android.provider.Settings.Global} settings.
      * Validation that the value of the setting is in the correct form for the setting type should
      * be performed by the caller.
@@ -6493,6 +6674,52 @@
     }
 
     /**
+     * Called by device owner to set the system wall clock time. This only takes effect if called
+     * when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
+     * returned.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+     * @param millis time in milliseconds since the Epoch
+     * @return {@code true} if set time succeeded, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public boolean setTime(@NonNull ComponentName admin, long millis) {
+        throwIfParentInstance("setTime");
+        if (mService != null) {
+            try {
+                return mService.setTime(admin, millis);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by device owner to set the system's persistent default time zone. This only takes
+     * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise
+     * {@code false} will be returned.
+     *
+     * @see android.app.AlarmManager#setTimeZone(String)
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+     * @param timeZone one of the Olson ids from the list returned by
+     *     {@link java.util.TimeZone#getAvailableIDs}
+     * @return {@code true} if set timezone succeeded, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) {
+        throwIfParentInstance("setTimeZone");
+        if (mService != null) {
+            try {
+                return mService.setTimeZone(admin, timeZone);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Called by profile or device owners to update {@link android.provider.Settings.Secure}
      * settings. Validation that the value of the setting is in the correct form for the setting
      * type should be performed by the caller.
@@ -6815,6 +7042,12 @@
      * Called by device owner to disable the status bar. Disabling the status bar blocks
      * notifications, quick settings and other screen overlays that allow escaping from a single use
      * device.
+     * <p>
+     * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the
+     * status bar in LockTask mode can be configured with
+     * {@link #setLockTaskFeatures(ComponentName, int)}. Calls to this method when the device is in
+     * LockTask mode will be registered, but will only take effect when the device leaves LockTask
+     * mode.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param disabled {@code true} disables the status bar, {@code false} reenables it.
@@ -7534,12 +7767,12 @@
     }
 
     /**
-     * Called by the device owner or profile owner to set the name of the organization under
-     * management.
-     * <p>
-     * If the organization name needs to be localized, it is the responsibility of the
-     * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
-     * and set a new version of this string accordingly.
+     * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of
+     * the organization under management.
+     *
+     * <p>If the organization name needs to be localized, it is the responsibility of the {@link
+     * DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set
+     * a new version of this string accordingly.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param title The organization name or {@code null} to clear a previously set name.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index acfb602..be0b920 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -95,7 +95,7 @@
 
     void lockNow(int flags, boolean parent);
 
-    void wipeData(int flags);
+    void wipeDataWithReason(int flags, String wipeReasonForUser);
 
     ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
     ComponentName getGlobalProxyAdmin(int userHandle);
@@ -226,9 +226,15 @@
     String[] getLockTaskPackages(in ComponentName who);
     boolean isLockTaskPermitted(in String pkg);
 
+    void setLockTaskFeatures(in ComponentName who, int flags);
+    int getLockTaskFeatures(in ComponentName who);
+
     void setGlobalSetting(in ComponentName who, in String setting, in String value);
     void setSecureSetting(in ComponentName who, in String setting, in String value);
 
+    boolean setTime(in ComponentName who, long millis);
+    boolean setTimeZone(in ComponentName who, String timeZone);
+
     void setMasterVolumeMuted(in ComponentName admin, boolean on);
     boolean isMasterVolumeMuted(in ComponentName admin);
 
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 55c22de..e491a4f 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -616,6 +616,9 @@
         CharSequence[] mAutofillOptions;
         boolean mSanitized;
         HtmlInfo mHtmlInfo;
+        int mMinEms = -1;
+        int mMaxEms = -1;
+        int mMaxLength = -1;
 
         // POJO used to override some autofill-related values when the node is parcelized.
         // Not written to parcel.
@@ -674,6 +677,7 @@
 
         ViewNodeText mText;
         int mInputType;
+        String mWebScheme;
         String mWebDomain;
         Bundle mExtras;
         LocaleList mLocaleList;
@@ -712,6 +716,9 @@
                 if (p instanceof HtmlInfo) {
                     mHtmlInfo = (HtmlInfo) p;
                 }
+                mMinEms = in.readInt();
+                mMaxEms = in.readInt();
+                mMaxLength = in.readInt();
             }
             if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
                 mX = in.readInt();
@@ -751,6 +758,7 @@
                 mInputType = in.readInt();
             }
             if ((flags&FLAGS_HAS_URL) != 0) {
+                mWebScheme = in.readString();
                 mWebDomain = in.readString();
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -813,7 +821,7 @@
             if (mInputType != 0) {
                 flags |= FLAGS_HAS_INPUT_TYPE;
             }
-            if (mWebDomain != null) {
+            if (mWebScheme != null || mWebDomain != null) {
                 flags |= FLAGS_HAS_URL;
             }
             if (mLocaleList != null) {
@@ -874,6 +882,9 @@
                 } else {
                     out.writeParcelable(null, 0);
                 }
+                out.writeInt(mMinEms);
+                out.writeInt(mMaxEms);
+                out.writeInt(mMaxLength);
             }
             if ((flags&FLAGS_HAS_LARGE_COORDS) != 0) {
                 out.writeInt(mX);
@@ -908,6 +919,7 @@
                 out.writeInt(mInputType);
             }
             if ((flags&FLAGS_HAS_URL) != 0) {
+                out.writeString(mWebScheme);
                 out.writeString(mWebDomain);
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
@@ -1260,18 +1272,31 @@
          * <p>Typically used when the view associated with the view is a container for an HTML
          * document.
          *
-         * <strong>WARNING:</strong> a {@link android.service.autofill.AutofillService} should only
-         * use this domain for autofill purposes when it trusts the app generating it (i.e., the app
-         * defined by {@link AssistStructure#getActivityComponent()}).
+         * <p><b>Warning:</b> an autofill service cannot trust the value reported by this method
+         * without verifing its authenticity&mdash;see the "Web security" section of
+         * {@link android.service.autofill.AutofillService} for more details.
          *
          * @return domain-only part of the document. For example, if the full URL is
-         * {@code http://my.site/login?user=my_user}, it returns {@code my.site}.
+         * {@code https://example.com/login?user=my_user}, it returns {@code example.com}.
          */
         @Nullable public String getWebDomain() {
             return mWebDomain;
         }
 
         /**
+         * Returns the scheme of the HTML document represented by this view.
+         *
+         * <p>Typically used when the view associated with the view is a container for an HTML
+         * document.
+         *
+         * @return scheme-only part of the document. For example, if the full URL is
+         * {@code https://example.com/login?user=my_user}, it returns {@code https}.
+         */
+        @Nullable public String getWebScheme() {
+            return mWebScheme;
+        }
+
+        /**
          * Returns the HTML properties associated with this view.
          *
          * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
@@ -1428,6 +1453,39 @@
         public ViewNode getChildAt(int index) {
             return mChildren[index];
         }
+
+        /**
+         * Returns the minimum width in ems of the text associated with this node, or {@code -1}
+         * if not supported by the node.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        public int getMinTextEms() {
+            return mMinEms;
+        }
+
+        /**
+         * Returns the maximum width in ems of the text associated with this node, or {@code -1}
+         * if not supported by the node.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        public int getMaxTextEms() {
+            return mMaxEms;
+        }
+
+        /**
+         * Returns the maximum length of the text associated with this node node, or {@code -1}
+         * if not supported by the node or not set.
+         *
+         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes,
+         * not for assist purposes.
+         */
+        public int getMaxTextLength() {
+            return mMaxLength;
+        }
     }
 
     /**
@@ -1760,6 +1818,21 @@
         }
 
         @Override
+        public void setMinTextEms(int minEms) {
+            mNode.mMinEms = minEms;
+        }
+
+        @Override
+        public void setMaxTextEms(int maxEms) {
+            mNode.mMaxEms = maxEms;
+        }
+
+        @Override
+        public void setMaxTextLength(int maxLength) {
+            mNode.mMaxLength = maxLength;
+        }
+
+        @Override
         public void setDataIsSensitive(boolean sensitive) {
             mNode.mSanitized = !sensitive;
         }
@@ -1767,10 +1840,13 @@
         @Override
         public void setWebDomain(@Nullable String domain) {
             if (domain == null) {
+                mNode.mWebScheme = null;
                 mNode.mWebDomain = null;
                 return;
             }
-            mNode.mWebDomain = Uri.parse(domain).getHost();
+            Uri uri = Uri.parse(domain);
+            mNode.mWebScheme = uri.getScheme();
+            mNode.mWebDomain = uri.getHost();
         }
 
         @Override
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 87e516c..b640bd5 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -18,6 +18,7 @@
 
 import static android.util.TimeUtils.formatDuration;
 
+import android.annotation.BytesLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -71,6 +72,9 @@
     /** This job requires metered connectivity such as most cellular data networks. */
     public static final int NETWORK_TYPE_METERED = 4;
 
+    /** Sentinel value indicating that bytes are unknown. */
+    public static final int NETWORK_BYTES_UNKNOWN = -1;
+
     /**
      * Amount of backoff a job has initially by default, in milliseconds.
      */
@@ -250,6 +254,7 @@
     private final boolean hasEarlyConstraint;
     private final boolean hasLateConstraint;
     private final int networkType;
+    private final long networkBytes;
     private final long minLatencyMillis;
     private final long maxExecutionDelayMillis;
     private final boolean isPeriodic;
@@ -317,7 +322,8 @@
     }
 
     /**
-     * Whether this job needs the device to be plugged in.
+     * Whether this job requires that the device be charging (or be a non-battery-powered
+     * device connected to permanent power, such as Android TV devices).
      */
     public boolean isRequireCharging() {
         return (constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0;
@@ -331,7 +337,10 @@
     }
 
     /**
-     * Whether this job needs the device to be in an Idle maintenance window.
+     * Whether this job requires that the user <em>not</em> be interacting with the device.
+     *
+     * <p class="note">This is <em>not</em> the same as "doze" or "device idle";
+     * it is purely about the user's direct interactions.</p>
      */
     public boolean isRequireDeviceIdle() {
         return (constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0;
@@ -383,6 +392,18 @@
     }
 
     /**
+     * Return the estimated size of network traffic that will be performed by
+     * this job, in bytes.
+     *
+     * @return Estimated size of network traffic, or
+     *         {@link #NETWORK_BYTES_UNKNOWN} when unknown.
+     * @see Builder#setEstimatedNetworkBytes(long)
+     */
+    public @BytesLong long getEstimatedNetworkBytes() {
+        return networkBytes;
+    }
+
+    /**
      * Set for a job that does not recur periodically, to specify a delay after which the job
      * will be eligible for execution. This value is not set if the job recurs periodically.
      */
@@ -520,6 +541,9 @@
         if (networkType != j.networkType) {
             return false;
         }
+        if (networkBytes != j.networkBytes) {
+            return false;
+        }
         if (minLatencyMillis != j.minLatencyMillis) {
             return false;
         }
@@ -578,6 +602,7 @@
         hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
         hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
         hashCode = 31 * hashCode + networkType;
+        hashCode = 31 * hashCode + Long.hashCode(networkBytes);
         hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
         hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
         hashCode = 31 * hashCode + Boolean.hashCode(isPeriodic);
@@ -608,6 +633,7 @@
         triggerContentUpdateDelay = in.readLong();
         triggerContentMaxDelay = in.readLong();
         networkType = in.readInt();
+        networkBytes = in.readLong();
         minLatencyMillis = in.readLong();
         maxExecutionDelayMillis = in.readLong();
         isPeriodic = in.readInt() == 1;
@@ -636,6 +662,7 @@
         triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
         triggerContentMaxDelay = b.mTriggerContentMaxDelay;
         networkType = b.mNetworkType;
+        networkBytes = b.mNetworkBytes;
         minLatencyMillis = b.mMinLatencyMillis;
         maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
         isPeriodic = b.mIsPeriodic;
@@ -673,6 +700,7 @@
         out.writeLong(triggerContentUpdateDelay);
         out.writeLong(triggerContentMaxDelay);
         out.writeInt(networkType);
+        out.writeLong(networkBytes);
         out.writeLong(minLatencyMillis);
         out.writeLong(maxExecutionDelayMillis);
         out.writeInt(isPeriodic ? 1 : 0);
@@ -806,6 +834,7 @@
         // Requirements.
         private int mConstraintFlags;
         private int mNetworkType;
+        private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
         private ArrayList<TriggerContentUri> mTriggerContentUris;
         private long mTriggerContentUpdateDelay = -1;
         private long mTriggerContentMaxDelay = -1;
@@ -905,12 +934,21 @@
         }
 
         /**
-         * Set some description of the kind of network type your job needs to have.
-         * Not calling this function means the network is not necessary, as the default is
-         * {@link #NETWORK_TYPE_NONE}.
-         * Bear in mind that calling this function defines network as a strict requirement for your
-         * job. If the network requested is not available your job will never run. See
-         * {@link #setOverrideDeadline(long)} to change this behaviour.
+         * Set some description of the kind of network type your job needs to
+         * have. Not calling this function means the network is not necessary,
+         * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
+         * calling this function defines network as a strict requirement for
+         * your job. If the network requested is not available your job will
+         * never run. See {@link #setOverrideDeadline(long)} to change this
+         * behaviour.
+         * <p class="note">
+         * Note: When your job executes in
+         * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+         * specific network returned by {@link JobParameters#getNetwork()},
+         * otherwise you'll use the default network which may not meet this
+         * constraint.
+         *
+         * @see JobParameters#getNetwork()
          */
         public Builder setRequiredNetworkType(@NetworkType int networkType) {
             mNetworkType = networkType;
@@ -918,9 +956,56 @@
         }
 
         /**
-         * Specify that to run this job, the device needs to be plugged in. This defaults to
-         * false.
-         * @param requiresCharging Whether or not the device is plugged in.
+         * Set the estimated size of network traffic that will be performed by
+         * this job, in bytes.
+         * <p>
+         * Apps are encouraged to provide values that are as accurate as
+         * possible, but when the exact size isn't available, an
+         * order-of-magnitude estimate can be provided instead. Here are some
+         * specific examples:
+         * <ul>
+         * <li>A job that is backing up a photo knows the exact size of that
+         * photo, so it should provide that size as the estimate.
+         * <li>A job that refreshes top news stories wouldn't know an exact
+         * size, but if the size is expected to be consistently around 100KB, it
+         * can provide that order-of-magnitude value as the estimate.
+         * <li>A job that synchronizes email could end up using an extreme range
+         * of data, from under 1KB when nothing has changed, to dozens of MB
+         * when there are new emails with attachments. Jobs that cannot provide
+         * reasonable estimates should leave this estimated value undefined.
+         * </ul>
+         * Note that the system may choose to delay jobs with large network
+         * usage estimates when the device has a poor network connection, in
+         * order to save battery.
+         *
+         * @param networkBytes The estimated size of network traffic that will
+         *            be performed by this job, in bytes. This value only
+         *            reflects the traffic that will be performed by the base
+         *            job; if you're using {@link JobWorkItem} then you also
+         *            need to define the network traffic used by each work item
+         *            when constructing them.
+         * @see JobInfo#getEstimatedNetworkBytes()
+         * @see JobWorkItem#JobWorkItem(android.content.Intent, long)
+         */
+        public Builder setEstimatedNetworkBytes(@BytesLong long networkBytes) {
+            mNetworkBytes = networkBytes;
+            return this;
+        }
+
+        /**
+         * Specify that to run this job, the device must be charging (or be a
+         * non-battery-powered device connected to permanent power, such as Android TV
+         * devices). This defaults to {@code false}.
+         *
+         * <p class="note">For purposes of running jobs, a battery-powered device
+         * "charging" is not quite the same as simply being connected to power.  If the
+         * device is so busy that the battery is draining despite a power connection, jobs
+         * with this constraint will <em>not</em> run.  This can happen during some
+         * common use cases such as video chat, particularly if the device is plugged in
+         * to USB rather than to wall power.
+         *
+         * @param requiresCharging Pass {@code true} to require that the device be
+         *     charging in order to run the job.
          */
         public Builder setRequiresCharging(boolean requiresCharging) {
             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_CHARGING)
@@ -942,14 +1027,22 @@
         }
 
         /**
-         * Specify that to run, the job needs the device to be in idle mode. This defaults to
-         * false.
-         * <p>Idle mode is a loose definition provided by the system, which means that the device
-         * is not in use, and has not been in use for some time. As such, it is a good time to
-         * perform resource heavy jobs. Bear in mind that battery usage will still be attributed
-         * to your application, and surfaced to the user in battery stats.</p>
-         * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
-         *                           window.
+         * When set {@code true}, ensure that this job will not run if the device is in active use.
+         * The default state is {@code false}: that is, the for the job to be runnable even when
+         * someone is interacting with the device.
+         *
+         * <p>This state is a loose definition provided by the system. In general, it means that
+         * the device is not currently being used interactively, and has not been in use for some
+         * time. As such, it is a good time to perform resource heavy jobs. Bear in mind that
+         * battery usage will still be attributed to your application, and surfaced to the user in
+         * battery stats.</p>
+         *
+         * <p class="note">Despite the similar naming, this job constraint is <em>not</em>
+         * related to the system's "device idle" or "doze" states.  This constraint only
+         * determines whether a job is allowed to run while the device is directly in use.
+         *
+         * @param requiresDeviceIdle Pass {@code true} to prevent the job from running
+         *     while the device is being used interactively.
          */
         public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
             mConstraintFlags = (mConstraintFlags&~CONSTRAINT_FLAG_DEVICE_IDLE)
@@ -1125,6 +1218,11 @@
                 throw new IllegalArgumentException("You're trying to build a job with no " +
                         "constraints, this is not allowed.");
             }
+            // Check that network estimates require network type
+            if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+                throw new IllegalArgumentException(
+                        "Can't provide estimated network usage without requiring a network");
+            }
             // Check that a deadline was not set on a periodic job.
             if (mIsPeriodic) {
                 if (mMaxExecutionDelayMillis != 0L) {
diff --git a/core/java/android/app/job/JobParameters.java b/core/java/android/app/job/JobParameters.java
index a6f6be2..5053dc6 100644
--- a/core/java/android/app/job/JobParameters.java
+++ b/core/java/android/app/job/JobParameters.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.job.IJobCallback;
 import android.content.ClipData;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -66,6 +67,7 @@
     private final boolean overrideDeadlineExpired;
     private final Uri[] mTriggeredContentUris;
     private final String[] mTriggeredContentAuthorities;
+    private final Network network;
 
     private int stopReason; // Default value of stopReason is REASON_CANCELED
 
@@ -73,7 +75,7 @@
     public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
             Bundle transientExtras, ClipData clipData, int clipGrantFlags,
             boolean overrideDeadlineExpired, Uri[] triggeredContentUris,
-            String[] triggeredContentAuthorities) {
+            String[] triggeredContentAuthorities, Network network) {
         this.jobId = jobId;
         this.extras = extras;
         this.transientExtras = transientExtras;
@@ -83,6 +85,7 @@
         this.overrideDeadlineExpired = overrideDeadlineExpired;
         this.mTriggeredContentUris = triggeredContentUris;
         this.mTriggeredContentAuthorities = triggeredContentAuthorities;
+        this.network = network;
     }
 
     /**
@@ -171,6 +174,28 @@
     }
 
     /**
+     * Return the network that should be used to perform any network requests
+     * for this job.
+     * <p>
+     * Devices may have multiple active network connections simultaneously, or
+     * they may not have a default network route at all. To correctly handle all
+     * situations like this, your job should always use the network returned by
+     * this method instead of implicitly using the default network route.
+     * <p>
+     * Note that the system may relax the constraints you originally requested,
+     * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over
+     * a metered network when there is a surplus of metered data available.
+     *
+     * @return the network that should be used to perform any network requests
+     *         for this job, or {@code null} if this job didn't set any required
+     *         network type.
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public @Nullable Network getNetwork() {
+        return network;
+    }
+
+    /**
      * Dequeue the next pending {@link JobWorkItem} from these JobParameters associated with their
      * currently running job.  Calling this method when there is no more work available and all
      * previously dequeued work has been completed will result in the system taking care of
@@ -257,6 +282,11 @@
         overrideDeadlineExpired = in.readInt() == 1;
         mTriggeredContentUris = in.createTypedArray(Uri.CREATOR);
         mTriggeredContentAuthorities = in.createStringArray();
+        if (in.readInt() != 0) {
+            network = Network.CREATOR.createFromParcel(in);
+        } else {
+            network = null;
+        }
         stopReason = in.readInt();
     }
 
@@ -286,6 +316,12 @@
         dest.writeInt(overrideDeadlineExpired ? 1 : 0);
         dest.writeTypedArray(mTriggeredContentUris, flags);
         dest.writeStringArray(mTriggeredContentAuthorities);
+        if (network != null) {
+            dest.writeInt(1);
+            network.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeInt(stopReason);
     }
 
diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java
index 3868439..0deb2e1 100644
--- a/core/java/android/app/job/JobScheduler.java
+++ b/core/java/android/app/job/JobScheduler.java
@@ -24,7 +24,6 @@
 import android.annotation.SystemService;
 import android.content.ClipData;
 import android.content.Context;
-import android.content.Intent;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 
@@ -40,16 +39,18 @@
  * and how to construct them. You will construct these JobInfo objects and pass them to the
  * JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the
  * system will execute this job on your application's {@link android.app.job.JobService}.
- * You identify which JobService is meant to execute the logic for your job when you create the
- * JobInfo with
+ * You identify the service component that implements the logic for your job when you
+ * construct the JobInfo using
  * {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
  * </p>
  * <p>
- * The framework will be intelligent about when you receive your callbacks, and attempt to batch
- * and defer them as much as possible. Typically if you don't specify a deadline on your job, it
- * can be run at any moment depending on the current state of the JobScheduler's internal queue,
- * however it might be deferred as long as until the next time the device is connected to a power
- * source.
+ * The framework will be intelligent about when it executes jobs, and attempt to batch
+ * and defer them as much as possible. Typically if you don't specify a deadline on a job, it
+ * can be run at any moment depending on the current state of the JobScheduler's internal queue.
+ * <p>
+ * While a job is running, the system holds a wakelock on behalf of your app.  For this reason,
+ * you do not need to take any action to guarantee that the device stays awake for the
+ * duration of the job.
  * </p>
  * <p>You do not
  * instantiate this class directly; instead, retrieve it through
@@ -141,30 +142,34 @@
             int userId, String tag);
 
     /**
-     * Cancel a job that is pending in the JobScheduler.
-     * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
-     * {@link #getAllPendingJobs()}.
+     * Cancel the specified job.  If the job is currently executing, it is stopped
+     * immediately and the return value from its {@link JobService#onStopJob(JobParameters)}
+     * method is ignored.
+     *
+     * @param jobId unique identifier for the job to be canceled, as supplied to
+     *     {@link JobInfo.Builder#JobInfo.Builder(int, android.content.ComponentName)
+     *     JobInfo.Builder(int, android.content.ComponentName)}.
      */
     public abstract void cancel(int jobId);
 
     /**
-     * Cancel all jobs that have been registered with the JobScheduler by this package.
+     * Cancel <em>all</em> jobs that have been scheduled by the calling application.
      */
     public abstract void cancelAll();
 
     /**
-     * Retrieve all jobs for this package that are pending in the JobScheduler.
+     * Retrieve all jobs that have been scheduled by the calling application.
      *
-     * @return a list of all the jobs registered by this package that have not
-     *         yet been executed.
+     * @return a list of all of the app's scheduled jobs.  This includes jobs that are
+     *     currently started as well as those that are still waiting to run.
      */
     public abstract @NonNull List<JobInfo> getAllPendingJobs();
 
     /**
-     * Retrieve a specific job for this package that is pending in the
-     * JobScheduler.
+     * Look up the description of a scheduled job.
      *
-     * @return job registered by this package that has not yet been executed.
+     * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
+     *     if the supplied job ID does not correspond to any job.
      */
     public abstract @Nullable JobInfo getPendingJob(int jobId);
 }
diff --git a/core/java/android/app/job/JobService.java b/core/java/android/app/job/JobService.java
index 9096b47..69afed2 100644
--- a/core/java/android/app/job/JobService.java
+++ b/core/java/android/app/job/JobService.java
@@ -18,16 +18,7 @@
 
 import android.app.Service;
 import android.content.Intent;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.lang.ref.WeakReference;
 
 /**
  * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
@@ -55,7 +46,7 @@
      * </pre>
      *
      * <p>If a job service is declared in the manifest but not protected with this
-     * permission, that service will be ignored by the OS.
+     * permission, that service will be ignored by the system.
      */
     public static final String PERMISSION_BIND =
             "android.permission.BIND_JOB_SERVICE";
@@ -81,14 +72,36 @@
     }
 
     /**
-     * Override this method with the callback logic for your job. Any such logic needs to be
-     * performed on a separate thread, as this function is executed on your application's main
-     * thread.
+     * Called to indicate that the job has begun executing.  Override this method with the
+     * logic for your job.  Like all other component lifecycle callbacks, this method executes
+     * on your application's main thread.
+     * <p>
+     * Return {@code true} from this method if your job needs to continue running.  If you
+     * do this, the job remains active until you call
+     * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed
+     * its work, or until the job's required constraints are no longer satisfied.  For
+     * example, if the job was scheduled using
+     * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)},
+     * it will be immediately halted by the system if the user unplugs the device from power,
+     * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app
+     * will be expected to shut down all ongoing work connected with that job.
+     * <p>
+     * The system holds a wakelock on behalf of your app as long as your job is executing.
+     * This wakelock is acquired before this method is invoked, and is not released until either
+     * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes
+     * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down
+     * prematurely.
+     * <p>
+     * Returning {@code false} from this method means your job is already finished.  The
+     * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)}
+     * will not be invoked.
      *
-     * @param params Parameters specifying info about this job, including the extras bundle you
-     *               optionally provided at job-creation time.
-     * @return True if your service needs to process the work (on a separate thread). False if
-     * there's no more work to be done for this job.
+     * @param params Parameters specifying info about this job, including the optional
+     *     extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
+     *     This object serves to identify this specific running job instance when calling
+     *     {@link #jobFinished(JobParameters, boolean)}.
+     * @return {@code true} if your service will continue running, using a separate thread
+     *     when appropriate.  {@code false} means that this job has completed its work.
      */
     public abstract boolean onStartJob(JobParameters params);
 
@@ -101,37 +114,44 @@
      * {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
      * job was executing the user toggled WiFi. Another example is if you had specified
      * {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
-     * idle maintenance window. You are solely responsible for the behaviour of your application
-     * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
-     * immediate repercussion is that the system will cease holding a wakelock for you.</p>
+     * idle maintenance window. You are solely responsible for the behavior of your application
+     * upon receipt of this message; your app will likely start to misbehave if you ignore it.
+     * <p>
+     * Once this method returns, the system releases the wakelock that it is holding on
+     * behalf of the job.</p>
      *
-     * @param params Parameters specifying info about this job.
-     * @return True to indicate to the JobManager whether you'd like to reschedule this job based
-     * on the retry criteria provided at job creation-time. False to drop the job. Regardless of
-     * the value returned, your job must stop executing.
+     * @param params The parameters identifying this job, as supplied to
+     *               the job in the {@link #onStartJob(JobParameters)} callback.
+     * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+     * this job based on the retry criteria provided at job creation-time; or {@code false}
+     * to end the job entirely.  Regardless of the value returned, your job must stop executing.
      */
     public abstract boolean onStopJob(JobParameters params);
 
     /**
-     * Call this to inform the JobManager you've finished executing. This can be called from any
-     * thread, as it will ultimately be run on your application's main thread. When the system
-     * receives this message it will release the wakelock being held.
+     * Call this to inform the JobScheduler that the job has finished its work.  When the
+     * system receives this message, it releases the wakelock being held for the job.
      * <p>
-     *     You can specify post-execution behaviour to the scheduler here with
-     *     <code>needsReschedule </code>. This will apply a back-off timer to your job based on
-     *     the default, or what was set with
-     *     {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original
-     *     requirements are always honoured even for a backed-off job. Note that a job running in
-     *     idle mode will not be backed-off. Instead what will happen is the job will be re-added
-     *     to the queue and re-executed within a future idle maintenance window.
+     * You can request that the job be scheduled again by passing {@code true} as
+     * the <code>wantsReschedule</code> parameter. This will apply back-off policy
+     * for the job; this policy can be adjusted through the
+     * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
+     * when the job is originally scheduled.  The job's initial
+     * requirements are preserved when jobs are rescheduled, regardless of backed-off
+     * policy.
+     * <p class="note">
+     * A job running while the device is dozing will not be rescheduled with the normal back-off
+     * policy.  Instead, the job will be re-added to the queue and executed again during
+     * a future idle maintenance window.
      * </p>
      *
-     * @param params Parameters specifying system-provided info about this job, this was given to
-     *               your application in {@link #onStartJob(JobParameters)}.
-     * @param needsReschedule True if this job should be rescheduled according to the back-off
-     *                        criteria specified at schedule-time. False otherwise.
+     * @param params The parameters identifying this job, as supplied to
+     *               the job in the {@link #onStartJob(JobParameters)} callback.
+     * @param wantsReschedule {@code true} if this job should be rescheduled according
+     *     to the back-off criteria specified when it was first scheduled; {@code false}
+     *     otherwise.
      */
-    public final void jobFinished(JobParameters params, boolean needsReschedule) {
-        mEngine.jobFinished(params, needsReschedule);
+    public final void jobFinished(JobParameters params, boolean wantsReschedule) {
+        mEngine.jobFinished(params, wantsReschedule);
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/job/JobWorkItem.java b/core/java/android/app/job/JobWorkItem.java
index 0eb0450..1c46e8e 100644
--- a/core/java/android/app/job/JobWorkItem.java
+++ b/core/java/android/app/job/JobWorkItem.java
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.annotation.BytesLong;
 import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -27,6 +28,7 @@
  */
 final public class JobWorkItem implements Parcelable {
     final Intent mIntent;
+    final long mNetworkBytes;
     int mDeliveryCount;
     int mWorkId;
     Object mGrants;
@@ -39,6 +41,22 @@
      */
     public JobWorkItem(Intent intent) {
         mIntent = intent;
+        mNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+    }
+
+    /**
+     * Create a new piece of work, which can be submitted to
+     * {@link JobScheduler#enqueue JobScheduler.enqueue}.
+     *
+     * @param intent The general Intent describing this work.
+     * @param networkBytes The estimated size of network traffic that will be
+     *            performed by this job work item, in bytes. See
+     *            {@link JobInfo.Builder#setEstimatedNetworkBytes(long)} for
+     *            details about how to estimate.
+     */
+    public JobWorkItem(Intent intent, @BytesLong long networkBytes) {
+        mIntent = intent;
+        mNetworkBytes = networkBytes;
     }
 
     /**
@@ -49,6 +67,17 @@
     }
 
     /**
+     * Return the estimated size of network traffic that will be performed by
+     * this job work item, in bytes.
+     *
+     * @return estimated size, or {@link JobInfo#NETWORK_BYTES_UNKNOWN} when
+     *         unknown.
+     */
+    public @BytesLong long getEstimatedNetworkBytes() {
+        return mNetworkBytes;
+    }
+
+    /**
      * Return the count of the number of times this work item has been delivered
      * to the job.  The value will be > 1 if it has been redelivered because the job
      * was stopped or crashed while it had previously been delivered but before the
@@ -99,6 +128,10 @@
         sb.append(mWorkId);
         sb.append(" intent=");
         sb.append(mIntent);
+        if (mNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+            sb.append(" networkBytes=");
+            sb.append(mNetworkBytes);
+        }
         if (mDeliveryCount != 0) {
             sb.append(" dcount=");
             sb.append(mDeliveryCount);
@@ -118,6 +151,7 @@
         } else {
             out.writeInt(0);
         }
+        out.writeLong(mNetworkBytes);
         out.writeInt(mDeliveryCount);
         out.writeInt(mWorkId);
     }
@@ -139,6 +173,7 @@
         } else {
             mIntent = null;
         }
+        mNetworkBytes = in.readLong();
         mDeliveryCount = in.readInt();
         mWorkId = in.readInt();
     }
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
new file mode 100644
index 0000000..f6b6b86
--- /dev/null
+++ b/core/java/android/app/slice/Slice.java
@@ -0,0 +1,401 @@
+/*
+ * 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.app.slice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.ContentResolver;
+import android.content.IContentProvider;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A slice is a piece of app content and actions that can be surfaced outside of the app.
+ *
+ * <p>They are constructed using {@link Builder} in a tree structure
+ * that provides the OS some information about how the content should be displayed.
+ */
+public final class Slice implements Parcelable {
+
+    /**
+     * @hide
+     */
+    @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
+            HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
+    public @interface SliceHint{ }
+
+    /**
+     * Hint that this content is a title of other content in the slice.
+     */
+    public static final String HINT_TITLE       = "title";
+    /**
+     * Hint that all sub-items/sub-slices within this content should be considered
+     * to have {@link #HINT_LIST_ITEM}.
+     */
+    public static final String HINT_LIST        = "list";
+    /**
+     * Hint that this item is part of a list and should be formatted as if is part
+     * of a list.
+     */
+    public static final String HINT_LIST_ITEM   = "list_item";
+    /**
+     * Hint that this content is important and should be larger when displayed if
+     * possible.
+     */
+    public static final String HINT_LARGE       = "large";
+    /**
+     * Hint that this slice contains a number of actions that can be grouped together
+     * in a sort of controls area of the UI.
+     */
+    public static final String HINT_ACTIONS     = "actions";
+    /**
+     * Hint indicating that this item (and its sub-items) are the current selection.
+     */
+    public static final String HINT_SELECTED    = "selected";
+    /**
+     * Hint to indicate that this is a message as part of a communication
+     * sequence in this slice.
+     */
+    public static final String HINT_MESSAGE     = "message";
+    /**
+     * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
+     */
+    public static final String HINT_SOURCE      = "source";
+    /**
+     * Hint that list items within this slice or subslice would appear better
+     * if organized horizontally.
+     */
+    public static final String HINT_HORIZONTAL  = "horizontal";
+    /**
+     * Hint to indicate that this content should not be tinted.
+     */
+    public static final String HINT_NO_TINT     = "no_tint";
+    /**
+     * Hint to indicate that this slice is incomplete and an update will be sent once
+     * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
+     * OS and should not be cached by apps.
+     */
+    public static final String HINT_PARTIAL     = "partial";
+
+    // These two are coming over from prototyping, but we probably don't want in
+    // public API, at least not right now.
+    /**
+     * @hide
+     */
+    public static final String HINT_ALT         = "alt";
+
+    private final SliceItem[] mItems;
+    private final @SliceHint String[] mHints;
+    private Uri mUri;
+
+    Slice(ArrayList<SliceItem> items, @SliceHint String[] hints, Uri uri) {
+        mHints = hints;
+        mItems = items.toArray(new SliceItem[items.size()]);
+        mUri = uri;
+    }
+
+    protected Slice(Parcel in) {
+        mHints = in.readStringArray();
+        int n = in.readInt();
+        mItems = new SliceItem[n];
+        for (int i = 0; i < n; i++) {
+            mItems[i] = SliceItem.CREATOR.createFromParcel(in);
+        }
+        mUri = Uri.CREATOR.createFromParcel(in);
+    }
+
+    /**
+     * @return The Uri that this Slice represents.
+     */
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
+     * @return All child {@link SliceItem}s that this Slice contains.
+     */
+    public List<SliceItem> getItems() {
+        return Arrays.asList(mItems);
+    }
+
+    /**
+     * @return All hints associated with this Slice.
+     */
+    public @SliceHint List<String> getHints() {
+        return Arrays.asList(mHints);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringArray(mHints);
+        dest.writeInt(mItems.length);
+        for (int i = 0; i < mItems.length; i++) {
+            mItems[i].writeToParcel(dest, flags);
+        }
+        mUri.writeToParcel(dest, 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasHint(@SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
+     * A Builder used to construct {@link Slice}s
+     */
+    public static class Builder {
+
+        private final Uri mUri;
+        private ArrayList<SliceItem> mItems = new ArrayList<>();
+        private @SliceHint ArrayList<String> mHints = new ArrayList<>();
+
+        /**
+         * Create a builder which will construct a {@link Slice} for the Given Uri.
+         * @param uri Uri to tag for this slice.
+         */
+        public Builder(@NonNull Uri uri) {
+            mUri = uri;
+        }
+
+        /**
+         * Create a builder for a {@link Slice} that is a sub-slice of the slice
+         * being constructed by the provided builder.
+         * @param parent The builder constructing the parent slice
+         */
+        public Builder(@NonNull Slice.Builder parent) {
+            mUri = parent.mUri.buildUpon().appendPath("_gen")
+                    .appendPath(String.valueOf(mItems.size())).build();
+        }
+
+        /**
+         * Add hints to the Slice being constructed
+         */
+        public Builder addHints(@SliceHint String... hints) {
+            mHints.addAll(Arrays.asList(hints));
+            return this;
+        }
+
+        /**
+         * Add hints to the Slice being constructed
+         */
+        public Builder addHints(@SliceHint List<String> hints) {
+            return addHints(hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add a sub-slice to the slice being constructed
+         */
+        public Builder addSubSlice(@NonNull Slice slice) {
+            mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
+                    new String[slice.getHints().size()])));
+            return this;
+        }
+
+        /**
+         * Add an action to the slice being constructed
+         */
+        public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
+            mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
+            return this;
+        }
+
+        /**
+         * Add text to the slice being constructed
+         */
+        public Builder addText(CharSequence text, @SliceHint String... hints) {
+            mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
+            return this;
+        }
+
+        /**
+         * Add text to the slice being constructed
+         */
+        public Builder addText(CharSequence text, @SliceHint List<String> hints) {
+            return addText(text, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         */
+        public Builder addIcon(Icon icon, @SliceHint String... hints) {
+            mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
+            return this;
+        }
+
+        /**
+         * Add an image to the slice being constructed
+         */
+        public Builder addIcon(Icon icon, @SliceHint List<String> hints) {
+            return addIcon(icon, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * @hide This isn't final
+         */
+        public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
+            mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
+            return this;
+        }
+
+        /**
+         * Add remote input to the slice being constructed
+         */
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput,
+                @SliceHint List<String> hints) {
+            return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add remote input to the slice being constructed
+         */
+        public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
+            mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
+            return this;
+        }
+
+        /**
+         * Add a color to the slice being constructed
+         */
+        public Builder addColor(int color, @SliceHint String... hints) {
+            mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
+            return this;
+        }
+
+        /**
+         * Add a color to the slice being constructed
+         */
+        public Builder addColor(int color, @SliceHint List<String> hints) {
+            return addColor(color, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Add a timestamp to the slice being constructed
+         */
+        public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
+            mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
+            return this;
+        }
+
+        /**
+         * Add a timestamp to the slice being constructed
+         */
+        public Slice.Builder addTimestamp(long time, @SliceHint List<String> hints) {
+            return addTimestamp(time, hints.toArray(new String[hints.size()]));
+        }
+
+        /**
+         * Construct the slice.
+         */
+        public Slice build() {
+            return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
+        }
+    }
+
+    public static final Creator<Slice> CREATOR = new Creator<Slice>() {
+        @Override
+        public Slice createFromParcel(Parcel in) {
+            return new Slice(in);
+        }
+
+        @Override
+        public Slice[] newArray(int size) {
+            return new Slice[size];
+        }
+    };
+
+    /**
+     * @hide
+     * @return A string representation of this slice.
+     */
+    public String toString() {
+        return toString("");
+    }
+
+    private String toString(String indent) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < mItems.length; i++) {
+            sb.append(indent);
+            if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
+                sb.append("slice:\n");
+                sb.append(mItems[i].getSlice().toString(indent + "   "));
+            } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
+                sb.append("text: ");
+                sb.append(mItems[i].getText());
+                sb.append("\n");
+            } else {
+                sb.append(SliceItem.typeToString(mItems[i].getType()));
+                sb.append("\n");
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Turns a slice Uri into slice content.
+     *
+     * @param resolver ContentResolver to be used.
+     * @param uri The URI to a slice provider
+     * @return The Slice provided by the app or null if none is given.
+     * @see Slice
+     */
+    public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
+        Preconditions.checkNotNull(uri, "uri");
+        IContentProvider provider = resolver.acquireProvider(uri);
+        if (provider == null) {
+            throw new IllegalArgumentException("Unknown URI " + uri);
+        }
+        try {
+            Bundle extras = new Bundle();
+            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
+            final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
+                    null, extras);
+            Bundle.setDefusable(res, true);
+            if (res == null) {
+                return null;
+            }
+            return res.getParcelable(SliceProvider.EXTRA_SLICE);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return null;
+        } finally {
+            resolver.releaseProvider(provider);
+        }
+    }
+}
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
new file mode 100644
index 0000000..6e69b05
--- /dev/null
+++ b/core/java/android/app/slice/SliceItem.java
@@ -0,0 +1,346 @@
+/*
+ * 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.app.slice;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A SliceItem is a single unit in the tree structure of a {@link Slice}.
+ *
+ * A SliceItem a piece of content and some hints about what that content
+ * means or how it should be displayed. The types of content can be:
+ * <li>{@link #TYPE_SLICE}</li>
+ * <li>{@link #TYPE_TEXT}</li>
+ * <li>{@link #TYPE_IMAGE}</li>
+ * <li>{@link #TYPE_ACTION}</li>
+ * <li>{@link #TYPE_COLOR}</li>
+ * <li>{@link #TYPE_TIMESTAMP}</li>
+ * <li>{@link #TYPE_REMOTE_INPUT}</li>
+ *
+ * The hints that a {@link SliceItem} are a set of strings which annotate
+ * the content. The hints that are guaranteed to be understood by the system
+ * are defined on {@link Slice}.
+ */
+public final class SliceItem implements Parcelable {
+
+    /**
+     * @hide
+     */
+    @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
+            TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
+    public @interface SliceType {}
+
+    /**
+     * A {@link SliceItem} that contains a {@link Slice}
+     */
+    public static final int TYPE_SLICE        = 1;
+    /**
+     * A {@link SliceItem} that contains a {@link CharSequence}
+     */
+    public static final int TYPE_TEXT         = 2;
+    /**
+     * A {@link SliceItem} that contains an {@link Icon}
+     */
+    public static final int TYPE_IMAGE        = 3;
+    /**
+     * A {@link SliceItem} that contains a {@link PendingIntent}
+     *
+     * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
+     * item contains a {@link Slice} that the action applies to.
+     */
+    public static final int TYPE_ACTION       = 4;
+    /**
+     * @hide This isn't final
+     */
+    public static final int TYPE_REMOTE_VIEW  = 5;
+    /**
+     * A {@link SliceItem} that contains a Color int.
+     */
+    public static final int TYPE_COLOR        = 6;
+    /**
+     * A {@link SliceItem} that contains a timestamp.
+     */
+    public static final int TYPE_TIMESTAMP    = 8;
+    /**
+     * A {@link SliceItem} that contains a {@link RemoteInput}.
+     */
+    public static final int TYPE_REMOTE_INPUT = 9;
+
+    /**
+     * @hide
+     */
+    protected @Slice.SliceHint
+    String[] mHints;
+    private final int mType;
+    private final Object mObj;
+
+    /**
+     * @hide
+     */
+    public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
+        mHints = hints;
+        mType = type;
+        mObj = obj;
+    }
+
+    /**
+     * @hide
+     */
+    public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
+        this(new Pair<>(intent, slice), type, hints);
+    }
+
+    /**
+     * Gets all hints associated with this SliceItem.
+     * @return Array of hints.
+     */
+    public @NonNull @Slice.SliceHint List<String> getHints() {
+        return Arrays.asList(mHints);
+    }
+
+    /**
+     * @hide
+     */
+    public void addHint(@Slice.SliceHint String hint) {
+        mHints = ArrayUtils.appendElement(String.class, mHints, hint);
+    }
+
+    /**
+     * @hide
+     */
+    public void removeHint(String hint) {
+        ArrayUtils.removeElement(String.class, mHints, hint);
+    }
+
+    public @SliceType int getType() {
+        return mType;
+    }
+
+    /**
+     * @return The text held by this {@link #TYPE_TEXT} SliceItem
+     */
+    public CharSequence getText() {
+        return (CharSequence) mObj;
+    }
+
+    /**
+     * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
+     */
+    public Icon getIcon() {
+        return (Icon) mObj;
+    }
+
+    /**
+     * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
+     */
+    public PendingIntent getAction() {
+        return ((Pair<PendingIntent, Slice>) mObj).first;
+    }
+
+    /**
+     * @hide This isn't final
+     */
+    public RemoteViews getRemoteView() {
+        return (RemoteViews) mObj;
+    }
+
+    /**
+     * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
+     */
+    public RemoteInput getRemoteInput() {
+        return (RemoteInput) mObj;
+    }
+
+    /**
+     * @return The color held by this {@link #TYPE_COLOR} SliceItem
+     */
+    public int getColor() {
+        return (Integer) mObj;
+    }
+
+    /**
+     * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
+     */
+    public Slice getSlice() {
+        if (getType() == TYPE_ACTION) {
+            return ((Pair<PendingIntent, Slice>) mObj).second;
+        }
+        return (Slice) mObj;
+    }
+
+    /**
+     * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
+     */
+    public long getTimestamp() {
+        return (Long) mObj;
+    }
+
+    /**
+     * @param hint The hint to check for
+     * @return true if this item contains the given hint
+     */
+    public boolean hasHint(@Slice.SliceHint String hint) {
+        return ArrayUtils.contains(mHints, hint);
+    }
+
+    /**
+     * @hide
+     */
+    public SliceItem(Parcel in) {
+        mHints = in.readStringArray();
+        mType = in.readInt();
+        mObj = readObj(mType, in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringArray(mHints);
+        dest.writeInt(mType);
+        writeObj(dest, flags, mObj, mType);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasHints(@Slice.SliceHint String[] hints) {
+        if (hints == null) return true;
+        for (String hint : hints) {
+            if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
+        if (hints == null) return false;
+        for (String hint : hints) {
+            if (ArrayUtils.contains(mHints, hint)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void writeObj(Parcel dest, int flags, Object obj, int type) {
+        switch (type) {
+            case TYPE_SLICE:
+            case TYPE_REMOTE_VIEW:
+            case TYPE_IMAGE:
+            case TYPE_REMOTE_INPUT:
+                ((Parcelable) obj).writeToParcel(dest, flags);
+                break;
+            case TYPE_ACTION:
+                ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
+                ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
+                break;
+            case TYPE_TEXT:
+                TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
+                break;
+            case TYPE_COLOR:
+                dest.writeInt((Integer) mObj);
+                break;
+            case TYPE_TIMESTAMP:
+                dest.writeLong((Long) mObj);
+                break;
+        }
+    }
+
+    private static Object readObj(int type, Parcel in) {
+        switch (type) {
+            case TYPE_SLICE:
+                return Slice.CREATOR.createFromParcel(in);
+            case TYPE_TEXT:
+                return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+            case TYPE_IMAGE:
+                return Icon.CREATOR.createFromParcel(in);
+            case TYPE_ACTION:
+                return new Pair<PendingIntent, Slice>(
+                        PendingIntent.CREATOR.createFromParcel(in),
+                        Slice.CREATOR.createFromParcel(in));
+            case TYPE_REMOTE_VIEW:
+                return RemoteViews.CREATOR.createFromParcel(in);
+            case TYPE_COLOR:
+                return in.readInt();
+            case TYPE_TIMESTAMP:
+                return in.readLong();
+            case TYPE_REMOTE_INPUT:
+                return RemoteInput.CREATOR.createFromParcel(in);
+        }
+        throw new RuntimeException("Unsupported type " + type);
+    }
+
+    public static final Creator<SliceItem> CREATOR = new Creator<SliceItem>() {
+        @Override
+        public SliceItem createFromParcel(Parcel in) {
+            return new SliceItem(in);
+        }
+
+        @Override
+        public SliceItem[] newArray(int size) {
+            return new SliceItem[size];
+        }
+    };
+
+    /**
+     * @hide
+     */
+    public static String typeToString(int type) {
+        switch (type) {
+            case TYPE_SLICE:
+                return "Slice";
+            case TYPE_TEXT:
+                return "Text";
+            case TYPE_IMAGE:
+                return "Image";
+            case TYPE_ACTION:
+                return "Action";
+            case TYPE_REMOTE_VIEW:
+                return "RemoteView";
+            case TYPE_COLOR:
+                return "Color";
+            case TYPE_TIMESTAMP:
+                return "Timestamp";
+            case TYPE_REMOTE_INPUT:
+                return "RemoteInput";
+        }
+        return "Unrecognized type: " + type;
+    }
+}
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
new file mode 100644
index 0000000..33825b4
--- /dev/null
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -0,0 +1,189 @@
+/*
+ * 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.app.slice;
+
+import android.Manifest.permission;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A SliceProvider allows app to provide content to be displayed in system
+ * spaces. This content is templated and can contain actions, and the behavior
+ * of how it is surfaced is specific to the system surface.
+ *
+ * <p>Slices are not currently live content. They are bound once and shown to the
+ * user. If the content changes due to a callback from user interaction, then
+ * {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+ * should be used to notify the system.</p>
+ *
+ * <p>The provider needs to be declared in the manifest to provide the authority
+ * for the app. The authority for most slices is expected to match the package
+ * of the application.</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <provider
+ *     android:name="com.android.mypkg.MySliceProvider"
+ *     android:authorities="com.android.mypkg" />}
+ * </pre>
+ *
+ * @see Slice
+ */
+public abstract class SliceProvider extends ContentProvider {
+
+    /**
+     * This is the Android platform's MIME type for a slice: URI
+     * containing a slice implemented through {@link SliceProvider}.
+     */
+    public static final String SLICE_TYPE = "vnd.android.slice";
+
+    private static final String TAG = "SliceProvider";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_BIND_URI = "slice_uri";
+    /**
+     * @hide
+     */
+    public static final String METHOD_SLICE = "bind_slice";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_SLICE = "slice";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Implemented to create a slice. Will be called on the main thread.
+     * <p>
+     * onBindSlice should return as quickly as possible so that the UI tied
+     * to this slice can be responsive. No network or other IO will be allowed
+     * during onBindSlice. Any loading that needs to be done should happen
+     * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
+     * when the app is ready to provide the complete data in onBindSlice.
+     * <p>
+     *
+     * @see {@link Slice}.
+     * @see {@link Slice#HINT_PARTIAL}
+     */
+    // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
+    public abstract Slice onBindSlice(Uri sliceUri);
+
+    @Override
+    public final int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        if (DEBUG) Log.d(TAG, "update " + uri);
+        return 0;
+    }
+
+    @Override
+    public final int delete(Uri uri, String selection, String[] selectionArgs) {
+        if (DEBUG) Log.d(TAG, "delete " + uri);
+        return 0;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection, String[]
+            selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+            CancellationSignal cancellationSignal) {
+        if (DEBUG) Log.d(TAG, "query " + uri);
+        return null;
+    }
+
+    @Override
+    public final Uri insert(Uri uri, ContentValues values) {
+        if (DEBUG) Log.d(TAG, "insert " + uri);
+        return null;
+    }
+
+    @Override
+    public final String getType(Uri uri) {
+        if (DEBUG) Log.d(TAG, "getType " + uri);
+        return SLICE_TYPE;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (method.equals(METHOD_SLICE)) {
+            getContext().enforceCallingPermission(permission.BIND_SLICE,
+                    "Slice binding requires the permission BIND_SLICE");
+            Uri uri = extras.getParcelable(EXTRA_BIND_URI);
+
+            Slice s = handleBindSlice(uri);
+            Bundle b = new Bundle();
+            b.putParcelable(EXTRA_SLICE, s);
+            return b;
+        }
+        return super.call(method, arg, extras);
+    }
+
+    private Slice handleBindSlice(Uri sliceUri) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            return onBindSliceStrict(sliceUri);
+        } else {
+            CountDownLatch latch = new CountDownLatch(1);
+            Slice[] output = new Slice[1];
+            Handler.getMain().post(() -> {
+                output[0] = onBindSliceStrict(sliceUri);
+                latch.countDown();
+            });
+            try {
+                latch.await();
+                return output[0];
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private Slice onBindSliceStrict(Uri sliceUri) {
+        ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
+        try {
+            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                    .detectAll()
+                    .penaltyDeath()
+                    .build());
+            return onBindSlice(sliceUri);
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+}
diff --git a/core/java/android/app/slice/SliceQuery.java b/core/java/android/app/slice/SliceQuery.java
new file mode 100644
index 0000000..9943c49
--- /dev/null
+++ b/core/java/android/app/slice/SliceQuery.java
@@ -0,0 +1,171 @@
+/*
+ * 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.app.slice;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Spliterators;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * A bunch of utilities for searching the contents of a slice.
+ * @hide
+ */
+public class SliceQuery {
+    private static final String TAG = "SliceQuery";
+
+    /**
+     * @hide
+     */
+    public static SliceItem getPrimaryIcon(Slice slice) {
+        for (SliceItem item : slice.getItems()) {
+            if (item.getType() == SliceItem.TYPE_IMAGE) {
+                return item;
+            }
+            if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
+                    && !item.hasHint(Slice.HINT_ACTIONS)
+                    && !item.hasHint(Slice.HINT_LIST_ITEM)
+                    && (item.getType() != SliceItem.TYPE_ACTION)) {
+                SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
+                if (icon != null) {
+                    return icon;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem findNotContaining(SliceItem container, List<SliceItem> list) {
+        SliceItem ret = null;
+        while (ret == null && list.size() != 0) {
+            SliceItem remove = list.remove(0);
+            if (!contains(container, remove)) {
+                ret = remove;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * @hide
+     */
+    private static boolean contains(SliceItem container, SliceItem item) {
+        if (container == null || item == null) return false;
+        return stream(container).filter(s -> (s == item)).findAny().isPresent();
+    }
+
+    /**
+     * @hide
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type) {
+        return findAll(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String hints, String nonHints) {
+        return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     * @hide
+     */
+    public static List<SliceItem> findAll(SliceItem s, int type, String[] hints,
+            String[] nonHints) {
+        return stream(s).filter(item -> (type == -1 || item.getType() == type)
+                && (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(Slice s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(Slice s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(SliceItem s, int type) {
+        return find(s, type, (String[]) null, null);
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
+        return find(s, type, new String[]{ hints }, new String[]{ nonHints });
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
+        List<String> h = s.getHints();
+        return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
+                hints, nonHints);
+    }
+
+    /**
+     * @hide
+     */
+    public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
+        return stream(s).filter(item -> (item.getType() == type || type == -1)
+                && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
+    }
+
+    /**
+     * @hide
+     */
+    public static Stream<SliceItem> stream(SliceItem slice) {
+        Queue<SliceItem> items = new LinkedList();
+        items.add(slice);
+        Iterator<SliceItem> iterator = new Iterator<SliceItem>() {
+            @Override
+            public boolean hasNext() {
+                return items.size() != 0;
+            }
+
+            @Override
+            public SliceItem next() {
+                SliceItem item = items.poll();
+                if (item.getType() == SliceItem.TYPE_SLICE
+                        || item.getType() == SliceItem.TYPE_ACTION) {
+                    items.addAll(item.getSlice().getItems());
+                }
+                return item;
+            }
+        };
+        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
+    }
+}
diff --git a/core/java/android/app/slice/widget/ActionRow.java b/core/java/android/app/slice/widget/ActionRow.java
new file mode 100644
index 0000000..c96e6304
--- /dev/null
+++ b/core/java/android/app/slice/widget/ActionRow.java
@@ -0,0 +1,201 @@
+/*
+ * 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.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class ActionRow extends FrameLayout {
+
+    private static final int MAX_ACTIONS = 5;
+    private final int mSize;
+    private final int mIconPadding;
+    private final LinearLayout mActionsGroup;
+    private final boolean mFullActions;
+    private int mColor = Color.BLACK;
+
+    public ActionRow(Context context, boolean fullActions) {
+        super(context);
+        mFullActions = fullActions;
+        mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
+                context.getResources().getDisplayMetrics());
+        mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
+                context.getResources().getDisplayMetrics());
+        mActionsGroup = new LinearLayout(context);
+        mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
+        mActionsGroup.setLayoutParams(
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        addView(mActionsGroup);
+    }
+
+    private void setColor(int color) {
+        mColor = color;
+        for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
+            View view = mActionsGroup.getChildAt(i);
+            SliceItem item = (SliceItem) view.getTag();
+            boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
+            if (tint) {
+                ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
+            }
+        }
+    }
+
+    private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
+        ImageView imageView = new ImageView(getContext());
+        imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
+        imageView.setScaleType(ScaleType.FIT_CENTER);
+        imageView.setImageIcon(icon);
+        if (allowTint) {
+            imageView.setImageTintList(ColorStateList.valueOf(mColor));
+        }
+        imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
+                android.R.attr.selectableItemBackground));
+        imageView.setTag(image);
+        addAction(imageView);
+        return imageView;
+    }
+
+    /**
+     * Set the actions and color for this action row.
+     */
+    public void setActions(SliceItem actionRow, SliceItem defColor) {
+        removeAllViews();
+        mActionsGroup.removeAllViews();
+        addView(mActionsGroup);
+
+        SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
+        if (color == null) {
+            color = defColor;
+        }
+        if (color != null) {
+            setColor(color.getColor());
+        }
+        SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
+            if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
+                return;
+            }
+            SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
+            if (image == null) {
+                return;
+            }
+            boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
+            SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
+            if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
+                addAction(image.getIcon(), tint, image).setOnClickListener(
+                        v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
+                createRemoteInputView(mColor, getContext());
+            } else {
+                addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                action.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+            }
+        });
+        setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
+    }
+
+    private void addAction(View child) {
+        mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
+    }
+
+    private void createRemoteInputView(int color, Context context) {
+        View riv = RemoteInputView.inflate(context, this);
+        riv.setVisibility(View.INVISIBLE);
+        addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        riv.setBackgroundColor(color);
+    }
+
+    private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
+            RemoteInput input) {
+        if (input == null) {
+            return false;
+        }
+
+        ViewParent p = view.getParent().getParent();
+        RemoteInputView riv = null;
+        while (p != null) {
+            if (p instanceof View) {
+                View pv = (View) p;
+                riv = findRemoteInputView(pv);
+                if (riv != null) {
+                    break;
+                }
+            }
+            p = p.getParent();
+        }
+        if (riv == null) {
+            return false;
+        }
+
+        int width = view.getWidth();
+        if (view instanceof TextView) {
+            // Center the reveal on the text which might be off-center from the TextView
+            TextView tv = (TextView) view;
+            if (tv.getLayout() != null) {
+                int innerWidth = (int) tv.getLayout().getLineWidth(0);
+                innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
+                width = Math.min(width, innerWidth);
+            }
+        }
+        int cx = view.getLeft() + width / 2;
+        int cy = view.getTop() + view.getHeight() / 2;
+        int w = riv.getWidth();
+        int h = riv.getHeight();
+        int r = Math.max(
+                Math.max(cx + cy, cx + (h - cy)),
+                Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+
+        riv.setRevealParameters(cx, cy, r);
+        riv.setPendingIntent(pendingIntent);
+        riv.setRemoteInput(new RemoteInput[] {
+                input
+        }, input);
+        riv.focusAnimated();
+        return true;
+    }
+
+    private RemoteInputView findRemoteInputView(View v) {
+        if (v == null) {
+            return null;
+        }
+        return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
+    }
+}
diff --git a/core/java/android/app/slice/widget/GridView.java b/core/java/android/app/slice/widget/GridView.java
new file mode 100644
index 0000000..67a3c67
--- /dev/null
+++ b/core/java/android/app/slice/widget/GridView.java
@@ -0,0 +1,186 @@
+/*
+ * 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.app.slice.widget;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
+import android.content.Context;
+import android.graphics.Color;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class GridView extends LinearLayout implements SliceListView {
+
+    private static final String TAG = "GridView";
+
+    private static final int MAX_IMAGES = 3;
+    private static final int MAX_ALL = 5;
+    private boolean mIsAllImages;
+
+    public GridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mIsAllImages) {
+            int width = MeasureSpec.getSize(widthMeasureSpec);
+            int height = width / getChildCount();
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
+                    height);
+            getLayoutParams().height = height;
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).getLayoutParams().height = height;
+            }
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        mIsAllImages = true;
+        removeAllViews();
+        int total = 1;
+        if (slice.getType() == SliceItem.TYPE_SLICE) {
+            List<SliceItem> items = slice.getSlice().getItems();
+            total = items.size();
+            for (int i = 0; i < total; i++) {
+                SliceItem item = items.get(i);
+                if (isFull()) {
+                    continue;
+                }
+                if (!addItem(item)) {
+                    mIsAllImages = false;
+                }
+            }
+        } else {
+            if (!isFull()) {
+                if (!addItem(slice)) {
+                    mIsAllImages = false;
+                }
+            }
+        }
+        if (total > getChildCount() && mIsAllImages) {
+            addExtraCount(total - getChildCount());
+        }
+    }
+
+    private void addExtraCount(int numExtra) {
+        View last = getChildAt(getChildCount() - 1);
+        FrameLayout frame = new FrameLayout(getContext());
+        frame.setLayoutParams(last.getLayoutParams());
+
+        removeView(last);
+        frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        TextView v = new TextView(getContext());
+        v.setTextColor(Color.WHITE);
+        v.setBackgroundColor(0x4d000000);
+        v.setText(getResources().getString(R.string.slice_more_content, numExtra));
+        v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
+        v.setGravity(Gravity.CENTER);
+        frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+        addView(frame);
+    }
+
+    private boolean isFull() {
+        return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
+    }
+
+    /**
+     * Returns true if this item is just an image.
+     */
+    private boolean addItem(SliceItem item) {
+        if (item.getType() == SliceItem.TYPE_IMAGE) {
+            ImageView v = new ImageView(getContext());
+            v.setImageIcon(item.getIcon());
+            v.setScaleType(ScaleType.CENTER_CROP);
+            addView(v, new LayoutParams(0, MATCH_PARENT, 1));
+            return true;
+        } else {
+            LinearLayout v = new LinearLayout(getContext());
+            int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    12, getContext().getResources().getDisplayMetrics());
+            v.setPadding(0, s, 0, 0);
+            v.setOrientation(LinearLayout.VERTICAL);
+            v.setGravity(Gravity.CENTER_HORIZONTAL);
+            // TODO: Unify sporadic inflates that happen throughout the code.
+            ArrayList<SliceItem> items = new ArrayList<>();
+            if (item.getType() == SliceItem.TYPE_SLICE) {
+                items.addAll(item.getSlice().getItems());
+            }
+            items.forEach(i -> {
+                Context context = getContext();
+                switch (i.getType()) {
+                    case SliceItem.TYPE_TEXT:
+                        boolean title = false;
+                        if ((item.hasAnyHints(new String[] {
+                                Slice.HINT_LARGE, Slice.HINT_TITLE
+                        }))) {
+                            title = true;
+                        }
+                        TextView tv = (TextView) LayoutInflater.from(context).inflate(
+                                title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
+                        tv.setText(i.getText());
+                        v.addView(tv);
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        ImageView iv = new ImageView(context);
+                        iv.setImageIcon(i.getIcon());
+                        if (item.hasHint(Slice.HINT_LARGE)) {
+                            iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+                        } else {
+                            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                                    48, context.getResources().getDisplayMetrics());
+                            iv.setLayoutParams(new LayoutParams(size, size));
+                        }
+                        v.addView(iv);
+                        break;
+                    case SliceItem.TYPE_REMOTE_VIEW:
+                        v.addView(i.getRemoteView().apply(context, v));
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        // TODO: Support color to tint stuff here.
+                        break;
+                }
+            });
+            addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/app/slice/widget/LargeSliceAdapter.java b/core/java/android/app/slice/widget/LargeSliceAdapter.java
new file mode 100644
index 0000000..267fff6
--- /dev/null
+++ b/core/java/android/app/slice/widget/LargeSliceAdapter.java
@@ -0,0 +1,224 @@
+/*
+ * 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.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.widget.LargeSliceAdapter.SliceViewHolder;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.widget.RecyclerView;
+import com.android.internal.widget.RecyclerView.ViewHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+public class LargeSliceAdapter extends RecyclerView.Adapter<SliceViewHolder> {
+
+    public static final int TYPE_DEFAULT       = 1;
+    public static final int TYPE_HEADER        = 2;
+    public static final int TYPE_GRID          = 3;
+    public static final int TYPE_MESSAGE       = 4;
+    public static final int TYPE_MESSAGE_LOCAL = 5;
+    public static final int TYPE_REMOTE_VIEWS  = 6;
+
+    private final IdGenerator mIdGen = new IdGenerator();
+    private final Context mContext;
+    private List<SliceWrapper> mSlices = new ArrayList<>();
+    private SliceItem mColor;
+
+    public LargeSliceAdapter(Context context) {
+        mContext = context;
+        setHasStableIds(true);
+    }
+
+    /**
+     * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
+     */
+    public void setSliceItems(List<SliceItem> slices, SliceItem color) {
+        mColor = color;
+        mIdGen.resetUsage();
+        mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
+                .collect(Collectors.toList());
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View v = inflateForType(viewType);
+        v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        return new SliceViewHolder(v);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSlices.get(position).mType;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mSlices.get(position).mId;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSlices.size();
+    }
+
+    @Override
+    public void onBindViewHolder(SliceViewHolder holder, int position) {
+        SliceWrapper slice = mSlices.get(position);
+        if (holder.mSliceView != null) {
+            holder.mSliceView.setColor(mColor);
+            holder.mSliceView.setSliceItem(slice.mItem);
+        } else if (slice.mType == TYPE_REMOTE_VIEWS) {
+            FrameLayout frame = (FrameLayout) holder.itemView;
+            frame.removeAllViews();
+            frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
+        }
+    }
+
+    private View inflateForType(int viewType) {
+        switch (viewType) {
+            case TYPE_REMOTE_VIEWS:
+                return new FrameLayout(mContext);
+            case TYPE_GRID:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
+            case TYPE_MESSAGE:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
+            case TYPE_MESSAGE_LOCAL:
+                return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
+        }
+        return new SmallTemplateView(mContext);
+    }
+
+    protected static class SliceWrapper {
+        private final SliceItem mItem;
+        private final int mType;
+        private final long mId;
+
+        public SliceWrapper(SliceItem item, IdGenerator idGen) {
+            mItem = item;
+            mType = getType(item);
+            mId = idGen.getId(item);
+        }
+
+        public static int getType(SliceItem item) {
+            if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
+                return TYPE_REMOTE_VIEWS;
+            }
+            if (item.hasHint(Slice.HINT_MESSAGE)) {
+                // TODO: Better way to determine me or not? Something more like Messaging style.
+                if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
+                    return TYPE_MESSAGE;
+                } else {
+                    return TYPE_MESSAGE_LOCAL;
+                }
+            }
+            if (item.hasHint(Slice.HINT_HORIZONTAL)) {
+                return TYPE_GRID;
+            }
+            return TYPE_DEFAULT;
+        }
+    }
+
+    /**
+     * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
+     */
+    public static class SliceViewHolder extends ViewHolder {
+        public final SliceListView mSliceView;
+
+        public SliceViewHolder(View itemView) {
+            super(itemView);
+            mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
+        }
+    }
+
+    /**
+     * View slices being displayed in {@link LargeSliceAdapter}.
+     */
+    public interface SliceListView {
+        /**
+         * Set the slice item for this view.
+         */
+        void setSliceItem(SliceItem slice);
+
+        /**
+         * Set the color for the items in this view.
+         */
+        default void setColor(SliceItem color) {
+
+        }
+    }
+
+    private static class IdGenerator {
+        private long mNextLong = 0;
+        private final ArrayMap<String, Long> mCurrentIds = new ArrayMap<>();
+        private final ArrayMap<String, Integer> mUsedIds = new ArrayMap<>();
+
+        public long getId(SliceItem item) {
+            String str = genString(item);
+            if (!mCurrentIds.containsKey(str)) {
+                mCurrentIds.put(str, mNextLong++);
+            }
+            long id = mCurrentIds.get(str);
+            int index = mUsedIds.getOrDefault(str, 0);
+            mUsedIds.put(str, index + 1);
+            return id + index * 10000;
+        }
+
+        private String genString(SliceItem item) {
+            StringBuilder builder = new StringBuilder();
+            SliceQuery.stream(item).forEach(i -> {
+                builder.append(i.getType());
+                i.removeHint(Slice.HINT_SELECTED);
+                builder.append(i.getHints());
+                switch (i.getType()) {
+                    case SliceItem.TYPE_REMOTE_VIEW:
+                        builder.append(i.getRemoteView());
+                        break;
+                    case SliceItem.TYPE_IMAGE:
+                        builder.append(i.getIcon());
+                        break;
+                    case SliceItem.TYPE_TEXT:
+                        builder.append(i.getText());
+                        break;
+                    case SliceItem.TYPE_COLOR:
+                        builder.append(i.getColor());
+                        break;
+                }
+            });
+            return builder.toString();
+        }
+
+        public void resetUsage() {
+            mUsedIds.clear();
+        }
+    }
+}
diff --git a/core/java/android/app/slice/widget/LargeTemplateView.java b/core/java/android/app/slice/widget/LargeTemplateView.java
new file mode 100644
index 0000000..f45b2a8
--- /dev/null
+++ b/core/java/android/app/slice/widget/LargeTemplateView.java
@@ -0,0 +1,122 @@
+/*
+ * 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.app.slice.widget;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.widget.SliceView.SliceModeView;
+import android.content.Context;
+import android.util.TypedValue;
+
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class LargeTemplateView extends SliceModeView {
+
+    private final LargeSliceAdapter mAdapter;
+    private final RecyclerView mRecyclerView;
+    private final int mDefaultHeight;
+    private final int mMaxHeight;
+    private Slice mSlice;
+    private boolean mIsScrollable;
+
+    public LargeTemplateView(Context context) {
+        super(context);
+
+        mRecyclerView = new RecyclerView(getContext());
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        mAdapter = new LargeSliceAdapter(context);
+        mRecyclerView.setAdapter(mAdapter);
+        addView(mRecyclerView);
+        mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+        mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
+                getResources().getDisplayMetrics());
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_LARGE;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mRecyclerView.getMeasuredHeight() > mMaxHeight
+                || (mSlice != null && mSlice.hasHint(Slice.HINT_PARTIAL))) {
+            mRecyclerView.getLayoutParams().height = mDefaultHeight;
+        } else {
+            mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        mSlice = slice;
+        List<SliceItem> items = new ArrayList<>();
+        boolean[] hasHeader = new boolean[1];
+        if (slice.hasHint(Slice.HINT_LIST)) {
+            addList(slice, items);
+        } else {
+            slice.getItems().forEach(item -> {
+                if (item.hasHint(Slice.HINT_ACTIONS)) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_COLOR) {
+                    return;
+                } else if (item.getType() == SliceItem.TYPE_SLICE
+                        && item.hasHint(Slice.HINT_LIST)) {
+                    addList(item.getSlice(), items);
+                } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
+                    items.add(item);
+                } else if (!hasHeader[0]) {
+                    hasHeader[0] = true;
+                    items.add(0, item);
+                } else {
+                    item.addHint(Slice.HINT_LIST_ITEM);
+                    items.add(item);
+                }
+            });
+        }
+        mAdapter.setSliceItems(items, color);
+    }
+
+    private void addList(Slice slice, List<SliceItem> items) {
+        List<SliceItem> sliceItems = slice.getItems();
+        sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
+        items.addAll(sliceItems);
+    }
+
+    /**
+     * Whether or not the content in this template should be scrollable.
+     */
+    public void setScrollable(boolean isScrollable) {
+        // TODO -- restrict / enable how much this view can show
+        mIsScrollable = isScrollable;
+    }
+}
diff --git a/core/java/android/app/slice/widget/MessageView.java b/core/java/android/app/slice/widget/MessageView.java
new file mode 100644
index 0000000..3124398
--- /dev/null
+++ b/core/java/android/app/slice/widget/MessageView.java
@@ -0,0 +1,77 @@
+/*
+ * 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.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.text.SpannableStringBuilder;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class MessageView extends LinearLayout implements SliceListView {
+
+    private TextView mDetails;
+    private ImageView mIcon;
+
+    public MessageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDetails = findViewById(android.R.id.summary);
+        mIcon = findViewById(android.R.id.icon);
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
+        if (source != null) {
+            final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    24, getContext().getResources().getDisplayMetrics());
+            // TODO try and turn this into a drawable
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            Drawable d = source.getIcon().loadDrawable(getContext());
+            d.setBounds(0, 0, iconSize, iconSize);
+            d.draw(iconCanvas);
+            mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
+        }
+        SpannableStringBuilder builder = new SpannableStringBuilder();
+        SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
+            if (builder.length() != 0) {
+                builder.append('\n');
+            }
+            builder.append(text.getText());
+        });
+        mDetails.setText(builder.toString());
+    }
+
+}
diff --git a/core/java/android/app/slice/widget/RemoteInputView.java b/core/java/android/app/slice/widget/RemoteInputView.java
new file mode 100644
index 0000000..6eff5af
--- /dev/null
+++ b/core/java/android/app/slice/widget/RemoteInputView.java
@@ -0,0 +1,445 @@
+/*
+ * 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.app.slice.widget;
+
+import android.animation.Animator;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ShortcutManager;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+/**
+ * Host for the remote input.
+ *
+ * @hide
+ */
+// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
+public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
+
+    private static final String TAG = "RemoteInput";
+
+    /**
+     * A marker object that let's us easily find views of this class.
+     */
+    public static final Object VIEW_TAG = new Object();
+
+    private RemoteEditText mEditText;
+    private ImageButton mSendButton;
+    private ProgressBar mProgressBar;
+    private PendingIntent mPendingIntent;
+    private RemoteInput[] mRemoteInputs;
+    private RemoteInput mRemoteInput;
+
+    private int mRevealCx;
+    private int mRevealCy;
+    private int mRevealR;
+    private boolean mResetting;
+
+    public RemoteInputView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mProgressBar = findViewById(R.id.remote_input_progress);
+        mSendButton = findViewById(R.id.remote_input_send);
+        mSendButton.setOnClickListener(this);
+
+        mEditText = (RemoteEditText) getChildAt(0);
+        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                final boolean isSoftImeEvent = event == null
+                        && (actionId == EditorInfo.IME_ACTION_DONE
+                                || actionId == EditorInfo.IME_ACTION_NEXT
+                                || actionId == EditorInfo.IME_ACTION_SEND);
+                final boolean isKeyboardEnterKey = event != null
+                        && KeyEvent.isConfirmKey(event.getKeyCode())
+                        && event.getAction() == KeyEvent.ACTION_DOWN;
+
+                if (isSoftImeEvent || isKeyboardEnterKey) {
+                    if (mEditText.length() > 0) {
+                        sendRemoteInput();
+                    }
+                    // Consume action to prevent IME from closing.
+                    return true;
+                }
+                return false;
+            }
+        });
+        mEditText.addTextChangedListener(this);
+        mEditText.setInnerFocusable(false);
+        mEditText.mRemoteInputView = this;
+    }
+
+    private void sendRemoteInput() {
+        Bundle results = new Bundle();
+        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
+                results);
+
+        mEditText.setEnabled(false);
+        mSendButton.setVisibility(INVISIBLE);
+        mProgressBar.setVisibility(VISIBLE);
+        mEditText.mShowImeOnInputConnection = false;
+
+        // Tell ShortcutManager that this package has been "activated".  ShortcutManager
+        // will reset the throttling for this package.
+        // Strictly speaking, the intent receiver may be different from the intent creator,
+        // but that's an edge case, and also because we can't always know which package will receive
+        // an intent, so we just reset for the creator.
+        getContext().getSystemService(ShortcutManager.class).onApplicationActive(
+                mPendingIntent.getCreatorPackage(),
+                getContext().getUserId());
+
+        try {
+            mPendingIntent.send(mContext, 0, fillInIntent);
+            reset();
+        } catch (PendingIntent.CanceledException e) {
+            Log.i(TAG, "Unable to send remote input result", e);
+            Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
+                    Toast.LENGTH_SHORT).show();
+            reset();
+        }
+    }
+
+    /**
+     * Creates a remote input view.
+     */
+    public static RemoteInputView inflate(Context context, ViewGroup root) {
+        RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
+                R.layout.slice_remote_input, root, false);
+        v.setTag(VIEW_TAG);
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v == mSendButton) {
+            sendRemoteInput();
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        // We never want for a touch to escape to an outer view or one we covered.
+        return true;
+    }
+
+    private void onDefocus() {
+        setVisibility(INVISIBLE);
+    }
+
+    /**
+     * Set the pending intent for remote input.
+     */
+    public void setPendingIntent(PendingIntent pendingIntent) {
+        mPendingIntent = pendingIntent;
+    }
+
+    /**
+     * Set the remote inputs for this view.
+     */
+    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+        mRemoteInputs = remoteInputs;
+        mRemoteInput = remoteInput;
+        mEditText.setHint(mRemoteInput.getLabel());
+    }
+
+    /**
+     * Focuses the remote input view.
+     */
+    public void focusAnimated() {
+        if (getVisibility() != VISIBLE) {
+            Animator animator = ViewAnimationUtils.createCircularReveal(
+                    this, mRevealCx, mRevealCy, 0, mRevealR);
+            animator.setDuration(200);
+            animator.start();
+        }
+        focus();
+    }
+
+    private void focus() {
+        setVisibility(VISIBLE);
+        mEditText.setInnerFocusable(true);
+        mEditText.mShowImeOnInputConnection = true;
+        mEditText.setSelection(mEditText.getText().length());
+        mEditText.requestFocus();
+        updateSendButton();
+    }
+
+    private void reset() {
+        mResetting = true;
+
+        mEditText.getText().clear();
+        mEditText.setEnabled(true);
+        mSendButton.setVisibility(VISIBLE);
+        mProgressBar.setVisibility(INVISIBLE);
+        updateSendButton();
+        onDefocus();
+
+        mResetting = false;
+    }
+
+    @Override
+    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
+        if (mResetting && child == mEditText) {
+            // Suppress text events if it happens during resetting. Ideally this would be
+            // suppressed by the text view not being shown, but that doesn't work here because it
+            // needs to stay visible for the animation.
+            return false;
+        }
+        return super.onRequestSendAccessibilityEvent(child, event);
+    }
+
+    private void updateSendButton() {
+        mSendButton.setEnabled(mEditText.getText().length() != 0);
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        updateSendButton();
+    }
+
+    /**
+     * Tries to find an action that matches the current pending intent of this view and updates its
+     * state to that of the found action
+     *
+     * @return true if a matching action was found, false otherwise
+     */
+    public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
+        if (mPendingIntent == null || actions == null) {
+            return false;
+        }
+        Intent current = mPendingIntent.getIntent();
+        if (current == null) {
+            return false;
+        }
+
+        for (Notification.Action a : actions) {
+            RemoteInput[] inputs = a.getRemoteInputs();
+            if (a.actionIntent == null || inputs == null) {
+                continue;
+            }
+            Intent candidate = a.actionIntent.getIntent();
+            if (!current.filterEquals(candidate)) {
+                continue;
+            }
+
+            RemoteInput input = null;
+            for (RemoteInput i : inputs) {
+                if (i.getAllowFreeFormInput()) {
+                    input = i;
+                }
+            }
+            if (input == null) {
+                continue;
+            }
+            setPendingIntent(a.actionIntent);
+            setRemoteInput(inputs, input);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public void setRevealParameters(int cx, int cy, int r) {
+        mRevealCx = cx;
+        mRevealCy = cy;
+        mRevealR = r;
+    }
+
+    @Override
+    public void dispatchStartTemporaryDetach() {
+        super.dispatchStartTemporaryDetach();
+        // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
+        // won't lose IME focus.
+        detachViewFromParent(mEditText);
+    }
+
+    @Override
+    public void dispatchFinishTemporaryDetach() {
+        if (isAttachedToWindow()) {
+            attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
+        } else {
+            removeDetachedView(mEditText, false /* animate */);
+        }
+        super.dispatchFinishTemporaryDetach();
+    }
+
+    /**
+     * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
+     * whenever the user navigates away from it or it becomes invisible.
+     */
+    public static class RemoteEditText extends EditText {
+
+        private final Drawable mBackground;
+        private RemoteInputView mRemoteInputView;
+        boolean mShowImeOnInputConnection;
+
+        public RemoteEditText(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            mBackground = getBackground();
+        }
+
+        private void defocusIfNeeded(boolean animate) {
+            if (mRemoteInputView != null || isTemporarilyDetached()) {
+                if (isTemporarilyDetached()) {
+                    // We might get reattached but then the other one of HUN / expanded might steal
+                    // our focus, so we'll need to save our text here.
+                }
+                return;
+            }
+            if (isFocusable() && isEnabled()) {
+                setInnerFocusable(false);
+                if (mRemoteInputView != null) {
+                    mRemoteInputView.onDefocus();
+                }
+                mShowImeOnInputConnection = false;
+            }
+        }
+
+        @Override
+        protected void onVisibilityChanged(View changedView, int visibility) {
+            super.onVisibilityChanged(changedView, visibility);
+
+            if (!isShown()) {
+                defocusIfNeeded(false /* animate */);
+            }
+        }
+
+        @Override
+        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+            super.onFocusChanged(focused, direction, previouslyFocusedRect);
+            if (!focused) {
+                defocusIfNeeded(true /* animate */);
+            }
+        }
+
+        @Override
+        public void getFocusedRect(Rect r) {
+            super.getFocusedRect(r);
+            r.top = mScrollY;
+            r.bottom = mScrollY + (mBottom - mTop);
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // Eat the DOWN event here to prevent any default behavior.
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                defocusIfNeeded(true /* animate */);
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+
+        @Override
+        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
+
+            if (mShowImeOnInputConnection && inputConnection != null) {
+                final InputMethodManager imm = InputMethodManager.getInstance();
+                if (imm != null) {
+                    // onCreateInputConnection is called by InputMethodManager in the middle of
+                    // setting up the connection to the IME; wait with requesting the IME until that
+                    // work has completed.
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            imm.viewClicked(RemoteEditText.this);
+                            imm.showSoftInput(RemoteEditText.this, 0);
+                        }
+                    });
+                }
+            }
+
+            return inputConnection;
+        }
+
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
+        void setInnerFocusable(boolean focusable) {
+            setFocusableInTouchMode(focusable);
+            setFocusable(focusable);
+            setCursorVisible(focusable);
+
+            if (focusable) {
+                requestFocus();
+                setBackground(mBackground);
+            } else {
+                setBackground(null);
+            }
+
+        }
+    }
+}
diff --git a/core/java/android/app/slice/widget/ShortcutView.java b/core/java/android/app/slice/widget/ShortcutView.java
new file mode 100644
index 0000000..0bca8ce
--- /dev/null
+++ b/core/java/android/app/slice/widget/ShortcutView.java
@@ -0,0 +1,106 @@
+/*
+ * 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.app.slice.widget;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.widget.SliceView.SliceModeView;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.net.Uri;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ */
+public class ShortcutView extends SliceModeView {
+
+    private static final String TAG = "ShortcutView";
+
+    private PendingIntent mAction;
+    private Uri mUri;
+    private int mLargeIconSize;
+    private int mSmallIconSize;
+
+    public ShortcutView(Context context) {
+        super(context);
+        mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        removeAllViews();
+        SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        SliceItem iconItem = SliceQuery.getPrimaryIcon(slice);
+        SliceItem textItem = sliceItem != null
+                ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
+                : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
+        SliceItem colorItem = sliceItem != null
+                ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR)
+                : SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        if (colorItem == null) {
+            colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        }
+        // TODO: pick better default colour
+        final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
+        ShapeDrawable circle = new ShapeDrawable(new OvalShape());
+        circle.setTint(color);
+        setBackground(circle);
+        if (iconItem != null) {
+            final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
+            final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
+            SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
+                    isLarge, this /* parent */);
+            mAction = sliceItem != null ? sliceItem.getAction()
+                    : null;
+            mUri = slice.getUri();
+            setClickable(true);
+        } else {
+            setClickable(false);
+        }
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_SHORTCUT;
+    }
+
+    @Override
+    public boolean performClick() {
+        if (!callOnClick()) {
+            try {
+                if (mAction != null) {
+                    mAction.send();
+                } else {
+                    Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    getContext().startActivity(intent);
+                }
+            } catch (CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/app/slice/widget/SliceView.java b/core/java/android/app/slice/widget/SliceView.java
new file mode 100644
index 0000000..5bafbc0
--- /dev/null
+++ b/core/java/android/app/slice/widget/SliceView.java
@@ -0,0 +1,402 @@
+/*
+ * 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.app.slice.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * A view for displaying a {@link Slice} which is a piece of app content and actions. SliceView is
+ * able to present slice content in a templated format outside of the associated app. The way this
+ * content is displayed depends on the structure of the slice, the hints associated with the
+ * content, and the mode that SliceView is configured for. The modes that SliceView supports are:
+ * <ul>
+ * <li><b>Shortcut</b>: A shortcut is presented as an icon and a text label representing the main
+ * content or action associated with the slice.</li>
+ * <li><b>Small</b>: The small format has a restricted height and can present a single
+ * {@link SliceItem} or a limited collection of items.</li>
+ * <li><b>Large</b>: The large format displays multiple small templates in a list, if scrolling is
+ * not enabled (see {@link #setScrollable(boolean)}) the view will show as many items as it can
+ * comfortably fit.</li>
+ * </ul>
+ * <p>
+ * When constructing a slice, the contents of it can be annotated with hints, these provide the OS
+ * with some information on how the content should be displayed. For example, text annotated with
+ * {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
+ * with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
+ * <p>
+ * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
+ * observer will be set for that uri and the view will update if there are any changes to the slice.
+ * To use this the app must have a special permission to bind to the slice (see
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ * <p>
+ * Example usage:
+ *
+ * <pre class="prettyprint">
+ * SliceView v = new SliceView(getContext());
+ * v.setMode(desiredMode);
+ * v.setSlice(sliceUri);
+ * </pre>
+ */
+public class SliceView extends ViewGroup {
+
+    private static final String TAG = "SliceView";
+
+    /**
+     * @hide
+     */
+    public abstract static class SliceModeView extends FrameLayout {
+
+        public SliceModeView(Context context) {
+            super(context);
+        }
+
+        /**
+         * @return the mode of the slice being presented.
+         */
+        public abstract String getMode();
+
+        /**
+         * @param slice the slice to show in this view.
+         */
+        public abstract void setSlice(Slice slice);
+    }
+
+    /**
+     * @hide
+     */
+    @StringDef({
+            MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
+    })
+    public @interface SliceMode {}
+
+    /**
+     * Mode indicating this slice should be presented in small template format.
+     */
+    public static final String MODE_SMALL       = "SLICE_SMALL";
+    /**
+     * Mode indicating this slice should be presented in large template format.
+     */
+    public static final String MODE_LARGE       = "SLICE_LARGE";
+    /**
+     * Mode indicating this slice should be presented as an icon.
+     */
+    public static final String MODE_SHORTCUT    = "SLICE_ICON";
+
+    /**
+     * Will select the type of slice binding based on size of the View. TODO: Put in some info about
+     * that selection.
+     */
+    private static final String MODE_AUTO = "auto";
+
+    private String mMode = MODE_AUTO;
+    private SliceModeView mCurrentView;
+    private final ActionRow mActions;
+    private Slice mCurrentSlice;
+    private boolean mShowActions = true;
+    private boolean mIsScrollable;
+    private SliceObserver mObserver;
+    private final int mShortcutSize;
+
+    public SliceView(Context context) {
+        this(context, null);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SliceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+        mActions = new ActionRow(mContext, true);
+        mActions.setBackground(new ColorDrawable(0xffeeeeee));
+        mCurrentView = new LargeTemplateView(mContext);
+        addView(mCurrentView, getChildLp(mCurrentView));
+        addView(mActions, getChildLp(mActions));
+        mShortcutSize = getContext().getResources()
+                .getDimensionPixelSize(R.dimen.slice_shortcut_size);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+        int actionHeight = mActions.getVisibility() != View.GONE
+                ? mActions.getMeasuredHeight()
+                : 0;
+        int newHeightSpec = MeasureSpec.makeMeasureSpec(
+                mCurrentView.getMeasuredHeight() + actionHeight, MeasureSpec.EXACTLY);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        setMeasuredDimension(width, newHeightSpec);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mCurrentView.layout(l, t, l + mCurrentView.getMeasuredWidth(),
+                t + mCurrentView.getMeasuredHeight());
+        if (mActions.getVisibility() != View.GONE) {
+            mActions.layout(l, mCurrentView.getMeasuredHeight(), l + mActions.getMeasuredWidth(),
+                    mCurrentView.getMeasuredHeight() + mActions.getMeasuredHeight());
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void showSlice(Intent intent) {
+        // TODO
+    }
+
+    /**
+     * Populates this view with the {@link Slice} associated with the provided {@link Uri}. To use
+     * this method your app must have the permission
+     * {@link android.Manifest.permission#BIND_SLICE}).
+     * <p>
+     * Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
+     * updated when the slice identified by the provided URI changes. The lifecycle of this observer
+     * is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
+     * To unregister this observer outside of that you can call {@link #clearSlice}.
+     *
+     * @return true if the a slice was found for the provided uri.
+     * @see #clearSlice
+     */
+    public boolean setSlice(@NonNull Uri sliceUri) {
+        Preconditions.checkNotNull(sliceUri,
+                "Uri cannot be null, to remove the slice use clearSlice()");
+        if (sliceUri == null) {
+            clearSlice();
+            return false;
+        }
+        validate(sliceUri);
+        Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
+        if (s != null) {
+            mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
+            if (isAttachedToWindow()) {
+                registerSlice(sliceUri);
+            }
+            showSlice(s);
+        }
+        return s != null;
+    }
+
+    /**
+     * Populates this view to the provided {@link Slice}.
+     * <p>
+     * This does not register a content observer on the URI that the slice is backed by so it will
+     * not update if the content changes. To have the view update when the content changes use
+     * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
+     * any special permissions.
+     */
+    public void showSlice(@NonNull Slice slice) {
+        Preconditions.checkNotNull(slice,
+                "Slice cannot be null, to remove the slice use clearSlice()");
+        clearSlice();
+        mCurrentSlice = slice;
+        reinflate();
+    }
+
+    /**
+     * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
+     * done automatically during {@link #onDetachedFromWindow()}.
+     * <p>
+     * It is safe to call this method multiple times.
+     */
+    public void clearSlice() {
+        mCurrentSlice = null;
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+
+    /**
+     * Set the mode this view should present in.
+     */
+    public void setMode(@SliceMode String mode) {
+        setMode(mode, false /* animate */);
+    }
+
+    /**
+     * Set whether this view should allow scrollable content when presenting in {@link #MODE_LARGE}.
+     */
+    public void setScrollable(boolean isScrollable) {
+        mIsScrollable = isScrollable;
+        reinflate();
+    }
+
+    /**
+     * @hide
+     */
+    public void setMode(@SliceMode String mode, boolean animate) {
+        if (animate) {
+            Log.e(TAG, "Animation not supported yet");
+        }
+        mMode = mode;
+        reinflate();
+    }
+
+    /**
+     * @return the mode this view is presenting in.
+     */
+    public @SliceMode String getMode() {
+        if (mMode.equals(MODE_AUTO)) {
+            return MODE_LARGE;
+        }
+        return mMode;
+    }
+
+    /**
+     * @hide
+     *
+     * Whether this view should show a row of actions with it.
+     */
+    public void setShowActionRow(boolean show) {
+        mShowActions = show;
+        reinflate();
+    }
+
+    private SliceModeView createView(String mode) {
+        switch (mode) {
+            case MODE_SHORTCUT:
+                return new ShortcutView(getContext());
+            case MODE_SMALL:
+                return new SmallTemplateView(getContext());
+        }
+        return new LargeTemplateView(getContext());
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mObserver != null) {
+            getContext().getContentResolver().unregisterContentObserver(mObserver);
+            mObserver = null;
+        }
+    }
+
+    private void registerSlice(Uri sliceUri) {
+        if (sliceUri == null || mObserver == null) {
+            return;
+        }
+        mContext.getContentResolver().registerContentObserver(sliceUri,
+                false /* notifyForDescendants */, mObserver);
+    }
+
+    private void reinflate() {
+        if (mCurrentSlice == null) {
+            return;
+        }
+        // TODO: Smarter mapping here from one state to the next.
+        SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
+        List<SliceItem> items = mCurrentSlice.getItems();
+        SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
+                Slice.HINT_ACTIONS,
+                Slice.HINT_ALT);
+        String mode = getMode();
+        if (!mode.equals(mCurrentView.getMode())) {
+            removeAllViews();
+            mCurrentView = createView(mode);
+            addView(mCurrentView, getChildLp(mCurrentView));
+            addView(mActions, getChildLp(mActions));
+        }
+        if (mode.equals(MODE_LARGE)) {
+            ((LargeTemplateView) mCurrentView).setScrollable(mIsScrollable);
+        }
+        if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
+            mCurrentView.setVisibility(View.VISIBLE);
+            mCurrentView.setSlice(mCurrentSlice);
+        } else {
+            mCurrentView.setVisibility(View.GONE);
+        }
+
+        boolean showActions = mShowActions && actionRow != null
+                && !mode.equals(MODE_SHORTCUT);
+        if (showActions) {
+            mActions.setActions(actionRow, color);
+            mActions.setVisibility(View.VISIBLE);
+        } else {
+            mActions.setVisibility(View.GONE);
+        }
+    }
+
+    private LayoutParams getChildLp(View child) {
+        if (child instanceof ShortcutView) {
+            return new LayoutParams(mShortcutSize, mShortcutSize);
+        } else {
+            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    private static void validate(Uri sliceUri) {
+        if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+        if (sliceUri.getPathSegments().size() == 0) {
+            throw new RuntimeException("Invalid uri " + sliceUri);
+        }
+    }
+
+    private class SliceObserver extends ContentObserver {
+        SliceObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            this.onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            Slice s = Slice.bindSlice(mContext.getContentResolver(), uri);
+            mCurrentSlice = s;
+            reinflate();
+        }
+    }
+}
diff --git a/core/java/android/app/slice/widget/SliceViewUtil.java b/core/java/android/app/slice/widget/SliceViewUtil.java
new file mode 100644
index 0000000..0366998
--- /dev/null
+++ b/core/java/android/app/slice/widget/SliceViewUtil.java
@@ -0,0 +1,182 @@
+/*
+ * 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.app.slice.widget;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * A bunch of utilities for slice UI.
+ *
+ * @hide
+ */
+public class SliceViewUtil {
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorAccent(Context context) {
+        return getColorAttr(context, android.R.attr.colorAccent);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorError(Context context) {
+        return getColorAttr(context, android.R.attr.colorError);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getDefaultColor(Context context, int resId) {
+        final ColorStateList list = context.getResources().getColorStateList(resId,
+                context.getTheme());
+
+        return list.getDefaultColor();
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getDisabled(Context context, int inputColor) {
+        return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int applyAlphaAttr(Context context, int attr, int inputColor) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        float alpha = ta.getFloat(0, 0);
+        ta.recycle();
+        return applyAlpha(alpha, inputColor);
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int applyAlpha(float alpha, int inputColor) {
+        alpha *= Color.alpha(inputColor);
+        return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
+                Color.blue(inputColor));
+    }
+
+    /**
+     * @hide
+     */
+    @ColorInt
+    public static int getColorAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        @ColorInt
+        int colorAccent = ta.getColor(0, 0);
+        ta.recycle();
+        return colorAccent;
+    }
+
+    /**
+     * @hide
+     */
+    public static int getThemeAttr(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        int theme = ta.getResourceId(0, 0);
+        ta.recycle();
+        return theme;
+    }
+
+    /**
+     * @hide
+     */
+    public static Drawable getDrawable(Context context, int attr) {
+        TypedArray ta = context.obtainStyledAttributes(new int[] {
+                attr
+        });
+        Drawable drawable = ta.getDrawable(0);
+        ta.recycle();
+        return drawable;
+    }
+
+    /**
+     * @hide
+     */
+    public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
+            boolean isLarge, ViewGroup parent) {
+        ImageView v = new ImageView(context);
+        v.setImageIcon(icon);
+        parent.addView(v);
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        if (isLarge) {
+            // XXX better way to convert from icon -> bitmap or crop an icon (?)
+            Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+            Canvas iconCanvas = new Canvas(iconBm);
+            v.layout(0, 0, iconSize, iconSize);
+            v.draw(iconCanvas);
+            v.setImageBitmap(getCircularBitmap(iconBm));
+        } else {
+            v.setColorFilter(Color.WHITE);
+        }
+        lp.width = iconSize;
+        lp.height = iconSize;
+        lp.gravity = Gravity.CENTER;
+    }
+
+    /**
+     * @hide
+     */
+    public static Bitmap getCircularBitmap(Bitmap bitmap) {
+        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
+                bitmap.getHeight(), Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+        final Paint paint = new Paint();
+        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
+        paint.setAntiAlias(true);
+        canvas.drawARGB(0, 0, 0, 0);
+        canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
+                bitmap.getWidth() / 2, paint);
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, rect, rect, paint);
+        return output;
+    }
+}
diff --git a/core/java/android/app/slice/widget/SmallTemplateView.java b/core/java/android/app/slice/widget/SmallTemplateView.java
new file mode 100644
index 0000000..1c4c5df
--- /dev/null
+++ b/core/java/android/app/slice/widget/SmallTemplateView.java
@@ -0,0 +1,211 @@
+/*
+ * 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.app.slice.widget;
+
+import android.app.PendingIntent.CanceledException;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.app.slice.widget.LargeSliceAdapter.SliceListView;
+import android.app.slice.widget.SliceView.SliceModeView;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Small template is also used to construct list items for use with {@link LargeTemplateView}.
+ *
+ * @hide
+ */
+public class SmallTemplateView extends SliceModeView implements SliceListView {
+
+    private static final String TAG = "SmallTemplateView";
+
+    private int mIconSize;
+    private int mPadding;
+
+    private LinearLayout mStartContainer;
+    private TextView mTitleText;
+    private TextView mSecondaryText;
+    private LinearLayout mEndContainer;
+
+    public SmallTemplateView(Context context) {
+        super(context);
+        inflate(context, R.layout.slice_small_template, this);
+        mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
+        mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
+
+        mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
+        mTitleText = (TextView) findViewById(android.R.id.title);
+        mSecondaryText = (TextView) findViewById(android.R.id.summary);
+        mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
+    }
+
+    @Override
+    public String getMode() {
+        return SliceView.MODE_SMALL;
+    }
+
+    @Override
+    public void setSliceItem(SliceItem slice) {
+        resetViews();
+        SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
+        int color = colorItem != null ? colorItem.getColor() : -1;
+
+        // Look for any title elements
+        List<SliceItem> titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
+                null);
+        boolean hasTitleText = false;
+        boolean hasTitleItem = false;
+        for (int i = 0; i < titleItems.size(); i++) {
+            SliceItem item = titleItems.get(i);
+            if (!hasTitleItem) {
+                // icon, action icon, or timestamp
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    hasTitleItem = addIcon(item, color, mStartContainer);
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mStartContainer);
+                    hasTitleItem = true;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    hasTitleItem = true;
+                }
+            }
+            if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
+                mTitleText.setText(item.getText());
+                hasTitleText = true;
+            }
+            if (hasTitleText && hasTitleItem) {
+                break;
+            }
+        }
+        mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
+        mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
+
+        if (slice.getType() != SliceItem.TYPE_SLICE) {
+            return;
+        }
+
+        // Deal with remaining items
+        int itemCount = 0;
+        boolean hasSummary = false;
+        ArrayList<SliceItem> sliceItems = new ArrayList<SliceItem>(
+                slice.getSlice().getItems());
+        for (int i = 0; i < sliceItems.size(); i++) {
+            SliceItem item = sliceItems.get(i);
+            if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
+                    && !item.hasHint(Slice.HINT_TITLE)) {
+                // TODO -- Should combine all text items?
+                mSecondaryText.setText(item.getText());
+                hasSummary = true;
+            }
+            if (itemCount <= 3) {
+                if (item.getType() == SliceItem.TYPE_ACTION) {
+                    if (addIcon(item, color, mEndContainer)) {
+                        itemCount++;
+                    }
+                } else if (item.getType() == SliceItem.TYPE_IMAGE) {
+                    addIcon(item, color, mEndContainer);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
+                    TextView tv = new TextView(getContext());
+                    tv.setText(convertTimeToString(item.getTimestamp()));
+                    mEndContainer.addView(tv);
+                    itemCount++;
+                } else if (item.getType() == SliceItem.TYPE_SLICE) {
+                    List<SliceItem> subItems = item.getSlice().getItems();
+                    for (int j = 0; j < subItems.size(); j++) {
+                        sliceItems.add(subItems.get(j));
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setSlice(Slice slice) {
+        setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
+                slice.getHints().toArray(new String[slice.getHints().size()])));
+    }
+
+    /**
+     * @return Whether an icon was added.
+     */
+    private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
+        SliceItem image = null;
+        SliceItem action = null;
+        if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
+            image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
+            action = sliceItem;
+        } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
+            image = sliceItem;
+        }
+        if (image != null) {
+            ImageView iv = new ImageView(getContext());
+            iv.setImageIcon(image.getIcon());
+            if (action != null) {
+                final SliceItem sliceAction = action;
+                iv.setOnClickListener(v -> AsyncTask.execute(
+                        () -> {
+                            try {
+                                sliceAction.getAction().send();
+                            } catch (CanceledException e) {
+                                e.printStackTrace();
+                            }
+                        }));
+                iv.setBackground(SliceViewUtil.getDrawable(getContext(),
+                        android.R.attr.selectableItemBackground));
+            }
+            if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
+                iv.setColorFilter(color);
+            }
+            container.addView(iv);
+            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
+            lp.width = mIconSize;
+            lp.height = mIconSize;
+            lp.setMarginStart(mPadding);
+            return true;
+        }
+        return false;
+    }
+
+    private String convertTimeToString(long time) {
+        // TODO -- figure out what format(s) we support
+        Date date = new Date(time);
+        Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
+        return format.format(date);
+    }
+
+    private void resetViews() {
+        mStartContainer.removeAllViews();
+        mEndContainer.removeAllViews();
+        mTitleText.setText(null);
+        mSecondaryText.setText(null);
+    }
+}
diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java
index 9c62f46..74ed658 100644
--- a/core/java/android/app/timezone/RulesUpdaterContract.java
+++ b/core/java/android/app/timezone/RulesUpdaterContract.java
@@ -51,7 +51,7 @@
      * applies.
      */
     public static final String ACTION_TRIGGER_RULES_UPDATE_CHECK =
-            "android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
+            "com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK";
 
     /**
      * The extra containing the {@code byte[]} that should be passed to
@@ -61,7 +61,7 @@
      * {@link #ACTION_TRIGGER_RULES_UPDATE_CHECK} intent has been processed.
      */
     public static final String EXTRA_CHECK_TOKEN =
-            "android.intent.extra.timezone.CHECK_TOKEN";
+            "com.android.intent.extra.timezone.CHECK_TOKEN";
 
     /**
      * Creates an intent that would trigger a time zone rules update check.
@@ -83,8 +83,7 @@
         Intent intent = createUpdaterIntent(updaterAppPackageName);
         intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes);
         context.sendBroadcastAsUser(
-                intent,
-                UserHandle.of(UserHandle.myUserId()),
+                intent, UserHandle.SYSTEM,
                 RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION);
     }
 }
diff --git a/core/java/android/app/usage/AppStandby.java b/core/java/android/app/usage/AppStandby.java
new file mode 100644
index 0000000..6f9fc2f
--- /dev/null
+++ b/core/java/android/app/usage/AppStandby.java
@@ -0,0 +1,83 @@
+/*
+ * 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.app.usage;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets
+ * that affect how frequently they can run in the background or perform other battery-consuming
+ * actions. Buckets will be assigned based on how frequently or when the system thinks the user
+ * is likely to use the app.
+ * @hide
+ */
+public class AppStandby {
+
+    /** The app was used very recently, currently in use or likely to be used very soon. */
+    public static final int STANDBY_BUCKET_ACTIVE = 0;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app was used recently and/or likely to be used in the next few hours  */
+    public static final int STANDBY_BUCKET_WORKING_SET = 3;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app was used in the last few days and/or likely to be used in the next few days */
+    public static final int STANDBY_BUCKET_FREQUENT = 6;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app has not be used for several days and/or is unlikely to be used for several days */
+    public static final int STANDBY_BUCKET_RARE = 9;
+
+    // Leave some gap in case we want to increase the number of buckets
+
+    /** The app has never been used. */
+    public static final int STANDBY_BUCKET_NEVER = 12;
+
+    /** Reason for bucketing -- default initial state */
+    public static final String REASON_DEFAULT = "default";
+
+    /** Reason for bucketing -- timeout */
+    public static final String REASON_TIMEOUT = "timeout";
+
+    /** Reason for bucketing -- usage */
+    public static final String REASON_USAGE = "usage";
+
+    /** Reason for bucketing -- forced by user / shell command */
+    public static final String REASON_FORCED = "forced";
+
+    /**
+     * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will
+     * be appended.
+     */
+    public static final String REASON_PREDICTED = "predicted";
+
+    @IntDef(flag = false, value = {
+            STANDBY_BUCKET_ACTIVE,
+            STANDBY_BUCKET_WORKING_SET,
+            STANDBY_BUCKET_FREQUENT,
+            STANDBY_BUCKET_RARE,
+            STANDBY_BUCKET_NEVER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StandbyBuckets {}
+}
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 31b2359..4fbbdf2 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -36,4 +36,6 @@
     void onCarrierPrivilegedAppsChanged();
     void reportChooserSelection(String packageName, int userId, String contentType,
             in String[] annotations, String action);
+    int getAppStandbyBucket(String packageName, String callingPackage, int userId);
+    void setAppStandbyBucket(String packageName, int bucket, int userId);
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 051dccb..c827432 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -19,6 +19,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.app.usage.AppStandby.StandbyBuckets;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
@@ -48,10 +49,10 @@
  * </pre>
  * A request for data in the middle of a time interval will include that interval.
  * <p/>
- * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS, which
- * is a system-level permission and will not be granted to third-party apps. However, declaring
- * the permission implies intention to use the API and the user of the device can grant permission
- * through the Settings application.
+ * <b>NOTE:</b> This API requires the permission android.permission.PACKAGE_USAGE_STATS.
+ * However, declaring the permission implies intention to use the API and the user of the device
+ * still needs to grant permission through the Settings application.
+ * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}
  */
 @SystemService(Context.USAGE_STATS_SERVICE)
 public final class UsageStatsManager {
@@ -122,7 +123,7 @@
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return A list of {@link UsageStats} or null if none are available.
+     * @return A list of {@link UsageStats}
      *
      * @see #INTERVAL_DAILY
      * @see #INTERVAL_WEEKLY
@@ -139,7 +140,7 @@
                 return slice.getList();
             }
         } catch (RemoteException e) {
-            // fallthrough and return null.
+            // fallthrough and return the empty list.
         }
         return Collections.emptyList();
     }
@@ -152,7 +153,7 @@
      * @param intervalType The time interval by which the stats are aggregated.
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return A list of {@link ConfigurationStats} or null if none are available.
+     * @return A list of {@link ConfigurationStats}
      */
     public List<ConfigurationStats> queryConfigurations(int intervalType, long beginTime,
             long endTime) {
@@ -185,7 +186,7 @@
                 return iter;
             }
         } catch (RemoteException e) {
-            // fallthrough and return null
+            // fallthrough and return empty result.
         }
         return sEmptyResults;
     }
@@ -197,8 +198,7 @@
      *
      * @param beginTime The inclusive beginning of the range of stats to include in the results.
      * @param endTime The exclusive end of the range of stats to include in the results.
-     * @return A {@link java.util.Map} keyed by package name, or null if no stats are
-     *         available.
+     * @return A {@link java.util.Map} keyed by package name
      */
     public Map<String, UsageStats> queryAndAggregateUsageStats(long beginTime, long endTime) {
         List<UsageStats> stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
@@ -248,6 +248,29 @@
     }
 
     /**
+     * @hide
+     */
+    public @StandbyBuckets int getAppStandbyBucket(String packageName) {
+        try {
+            return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(),
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+        }
+        return AppStandby.STANDBY_BUCKET_ACTIVE;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
+        try {
+            mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
+        } catch (RemoteException e) {
+            // Nothing to do
+        }
+    }
+
+    /**
      * {@hide}
      * Temporarily whitelist the specified app for a short duration. This is to allow an app
      * receiving a high priority message to be able to access the network and acquire wakelocks
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index dc9970a..ab0eb92 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -19,8 +19,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Color;
@@ -66,11 +64,8 @@
 
     // When we're inflating the initialLayout for a AppWidget, we only allow
     // views that are allowed in RemoteViews.
-    static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() {
-        public boolean onLoadClass(Class clazz) {
-            return clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
-        }
-    };
+    private static final LayoutInflater.Filter INFLATER_FILTER =
+            (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
 
     Context mContext;
     Context mRemoteContext;
@@ -136,13 +131,19 @@
         mAppWidgetId = appWidgetId;
         mInfo = info;
 
+        // We add padding to the AppWidgetHostView if necessary
+        Rect padding = getDefaultPadding();
+        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
         // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
         // a widget, eg. for some widgets in safe mode.
         if (info != null) {
-            // We add padding to the AppWidgetHostView if necessary
-            Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
-            setPadding(padding.left, padding.top, padding.right, padding.bottom);
-            updateContentDescription(info);
+            String description = info.loadLabel(getContext().getPackageManager());
+            if ((info.providerInfo.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
+                description = Resources.getSystem().getString(
+                        com.android.internal.R.string.suspended_widget_accessibility, description);
+            }
+            setContentDescription(description);
         }
     }
 
@@ -164,23 +165,23 @@
      */
     public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
             Rect padding) {
-        PackageManager packageManager = context.getPackageManager();
-        ApplicationInfo appInfo;
+        ApplicationInfo appInfo = null;
+        try {
+            appInfo = context.getPackageManager().getApplicationInfo(component.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            // if we can't find the package, ignore
+        }
+        return getDefaultPaddingForWidget(context, appInfo, padding);
+    }
 
+    private static Rect getDefaultPaddingForWidget(Context context, ApplicationInfo appInfo,
+            Rect padding) {
         if (padding == null) {
             padding = new Rect(0, 0, 0, 0);
         } else {
             padding.set(0, 0, 0, 0);
         }
-
-        try {
-            appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
-        } catch (NameNotFoundException e) {
-            // if we can't find the package, return 0 padding
-            return padding;
-        }
-
-        if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+        if (appInfo != null && appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
             Resources r = context.getResources();
             padding.left = r.getDimensionPixelSize(com.android.internal.
                     R.dimen.default_app_widget_padding_left);
@@ -194,6 +195,11 @@
         return padding;
     }
 
+    private Rect getDefaultPadding() {
+        return getDefaultPaddingForWidget(mContext,
+                mInfo == null ? null : mInfo.providerInfo.applicationInfo, null);
+    }
+
     public int getAppWidgetId() {
         return mAppWidgetId;
     }
@@ -284,10 +290,7 @@
             newOptions = new Bundle();
         }
 
-        Rect padding = new Rect();
-        if (mInfo != null) {
-            padding = getDefaultPaddingForWidget(mContext, mInfo.provider, padding);
-        }
+        Rect padding = getDefaultPadding();
         float density = getResources().getDisplayMetrics().density;
 
         int xPaddingDips = (int) ((padding.left + padding.right) / density);
@@ -361,7 +364,7 @@
      * initial layout.
      */
     void resetAppWidget(AppWidgetProviderInfo info) {
-        mInfo = info;
+        setAppWidget(mAppWidgetId, info);
         mViewMode = VIEW_MODE_NOINIT;
         updateAppWidget(null);
     }
@@ -433,7 +436,6 @@
         }
 
         applyContent(content, recycled, exception);
-        updateContentDescription(mInfo);
     }
 
     private void applyContent(View content, boolean recycled, Exception exception) {
@@ -460,27 +462,6 @@
         }
     }
 
-    private void updateContentDescription(AppWidgetProviderInfo info) {
-        if (info != null) {
-            LauncherApps launcherApps = getContext().getSystemService(LauncherApps.class);
-            ApplicationInfo appInfo = null;
-            try {
-                appInfo = launcherApps.getApplicationInfo(
-                        info.provider.getPackageName(), 0, info.getProfile());
-            } catch (NameNotFoundException e) {
-                // ignore -- use null.
-            }
-            if (appInfo != null &&
-                    (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0) {
-                setContentDescription(
-                        Resources.getSystem().getString(
-                        com.android.internal.R.string.suspended_widget_accessibility, info.label));
-            } else {
-                setContentDescription(info.label);
-            }
-        }
-    }
-
     private void inflateAsync(RemoteViews remoteViews) {
         // Prepare a local reference to the remote Context so we're ready to
         // inflate any requested LayoutParams.
@@ -614,7 +595,7 @@
                 LayoutInflater inflater = (LayoutInflater)
                         theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                 inflater = inflater.cloneInContext(theirContext);
-                inflater.setFilter(sInflaterFilter);
+                inflater.setFilter(INFLATER_FILTER);
                 AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
                 Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
 
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 969b19e..37bb6b0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -20,17 +20,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.IServiceConnection;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -1051,43 +1053,23 @@
      * The appWidgetId specified must already be bound to the calling AppWidgetHost via
      * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
      *
-     * @param packageName   The package from which the binding is requested.
      * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
      * @param intent        The intent of the service which will be providing the data to the
      *                      RemoteViewsAdapter.
      * @param connection    The callback interface to be notified when a connection is made or lost.
+     * @param flags         Flags used for binding to the service
+     *
+     * @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
      * @hide
      */
-    public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
-            IBinder connection) {
+    public boolean bindRemoteViewsService(Context context, int appWidgetId, Intent intent,
+            IServiceConnection connection, @Context.BindServiceFlags int flags) {
         if (mService == null) {
-            return;
+            return false;
         }
         try {
-            mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Unbinds the RemoteViewsService for a given appWidgetId and intent.
-     *
-     * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
-     * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
-     *
-     * @param packageName   The package from which the binding is requested.
-     * @param appWidgetId   The AppWidget instance for which to bind the RemoteViewsService.
-     * @param intent        The intent of the service which will be providing the data to the
-     *                      RemoteViewsAdapter.
-     * @hide
-     */
-    public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
-        if (mService == null) {
-            return;
-        }
-        try {
-            mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
+            return mService.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent,
+                    context.getIApplicationThread(), context.getActivityToken(), connection, flags);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 70591d4..578a5b8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1134,6 +1134,53 @@
     }
 
     /**
+     * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+     * adapter.
+     *
+     * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public BluetoothClass getBluetoothClass() {
+        if (getState() != STATE_ON) return null;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getBluetoothClass();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+     * adapter.
+     *
+     * <p>Note: This value persists across system reboot.
+     *
+     * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
+     * @return true if successful, false if unsuccessful.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+        if (getState() != STATE_ON) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.setBluetoothClass(bluetoothClass);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
      * Get the current Bluetooth scan mode of the local Bluetooth adapter.
      * <p>The Bluetooth scan mode determines if the local adapter is
      * connectable and/or discoverable from remote Bluetooth devices.
@@ -2081,8 +2128,8 @@
         } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
             BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
             return true;
-        } else if (profile == BluetoothProfile.INPUT_DEVICE) {
-            BluetoothInputDevice iDev = new BluetoothInputDevice(context, listener);
+        } else if (profile == BluetoothProfile.HID_HOST) {
+            BluetoothHidHost iDev = new BluetoothHidHost(context, listener);
             return true;
         } else if (profile == BluetoothProfile.PAN) {
             BluetoothPan pan = new BluetoothPan(context, listener);
@@ -2105,8 +2152,8 @@
         } else if (profile == BluetoothProfile.MAP_CLIENT) {
             BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
             return true;
-        } else if (profile == BluetoothProfile.INPUT_HOST) {
-            BluetoothInputHost iHost = new BluetoothInputHost(context, listener);
+        } else if (profile == BluetoothProfile.HID_DEVICE) {
+            BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
             return true;
         } else {
             return false;
@@ -2144,8 +2191,8 @@
                 BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy;
                 avrcp.close();
                 break;
-            case BluetoothProfile.INPUT_DEVICE:
-                BluetoothInputDevice iDev = (BluetoothInputDevice) proxy;
+            case BluetoothProfile.HID_HOST:
+                BluetoothHidHost iDev = (BluetoothHidHost) proxy;
                 iDev.close();
                 break;
             case BluetoothProfile.PAN:
@@ -2184,9 +2231,9 @@
                 BluetoothMapClient mapClient = (BluetoothMapClient) proxy;
                 mapClient.close();
                 break;
-            case BluetoothProfile.INPUT_HOST:
-                BluetoothInputHost iHost = (BluetoothInputHost) proxy;
-                iHost.close();
+            case BluetoothProfile.HID_DEVICE:
+                BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy;
+                hidDevice.close();
                 break;
         }
     }
@@ -2254,6 +2301,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean enableNoAutoConnect() {
         if (isEnabled()) {
             if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT already enabled!");
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
index 57e4abb..f22ea6e 100755
--- a/core/java/android/bluetooth/BluetoothClass.java
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -19,6 +19,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
 /**
  * Represents a Bluetooth class, which describes general characteristics
  * and capabilities of a device. For example, a Bluetooth class will
@@ -275,6 +279,48 @@
         return (mClass & Device.BITMASK);
     }
 
+    /**
+     * Return the Bluetooth Class of Device (CoD) value including the
+     * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+     * minor device fields.
+     *
+     * <p>This value is an integer representation of Bluetooth CoD as in
+     * Bluetooth specification.
+     *
+     * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+     *
+     * @hide
+     */
+    public int getClassOfDevice() {
+        return mClass;
+    }
+
+    /**
+     * Return the Bluetooth Class of Device (CoD) value including the
+     * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+     * minor device fields.
+     *
+     * <p>This value is a byte array representation of Bluetooth CoD as in
+     * Bluetooth specification.
+     *
+     * <p>Bluetooth COD information is 3 bytes, but stored as an int. Hence the
+     * MSB is useless and needs to be thrown away. The lower 3 bytes are
+     * converted into a byte array MSB to LSB. Hence, using BIG_ENDIAN.
+     *
+     * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+     *
+     * @hide
+     */
+    public byte[] getClassOfDeviceBytes() {
+        byte[] bytes = ByteBuffer.allocate(4)
+                .order(ByteOrder.BIG_ENDIAN)
+                .putInt(mClass)
+                .array();
+
+        // Discard the top byte
+        return Arrays.copyOfRange(bytes, 1, bytes.length);
+    }
+
     /** @hide */
     public static final int PROFILE_HEADSET = 0;
     /** @hide */
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index d982bb7..ad7a93c 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1098,6 +1098,8 @@
      * @return true on success, false on error
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean cancelBondProcess() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1125,6 +1127,8 @@
      * @return true on success, false on error
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean removeBond() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1174,6 +1178,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
     public boolean isConnected() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1197,6 +1202,7 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
     public boolean isEncrypted() {
         final IBluetooth service = sService;
         if (service == null) {
@@ -1444,6 +1450,8 @@
      * @return Whether the value has been successfully set.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setPhonebookAccessPermission(int value) {
         final IBluetooth service = sService;
         if (service == null) {
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 2c12403..243ad35 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -120,7 +120,7 @@
     public static final int WRITE_TYPE_DEFAULT = 0x02;
 
     /**
-     * Wrtite characteristic without requiring a response by the remote device
+     * Write characteristic without requiring a response by the remote device
      */
     public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
 
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 85550c7..1241f23 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,8 +16,10 @@
 
 package android.bluetooth;
 
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
@@ -416,6 +418,8 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -456,6 +460,8 @@
      * @return false on immediate error, true otherwise
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHeadset service = mService;
@@ -543,6 +549,8 @@
      * @return true if priority is set, false on error
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         final IBluetoothHeadset service = mService;
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
new file mode 100644
index 0000000..e3d763a
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2016 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.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Provides the public APIs to control the Bluetooth HID Device
+ * profile.
+ *
+ * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID
+ * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHidDevice proxy object.
+ *
+ * {@hide}
+ */
+public final class BluetoothHidDevice implements BluetoothProfile {
+
+    private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+
+    /**
+     * Intent used to broadcast the change in connection state of the Input
+     * Host profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Constants representing device subclass.
+     *
+     * @see #registerApp
+     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
+     */
+    public static final byte SUBCLASS1_NONE = (byte) 0x00;
+    public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
+    public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
+    public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
+
+    public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
+    public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
+    public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
+    public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
+    public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
+    public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05;
+    public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
+
+    /**
+     * Constants representing report types.
+     *
+     * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
+     * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
+     */
+    public static final byte REPORT_TYPE_INPUT = (byte) 1;
+    public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
+    public static final byte REPORT_TYPE_FEATURE = (byte) 3;
+
+    /**
+     * Constants representing error response for Set Report.
+     *
+     * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_SUCCESS = (byte) 0;
+    public static final byte ERROR_RSP_NOT_READY = (byte) 1;
+    public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
+    public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
+    public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
+    public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
+
+    /**
+     * Constants representing protocol mode used set by host. Default is always
+     * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
+     *
+     * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte)
+     */
+    public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
+    public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
+
+    private Context mContext;
+
+    private ServiceListener mServiceListener;
+
+    private volatile IBluetoothHidDevice mService;
+
+    private BluetoothAdapter mAdapter;
+
+    private static class BluetoothHidDeviceCallbackWrapper extends
+            IBluetoothHidDeviceCallback.Stub {
+
+        private BluetoothHidDeviceCallback mCallback;
+
+        public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAppStatusChanged(BluetoothDevice pluggedDevice,
+                BluetoothHidDeviceAppConfiguration config, boolean registered) {
+            mCallback.onAppStatusChanged(pluggedDevice, config, registered);
+        }
+
+        @Override
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+            mCallback.onConnectionStateChanged(device, state);
+        }
+
+        @Override
+        public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
+            mCallback.onGetReport(device, type, id, bufferSize);
+        }
+
+        @Override
+        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            mCallback.onSetReport(device, type, id, data);
+        }
+
+        @Override
+        public void onSetProtocol(BluetoothDevice device, byte protocol) {
+            mCallback.onSetProtocol(device, protocol);
+        }
+
+        @Override
+        public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
+            mCallback.onIntrData(device, reportId, data);
+        }
+
+        @Override
+        public void onVirtualCableUnplug(BluetoothDevice device) {
+            mCallback.onVirtualCableUnplug(device);
+        }
+    }
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+
+                public void onBluetoothStateChange(boolean up) {
+                    Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    synchronized (mConnection) {
+                        if (up) {
+                            try {
+                                if (mService == null) {
+                                    Log.d(TAG, "Binding HID Device service...");
+                                    doBind();
+                                }
+                            } catch (IllegalStateException e) {
+                                Log.e(TAG,
+                                        "onBluetoothStateChange: could not bind to HID Dev "
+                                                + "service: ", e);
+                            } catch (SecurityException e) {
+                                Log.e(TAG,
+                                        "onBluetoothStateChange: could not bind to HID Dev "
+                                                + "service: ", e);
+                            }
+                        } else {
+                            Log.d(TAG, "Unbinding service...");
+                            doUnbind();
+                        }
+                    }
+                }
+            };
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "onServiceConnected()");
+            mService = IBluetoothHidDevice.Stub.asInterface(service);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE,
+                        BluetoothHidDevice.this);
+            }
+        }
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "onServiceDisconnected()");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
+            }
+        }
+    };
+
+    BluetoothHidDevice(Context context, ServiceListener listener) {
+        Log.v(TAG, "BluetoothHidDevice");
+
+        mContext = context;
+        mServiceListener = listener;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothHidDevice.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
+            return false;
+        }
+        Log.d(TAG, "Bound to HID Device Service");
+        return true;
+    }
+
+    void doUnbind() {
+        Log.d(TAG, "Unbinding HidDevService");
+        if (mService != null) {
+            mService = null;
+            try {
+                mContext.unbindService(mConnection);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Unable to unbind HidDevService", e);
+            }
+        }
+    }
+
+    void close() {
+        Log.v(TAG, "close()");
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        synchronized (mConnection) {
+            doUnbind();
+        }
+        mServiceListener = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        Log.v(TAG, "getConnectedDevices()");
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        Log.v(TAG, "getConnectionState(): device=" + device);
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Registers application to be used for HID device. Connections to HID
+     * Device are only possible when application is registered. Only one
+     * application can be registered at time. When no longer used, application
+     * should be unregistered using
+     * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
+     *
+     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record.
+     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings.
+     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings.
+     * @param callback {@link BluetoothHidDeviceCallback} object to which callback messages will be
+     * sent.
+     * @return
+     */
+    public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
+            BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
+            BluetoothHidDeviceCallback callback) {
+        Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
+                + " callback=" + callback);
+
+        boolean result = false;
+
+        if (sdp == null || callback == null) {
+            return false;
+        }
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                BluetoothHidDeviceAppConfiguration config =
+                        new BluetoothHidDeviceAppConfiguration();
+                BluetoothHidDeviceCallbackWrapper cbw =
+                        new BluetoothHidDeviceCallbackWrapper(callback);
+                result = service.registerApp(config, sdp, inQos, outQos, cbw);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Unregisters application. Active connection will be disconnected and no
+     * new connections will be allowed until registered again using
+     * {@link #registerApp
+     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
+     *
+     * @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link
+     * BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
+     * BluetoothHidDeviceAppConfiguration,
+     * boolean)}
+     * @return
+     */
+    public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
+        Log.v(TAG, "unregisterApp()");
+
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.unregisterApp(config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends report to remote host using interrupt channel.
+     *
+     * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
+     * descriptor.
+     * @param data Report data, not including Report Id.
+     * @return
+     */
+    public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.sendReport(device, id, data);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends report to remote host as reply for GET_REPORT request from
+     * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
+     *
+     * @param type Report Type, as in request.
+     * @param id Report Id, as in request.
+     * @param data Report data, not including Report Id.
+     * @return
+     */
+    public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+        Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
+
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.replyReport(device, type, id, data);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends error handshake message as reply for invalid SET_REPORT request
+     * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
+     *
+     * @param error Error to be sent for SET_REPORT via HANDSHAKE.
+     * @return
+     */
+    public boolean reportError(BluetoothDevice device, byte error) {
+        Log.v(TAG, "reportError(): device=" + device + " error=" + error);
+
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.reportError(device, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends Virtual Cable Unplug to currently connected host.
+     *
+     * @return
+     */
+    public boolean unplug(BluetoothDevice device) {
+        Log.v(TAG, "unplug(): device=" + device);
+
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.unplug(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Initiates connection to host which currently has Virtual Cable
+     * established with device.
+     *
+     * @return
+     */
+    public boolean connect(BluetoothDevice device) {
+        Log.v(TAG, "connect(): device=" + device);
+
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Disconnects from currently connected host.
+     *
+     * @return
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        Log.v(TAG, "disconnect(): device=" + device);
+
+        boolean result = false;
+
+        final IBluetoothHidDevice service = mService;
+        if (service != null) {
+            try {
+                result = service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
index 2731935..d1efa2d 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -21,7 +21,16 @@
 
 import java.util.Random;
 
-/** @hide */
+/**
+ * Represents the app configuration for a Bluetooth HID Device application.
+ *
+ * The app needs a BluetoothHidDeviceAppConfiguration token to unregister
+ * the Bluetooth HID Device service.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public final class BluetoothHidDeviceAppConfiguration implements Parcelable {
     private final long mHash;
 
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index 1f80ed7..ccc3ef4 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -19,7 +19,17 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** @hide */
+/**
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device
+ * application.
+ *
+ * The BluetoothHidDevice framework will update the L2CAP QoS settings for the
+ * app during registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
 
     public final int serviceType;
@@ -36,8 +46,7 @@
     public static final int MAX = (int) 0xffffffff;
 
     public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
-            int peakBandwidth,
-            int latency, int delayVariation) {
+            int peakBandwidth, int latency, int delayVariation) {
         this.serviceType = serviceType;
         this.tokenRate = tokenRate;
         this.tokenBucketSize = tokenBucketSize;
@@ -66,10 +75,13 @@
                 @Override
                 public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) {
 
-                    return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(),
+                    return new BluetoothHidDeviceAppQosSettings(
                             in.readInt(),
                             in.readInt(),
-                            in.readInt(), in.readInt());
+                            in.readInt(),
+                            in.readInt(),
+                            in.readInt(),
+                            in.readInt());
                 }
 
                 @Override
@@ -90,7 +102,7 @@
 
     /** @return an int array representation of this instance */
     public int[] toArray() {
-        return new int[]{
+        return new int[] {
                 serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
         };
     }
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index d21d506..f01c493 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -19,7 +19,18 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-/** @hide */
+/**
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
+ * HID Device application.
+ *
+ * The BluetoothHidDevice framework adds the SDP record during app
+ * registration, so that the Android device can be discovered as a Bluetooth
+ * HID Device.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
 
     public final String name;
@@ -57,8 +68,12 @@
                 @Override
                 public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) {
 
-                    return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(),
-                            in.readString(), in.readByte(), in.createByteArray());
+                    return new BluetoothHidDeviceAppSdpSettings(
+                            in.readString(),
+                            in.readString(),
+                            in.readString(),
+                            in.readByte(),
+                            in.createByteArray());
                 }
 
                 @Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index 3d407a6..5ccda0d 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -18,16 +18,24 @@
 
 import android.util.Log;
 
-/** @hide */
+/**
+ * The template class that applications use to call callback functions on
+ * events from the HID host. Callback functions are wrapped in this class and
+ * registered to the Android system during app registration.
+ *
+ * {@see BluetoothHidDevice}
+ *
+ * {@hide}
+ */
 public abstract class BluetoothHidDeviceCallback {
 
-    private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName();
+    private static final String TAG = "BluetoothHidDevCallback";
 
     /**
      * Callback called when application registration state changes. Usually it's
      * called due to either
-     * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[],
-     * BluetoothHidDeviceCallback)}
+     * {@link BluetoothHidDevice#registerApp
+     * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
      * or
      * {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
      * , but can be also unsolicited in case e.g. Bluetooth was turned off in
@@ -79,7 +87,7 @@
     /**
      * Callback called when SET_REPORT is received from remote host. In case
      * received data are invalid, application shall respond with
-     * {@link BluetoothHidDevice#reportError(BluetoothDevice)}.
+     * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
      *
      * @param type Report Type.
      * @param id Report Id.
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
new file mode 100644
index 0000000..8ad0f9d
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -0,0 +1,742 @@
+/*
+ * Copyright (C) 2011 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.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * This class provides the public APIs to control the Bluetooth Input
+ * Device Profile.
+ *
+ * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHidHost proxy object.
+ *
+ * <p>Each method is protected with its appropriate permission.
+ *
+ * @hide
+ */
+public final class BluetoothHidHost implements BluetoothProfile {
+    private static final String TAG = "BluetoothHidHost";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Input
+     * Device profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PROTOCOL_MODE_CHANGED =
+            "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_HANDSHAKE =
+            "android.bluetooth.input.profile.action.HANDSHAKE";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_REPORT =
+            "android.bluetooth.input.profile.action.REPORT";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
+            "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_IDLE_TIME_CHANGED =
+            "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
+
+    /**
+     * Return codes for the connect and disconnect Bluez / Dbus calls.
+     *
+     * @hide
+     */
+    public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_OPERATION_SUCCESS = 5004;
+
+    /**
+     * @hide
+     */
+    public static final int PROTOCOL_REPORT_MODE = 0;
+
+    /**
+     * @hide
+     */
+    public static final int PROTOCOL_BOOT_MODE = 1;
+
+    /**
+     * @hide
+     */
+    public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
+
+    /*  int reportType, int reportType, int bufferSize */
+    /**
+     * @hide
+     */
+    public static final byte REPORT_TYPE_INPUT = 1;
+
+    /**
+     * @hide
+     */
+    public static final byte REPORT_TYPE_OUTPUT = 2;
+
+    /**
+     * @hide
+     */
+    public static final byte REPORT_TYPE_FEATURE = 3;
+
+    /**
+     * @hide
+     */
+    public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
+
+    /**
+     * @hide
+     */
+    public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_PROTOCOL_MODE =
+            "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT_TYPE =
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT_ID =
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT_BUFFER_SIZE =
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
+            "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_IDLE_TIME =
+            "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private BluetoothAdapter mAdapter;
+    private volatile IBluetoothHidHost mService;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        if (VDBG) Log.d(TAG, "Unbinding service...");
+                        synchronized (mConnection) {
+                            try {
+                                mService = null;
+                                mContext.unbindService(mConnection);
+                            } catch (Exception re) {
+                                Log.e(TAG, "", re);
+                            }
+                        }
+                    } else {
+                        synchronized (mConnection) {
+                            try {
+                                if (mService == null) {
+                                    if (VDBG) Log.d(TAG, "Binding service...");
+                                    doBind();
+                                }
+                            } catch (Exception re) {
+                                Log.e(TAG, "", re);
+                            }
+                        }
+                    }
+                }
+            };
+
+    /**
+     * Create a BluetoothHidHost proxy object for interacting with the local
+     * Bluetooth Service which handles the InputDevice profile
+     */
+    /*package*/ BluetoothHidHost(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothHidHost.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
+            return false;
+        }
+        return true;
+    }
+
+    /*package*/ void close() {
+        if (VDBG) log("close()");
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (Exception e) {
+                Log.e(TAG, "", e);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mService = null;
+                    mContext.unbindService(mConnection);
+                } catch (Exception re) {
+                    Log.e(TAG, "", re);
+                }
+            }
+        }
+        mServiceListener = null;
+    }
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> The system supports connection to multiple input devices.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or
+     * {@link #PRIORITY_OFF},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
+            }
+            try {
+                return service.setPriority(device, priority);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getPriority(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.PRIORITY_OFF;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.PRIORITY_OFF;
+    }
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "Proxy object connected");
+            mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST,
+                        BluetoothHidHost.this);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) Log.d(TAG, "Proxy object disconnected");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST);
+            }
+        }
+    };
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    /**
+     * Initiate virtual unplug for a HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean virtualUnplug(BluetoothDevice device) {
+        if (DBG) log("virtualUnplug(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.virtualUnplug(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+
+    }
+
+    /**
+     * Send Get_Protocol_Mode command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean getProtocolMode(BluetoothDevice device) {
+        if (VDBG) log("getProtocolMode(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getProtocolMode(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Protocol_Mode command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
+        if (DBG) log("setProtocolMode(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.setProtocolMode(device, protocolMode);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Get_Report command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param reportType Report type
+     * @param reportId Report ID
+     * @param bufferSize Report receiving buffer size
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
+            int bufferSize) {
+        if (VDBG) {
+            log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
+                    + "bufferSize=" + bufferSize);
+        }
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getReport(device, reportType, reportId, bufferSize);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Report command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param reportType Report type
+     * @param report Report receiving buffer size
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setReport(BluetoothDevice device, byte reportType, String report) {
+        if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.setReport(device, reportType, report);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Send_Data command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param report Report to send
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean sendData(BluetoothDevice device, String report) {
+        if (DBG) log("sendData(" + device + "), report=" + report);
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendData(device, report);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Get_Idle_Time command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean getIdleTime(BluetoothDevice device) {
+        if (DBG) log("getIdletime(" + device + ")");
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getIdleTime(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Idle_Time command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param idleTime Idle time to be set on HID Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
+        if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
+        final IBluetoothHidHost service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.setIdleTime(device, idleTime);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
deleted file mode 100644
index 3261576..0000000
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * Copyright (C) 2011 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.bluetooth;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * This class provides the public APIs to control the Bluetooth Input
- * Device Profile.
- *
- * <p>BluetoothInputDevice is a proxy object for controlling the Bluetooth
- * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
- * the BluetoothInputDevice proxy object.
- *
- * <p>Each method is protected with its appropriate permission.
- *
- * @hide
- */
-public final class BluetoothInputDevice implements BluetoothProfile {
-    private static final String TAG = "BluetoothInputDevice";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
-    /**
-     * Intent used to broadcast the change in connection state of the Input
-     * Device profile.
-     *
-     * <p>This intent will have 3 extras:
-     * <ul>
-     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
-     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
-     * </ul>
-     *
-     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
-     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
-     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_CONNECTION_STATE_CHANGED =
-            "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
-
-    /**
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_PROTOCOL_MODE_CHANGED =
-            "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
-
-    /**
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_HANDSHAKE =
-            "android.bluetooth.input.profile.action.HANDSHAKE";
-
-    /**
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_REPORT =
-            "android.bluetooth.input.profile.action.REPORT";
-
-    /**
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
-            "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
-
-    /**
-     * @hide
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_IDLE_TIME_CHANGED =
-            "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
-
-    /**
-     * Return codes for the connect and disconnect Bluez / Dbus calls.
-     *
-     * @hide
-     */
-    public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
-
-    /**
-     * @hide
-     */
-    public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
-
-    /**
-     * @hide
-     */
-    public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
-
-    /**
-     * @hide
-     */
-    public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
-
-    /**
-     * @hide
-     */
-    public static final int INPUT_OPERATION_SUCCESS = 5004;
-
-    /**
-     * @hide
-     */
-    public static final int PROTOCOL_REPORT_MODE = 0;
-
-    /**
-     * @hide
-     */
-    public static final int PROTOCOL_BOOT_MODE = 1;
-
-    /**
-     * @hide
-     */
-    public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
-
-    /*  int reportType, int reportType, int bufferSize */
-    /**
-     * @hide
-     */
-    public static final byte REPORT_TYPE_INPUT = 1;
-
-    /**
-     * @hide
-     */
-    public static final byte REPORT_TYPE_OUTPUT = 2;
-
-    /**
-     * @hide
-     */
-    public static final byte REPORT_TYPE_FEATURE = 3;
-
-    /**
-     * @hide
-     */
-    public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
-
-    /**
-     * @hide
-     */
-    public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_PROTOCOL_MODE =
-            "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_REPORT_TYPE =
-            "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_REPORT_ID =
-            "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_REPORT_BUFFER_SIZE =
-            "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_STATUS = "android.bluetooth.BluetoothInputDevice.extra.STATUS";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
-            "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
-
-    /**
-     * @hide
-     */
-    public static final String EXTRA_IDLE_TIME =
-            "android.bluetooth.BluetoothInputDevice.extra.IDLE_TIME";
-
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private BluetoothAdapter mAdapter;
-    private volatile IBluetoothInputDevice mService;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
-                }
-            };
-
-    /**
-     * Create a BluetoothInputDevice proxy object for interacting with the local
-     * Bluetooth Service which handles the InputDevice profile
-     */
-    /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothInputDevice.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                android.os.Process.myUserHandle())) {
-            Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
-            return false;
-        }
-        return true;
-    }
-
-    /*package*/ void close() {
-        if (VDBG) log("close()");
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
-    }
-
-    /**
-     * Initiate connection to a profile of the remote bluetooth device.
-     *
-     * <p> The system supports connection to multiple input devices.
-     *
-     * <p> This API returns false in scenarios like the profile on the
-     * device is already connected or Bluetooth is not turned on.
-     * When this API returns true, it is guaranteed that
-     * connection state intent for the profile will be broadcasted with
-     * the state. Users can get the connection state of the profile
-     * from this intent.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean connect(BluetoothDevice device) {
-        if (DBG) log("connect(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.connect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Initiate disconnection from a profile
-     *
-     * <p> This API will return false in scenarios like the profile on the
-     * Bluetooth device is not in connected state etc. When this API returns,
-     * true, it is guaranteed that the connection state change
-     * intent will be broadcasted with the state. Users can get the
-     * disconnection state of the profile from this intent.
-     *
-     * <p> If the disconnection is initiated by a remote device, the state
-     * will transition from {@link #STATE_CONNECTED} to
-     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
-     * host (local) device the state will transition from
-     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
-     * state {@link #STATE_DISCONNECTED}. The transition to
-     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
-     * two scenarios.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean disconnect(BluetoothDevice device) {
-        if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.disconnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public List<BluetoothDevice> getConnectedDevices() {
-        if (VDBG) log("getConnectedDevices()");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.getConnectedDevices();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.getDevicesMatchingConnectionStates(states);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int getConnectionState(BluetoothDevice device) {
-        if (VDBG) log("getState(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionState(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
-    }
-
-    /**
-     * Set priority of the profile
-     *
-     * <p> The device should already be paired.
-     * Priority can be one of {@link #PRIORITY_ON} or
-     * {@link #PRIORITY_OFF},
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
-     * permission.
-     *
-     * @param device Paired bluetooth device
-     * @param priority
-     * @return true if priority is set, false on error
-     * @hide
-     */
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF
-                    && priority != BluetoothProfile.PRIORITY_ON) {
-                return false;
-            }
-            try {
-                return service.setPriority(device, priority);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Get the priority of the profile.
-     *
-     * <p> The priority can be any of:
-     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
-     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
-     * @param device Bluetooth device
-     * @return priority of the device
-     * @hide
-     */
-    public int getPriority(BluetoothDevice device) {
-        if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getPriority(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.PRIORITY_OFF;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.PRIORITY_OFF;
-    }
-
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothInputDevice.Stub.asInterface(Binder.allowBlocking(service));
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE,
-                        BluetoothInputDevice.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
-            }
-        }
-    };
-
-    private boolean isEnabled() {
-        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
-    }
-
-    private static boolean isValidDevice(BluetoothDevice device) {
-        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
-    }
-
-    /**
-     * Initiate virtual unplug for a HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean virtualUnplug(BluetoothDevice device) {
-        if (DBG) log("virtualUnplug(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.virtualUnplug(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-
-    }
-
-    /**
-     * Send Get_Protocol_Mode command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean getProtocolMode(BluetoothDevice device) {
-        if (VDBG) log("getProtocolMode(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getProtocolMode(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Send Set_Protocol_Mode command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
-        if (DBG) log("setProtocolMode(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.setProtocolMode(device, protocolMode);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Send Get_Report command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @param reportType Report type
-     * @param reportId Report ID
-     * @param bufferSize Report receiving buffer size
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
-            int bufferSize) {
-        if (VDBG) {
-            log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
-                    + "bufferSize=" + bufferSize);
-        }
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getReport(device, reportType, reportId, bufferSize);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Send Set_Report command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @param reportType Report type
-     * @param report Report receiving buffer size
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean setReport(BluetoothDevice device, byte reportType, String report) {
-        if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.setReport(device, reportType, report);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Send Send_Data command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @param report Report to send
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean sendData(BluetoothDevice device, String report) {
-        if (DBG) log("sendData(" + device + "), report=" + report);
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.sendData(device, report);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Send Get_Idle_Time command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean getIdleTime(BluetoothDevice device) {
-        if (DBG) log("getIdletime(" + device + ")");
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getIdleTime(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    /**
-     * Send Set_Idle_Time command to the connected HID input device.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
-     *
-     * @param device Remote Bluetooth Device
-     * @param idleTime Idle time to be set on HID Device
-     * @return false on immediate error, true otherwise
-     * @hide
-     */
-    public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
-        if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
-        final IBluetoothInputDevice service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.setIdleTime(device, idleTime);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-    }
-
-    private static void log(String msg) {
-        Log.d(TAG, msg);
-    }
-}
diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothInputHost.java
deleted file mode 100644
index 37f0427..0000000
--- a/core/java/android/bluetooth/BluetoothInputHost.java
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Copyright (C) 2016 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.bluetooth;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @hide
- */
-public final class BluetoothInputHost implements BluetoothProfile {
-
-    private static final String TAG = BluetoothInputHost.class.getSimpleName();
-
-    /**
-     * Intent used to broadcast the change in connection state of the Input
-     * Host profile.
-     *
-     * <p>This intent will have 3 extras:
-     * <ul>
-     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
-     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
-     * </ul>
-     *
-     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
-     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
-     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
-     */
-    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_CONNECTION_STATE_CHANGED =
-            "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
-
-    /**
-     * Constants representing device subclass.
-     *
-     * @see #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)
-     */
-    public static final byte SUBCLASS1_NONE = (byte) 0x00;
-    public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
-    public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
-    public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
-
-    public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
-    public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
-    public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
-    public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
-    public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
-    public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
-    public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
-
-    /**
-     * Constants representing report types.
-     *
-     * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
-     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
-     * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
-     */
-    public static final byte REPORT_TYPE_INPUT = (byte) 1;
-    public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
-    public static final byte REPORT_TYPE_FEATURE = (byte) 3;
-
-    /**
-     * Constants representing error response for Set Report.
-     *
-     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
-     */
-    public static final byte ERROR_RSP_SUCCESS = (byte) 0;
-    public static final byte ERROR_RSP_NOT_READY = (byte) 1;
-    public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
-    public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
-    public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
-    public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
-
-    /**
-     * Constants representing protocol mode used set by host. Default is always
-     * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
-     *
-     * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
-     */
-    public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
-    public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
-
-    private Context mContext;
-
-    private ServiceListener mServiceListener;
-
-    private volatile IBluetoothInputHost mService;
-
-    private BluetoothAdapter mAdapter;
-
-    private static class BluetoothHidDeviceCallbackWrapper extends
-            IBluetoothHidDeviceCallback.Stub {
-
-        private BluetoothHidDeviceCallback mCallback;
-
-        public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onAppStatusChanged(BluetoothDevice pluggedDevice,
-                BluetoothHidDeviceAppConfiguration config, boolean registered) {
-            mCallback.onAppStatusChanged(pluggedDevice, config, registered);
-        }
-
-        @Override
-        public void onConnectionStateChanged(BluetoothDevice device, int state) {
-            mCallback.onConnectionStateChanged(device, state);
-        }
-
-        @Override
-        public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
-            mCallback.onGetReport(device, type, id, bufferSize);
-        }
-
-        @Override
-        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-            mCallback.onSetReport(device, type, id, data);
-        }
-
-        @Override
-        public void onSetProtocol(BluetoothDevice device, byte protocol) {
-            mCallback.onSetProtocol(device, protocol);
-        }
-
-        @Override
-        public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
-            mCallback.onIntrData(device, reportId, data);
-        }
-
-        @Override
-        public void onVirtualCableUnplug(BluetoothDevice device) {
-            mCallback.onVirtualCableUnplug(device);
-        }
-    }
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-
-                public void onBluetoothStateChange(boolean up) {
-                    Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    synchronized (mConnection) {
-                        if (!up) {
-                            Log.d(TAG, "Unbinding service...");
-                            if (mService != null) {
-                                mService = null;
-                                try {
-                                    mContext.unbindService(mConnection);
-                                } catch (IllegalArgumentException e) {
-                                    Log.e(TAG, "onBluetoothStateChange: could not unbind service:",
-                                            e);
-                                }
-                            }
-                        } else {
-                            try {
-                                if (mService == null) {
-                                    Log.d(TAG, "Binding HID Device service...");
-                                    doBind();
-                                }
-                            } catch (IllegalStateException e) {
-                                Log.e(TAG,
-                                        "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ",
-                                        e);
-                            } catch (SecurityException e) {
-                                Log.e(TAG,
-                                        "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ",
-                                        e);
-                            }
-                        }
-                    }
-                }
-            };
-
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            Log.d(TAG, "onServiceConnected()");
-            mService = IBluetoothInputHost.Stub.asInterface(service);
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
-                        BluetoothInputHost.this);
-            }
-        }
-        public void onServiceDisconnected(ComponentName className) {
-            Log.d(TAG, "onServiceDisconnected()");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
-            }
-        }
-    };
-
-    BluetoothInputHost(Context context, ServiceListener listener) {
-        Log.v(TAG, "BluetoothInputHost");
-
-        mContext = context;
-        mServiceListener = listener;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothInputHost.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                android.os.Process.myUserHandle())) {
-            Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
-            return false;
-        }
-        Log.d(TAG, "Bound to HID Device Service");
-        return true;
-    }
-
-    void close() {
-        Log.v(TAG, "close()");
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                mService = null;
-                try {
-                    mContext.unbindService(mConnection);
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, "close: could not unbind HID Dev service: ", e);
-                }
-            }
-        }
-
-        mServiceListener = null;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public List<BluetoothDevice> getConnectedDevices() {
-        Log.v(TAG, "getConnectedDevices()");
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                return service.getConnectedDevices();
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return new ArrayList<BluetoothDevice>();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                return service.getDevicesMatchingConnectionStates(states);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return new ArrayList<BluetoothDevice>();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int getConnectionState(BluetoothDevice device) {
-        Log.v(TAG, "getConnectionState(): device=" + device);
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                return service.getConnectionState(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return STATE_DISCONNECTED;
-    }
-
-    /**
-     * Registers application to be used for HID device. Connections to HID
-     * Device are only possible when application is registered. Only one
-     * application can be registered at time. When no longer used, application
-     * should be unregistered using
-     * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
-     *
-     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record.
-     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings.
-     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings.
-     * @param callback {@link BluetoothHidDeviceCallback} object to which callback messages will be
-     * sent.
-     * @return
-     */
-    public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
-            BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
-            BluetoothHidDeviceCallback callback) {
-        Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
-                + " callback=" + callback);
-
-        boolean result = false;
-
-        if (sdp == null || callback == null) {
-            return false;
-        }
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                BluetoothHidDeviceAppConfiguration config =
-                        new BluetoothHidDeviceAppConfiguration();
-                BluetoothHidDeviceCallbackWrapper cbw =
-                        new BluetoothHidDeviceCallbackWrapper(callback);
-                result = service.registerApp(config, sdp, inQos, outQos, cbw);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Unregisters application. Active connection will be disconnected and no
-     * new connections will be allowed until registered again using
-     * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
-     *
-     * @param config {@link BluetoothHidDeviceAppConfiguration} object as obtained from {@link
-     * BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
-     * BluetoothHidDeviceAppConfiguration,
-     * boolean)}
-     * @return
-     */
-    public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
-        Log.v(TAG, "unregisterApp()");
-
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.unregisterApp(config);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Sends report to remote host using interrupt channel.
-     *
-     * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
-     * descriptor.
-     * @param data Report data, not including Report Id.
-     * @return
-     */
-    public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.sendReport(device, id, data);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Sends report to remote host as reply for GET_REPORT request from
-     * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
-     *
-     * @param type Report Type, as in request.
-     * @param id Report Id, as in request.
-     * @param data Report data, not including Report Id.
-     * @return
-     */
-    public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-        Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
-
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.replyReport(device, type, id, data);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Sends error handshake message as reply for invalid SET_REPORT request
-     * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
-     *
-     * @param error Error to be sent for SET_REPORT via HANDSHAKE.
-     * @return
-     */
-    public boolean reportError(BluetoothDevice device, byte error) {
-        Log.v(TAG, "reportError(): device=" + device + " error=" + error);
-
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.reportError(device, error);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Sends Virtual Cable Unplug to currently connected host.
-     *
-     * @return
-     */
-    public boolean unplug(BluetoothDevice device) {
-        Log.v(TAG, "unplug(): device=" + device);
-
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.unplug(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Initiates connection to host which currently has Virtual Cable
-     * established with device.
-     *
-     * @return
-     */
-    public boolean connect(BluetoothDevice device) {
-        Log.v(TAG, "connect(): device=" + device);
-
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.connect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-
-    /**
-     * Disconnects from currently connected host.
-     *
-     * @return
-     */
-    public boolean disconnect(BluetoothDevice device) {
-        Log.v(TAG, "disconnect(): device=" + device);
-
-        boolean result = false;
-
-        final IBluetoothInputHost service = mService;
-        if (service != null) {
-            try {
-                result = service.disconnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-        }
-
-        return result;
-    }
-}
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 19f5198..a1a9347 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.annotation.SdkConstant;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -53,35 +54,32 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
-    /** int extra for PBAP_STATE_CHANGED_ACTION */
-    public static final String PBAP_STATE =
-            "android.bluetooth.pbap.intent.PBAP_STATE";
-    /** int extra for PBAP_STATE_CHANGED_ACTION */
-    public static final String PBAP_PREVIOUS_STATE =
-            "android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE";
-
     /**
-     * Indicates the state of a pbap connection state has changed.
-     * This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and
-     * BluetoothIntent.ADDRESS extras.
+     * Intent used to broadcast the change in connection state of the PBAP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+     *  can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
+     *  {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
+     *  {@link BluetoothProfile#STATE_DISCONNECTING}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
      */
-    public static final String PBAP_STATE_CHANGED_ACTION =
-            "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED";
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
 
     private volatile IBluetoothPbap mService;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
 
-    /** There was an error trying to obtain the state */
-    public static final int STATE_ERROR = -1;
-    /** No client currently connected */
-    public static final int STATE_DISCONNECTED = 0;
-    /** Connection attempt in progress */
-    public static final int STATE_CONNECTING = 1;
-    /** Client is currently connected */
-    public static final int STATE_CONNECTED = 2;
-
     public static final int RESULT_FAILURE = 0;
     public static final int RESULT_SUCCESS = 1;
     /** Connection canceled before completion. */
@@ -209,8 +207,8 @@
     /**
      * Get the current state of the BluetoothPbap service.
      *
-     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
-     * connected to the Pbap service.
+     * @return One of the STATE_ return codes, or {@link BluetoothProfile#STATE_DISCONNECTED}
+     * if this proxy object is currently not connected to the Pbap service.
      */
     public int getState() {
         if (VDBG) log("getState()");
@@ -225,7 +223,7 @@
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
         }
-        return BluetoothPbap.STATE_ERROR;
+        return BluetoothProfile.STATE_DISCONNECTED;
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index 00a15f3..01b3f6e 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -40,7 +40,7 @@
     private static final boolean VDBG = false;
 
     public static final String ACTION_CONNECTION_STATE_CHANGED =
-            "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+            "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
 
     private volatile IBluetoothPbapClient mService;
     private final Context mContext;
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index bc8fa84..46a230b 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -73,11 +73,11 @@
     public static final int HEALTH = 3;
 
     /**
-     * Input Device Profile
+     * HID Host
      *
      * @hide
      */
-    public static final int INPUT_DEVICE = 4;
+    public static final int HID_HOST = 4;
 
     /**
      * PAN Profile
@@ -152,11 +152,11 @@
     public static final int MAP_CLIENT = 18;
 
     /**
-     * Input Host
+     * HID Device
      *
      * @hide
      */
-    public static final int INPUT_HOST = 19;
+    public static final int HID_DEVICE = 19;
 
     /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index ea6b769..0d36bdd 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -21,9 +21,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
-import java.lang.Comparable;
 
 /**
  * Identifier for a specific application component
@@ -33,7 +33,7 @@
  * pieces of information, encapsulated here, are required to identify
  * a component: the package (a String) it exists in, and the class (a String)
  * name inside of that package.
- * 
+ *
  */
 public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
     private final String mPackage;
@@ -91,7 +91,7 @@
 
     /**
      * Create a new component identifier.
-     * 
+     *
      * @param pkg The name of the package that the component exists in.  Can
      * not be null.
      * @param cls The name of the class inside of <var>pkg</var> that
@@ -106,7 +106,7 @@
 
     /**
      * Create a new component identifier from a Context and class name.
-     * 
+     *
      * @param pkg A Context for the package implementing the component,
      * from which the actual package name will be retrieved.
      * @param cls The name of the class inside of <var>pkg</var> that
@@ -120,7 +120,7 @@
 
     /**
      * Create a new component identifier from a Context and Class object.
-     * 
+     *
      * @param pkg A Context for the package implementing the component, from
      * which the actual package name will be retrieved.
      * @param cls The Class object of the desired component, from which the
@@ -141,14 +141,14 @@
     public @NonNull String getPackageName() {
         return mPackage;
     }
-    
+
     /**
      * Return the class name of this component.
      */
     public @NonNull String getClassName() {
         return mClass;
     }
-    
+
     /**
      * Return the class name, either fully qualified or in a shortened form
      * (with a leading '.') if it is a suffix of the package.
@@ -163,7 +163,7 @@
         }
         return mClass;
     }
-    
+
     private static void appendShortClassName(StringBuilder sb, String packageName,
             String className) {
         if (className.startsWith(packageName)) {
@@ -195,26 +195,26 @@
      * class names contained in the ComponentName.  You can later recover
      * the ComponentName from this string through
      * {@link #unflattenFromString(String)}.
-     * 
+     *
      * @return Returns a new String holding the package and class names.  This
      * is represented as the package name, concatenated with a '/' and then the
      * class name.
-     * 
+     *
      * @see #unflattenFromString(String)
      */
     public @NonNull String flattenToString() {
         return mPackage + "/" + mClass;
     }
-    
+
     /**
      * The same as {@link #flattenToString()}, but abbreviates the class
      * name if it is a suffix of the package.  The result can still be used
      * with {@link #unflattenFromString(String)}.
-     * 
+     *
      * @return Returns a new String holding the package and class names.  This
      * is represented as the package name, concatenated with a '/' and then the
      * class name.
-     * 
+     *
      * @see #unflattenFromString(String)
      */
     public @NonNull String flattenToShortString() {
@@ -250,11 +250,11 @@
      * followed by a '.' then the final class name will be the concatenation
      * of the package name with the string following the '/'.  Thus
      * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
-     * 
+     *
      * @param str The String that was returned by flattenToString().
      * @return Returns a new ComponentName containing the package and class
      * names that were encoded in <var>str</var>
-     * 
+     *
      * @see #flattenToString()
      */
     public static @Nullable ComponentName unflattenFromString(@NonNull String str) {
@@ -269,7 +269,7 @@
         }
         return new ComponentName(pkg, cls);
     }
-    
+
     /**
      * Return string representation of this class without the class's name
      * as a prefix.
@@ -283,6 +283,12 @@
         return "ComponentInfo{" + mPackage + "/" + mClass + "}";
     }
 
+    /** Put this here so that individual services don't have to reimplement this. @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(ComponentNameProto.PACKAGE_NAME, mPackage);
+        proto.write(ComponentNameProto.CLASS_NAME, mClass);
+    }
+
     @Override
     public boolean equals(Object obj) {
         try {
@@ -311,7 +317,7 @@
         }
         return this.mClass.compareTo(that.mClass);
     }
-    
+
     public int describeContents() {
         return 0;
     }
@@ -324,10 +330,10 @@
     /**
      * Write a ComponentName to a Parcel, handling null pointers.  Must be
      * read with {@link #readFromParcel(Parcel)}.
-     * 
+     *
      * @param c The ComponentName to be written.
      * @param out The Parcel in which the ComponentName will be placed.
-     * 
+     *
      * @see #readFromParcel(Parcel)
      */
     public static void writeToParcel(ComponentName c, Parcel out) {
@@ -337,23 +343,23 @@
             out.writeString(null);
         }
     }
-    
+
     /**
      * Read a ComponentName from a Parcel that was previously written
      * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
      * a null or new object as appropriate.
-     * 
+     *
      * @param in The Parcel from which to read the ComponentName
      * @return Returns a new ComponentName matching the previously written
      * object, or null if a null had been written.
-     * 
+     *
      * @see #writeToParcel(ComponentName, Parcel)
      */
     public static ComponentName readFromParcel(Parcel in) {
         String pkg = in.readString();
         return pkg != null ? new ComponentName(pkg, in) : null;
     }
-    
+
     public static final Parcelable.Creator<ComponentName> CREATOR
             = new Parcelable.Creator<ComponentName>() {
         public ComponentName createFromParcel(Parcel in) {
@@ -371,7 +377,7 @@
      * must not use this with data written by
      * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
      * to handle a null ComponentObject here.
-     * 
+     *
      * @param in The Parcel containing the previously written ComponentName,
      * positioned at the location in the buffer where it was written.
      */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 03e4dfe..c165fb3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -64,6 +64,7 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.WindowManager;
+import android.view.autofill.AutofillManager.AutofillClient;
 import android.view.textclassifier.TextClassificationManager;
 
 import java.io.File;
@@ -3034,6 +3035,9 @@
      *  <dt> {@link #CONNECTIVITY_SERVICE} ("connection")
      *  <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for
      *  handling management of network connections.
+     *  <dt> {@link #IPSEC_SERVICE} ("ipsec")
+     *  <dd> A {@link android.net.IpSecManager IpSecManager} for managing IPSec on
+     *  sockets and networks.
      *  <dt> {@link #WIFI_SERVICE} ("wifi")
      *  <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
      *  connectivity.  On releases before NYC, it should only be obtained from an application
@@ -3378,7 +3382,6 @@
      * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
      * IPSec.
      *
-     * @hide
      * @see #getSystemService
      */
     public static final String IPSEC_SERVICE = "ipsec";
@@ -3465,6 +3468,19 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
+     * android.net.wifi.rtt.WifiRttManager} for ranging devices with wifi
+     *
+     * Note: this is a replacement for WIFI_RTT_SERVICE above. It will
+     * be renamed once final implementation in place.
+     *
+     * @see #getSystemService
+     * @see android.net.wifi.rtt.WifiRttManager
+     * @hide
+     */
+    public static final String WIFI_RTT2_SERVICE = "rttmanager2";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a {@link
      * android.net.lowpan.LowpanManager} for handling management of
      * LoWPAN access.
      *
@@ -3588,7 +3604,6 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
-     * {@link android.text.ClipboardManager} for accessing and modifying
      * {@link android.content.ClipboardManager} for accessing and modifying
      * the contents of the global clipboard.
      *
@@ -4772,6 +4787,19 @@
     }
 
     /**
+     * @hide
+     */
+    public AutofillClient getAutofillClient() {
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAutofillClient(AutofillClient client) {
+    }
+
+    /**
      * Throws an exception if the Context is using system resources,
      * which are non-runtime-overlay-themable and may show inconsistent UI.
      * @hide
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index a9fd58b..85acdc6 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -37,6 +37,7 @@
 import android.os.UserHandle;
 import android.view.Display;
 import android.view.DisplayAdjustments;
+import android.view.autofill.AutofillManager.AutofillClient;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -967,7 +968,24 @@
     /**
      * @hide
      */
+    @Override
     public int getNextAutofillId() {
         return mBase.getNextAutofillId();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public AutofillClient getAutofillClient() {
+        return mBase.getAutofillClient();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void setAutofillClient(AutofillClient client) {
+        mBase.setAutofillClient(client);
+    }
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c238ffb..e47de75 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -53,6 +53,7 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 
@@ -9371,6 +9372,57 @@
         }
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean secure, boolean comp,
+            boolean extras, boolean clip) {
+        long token = proto.start(fieldId);
+        if (mAction != null) {
+            proto.write(IntentProto.ACTION, mAction);
+        }
+        if (mCategories != null)  {
+            for (String category : mCategories) {
+                proto.write(IntentProto.CATEGORIES, category);
+            }
+        }
+        if (mData != null) {
+            proto.write(IntentProto.DATA, secure ? mData.toSafeString() : mData.toString());
+        }
+        if (mType != null) {
+            proto.write(IntentProto.TYPE, mType);
+        }
+        if (mFlags != 0) {
+            proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
+        }
+        if (mPackage != null) {
+            proto.write(IntentProto.PACKAGE, mPackage);
+        }
+        if (comp && mComponent != null) {
+            proto.write(IntentProto.COMPONENT, mComponent.flattenToShortString());
+        }
+        if (mSourceBounds != null) {
+            proto.write(IntentProto.SOURCE_BOUNDS, mSourceBounds.toShortString());
+        }
+        if (mClipData != null) {
+            StringBuilder b = new StringBuilder();
+            if (clip) {
+                mClipData.toShortString(b);
+            } else {
+                mClipData.toShortStringShortItems(b, false);
+            }
+            proto.write(IntentProto.CLIP_DATA, b.toString());
+        }
+        if (extras && mExtras != null) {
+            proto.write(IntentProto.EXTRAS, mExtras.toShortString());
+        }
+        if (mContentUserHint != 0) {
+            proto.write(IntentProto.CONTENT_USER_HINT, mContentUserHint);
+        }
+        if (mSelector != null) {
+            proto.write(IntentProto.SELECTOR, mSelector.toShortString(secure, comp, extras, clip));
+        }
+        proto.end(token);
+    }
+
     /**
      * Call {@link #toUri} with 0 flags.
      * @deprecated Use {@link #toUri} instead.
@@ -9444,7 +9496,7 @@
                 for (int i=0; i<N; i++) {
                     char c = data.charAt(i);
                     if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
-                            || c == '.' || c == '-') {
+                            || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+') {
                         continue;
                     }
                     if (c == ':' && i > 0) {
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index c9bce53..a957aed 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -26,6 +26,7 @@
 import android.util.AndroidException;
 import android.util.Log;
 import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 
@@ -918,6 +919,15 @@
             dest.writeInt(mPort);
         }
 
+        void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            // The original host information is already contained in host and wild, no output now.
+            proto.write(AuthorityEntryProto.HOST, mHost);
+            proto.write(AuthorityEntryProto.WILD, mWild);
+            proto.write(AuthorityEntryProto.PORT, mPort);
+            proto.end(token);
+        }
+
         public String getHost() {
             return mOrigHost;
         }
@@ -1739,6 +1749,59 @@
         }
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        if (mActions.size() > 0) {
+            Iterator<String> it = mActions.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.ACTIONS, it.next());
+            }
+        }
+        if (mCategories != null) {
+            Iterator<String> it = mCategories.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.CATEGORIES, it.next());
+            }
+        }
+        if (mDataSchemes != null) {
+            Iterator<String> it = mDataSchemes.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.DATA_SCHEMES, it.next());
+            }
+        }
+        if (mDataSchemeSpecificParts != null) {
+            Iterator<PatternMatcher> it = mDataSchemeSpecificParts.iterator();
+            while (it.hasNext()) {
+                it.next().writeToProto(proto, IntentFilterProto.DATA_SCHEME_SPECS);
+            }
+        }
+        if (mDataAuthorities != null) {
+            Iterator<AuthorityEntry> it = mDataAuthorities.iterator();
+            while (it.hasNext()) {
+                it.next().writeToProto(proto, IntentFilterProto.DATA_AUTHORITIES);
+            }
+        }
+        if (mDataPaths != null) {
+            Iterator<PatternMatcher> it = mDataPaths.iterator();
+            while (it.hasNext()) {
+                it.next().writeToProto(proto, IntentFilterProto.DATA_PATHS);
+            }
+        }
+        if (mDataTypes != null) {
+            Iterator<String> it = mDataTypes.iterator();
+            while (it.hasNext()) {
+                proto.write(IntentFilterProto.DATA_TYPES, it.next());
+            }
+        }
+        if (mPriority != 0 || mHasPartialTypes) {
+            proto.write(IntentFilterProto.PRIORITY, mPriority);
+            proto.write(IntentFilterProto.HAS_PARTIAL_TYPES, mHasPartialTypes);
+        }
+        proto.write(IntentFilterProto.GET_AUTO_VERIFY, getAutoVerify());
+        proto.end(token);
+    }
+
     public void dump(Printer du, String prefix) {
         StringBuilder sb = new StringBuilder(256);
         if (mActions.size() > 0) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index cd8201f..41667c4 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1217,7 +1217,7 @@
      * @hide
      */
     public static String screenOrientationToString(int orientation) {
-        switch(orientation) {
+        switch (orientation) {
             case SCREEN_ORIENTATION_UNSET:
                 return "SCREEN_ORIENTATION_UNSET";
             case SCREEN_ORIENTATION_UNSPECIFIED:
@@ -1257,6 +1257,22 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public static String colorModeToString(@ColorMode int colorMode) {
+        switch (colorMode) {
+            case COLOR_MODE_DEFAULT:
+                return "COLOR_MODE_DEFAULT";
+            case COLOR_MODE_WIDE_COLOR_GAMUT:
+                return "COLOR_MODE_WIDE_COLOR_GAMUT";
+            case COLOR_MODE_HDR:
+                return "COLOR_MODE_HDR";
+            default:
+                return Integer.toString(colorMode);
+        }
+    }
+
     public static final Parcelable.Creator<ActivityInfo> CREATOR
             = new Parcelable.Creator<ActivityInfo>() {
         public ActivityInfo createFromParcel(Parcel source) {
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index d73f852..2034280 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -375,7 +375,7 @@
      * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
      * traffic. Third-party libraries are encouraged to honor this flag as well.
      *
-     * <p>NOTE: {@code WebView} does not honor this flag.
+     * <p>NOTE: {@code WebView} honors this flag for applications targeting API level 26 and up.
      *
      * <p>This flag is ignored on Android N and above if an Android Network Security Config is
      * present.
@@ -1463,47 +1463,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public boolean isForwardLocked() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    @TestApi
-    public boolean isSystemApp() {
-        return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    @TestApi
-    public boolean isPrivilegedApp() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
-    }
-
-    /**
-     * @hide
-     */
-    public boolean isUpdatedSystemApp() {
-        return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
-    }
-
-    /** @hide */
-    public boolean isInternal() {
-        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
-    }
-
-    /** @hide */
-    public boolean isExternalAsec() {
-        return TextUtils.isEmpty(volumeUuid)
-                && (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
-    }
-
     /** @hide */
     public boolean isDefaultToDeviceProtectedStorage() {
         return (privateFlags
@@ -1516,45 +1475,72 @@
     }
 
     /** @hide */
+    public boolean isEncryptionAware() {
+        return isDirectBootAware() || isPartiallyDirectBootAware();
+    }
+
+    /** @hide */
+    public boolean isExternal() {
+        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+    }
+
+    /** @hide */
+    public boolean isExternalAsec() {
+        return TextUtils.isEmpty(volumeUuid) && isExternal();
+    }
+
+    /** @hide */
+    public boolean isForwardLocked() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0;
+    }
+
+    /** @hide */
+    public boolean isInstantApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
+    }
+
+    /** @hide */
+    public boolean isInternal() {
+        return (flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0;
+    }
+
+    /** @hide */
+    public boolean isOem() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    }
+
+    /** @hide */
     public boolean isPartiallyDirectBootAware() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
     }
 
     /** @hide */
-    public boolean isEncryptionAware() {
-        return isDirectBootAware() || isPartiallyDirectBootAware();
+    @TestApi
+    public boolean isPrivilegedApp() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
 
-    /**
-     * @hide
-     */
-    public boolean isInstantApp() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public boolean isRequiredForSystemUser() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
     }
 
-    /**
-     * Returns true if the app has declared in its manifest that it wants its split APKs to be
-     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
-     * @hide
-     */
-    public boolean requestsIsolatedSplitLoading() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
-    }
-
-    /**
-     * @hide
-     */
+    /** @hide */
     public boolean isStaticSharedLibrary() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY) != 0;
     }
 
+    /** @hide */
+    @TestApi
+    public boolean isSystemApp() {
+        return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    /** @hide */
+    public boolean isUpdatedSystemApp() {
+        return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
     /**
      * Returns whether or not this application was installed as a virtual preload.
      */
@@ -1563,10 +1549,12 @@
     }
 
     /**
+     * Returns true if the app has declared in its manifest that it wants its split APKs to be
+     * loaded into isolated Contexts, with their own ClassLoaders and Resources objects.
      * @hide
      */
-    public boolean isOem() {
-        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
+    public boolean requestsIsolatedSplitLoading() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING) != 0;
     }
 
     /**
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index 9ee6fa2..ff9fd8e 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.proto.ProtoOutputStream;
 
 /**
  * Definition of a single optional hardware or software feature of an Android
@@ -113,6 +114,18 @@
         dest.writeInt(flags);
     }
 
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        if (name != null) {
+            proto.write(FeatureInfoProto.NAME, name);
+        }
+        proto.write(FeatureInfoProto.VERSION, version);
+        proto.write(FeatureInfoProto.GLES_VERSION, getGlEsVersion());
+        proto.write(FeatureInfoProto.FLAGS, flags);
+        proto.end(token);
+    }
+
     public static final Creator<FeatureInfo> CREATOR = new Creator<FeatureInfo>() {
         @Override
         public FeatureInfo createFromParcel(Parcel source) {
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 0e70645..1352bc2 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -554,14 +554,6 @@
      */
     void reconcileSecondaryDexFiles(String packageName);
 
-    /**
-     * Update status of external media on the package manager to scan and
-     * install packages installed on the external media. Like say the
-     * StorageManagerService uses this to call into the package manager to update
-     * status of sdcard.
-     */
-    void updateExternalMediaStatus(boolean mounted, boolean reportStatus);
-
     PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage);
 
     int getMoveStatus(int moveId);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index aa9562f..b94a410 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,8 +20,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetManager;
@@ -37,10 +37,10 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
-import android.graphics.drawable.AdaptiveIconDrawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -282,12 +282,27 @@
         public static final int FLAG_GET_MANIFEST = FLAG_MATCH_MANIFEST;
 
         /**
-         * Does not retrieve CHOOSER only shortcuts.
-         * TODO: Add another flag for MATCH_ALL_PINNED
+         * @hide include all pinned shortcuts by any launchers, not just by the caller,
+         * in the result.
+         * If the caller doesn't havve the {@link android.Manifest.permission#ACCESS_SHORTCUTS}
+         * permission, this flag will be ignored.
+         */
+        @TestApi
+        public static final int FLAG_MATCH_ALL_PINNED = 1 << 10;
+
+        /**
+         * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST
          * @hide
          */
         public static final int FLAG_MATCH_ALL_KINDS =
-                FLAG_GET_DYNAMIC | FLAG_GET_PINNED | FLAG_GET_MANIFEST;
+                FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST;
+
+        /**
+         * FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST | FLAG_MATCH_ALL_PINNED
+         * @hide
+         */
+        public static final int FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED =
+                FLAG_MATCH_ALL_KINDS | FLAG_MATCH_ALL_PINNED;
 
         /** @hide kept for unit tests */
         @Deprecated
@@ -319,6 +334,7 @@
                         FLAG_MATCH_PINNED,
                         FLAG_MATCH_MANIFEST,
                         FLAG_GET_KEY_FIELDS_ONLY,
+                        FLAG_MATCH_MANIFEST,
                 })
         @Retention(RetentionPolicy.SOURCE)
         public @interface QueryFlags {}
@@ -678,6 +694,21 @@
         }
     }
 
+    private List<ShortcutInfo> maybeUpdateDisabledMessage(List<ShortcutInfo> shortcuts) {
+        if (shortcuts == null) {
+            return null;
+        }
+        for (int i = shortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = shortcuts.get(i);
+            final String message = ShortcutInfo.getDisabledReasonForRestoreIssue(mContext,
+                    si.getDisabledReason());
+            if (message != null) {
+                si.setDisabledMessage(message);
+            }
+        }
+        return shortcuts;
+    }
+
     /**
      * Returns {@link ShortcutInfo}s that match {@code query}.
      *
@@ -698,10 +729,16 @@
             @NonNull UserHandle user) {
         logErrorForInvalidProfileAccess(user);
         try {
-            return mService.getShortcuts(mContext.getPackageName(),
+            // Note this is the only case we need to update the disabled message for shortcuts
+            // that weren't restored.
+            // The restore problem messages are only shown by the user, and publishers will never
+            // see them. The only other API that the launcher gets shortcuts is the shortcut
+            // changed callback, but that only returns shortcuts with the "key" information, so
+            // that won't return disabled message.
+            return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
                     query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
                     query.mQueryFlags, user)
-                    .getList();
+                    .getList());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ef8f84b..31ca198 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2330,6 +2330,16 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports Wi-Fi RTT (IEEE 802.11mc).
+     *
+     * @hide RTT_API
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports LoWPAN networking.
      * @hide
      */
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 4c981cd..14cf855 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,6 +16,9 @@
 
 package android.content.pm;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager.ApplicationInfoFlags;
@@ -25,6 +28,8 @@
 import android.os.Bundle;
 import android.util.SparseArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -33,6 +38,20 @@
  * @hide Only for use within the system server.
  */
 public abstract class PackageManagerInternal {
+    public static final int PACKAGE_SYSTEM = 0;
+    public static final int PACKAGE_SETUP_WIZARD = 1;
+    public static final int PACKAGE_INSTALLER = 2;
+    public static final int PACKAGE_VERIFIER = 3;
+    public static final int PACKAGE_BROWSER = 4;
+    @IntDef(value = {
+        PACKAGE_SYSTEM,
+        PACKAGE_SETUP_WIZARD,
+        PACKAGE_INSTALLER,
+        PACKAGE_VERIFIER,
+        PACKAGE_BROWSER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface KnownPackage {}
 
     /**
      * Provider for package names.
@@ -172,6 +191,13 @@
             @ResolveInfoFlags int flags, int filterCallingUid, int userId);
 
     /**
+     * Retrieve all services that can be performed for the given intent.
+     * @see PackageManager#queryIntentServices(Intent, int)
+     */
+    public abstract List<ResolveInfo> queryIntentServices(
+            Intent intent, int flags, int callingUid, int userId);
+
+    /**
      * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
      */
     public abstract ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
@@ -311,6 +337,12 @@
     public abstract boolean isPackagePersistent(String packageName);
 
     /**
+     * Returns whether or not the given package represents a legacy system application released
+     * prior to runtime permissions.
+     */
+    public abstract boolean isLegacySystemApp(PackageParser.Package pkg);
+
+    /**
      * Get all overlay packages for a user.
      * @param userId The user for which to get the overlays.
      * @return A list of overlay packages. An empty list is returned if the
@@ -343,14 +375,19 @@
      * Resolves an activity intent, allowing instant apps to be resolved.
      */
     public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
-            int flags, int userId);
+            int flags, int userId, boolean resolveForStart);
 
     /**
     * Resolves a service intent, allowing instant apps to be resolved.
     */
-   public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
+    public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
            int flags, int userId, int callingUid);
 
+   /**
+    * Resolves a content provider intent.
+    */
+    public abstract ProviderInfo resolveContentProvider(String name, int flags, int userId);
+
     /**
      * Track the creator of a new isolated uid.
      * @param isolatedUid The newly created isolated uid.
@@ -383,4 +420,57 @@
      * Updates a package last used time.
      */
     public abstract void notifyPackageUse(String packageName, int reason);
+
+    /**
+     * Returns a package object for the given package name.
+     */
+    public abstract @Nullable PackageParser.Package getPackage(@NonNull String packageName);
+
+    /**
+     * Returns a package object for the disabled system package name.
+     */
+    public abstract @Nullable PackageParser.Package getDisabledPackage(@NonNull String packageName);
+
+    /**
+     * Returns whether or not the component is the resolver activity.
+     */
+    public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
+
+    /**
+     * Returns the package name for a known package.
+     */
+    public abstract @Nullable String getKnownPackageName(
+            @KnownPackage int knownPackage, int userId);
+
+    /**
+     * Returns whether the package is an instant app.
+     */
+    public abstract boolean isInstantApp(String packageName, int userId);
+
+    /**
+     * Returns whether the package is an instant app.
+     */
+    public abstract @Nullable String getInstantAppPackageName(int uid);
+
+    /**
+     * Returns whether or not access to the application should be filtered.
+     * <p>
+     * Access may be limited based upon whether the calling or target applications
+     * are instant applications.
+     *
+     * @see #canAccessInstantApps(int)
+     */
+    public abstract boolean filterAppAccess(
+            @Nullable PackageParser.Package pkg, int callingUid, int userId);
+
+    /*
+     * NOTE: The following methods are temporary until permissions are extracted from
+     * the package manager into a component specifically for handling permissions.
+     */
+    /** Returns the flags for the given permission. */
+    public abstract @Nullable int getPermissionFlagsTEMP(@NonNull String permName,
+            @NonNull String packageName, int userId);
+    /** Updates the flags for the given permission. */
+    public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
+            @NonNull String packageName, int flagMask, int flagValues, int userId);
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 6c7c8a07..1c5cf15 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -102,6 +102,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -1708,13 +1709,33 @@
      */
     public static ApkLite parseApkLite(File apkFile, int flags)
             throws PackageParserException {
-        final String apkPath = apkFile.getAbsolutePath();
+        return parseApkLiteInner(apkFile, null, null, flags);
+    }
+
+    /**
+     * Utility method that retrieves lightweight details about a single APK
+     * file, including package name, split name, and install location.
+     *
+     * @param fd already open file descriptor of an apk file
+     * @param debugPathName arbitrary text name for this file, for debug output
+     * @param flags optional parse flags, such as
+     *            {@link #PARSE_COLLECT_CERTIFICATES}
+     */
+    public static ApkLite parseApkLite(FileDescriptor fd, String debugPathName, int flags)
+            throws PackageParserException {
+        return parseApkLiteInner(null, fd, debugPathName, flags);
+    }
+
+    private static ApkLite parseApkLiteInner(File apkFile, FileDescriptor fd, String debugPathName,
+            int flags) throws PackageParserException {
+        final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
 
         AssetManager assets = null;
         XmlResourceParser parser = null;
         try {
             assets = newConfiguredAssetManager();
-            int cookie = assets.addAssetPath(apkPath);
+            int cookie = fd != null
+                    ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
             if (cookie == 0) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                         "Failed to parse " + apkPath);
@@ -3711,17 +3732,15 @@
                 ai.flags |= ApplicationInfo.FLAG_IS_GAME;
             }
 
-            if (false) {
-                if (sa.getBoolean(
-                        com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
-                        false)) {
-                    ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
+                    false)) {
+                ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE;
 
-                    // A heavy-weight application can not be in a custom process.
-                    // We can do direct compare because we intern all strings.
-                    if (ai.processName != null && ai.processName != ai.packageName) {
-                        outError[0] = "cantSaveState applications can not use custom processes";
-                    }
+                // A heavy-weight application can not be in a custom process.
+                // We can do direct compare because we intern all strings.
+                if (ai.processName != null && !ai.processName.equals(ai.packageName)) {
+                    outError[0] = "cantSaveState applications can not use custom processes";
                 }
             }
         }
@@ -6223,48 +6242,48 @@
             return false;
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
+        public boolean isExternal() {
+            return applicationInfo.isExternal();
+        }
+
+        /** @hide */
         public boolean isForwardLocked() {
             return applicationInfo.isForwardLocked();
         }
 
-        /**
-         * @hide
-         */
-        public boolean isSystemApp() {
-            return applicationInfo.isSystemApp();
+        /** @hide */
+        public boolean isOem() {
+            return applicationInfo.isOem();
         }
 
-        /**
-         * @hide
-         */
-        public boolean isPrivilegedApp() {
+        /** @hide */
+        public boolean isPrivileged() {
             return applicationInfo.isPrivilegedApp();
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
+        public boolean isSystem() {
+            return applicationInfo.isSystemApp();
+        }
+
+        /** @hide */
         public boolean isUpdatedSystemApp() {
             return applicationInfo.isUpdatedSystemApp();
         }
 
-        /**
-         * @hide
-         */
+        /** @hide */
         public boolean canHaveOatDir() {
             // The following app types CANNOT have oat directory
             // - non-updated system apps
             // - forward-locked apps or apps installed in ASEC containers
-            return (!isSystemApp() || isUpdatedSystemApp())
+            return (!isSystem() || isUpdatedSystemApp())
                     && !isForwardLocked() && !applicationInfo.isExternalAsec();
         }
 
         public boolean isMatch(int flags) {
             if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
-                return isSystemApp();
+                return isSystem();
             }
             return true;
         }
@@ -6849,6 +6868,11 @@
             dest.writeParcelable(group, flags);
         }
 
+        /** @hide */
+        public boolean isAppOp() {
+            return info.isAppOp();
+        }
+
         private Permission(Parcel in) {
             super(in);
             final ClassLoader boot = Object.class.getClassLoader();
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 17b4f87..5dd7aed 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -318,16 +318,19 @@
         return null;
     }
 
+    @Override
     public String toString() {
         return "PermissionInfo{"
             + Integer.toHexString(System.identityHashCode(this))
             + " " + name + "}";
     }
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
         dest.writeInt(protectionLevel);
@@ -338,11 +341,30 @@
         TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
     }
 
+    /** @hide */
+    public int calculateFootprint() {
+        int size = name.length();
+        if (nonLocalizedLabel != null) {
+            size += nonLocalizedLabel.length();
+        }
+        if (nonLocalizedDescription != null) {
+            size += nonLocalizedDescription.length();
+        }
+        return size;
+    }
+
+    /** @hide */
+    public boolean isAppOp() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+    }
+
     public static final Creator<PermissionInfo> CREATOR =
         new Creator<PermissionInfo>() {
+        @Override
         public PermissionInfo createFromParcel(Parcel source) {
             return new PermissionInfo(source);
         }
+        @Override
         public PermissionInfo[] newArray(int size) {
             return new PermissionInfo[size];
         }
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index 7993167..3f63d80 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -222,6 +222,40 @@
     }
 
     /**
+     * @return The resource that would be used when loading
+     * the label for this resolve info.
+     *
+     * @hide
+     */
+    public int resolveLabelResId() {
+        if (labelRes != 0) {
+            return labelRes;
+        }
+        final ComponentInfo componentInfo = getComponentInfo();
+        if (componentInfo.labelRes != 0) {
+            return componentInfo.labelRes;
+        }
+        return componentInfo.applicationInfo.labelRes;
+    }
+
+    /**
+     * @return The resource that would be used when loading
+     * the icon for this resolve info.
+     *
+     * @hide
+     */
+    public int resolveIconResId() {
+        if (icon != 0) {
+            return icon;
+        }
+        final ComponentInfo componentInfo = getComponentInfo();
+        if (componentInfo.icon != 0) {
+            return componentInfo.icon;
+        }
+        return componentInfo.applicationInfo.icon;
+    }
+
+    /**
      * Retrieve the current graphical icon associated with this resolution.  This
      * will call back on the given PackageManager to load the icon from
      * the application.
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index d3a3560..9ff0775 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -18,6 +18,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.app.TaskStackBuilder;
 import android.content.ComponentName;
@@ -100,6 +101,13 @@
     /** @hide When this is set, the bitmap icon is waiting to be saved. */
     public static final int FLAG_ICON_FILE_PENDING_SAVE = 1 << 11;
 
+    /**
+     * "Shadow" shortcuts are the ones that are restored, but the owner package hasn't been
+     * installed yet.
+     * @hide
+     */
+    public static final int FLAG_SHADOW = 1 << 12;
+
     /** @hide */
     @IntDef(flag = true,
             value = {
@@ -158,6 +166,124 @@
     public @interface CloneFlags {}
 
     /**
+     * Shortcut is not disabled.
+     */
+    public static final int DISABLED_REASON_NOT_DISABLED = 0;
+
+    /**
+     * Shortcut has been disabled by the publisher app with the
+     * {@link ShortcutManager#disableShortcuts(List)} API.
+     */
+    public static final int DISABLED_REASON_BY_APP = 1;
+
+    /**
+     * Shortcut has been disabled due to changes to the publisher app. (e.g. a manifest shortcut
+     * no longer exists.)
+     */
+    public static final int DISABLED_REASON_APP_CHANGED = 2;
+
+    /**
+     * A disabled reason that's equal to or bigger than this is due to backup and restore issue.
+     * A shortcut with such a reason wil be visible to the launcher, but not to the publisher.
+     * ({@link #isVisibleToPublisher()} will be false.)
+     */
+    private static final int DISABLED_REASON_RESTORE_ISSUE_START = 100;
+
+    /**
+     * Shortcut has been restored from the previous device, but the publisher app on the current
+     * device is of a lower version. The shortcut will not be usable until the app is upgraded to
+     * the same version or higher.
+     */
+    public static final int DISABLED_REASON_VERSION_LOWER = 100;
+
+    /**
+     * Shortcut has not been restored because the publisher app does not support backup and restore.
+     */
+    public static final int DISABLED_REASON_BACKUP_NOT_SUPPORTED = 101;
+
+    /**
+     * Shortcut has not been restored because the publisher app's signature has changed.
+     */
+    public static final int DISABLED_REASON_SIGNATURE_MISMATCH = 102;
+
+    /**
+     * Shortcut has not been restored for unknown reason.
+     */
+    public static final int DISABLED_REASON_OTHER_RESTORE_ISSUE = 103;
+
+    /** @hide */
+    @IntDef(value = {
+            DISABLED_REASON_NOT_DISABLED,
+            DISABLED_REASON_BY_APP,
+            DISABLED_REASON_APP_CHANGED,
+            DISABLED_REASON_VERSION_LOWER,
+            DISABLED_REASON_BACKUP_NOT_SUPPORTED,
+            DISABLED_REASON_SIGNATURE_MISMATCH,
+            DISABLED_REASON_OTHER_RESTORE_ISSUE,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisabledReason{}
+
+    /**
+     * Return a label for disabled reasons, which are *not* supposed to be shown to the user.
+     * @hide
+     */
+    public static String getDisabledReasonDebugString(@DisabledReason int disabledReason) {
+        switch (disabledReason) {
+            case DISABLED_REASON_NOT_DISABLED:
+                return "[Not disabled]";
+            case DISABLED_REASON_BY_APP:
+                return "[Disabled: by app]";
+            case DISABLED_REASON_APP_CHANGED:
+                return "[Disabled: app changed]";
+            case DISABLED_REASON_VERSION_LOWER:
+                return "[Disabled: lower version]";
+            case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+                return "[Disabled: backup not supported]";
+            case DISABLED_REASON_SIGNATURE_MISMATCH:
+                return "[Disabled: signature mismatch]";
+            case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+                return "[Disabled: unknown restore issue]";
+        }
+        return "[Disabled: unknown reason:" + disabledReason + "]";
+    }
+
+    /**
+     * Return a label for a disabled reason for shortcuts that are disabled due to a backup and
+     * restore issue. If the reason is not due to backup & restore, then it'll return null.
+     *
+     * This method returns localized, user-facing strings, which will be returned by
+     * {@link #getDisabledMessage()}.
+     *
+     * @hide
+     */
+    public static String getDisabledReasonForRestoreIssue(Context context,
+            @DisabledReason int disabledReason) {
+        final Resources res = context.getResources();
+
+        switch (disabledReason) {
+            case DISABLED_REASON_VERSION_LOWER:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restored_on_lower_version);
+            case DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restore_not_supported);
+            case DISABLED_REASON_SIGNATURE_MISMATCH:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restore_signature_mismatch);
+            case DISABLED_REASON_OTHER_RESTORE_ISSUE:
+                return res.getString(
+                        com.android.internal.R.string.shortcut_restore_unknown_issue);
+        }
+        return null;
+    }
+
+    /** @hide */
+    public static boolean isDisabledForRestoreIssue(@DisabledReason int disabledReason) {
+        return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
+    }
+
+    /**
      * Shortcut category for messaging related actions, such as chat.
      */
     public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -240,6 +366,11 @@
 
     private final int mUserId;
 
+    /** @hide */
+    public static final int VERSION_CODE_UNKNOWN = -1;
+
+    private int mDisabledReason;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -352,6 +483,7 @@
         mActivity = source.mActivity;
         mFlags = source.mFlags;
         mLastChangedTimestamp = source.mLastChangedTimestamp;
+        mDisabledReason = source.mDisabledReason;
 
         // Just always keep it since it's cheep.
         mIconResId = source.mIconResId;
@@ -615,13 +747,23 @@
 
     /**
      * @hide
+     *
+     * @isUpdating set true if it's "update", as opposed to "replace".
      */
-    public void ensureUpdatableWith(ShortcutInfo source) {
+    public void ensureUpdatableWith(ShortcutInfo source, boolean isUpdating) {
+        if (isUpdating) {
+            Preconditions.checkState(isVisibleToPublisher(),
+                    "[Framework BUG] Invisible shortcuts can't be updated");
+        }
         Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
         Preconditions.checkState(mId.equals(source.mId), "ID must match");
         Preconditions.checkState(mPackageName.equals(source.mPackageName),
                 "Package name must match");
-        Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+
+        if (isVisibleToPublisher()) {
+            // Don't do this check for restore-blocked shortcuts.
+            Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
+        }
     }
 
     /**
@@ -638,7 +780,7 @@
      * @hide
      */
     public void copyNonNullFieldsFrom(ShortcutInfo source) {
-        ensureUpdatableWith(source);
+        ensureUpdatableWith(source, /*isUpdating=*/ true);
 
         if (source.mActivity != null) {
             mActivity = source.mActivity;
@@ -1169,6 +1311,19 @@
         return mDisabledMessageResId;
     }
 
+    /** @hide */
+    public void setDisabledReason(@DisabledReason int reason) {
+        mDisabledReason = reason;
+    }
+
+    /**
+     * Returns why a shortcut has been disabled.
+     */
+    @DisabledReason
+    public int getDisabledReason() {
+        return mDisabledReason;
+    }
+
     /**
      * Return the shortcut's categories.
      *
@@ -1403,6 +1558,21 @@
         return hasFlags(FLAG_IMMUTABLE);
     }
 
+    /** @hide */
+    public boolean isDynamicVisible() {
+        return isDynamic() && isVisibleToPublisher();
+    }
+
+    /** @hide */
+    public boolean isPinnedVisible() {
+        return isPinned() && isVisibleToPublisher();
+    }
+
+    /** @hide */
+    public boolean isManifestVisible() {
+        return isDeclaredInManifest() && isVisibleToPublisher();
+    }
+
     /**
      * Return if a shortcut is immutable, in which case it cannot be modified with any of
      * {@link ShortcutManager} APIs.
@@ -1491,6 +1661,18 @@
     }
 
     /**
+     * When the system wasn't able to restore a shortcut, it'll still be registered to the system
+     * but disabled, and such shortcuts will not be visible to the publisher. They're still visible
+     * to launchers though.
+     *
+     * @hide
+     */
+    @TestApi
+    public boolean isVisibleToPublisher() {
+        return !isDisabledForRestoreIssue(mDisabledReason);
+    }
+
+    /**
      * Return whether a shortcut only contains "key" information only or not.  If true, only the
      * following fields are available.
      * <ul>
@@ -1668,6 +1850,7 @@
         mFlags = source.readInt();
         mIconResId = source.readInt();
         mLastChangedTimestamp = source.readLong();
+        mDisabledReason = source.readInt();
 
         if (source.readInt() == 0) {
             return; // key information only.
@@ -1711,6 +1894,7 @@
         dest.writeInt(mFlags);
         dest.writeInt(mIconResId);
         dest.writeLong(mLastChangedTimestamp);
+        dest.writeInt(mDisabledReason);
 
         if (hasKeyFieldsOnly()) {
             dest.writeInt(0);
@@ -1763,21 +1947,43 @@
         return 0;
     }
 
+
     /**
      * Return a string representation, intended for logging.  Some fields will be retracted.
      */
     @Override
     public String toString() {
-        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false);
+        return toStringInner(/* secure =*/ true, /* includeInternalData =*/ false,
+                /*indent=*/ null);
     }
 
     /** @hide */
     public String toInsecureString() {
-        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true);
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true,
+                /*indent=*/ null);
     }
 
-    private String toStringInner(boolean secure, boolean includeInternalData) {
+    /** @hide */
+    public String toDumpString(String indent) {
+        return toStringInner(/* secure =*/ false, /* includeInternalData =*/ true, indent);
+    }
+
+    private void addIndentOrComma(StringBuilder sb, String indent) {
+        if (indent != null) {
+            sb.append("\n  ");
+            sb.append(indent);
+        } else {
+            sb.append(", ");
+        }
+    }
+
+    private String toStringInner(boolean secure, boolean includeInternalData, String indent) {
         final StringBuilder sb = new StringBuilder();
+
+        if (indent != null) {
+            sb.append(indent);
+        }
+
         sb.append("ShortcutInfo {");
 
         sb.append("id=");
@@ -1786,48 +1992,59 @@
         sb.append(", flags=0x");
         sb.append(Integer.toHexString(mFlags));
         sb.append(" [");
+        if ((mFlags & FLAG_SHADOW) != 0) {
+            // Note the shadow flag isn't actually used anywhere and it's just for dumpsys, so
+            // we don't have an isXxx for this.
+            sb.append("Sdw");
+        }
         if (!isEnabled()) {
-            sb.append("X");
+            sb.append("Dis");
         }
         if (isImmutable()) {
             sb.append("Im");
         }
         if (isManifestShortcut()) {
-            sb.append("M");
+            sb.append("Man");
         }
         if (isDynamic()) {
-            sb.append("D");
+            sb.append("Dyn");
         }
         if (isPinned()) {
-            sb.append("P");
+            sb.append("Pin");
         }
         if (hasIconFile()) {
-            sb.append("If");
+            sb.append("Ic-f");
         }
         if (isIconPendingSave()) {
-            sb.append("^");
+            sb.append("Pens");
         }
         if (hasIconResource()) {
-            sb.append("Ir");
+            sb.append("Ic-r");
         }
         if (hasKeyFieldsOnly()) {
-            sb.append("K");
+            sb.append("Key");
         }
         if (hasStringResourcesResolved()) {
-            sb.append("Sr");
+            sb.append("Str");
         }
         if (isReturnedByServer()) {
-            sb.append("V");
+            sb.append("Rets");
         }
         sb.append("]");
 
-        sb.append(", packageName=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("packageName=");
         sb.append(mPackageName);
 
-        sb.append(", activity=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("activity=");
         sb.append(mActivity);
 
-        sb.append(", shortLabel=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("shortLabel=");
         sb.append(secure ? "***" : mTitle);
         sb.append(", resId=");
         sb.append(mTitleResId);
@@ -1835,7 +2052,9 @@
         sb.append(mTitleResName);
         sb.append("]");
 
-        sb.append(", longLabel=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("longLabel=");
         sb.append(secure ? "***" : mText);
         sb.append(", resId=");
         sb.append(mTextResId);
@@ -1843,7 +2062,9 @@
         sb.append(mTextResName);
         sb.append("]");
 
-        sb.append(", disabledMessage=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("disabledMessage=");
         sb.append(secure ? "***" : mDisabledMessage);
         sb.append(", resId=");
         sb.append(mDisabledMessageResId);
@@ -1851,19 +2072,32 @@
         sb.append(mDisabledMessageResName);
         sb.append("]");
 
-        sb.append(", categories=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("disabledReason=");
+        sb.append(getDisabledReasonDebugString(mDisabledReason));
+
+        addIndentOrComma(sb, indent);
+
+        sb.append("categories=");
         sb.append(mCategories);
 
-        sb.append(", icon=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("icon=");
         sb.append(mIcon);
 
-        sb.append(", rank=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("rank=");
         sb.append(mRank);
 
         sb.append(", timestamp=");
         sb.append(mLastChangedTimestamp);
 
-        sb.append(", intents=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("intents=");
         if (mIntents == null) {
             sb.append("null");
         } else {
@@ -1885,12 +2119,15 @@
             }
         }
 
-        sb.append(", extras=");
+        addIndentOrComma(sb, indent);
+
+        sb.append("extras=");
         sb.append(mExtras);
 
         if (includeInternalData) {
+            addIndentOrComma(sb, indent);
 
-            sb.append(", iconRes=");
+            sb.append("iconRes=");
             sb.append(mIconResId);
             sb.append("[");
             sb.append(mIconResName);
@@ -1912,7 +2149,7 @@
             CharSequence disabledMessage, int disabledMessageResId, String disabledMessageResName,
             Set<String> categories, Intent[] intentsWithExtras, int rank, PersistableBundle extras,
             long lastChangedTimestamp,
-            int flags, int iconResId, String iconResName, String bitmapPath) {
+            int flags, int iconResId, String iconResName, String bitmapPath, int disabledReason) {
         mUserId = userId;
         mId = id;
         mPackageName = packageName;
@@ -1937,5 +2174,6 @@
         mIconResId = iconResId;
         mIconResName = iconResName;
         mBitmapPath = bitmapPath;
+        mDisabledReason = disabledReason;
     }
 }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 7b7d8ae..7fc25d8 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -46,7 +46,7 @@
             @NonNull String callingPackage, long changedSince,
             @Nullable String packageName, @Nullable List<String> shortcutIds,
             @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
-            int userId);
+            int userId, int callingPid, int callingUid);
 
     public abstract boolean
             isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
@@ -58,7 +58,8 @@
 
     public abstract Intent[] createShortcutIntents(
             int launcherUserId, @NonNull String callingPackage,
-            @NonNull String packageName, @NonNull String shortcutId, int userId);
+            @NonNull String packageName, @NonNull String shortcutId, int userId,
+            int callingPid, int callingUid);
 
     public abstract void addListener(@NonNull ShortcutChangeListener listener);
 
@@ -70,7 +71,7 @@
             @NonNull String packageName, @NonNull String shortcutId, int userId);
 
     public abstract boolean hasShortcutHostPermission(int launcherUserId,
-            @NonNull String callingPackage);
+            @NonNull String callingPackage, int callingPid, int callingUid);
 
     public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
             @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index f0adcd6..7866560 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -28,8 +28,7 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 
-import dalvik.annotation.optimization.FastNative;
-
+import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -694,7 +693,35 @@
 
     private native final int addAssetPathNative(String path, boolean appAsLib);
 
-     /**
+    /**
+     * Add an additional set of assets to the asset manager from an already open
+     * FileDescriptor.  Not for use by applications.
+     * This does not give full AssetManager functionality for these assets,
+     * since the origin of the file is not known for purposes of sharing,
+     * overlay resolution, and other features.  However it does allow you
+     * to do simple access to the contents of the given fd as an apk file.
+     * Performs a dup of the underlying fd, so you must take care of still closing
+     * the FileDescriptor yourself (and can do that whenever you want).
+     * Returns the cookie of the added asset, or 0 on failure.
+     * {@hide}
+     */
+    public int addAssetFd(FileDescriptor fd, String debugPathName) {
+        return addAssetFdInternal(fd, debugPathName, false);
+    }
+
+    private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
+            boolean appAsLib) {
+        synchronized (this) {
+            int res = addAssetFdNative(fd, debugPathName, appAsLib);
+            makeStringBlocks(mStringBlocks);
+            return res;
+        }
+    }
+
+    private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
+            boolean appAsLib);
+
+    /**
      * Add a set of assets to overlay an already added set of assets.
      *
      * This is only intended for application resources. System wide resources
diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java
index 042eb87..6a4aae6 100644
--- a/core/java/android/content/res/FontResourcesParser.java
+++ b/core/java/android/content/res/FontResourcesParser.java
@@ -15,7 +15,6 @@
  */
 package android.content.res;
 
-import com.android.internal.R;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Typeface;
@@ -23,6 +22,8 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -78,12 +79,17 @@
         private final @NonNull String mFileName;
         private int mWeight;
         private int mItalic;
+        private int mTtcIndex;
+        private String mVariationSettings;
         private int mResourceId;
 
-        public FontFileResourceEntry(@NonNull String fileName, int weight, int italic) {
+        public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
+                @Nullable String variationSettings, int ttcIndex) {
             mFileName = fileName;
             mWeight = weight;
             mItalic = italic;
+            mVariationSettings = variationSettings;
+            mTtcIndex = ttcIndex;
         }
 
         public @NonNull String getFileName() {
@@ -97,6 +103,14 @@
         public int getItalic() {
             return mItalic;
         }
+
+        public @Nullable String getVariationSettings() {
+            return mVariationSettings;
+        }
+
+        public int getTtcIndex() {
+            return mTtcIndex;
+        }
     }
 
     // A class represents file based font-family element in xml file.
@@ -203,6 +217,9 @@
                 Typeface.RESOLVE_BY_FONT_TABLE);
         int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
                 Typeface.RESOLVE_BY_FONT_TABLE);
+        String variationSettings = array.getString(
+                R.styleable.FontFamilyFont_fontVariationSettings);
+        int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
         String filename = array.getString(R.styleable.FontFamilyFont_font);
         array.recycle();
         while (parser.next() != XmlPullParser.END_TAG) {
@@ -211,7 +228,7 @@
         if (filename == null) {
             return null;
         }
-        return new FontFileResourceEntry(filename, weight, italic);
+        return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
     }
 
     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index a8b8c4b..386239c 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -796,7 +796,7 @@
                 dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
                 is.close();
             }
-        } catch (Exception e) {
+        } catch (Exception | StackOverflowError e) {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
             final NotFoundException rnf = new NotFoundException(
                     "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index a75372f..f84ec65 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -16,8 +16,7 @@
 
 package android.database;
 
-import dalvik.system.CloseGuard;
-
+import android.annotation.BytesLong;
 import android.content.res.Resources;
 import android.database.sqlite.SQLiteClosable;
 import android.database.sqlite.SQLiteException;
@@ -26,8 +25,10 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.util.Log;
-import android.util.SparseIntArray;
 import android.util.LongSparseArray;
+import android.util.SparseIntArray;
+
+import dalvik.system.CloseGuard;
 
 /**
  * A buffer containing multiple cursor rows.
@@ -94,19 +95,29 @@
      * @param name The name of the cursor window, or null if none.
      */
     public CursorWindow(String name) {
+        this(name, getCursorWindowSize());
+    }
+
+    /**
+     * Creates a new empty cursor window and gives it a name.
+     * <p>
+     * The cursor initially has no rows or columns.  Call {@link #setNumColumns(int)} to
+     * set the number of columns before adding any rows to the cursor.
+     * </p>
+     *
+     * @param name The name of the cursor window, or null if none.
+     * @param windowSizeBytes Size of cursor window in bytes.
+     * <p><strong>Note:</strong> Memory is dynamically allocated as data rows are added to the
+     * window. Depending on the amount of data stored, the actual amount of memory allocated can be
+     * lower than specified size, but cannot exceed it.
+     */
+    public CursorWindow(String name, @BytesLong long windowSizeBytes) {
         mStartPos = 0;
         mName = name != null && name.length() != 0 ? name : "<unnamed>";
-        if (sCursorWindowSize < 0) {
-            /** The cursor window size. resource xml file specifies the value in kB.
-             * convert it to bytes here by multiplying with 1024.
-             */
-            sCursorWindowSize = Resources.getSystem().getInteger(
-                com.android.internal.R.integer.config_cursorWindowSize) * 1024;
-        }
-        mWindowPtr = nativeCreate(mName, sCursorWindowSize);
+        mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
         if (mWindowPtr == 0) {
             throw new CursorWindowAllocationException("Cursor window allocation of " +
-                    (sCursorWindowSize / 1024) + " kb failed. " + printStats());
+                    windowSizeBytes + " bytes failed. " + printStats());
         }
         mCloseGuard.open("close");
         recordNewWindow(Binder.getCallingPid(), mWindowPtr);
@@ -773,6 +784,16 @@
         return "# Open Cursors=" + total + s;
     }
 
+    private static int getCursorWindowSize() {
+        if (sCursorWindowSize < 0) {
+            // The cursor window size. resource xml file specifies the value in kB.
+            // convert it to bytes here by multiplying with 1024.
+            sCursorWindowSize = Resources.getSystem().getInteger(
+                    com.android.internal.R.integer.config_cursorWindowSize) * 1024;
+        }
+        return sCursorWindowSize;
+    }
+
     @Override
     public String toString() {
         return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
diff --git a/core/java/android/hardware/LegacySensorManager.java b/core/java/android/hardware/LegacySensorManager.java
index f5cf3f7..098121d 100644
--- a/core/java/android/hardware/LegacySensorManager.java
+++ b/core/java/android/hardware/LegacySensorManager.java
@@ -204,7 +204,7 @@
     }
 
     private static final class LegacyListener implements SensorEventListener {
-        private float mValues[] = new float[6];
+        private float[] mValues = new float[6];
         private SensorListener mTarget;
         private int mSensors;
         private final LmsFilter mYawfilter = new LmsFilter();
@@ -256,7 +256,7 @@
         }
 
         public void onSensorChanged(SensorEvent event) {
-            final float v[] = mValues;
+            final float[] v = mValues;
             v[0] = event.values[0];
             v[1] = event.values[1];
             v[2] = event.values[2];
@@ -264,10 +264,10 @@
             int legacyType = getLegacySensorType(type);
             mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
             if (type == Sensor.TYPE_ORIENTATION) {
-                if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW)!=0) {
+                if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW) != 0) {
                     mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
                 }
-                if ((mSensors & SensorManager.SENSOR_ORIENTATION)!=0) {
+                if ((mSensors & SensorManager.SENSOR_ORIENTATION) != 0) {
                     v[0] = mYawfilter.filter(event.timestamp, v[0]);
                     mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
                 }
@@ -317,7 +317,7 @@
                 switch (sensor) {
                     case SensorManager.SENSOR_ACCELEROMETER:
                     case SensorManager.SENSOR_MAGNETIC_FIELD:
-                        values[0] =-y;
+                        values[0] = -y;
                         values[1] = x;
                         values[2] = z;
                         break;
@@ -337,15 +337,15 @@
                 switch (sensor) {
                     case SensorManager.SENSOR_ACCELEROMETER:
                     case SensorManager.SENSOR_MAGNETIC_FIELD:
-                        values[0] =-x;
-                        values[1] =-y;
+                        values[0] = -x;
+                        values[1] = -y;
                         values[2] = z;
                         break;
                     case SensorManager.SENSOR_ORIENTATION:
                     case SensorManager.SENSOR_ORIENTATION_RAW:
                         values[0] = (x >= 180) ? (x - 180) : (x + 180);
-                        values[1] =-y;
-                        values[2] =-z;
+                        values[1] = -y;
+                        values[2] = -z;
                         break;
                 }
             }
@@ -369,10 +369,11 @@
     private static final class LmsFilter {
         private static final int SENSORS_RATE_MS = 20;
         private static final int COUNT = 12;
-        private static final float PREDICTION_RATIO = 1.0f/3.0f;
-        private static final float PREDICTION_TIME = (SENSORS_RATE_MS*COUNT/1000.0f)*PREDICTION_RATIO;
-        private float mV[] = new float[COUNT*2];
-        private long mT[] = new long[COUNT*2];
+        private static final float PREDICTION_RATIO = 1.0f / 3.0f;
+        private static final float PREDICTION_TIME =
+                (SENSORS_RATE_MS * COUNT / 1000.0f) * PREDICTION_RATIO;
+        private float[] mV = new float[COUNT * 2];
+        private long[] mT = new long[COUNT * 2];
         private int mIndex;
 
         public LmsFilter() {
@@ -383,9 +384,9 @@
             float v = in;
             final float ns = 1.0f / 1000000000.0f;
             float v1 = mV[mIndex];
-            if ((v-v1) > 180) {
+            if ((v - v1) > 180) {
                 v -= 360;
-            } else if ((v1-v) > 180) {
+            } else if ((v1 - v) > 180) {
                 v += 360;
             }
             /* Manage the circular buffer, we write the data twice spaced
@@ -393,40 +394,43 @@
              * when it's full
              */
             mIndex++;
-            if (mIndex >= COUNT*2)
+            if (mIndex >= COUNT * 2) {
                 mIndex = COUNT;
+            }
             mV[mIndex] = v;
             mT[mIndex] = time;
-            mV[mIndex-COUNT] = v;
-            mT[mIndex-COUNT] = time;
+            mV[mIndex - COUNT] = v;
+            mT[mIndex - COUNT] = time;
 
             float A, B, C, D, E;
             float a, b;
             int i;
 
             A = B = C = D = E = 0;
-            for (i=0 ; i<COUNT-1 ; i++) {
+            for (i = 0; i < COUNT - 1; i++) {
                 final int j = mIndex - 1 - i;
                 final float Z = mV[j];
-                final float T = (mT[j]/2 + mT[j+1]/2 - time)*ns;
-                float dT = (mT[j] - mT[j+1])*ns;
+                final float T = (mT[j] / 2 + mT[j + 1] / 2 - time) * ns;
+                float dT = (mT[j] - mT[j + 1]) * ns;
                 dT *= dT;
-                A += Z*dT;
-                B += T*(T*dT);
-                C +=   (T*dT);
-                D += Z*(T*dT);
+                A += Z * dT;
+                B += T * (T * dT);
+                C += (T * dT);
+                D += Z * (T * dT);
                 E += dT;
             }
-            b = (A*B + C*D) / (E*B + C*C);
-            a = (E*b - A) / C;
-            float f = b + PREDICTION_TIME*a;
+            b = (A * B + C * D) / (E * B + C * C);
+            a = (E * b - A) / C;
+            float f = b + PREDICTION_TIME * a;
 
             // Normalize
             f *= (1.0f / 360.0f);
-            if (((f>=0)?f:-f) >= 0.5f)
-                f = f - (float)Math.ceil(f + 0.5f) + 1.0f;
-            if (f < 0)
+            if (((f >= 0) ? f : -f) >= 0.5f) {
+                f = f - (float) Math.ceil(f + 0.5f) + 1.0f;
+            }
+            if (f < 0) {
                 f += 1.0f;
+            }
             f *= 360.0f;
             return f;
         }
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index f02e484..7fb0c89 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -794,12 +794,12 @@
             1, // SENSOR_TYPE_PICK_UP_GESTURE
             1, // SENSOR_TYPE_WRIST_TILT_GESTURE
             1, // SENSOR_TYPE_DEVICE_ORIENTATION
-            16,// SENSOR_TYPE_POSE_6DOF
+            16, // SENSOR_TYPE_POSE_6DOF
             1, // SENSOR_TYPE_STATIONARY_DETECT
             1, // SENSOR_TYPE_MOTION_DETECT
             1, // SENSOR_TYPE_HEART_BEAT
             2, // SENSOR_TYPE_DYNAMIC_SENSOR_META
-            16,// skip over additional sensor info type
+            16, // skip over additional sensor info type
             1, // SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT
             6, // SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED
     };
@@ -857,8 +857,8 @@
     static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
         // RotationVector length has changed to 3 to 5 for API level 18
         // Set it to 3 for backward compatibility.
-        if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR &&
-                sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        if (sensor.mType == Sensor.TYPE_ROTATION_VECTOR
+                && sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
             return 3;
         }
         int offset = sensor.mType;
@@ -1033,9 +1033,9 @@
      * Returns true if the sensor is a wake-up sensor.
      * <p>
      * <b>Application Processor Power modes</b> <p>
-     * Application Processor(AP), is the processor on which applications run.  When no wake lock is held
-     * and the user is not interacting with the device, this processor can enter a “Suspend” mode,
-     * reducing the power consumption by 10 times or more.
+     * Application Processor(AP), is the processor on which applications run.  When no wake lock is
+     * held and the user is not interacting with the device, this processor can enter a “Suspend”
+     * mode, reducing the power consumption by 10 times or more.
      * </p>
      * <p>
      * <b>Non-wake-up sensors</b> <p>
@@ -1232,6 +1232,6 @@
      */
     private void setUuid(long msb, long lsb) {
         // TODO(b/29547335): Rename this method to setId.
-        mId = (int)msb;
+        mId = (int) msb;
     }
 }
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 0c6a415..7c876cf 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -200,7 +200,7 @@
     public static final int TYPE_DEBUG_INFO  = 0x40000000;
 
     SensorAdditionalInfo(
-            Sensor aSensor, int aType, int aSerial, int [] aIntValues, float [] aFloatValues) {
+            Sensor aSensor, int aType, int aSerial, int[] aIntValues, float[] aFloatValues) {
         sensor = aSensor;
         type = aType;
         serial = aSerial;
@@ -222,10 +222,10 @@
                 null, new float[] { strength, declination, inclination});
     }
     /** @hide */
-    public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float [] data) {
+    public static SensorAdditionalInfo createCustomInfo(Sensor aSensor, int type, float[] data) {
         if (type < TYPE_CUSTOM_INFO || type >= TYPE_DEBUG_INFO || aSensor == null) {
-            throw new IllegalArgumentException("invalid parameter(s): type: " + type +
-                    "; sensor: " + aSensor);
+            throw new IllegalArgumentException(
+                    "invalid parameter(s): type: " + type + "; sensor: " + aSensor);
         }
 
         return new SensorAdditionalInfo(aSensor, type, 0, null, data);
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index c0bca97..bbd04a3 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -207,8 +207,8 @@
      *          timestamp = event.timestamp;
      *          float[] deltaRotationMatrix = new float[9];
      *          SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector);
-     *          // User code should concatenate the delta rotation we computed with the current rotation
-     *          // in order to get the updated rotation.
+     *          // User code should concatenate the delta rotation we computed with the current
+     *          // rotation in order to get the updated rotation.
      *          // rotationCurrent = rotationCurrent * deltaRotationMatrix;
      *     }
      * </pre>
@@ -244,21 +244,22 @@
      *  <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4>
      *  <p>A three dimensional vector indicating the direction and magnitude of gravity.  Units
      *  are m/s^2. The coordinate system is the same as is used by the acceleration sensor.</p>
-     *  <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be identical
-     *  to that of the accelerometer.</p>
+     *  <p><b>Note:</b> When the device is at rest, the output of the gravity sensor should be
+     *  identical to that of the accelerometer.</p>
      *
-     *  <h4>{@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:</h4>
-     *  A three dimensional vector indicating acceleration along each device axis, not including
-     *  gravity.  All values have units of m/s^2.  The coordinate system is the same as is used by the
-     *  acceleration sensor.
+     *  <h4>
+     *  {@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:
+     *  </h4> A three dimensional vector indicating acceleration along each device axis, not
+     *  including gravity. All values have units of m/s^2.  The coordinate system is the same as is
+     *  used by the acceleration sensor.
      *  <p>The output of the accelerometer, gravity and  linear-acceleration sensors must obey the
      *  following relation:</p>
-     *   <p><ul>acceleration = gravity + linear-acceleration</ul></p>
+     *  <p><ul>acceleration = gravity + linear-acceleration</ul></p>
      *
      *  <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4>
-     *  <p>The rotation vector represents the orientation of the device as a combination of an <i>angle</i>
-     *  and an <i>axis</i>, in which the device has rotated through an angle &#952 around an axis
-     *  &lt;x, y, z>.</p>
+     *  <p>The rotation vector represents the orientation of the device as a combination of an
+     *  <i>angle</i> and an <i>axis</i>, in which the device has rotated through an angle &#952
+     *  around an axis &lt;x, y, z>.</p>
      *  <p>The three elements of the rotation vector are
      *  &lt;x*sin(&#952/2), y*sin(&#952/2), z*sin(&#952/2)>, such that the magnitude of the rotation
      *  vector is equal to sin(&#952/2), and the direction of the rotation vector is equal to the
diff --git a/core/java/android/hardware/SensorListener.java b/core/java/android/hardware/SensorListener.java
index c71e968..e2033b6 100644
--- a/core/java/android/hardware/SensorListener.java
+++ b/core/java/android/hardware/SensorListener.java
@@ -19,8 +19,8 @@
 /**
  * Used for receiving notifications from the SensorManager when
  * sensor values have changed.
- * 
- * @deprecated Use 
+ *
+ * @deprecated Use
  * {@link android.hardware.SensorEventListener SensorEventListener} instead.
  */
 @Deprecated
@@ -36,7 +36,7 @@
      * <p><u>Definition of the coordinate system used below.</u><p>
      * <p>The X axis refers to the screen's horizontal axis
      * (the small edge in portrait mode, the long edge in landscape mode) and
-     * points to the right. 
+     * points to the right.
      * <p>The Y axis refers to the screen's vertical axis and points towards
      * the top of the screen (the origin is in the lower-left corner).
      * <p>The Z axis points toward the sky when the device is lying on its back
@@ -44,18 +44,18 @@
      * <p> <b>IMPORTANT NOTE:</b> The axis <b><u>are swapped</u></b> when the
      * device's screen orientation changes. To access the unswapped values,
      * use indices 3, 4 and 5 in values[].
-     * 
+     *
      * <p>{@link android.hardware.SensorManager#SENSOR_ORIENTATION SENSOR_ORIENTATION},
      * {@link android.hardware.SensorManager#SENSOR_ORIENTATION_RAW SENSOR_ORIENTATION_RAW}:<p>
      *  All values are angles in degrees.
-     * 
+     *
      * <p>values[0]: Azimuth, rotation around the Z axis (0<=azimuth<360).
      * 0 = North, 90 = East, 180 = South, 270 = West
-     * 
+     *
      * <p>values[1]: Pitch, rotation around X axis (-180<=pitch<=180), with positive
      * values when the z-axis moves toward the y-axis.
      *
-     * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values 
+     * <p>values[2]: Roll, rotation around Y axis (-90<=roll<=90), with positive values
      * when the z-axis moves toward the x-axis.
      *
      * <p>Note that this definition of yaw, pitch and roll is different from the
@@ -64,17 +64,17 @@
      *
      * <p>{@link android.hardware.SensorManager#SENSOR_ACCELEROMETER SENSOR_ACCELEROMETER}:<p>
      *  All values are in SI units (m/s^2) and measure contact forces.
-     *  
-     *  <p>values[0]: force applied by the device on the x-axis 
-     *  <p>values[1]: force applied by the device on the y-axis 
+     *
+     *  <p>values[0]: force applied by the device on the x-axis
+     *  <p>values[1]: force applied by the device on the y-axis
      *  <p>values[2]: force applied by the device on the z-axis
-     *  
+     *
      *  <p><u>Examples</u>:
      *    <li>When the device is pushed on its left side toward the right, the
      *    x acceleration value is negative (the device applies a reaction force
      *    to the push toward the left)</li>
-     *    
-     *    <li>When the device lies flat on a table, the acceleration value is 
+     *
+     *    <li>When the device lies flat on a table, the acceleration value is
      *    {@link android.hardware.SensorManager#STANDARD_GRAVITY -STANDARD_GRAVITY},
      *    which correspond to the force the device applies on the table in reaction
      *    to gravity.</li>
@@ -83,7 +83,7 @@
      *  All values are in micro-Tesla (uT) and measure the ambient magnetic
      *  field in the X, Y and -Z axis.
      *  <p><b><u>Note:</u></b> the magnetic field's Z axis is inverted.
-     *  
+     *
      * @param sensor The ID of the sensor being monitored
      * @param values The new values for the sensor.
      */
@@ -97,5 +97,5 @@
      * @param sensor The ID of the sensor being monitored
      * @param accuracy The new accuracy of this sensor.
      */
-    public void onAccuracyChanged(int sensor, int accuracy);    
+    public void onAccuracyChanged(int sensor, int accuracy);
 }
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index e1cd451..35aaf78 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -83,7 +83,7 @@
     /** @hide */
     protected static final String TAG = "SensorManager";
 
-    private static final float[] mTempMatrix = new float[16];
+    private static final float[] sTempMatrix = new float[16];
 
     // Cached lists of sensors by type.  Guarded by mSensorListByType.
     private final SparseArray<List<Sensor>> mSensorListByType =
@@ -188,7 +188,7 @@
      * @deprecated use {@link android.hardware.Sensor Sensor} instead.
      */
     @Deprecated
-    public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
+    public static final int SENSOR_MAX = ((SENSOR_ALL + 1) >> 1);
 
 
     /**
@@ -425,8 +425,9 @@
                 } else {
                     list = new ArrayList<Sensor>();
                     for (Sensor i : fullList) {
-                        if (i.getType() == type)
+                        if (i.getType() == type) {
                             list.add(i);
+                        }
                     }
                 }
                 list = Collections.unmodifiableList(list);
@@ -461,8 +462,9 @@
         } else {
             List<Sensor> list = new ArrayList();
             for (Sensor i : fullList) {
-                if (i.getType() == type)
+                if (i.getType() == type) {
                     list.add(i);
+                }
             }
             return Collections.unmodifiableList(list);
         }
@@ -490,10 +492,11 @@
         // For the following sensor types, return a wake-up sensor. These types are by default
         // defined as wake-up sensors. For the rest of the SDK defined sensor types return a
         // non_wake-up version.
-        if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION ||
-                type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE ||
-                type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE ||
-                type == Sensor.TYPE_WRIST_TILT_GESTURE || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
+        if (type == Sensor.TYPE_PROXIMITY || type == Sensor.TYPE_SIGNIFICANT_MOTION
+                || type == Sensor.TYPE_TILT_DETECTOR || type == Sensor.TYPE_WAKE_GESTURE
+                || type == Sensor.TYPE_GLANCE_GESTURE || type == Sensor.TYPE_PICK_UP_GESTURE
+                || type == Sensor.TYPE_WRIST_TILT_GESTURE
+                || type == Sensor.TYPE_DYNAMIC_SENSOR_META) {
             wakeUpSensor = true;
         }
 
@@ -509,12 +512,12 @@
      * <p>
      * For example,
      * <ul>
-     *     <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up accelerometer
-     *     sensor if it exists. </li>
-     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up proximity
-     *     sensor if it exists. </li>
-     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity sensor
-     *     which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
+     *     <li>getDefaultSensor({@link Sensor#TYPE_ACCELEROMETER}, true) returns a wake-up
+     *     accelerometer sensor if it exists. </li>
+     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, false) returns a non wake-up
+     *     proximity sensor if it exists. </li>
+     *     <li>getDefaultSensor({@link Sensor#TYPE_PROXIMITY}, true) returns a wake-up proximity
+     *     sensor which is the same as the Sensor returned by {@link #getDefaultSensor(int)}. </li>
      * </ul>
      * </p>
      * <p class="note">
@@ -532,8 +535,9 @@
     public Sensor getDefaultSensor(int type, boolean wakeUp) {
         List<Sensor> l = getSensorList(type);
         for (Sensor sensor : l) {
-            if (sensor.isWakeUpSensor() == wakeUp)
+            if (sensor.isWakeUpSensor() == wakeUp) {
                 return sensor;
+            }
         }
         return null;
     }
@@ -842,8 +846,8 @@
      * @return <code>true</code> if the sensor is supported and successfully enabled.
      * @see #registerListener(SensorEventListener, Sensor, int, int)
      */
-    public boolean registerListener(SensorEventListener listener, Sensor sensor, int samplingPeriodUs,
-            int maxReportLatencyUs, Handler handler) {
+    public boolean registerListener(SensorEventListener listener, Sensor sensor,
+            int samplingPeriodUs, int maxReportLatencyUs, Handler handler) {
         int delayUs = getDelay(samplingPeriodUs);
         return registerListenerImpl(listener, sensor, delayUs, handler, maxReportLatencyUs, 0);
     }
@@ -953,7 +957,7 @@
      * Used for receiving notifications from the SensorManager when dynamic sensors are connected or
      * disconnected.
      */
-    public static abstract class DynamicSensorCallback {
+    public abstract static class DynamicSensorCallback {
         /**
          * Called when there is a dynamic sensor being connected to the system.
          *
@@ -1180,7 +1184,7 @@
         float Ay = gravity[1];
         float Az = gravity[2];
 
-        final float normsqA = (Ax*Ax + Ay*Ay + Az*Az);
+        final float normsqA = (Ax * Ax + Ay * Ay + Az * Az);
         final float g = 9.81f;
         final float freeFallGravitySquared = 0.01f * g * g;
         if (normsqA < freeFallGravitySquared) {
@@ -1191,10 +1195,10 @@
         final float Ex = geomagnetic[0];
         final float Ey = geomagnetic[1];
         final float Ez = geomagnetic[2];
-        float Hx = Ey*Az - Ez*Ay;
-        float Hy = Ez*Ax - Ex*Az;
-        float Hz = Ex*Ay - Ey*Ax;
-        final float normH = (float)Math.sqrt(Hx*Hx + Hy*Hy + Hz*Hz);
+        float Hx = Ey * Az - Ez * Ay;
+        float Hy = Ez * Ax - Ex * Az;
+        float Hz = Ex * Ay - Ey * Ax;
+        final float normH = (float) Math.sqrt(Hx * Hx + Hy * Hy + Hz * Hz);
 
         if (normH < 0.1f) {
             // device is close to free fall (or in space?), or close to
@@ -1205,13 +1209,13 @@
         Hx *= invH;
         Hy *= invH;
         Hz *= invH;
-        final float invA = 1.0f / (float)Math.sqrt(Ax*Ax + Ay*Ay + Az*Az);
+        final float invA = 1.0f / (float) Math.sqrt(Ax * Ax + Ay * Ay + Az * Az);
         Ax *= invA;
         Ay *= invA;
         Az *= invA;
-        final float Mx = Ay*Hz - Az*Hy;
-        final float My = Az*Hx - Ax*Hz;
-        final float Mz = Ax*Hy - Ay*Hx;
+        final float Mx = Ay * Hz - Az * Hy;
+        final float My = Az * Hx - Ax * Hz;
+        final float Mz = Ax * Hy - Ay * Hx;
         if (R != null) {
             if (R.length == 9) {
                 R[0] = Hx;     R[1] = Hy;     R[2] = Hz;
@@ -1228,17 +1232,17 @@
             // compute the inclination matrix by projecting the geomagnetic
             // vector onto the Z (gravity) and X (horizontal component
             // of geomagnetic vector) axes.
-            final float invE = 1.0f / (float)Math.sqrt(Ex*Ex + Ey*Ey + Ez*Ez);
-            final float c = (Ex*Mx + Ey*My + Ez*Mz) * invE;
-            final float s = (Ex*Ax + Ey*Ay + Ez*Az) * invE;
+            final float invE = 1.0f / (float) Math.sqrt(Ex * Ex + Ey * Ey + Ez * Ez);
+            final float c = (Ex * Mx + Ey * My + Ez * Mz) * invE;
+            final float s = (Ex * Ax + Ey * Ay + Ez * Az) * invE;
             if (I.length == 9) {
                 I[0] = 1;     I[1] = 0;     I[2] = 0;
                 I[3] = 0;     I[4] = c;     I[5] = s;
-                I[6] = 0;     I[7] =-s;     I[8] = c;
+                I[6] = 0;     I[7] = -s;     I[8] = c;
             } else if (I.length == 16) {
                 I[0] = 1;     I[1] = 0;     I[2] = 0;
                 I[4] = 0;     I[5] = c;     I[6] = s;
-                I[8] = 0;     I[9] =-s;     I[10]= c;
+                I[8] = 0;     I[9] = -s;     I[10] = c;
                 I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
                 I[15] = 1;
             }
@@ -1262,9 +1266,9 @@
      */
     public static float getInclination(float[] I) {
         if (I.length == 9) {
-            return (float)Math.atan2(I[5], I[4]);
+            return (float) Math.atan2(I[5], I[4]);
         } else {
-            return (float)Math.atan2(I[6], I[5]);
+            return (float) Math.atan2(I[6], I[5]);
         }
     }
 
@@ -1343,17 +1347,16 @@
      * @see #getRotationMatrix(float[], float[], float[], float[])
      */
 
-    public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
-            float[] outR)
-    {
+    public static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) {
         if (inR == outR) {
-            final float[] temp = mTempMatrix;
-            synchronized(temp) {
+            final float[] temp = sTempMatrix;
+            synchronized (temp) {
                 // we don't expect to have a lot of contention
                 if (remapCoordinateSystemImpl(inR, X, Y, temp)) {
                     final int size = outR.length;
-                    for (int i=0 ; i<size ; i++)
+                    for (int i = 0; i < size; i++) {
                         outR[i] = temp[i];
+                    }
                     return true;
                 }
             }
@@ -1361,9 +1364,7 @@
         return remapCoordinateSystemImpl(inR, X, Y, outR);
     }
 
-    private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y,
-            float[] outR)
-    {
+    private static boolean remapCoordinateSystemImpl(float[] inR, int X, int Y, float[] outR) {
         /*
          * X and Y define a rotation matrix 'r':
          *
@@ -1376,14 +1377,18 @@
          */
 
         final int length = outR.length;
-        if (inR.length != length)
+        if (inR.length != length) {
             return false;   // invalid parameter
-        if ((X & 0x7C)!=0 || (Y & 0x7C)!=0)
+        }
+        if ((X & 0x7C) != 0 || (Y & 0x7C) != 0) {
             return false;   // invalid parameter
-        if (((X & 0x3)==0) || ((Y & 0x3)==0))
+        }
+        if (((X & 0x3) == 0) || ((Y & 0x3) == 0)) {
             return false;   // no axis specified
-        if ((X & 0x3) == (Y & 0x3))
+        }
+        if ((X & 0x3) == (Y & 0x3)) {
             return false;   // same axis specified
+        }
 
         // Z is "the other" axis, its sign is either +/- sign(X)*sign(Y)
         // this can be calculated by exclusive-or'ing X and Y; except for
@@ -1391,28 +1396,29 @@
         int Z = X ^ Y;
 
         // extract the axis (remove the sign), offset in the range 0 to 2.
-        final int x = (X & 0x3)-1;
-        final int y = (Y & 0x3)-1;
-        final int z = (Z & 0x3)-1;
+        final int x = (X & 0x3) - 1;
+        final int y = (Y & 0x3) - 1;
+        final int z = (Z & 0x3) - 1;
 
         // compute the sign of Z (whether it needs to be inverted)
-        final int axis_y = (z+1)%3;
-        final int axis_z = (z+2)%3;
-        if (((x^axis_y)|(y^axis_z)) != 0)
+        final int axis_y = (z + 1) % 3;
+        final int axis_z = (z + 2) % 3;
+        if (((x ^ axis_y) | (y ^ axis_z)) != 0) {
             Z ^= 0x80;
+        }
 
-        final boolean sx = (X>=0x80);
-        final boolean sy = (Y>=0x80);
-        final boolean sz = (Z>=0x80);
+        final boolean sx = (X >= 0x80);
+        final boolean sy = (Y >= 0x80);
+        final boolean sz = (Z >= 0x80);
 
         // Perform R * r, in avoiding actual muls and adds.
-        final int rowLength = ((length==16)?4:3);
-        for (int j=0 ; j<3 ; j++) {
-            final int offset = j*rowLength;
-            for (int i=0 ; i<3 ; i++) {
-                if (x==i)   outR[offset+i] = sx ? -inR[offset+0] : inR[offset+0];
-                if (y==i)   outR[offset+i] = sy ? -inR[offset+1] : inR[offset+1];
-                if (z==i)   outR[offset+i] = sz ? -inR[offset+2] : inR[offset+2];
+        final int rowLength = ((length == 16) ? 4 : 3);
+        for (int j = 0; j < 3; j++) {
+            final int offset = j * rowLength;
+            for (int i = 0; i < 3; i++) {
+                if (x == i)   outR[offset + i] = sx ? -inR[offset + 0] : inR[offset + 0];
+                if (y == i)   outR[offset + i] = sy ? -inR[offset + 1] : inR[offset + 1];
+                if (z == i)   outR[offset + i] = sz ? -inR[offset + 2] : inR[offset + 2];
             }
         }
         if (length == 16) {
@@ -1466,7 +1472,7 @@
      * @see #getRotationMatrix(float[], float[], float[], float[])
      * @see GeomagneticField
      */
-    public static float[] getOrientation(float[] R, float values[]) {
+    public static float[] getOrientation(float[] R, float[] values) {
         /*
          * 4x4 (length=16) case:
          *   /  R[ 0]   R[ 1]   R[ 2]   0  \
@@ -1481,13 +1487,13 @@
          *
          */
         if (R.length == 9) {
-            values[0] = (float)Math.atan2(R[1], R[4]);
-            values[1] = (float)Math.asin(-R[7]);
-            values[2] = (float)Math.atan2(-R[6], R[8]);
+            values[0] = (float) Math.atan2(R[1], R[4]);
+            values[1] = (float) Math.asin(-R[7]);
+            values[2] = (float) Math.atan2(-R[6], R[8]);
         } else {
-            values[0] = (float)Math.atan2(R[1], R[5]);
-            values[1] = (float)Math.asin(-R[9]);
-            values[2] = (float)Math.atan2(-R[8], R[10]);
+            values[0] = (float) Math.atan2(R[1], R[5]);
+            values[1] = (float) Math.asin(-R[9]);
+            values[2] = (float) Math.atan2(-R[8], R[10]);
         }
 
         return values;
@@ -1524,7 +1530,7 @@
      */
     public static float getAltitude(float p0, float p) {
         final float coef = 1.0f / 5.255f;
-        return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef));
+        return 44330.0f * (1.0f - (float) Math.pow(p / p0, coef));
     }
 
     /** Helper function to compute the angle change between two rotation matrices.
@@ -1557,12 +1563,13 @@
      *        (in radians) is stored
      */
 
-    public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) {
-        float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0;
-        float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0;
-        float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0;
+    public static void getAngleChange(float[] angleChange, float[] R, float[] prevR) {
+        float rd1 = 0, rd4 = 0, rd6 = 0, rd7 = 0, rd8 = 0;
+        float ri0 = 0, ri1 = 0, ri2 = 0, ri3 = 0, ri4 = 0, ri5 = 0, ri6 = 0, ri7 = 0, ri8 = 0;
+        float pri0 = 0, pri1 = 0, pri2 = 0, pri3 = 0, pri4 = 0;
+        float pri5 = 0, pri6 = 0, pri7 = 0, pri8 = 0;
 
-        if(R.length == 9) {
+        if (R.length == 9) {
             ri0 = R[0];
             ri1 = R[1];
             ri2 = R[2];
@@ -1572,7 +1579,7 @@
             ri6 = R[6];
             ri7 = R[7];
             ri8 = R[8];
-        } else if(R.length == 16) {
+        } else if (R.length == 16) {
             ri0 = R[0];
             ri1 = R[1];
             ri2 = R[2];
@@ -1584,7 +1591,7 @@
             ri8 = R[10];
         }
 
-        if(prevR.length == 9) {
+        if (prevR.length == 9) {
             pri0 = prevR[0];
             pri1 = prevR[1];
             pri2 = prevR[2];
@@ -1594,7 +1601,7 @@
             pri6 = prevR[6];
             pri7 = prevR[7];
             pri8 = prevR[8];
-        } else if(prevR.length == 16) {
+        } else if (prevR.length == 16) {
             pri0 = prevR[0];
             pri1 = prevR[1];
             pri2 = prevR[2];
@@ -1615,9 +1622,9 @@
         rd7 = pri2 * ri1 + pri5 * ri4 + pri8 * ri7; //rd[2][1]
         rd8 = pri2 * ri2 + pri5 * ri5 + pri8 * ri8; //rd[2][2]
 
-        angleChange[0] = (float)Math.atan2(rd1, rd4);
-        angleChange[1] = (float)Math.asin(-rd7);
-        angleChange[2] = (float)Math.atan2(-rd6, rd8);
+        angleChange[0] = (float) Math.atan2(rd1, rd4);
+        angleChange[1] = (float) Math.asin(-rd7);
+        angleChange[2] = (float) Math.atan2(-rd6, rd8);
 
     }
 
@@ -1650,8 +1657,8 @@
         if (rotationVector.length >= 4) {
             q0 = rotationVector[3];
         } else {
-            q0 = 1 - q1*q1 - q2*q2 - q3*q3;
-            q0 = (q0 > 0) ? (float)Math.sqrt(q0) : 0;
+            q0 = 1 - q1 * q1 - q2 * q2 - q3 * q3;
+            q0 = (q0 > 0) ? (float) Math.sqrt(q0) : 0;
         }
 
         float sq_q1 = 2 * q1 * q1;
@@ -1664,7 +1671,7 @@
         float q2_q3 = 2 * q2 * q3;
         float q1_q0 = 2 * q1 * q0;
 
-        if(R.length == 9) {
+        if (R.length == 9) {
             R[0] = 1 - sq_q2 - sq_q3;
             R[1] = q1_q2 - q3_q0;
             R[2] = q1_q3 + q2_q0;
@@ -1707,8 +1714,8 @@
         if (rv.length >= 4) {
             Q[0] = rv[3];
         } else {
-            Q[0] = 1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2];
-            Q[0] = (Q[0] > 0) ? (float)Math.sqrt(Q[0]) : 0;
+            Q[0] = 1 - rv[0] * rv[0] - rv[1] * rv[1] - rv[2] * rv[2];
+            Q[0] = (Q[0] > 0) ? (float) Math.sqrt(Q[0]) : 0;
         }
         Q[1] = rv[0];
         Q[2] = rv[1];
@@ -1800,7 +1807,7 @@
      */
     @SystemApi
     public boolean initDataInjection(boolean enable) {
-          return initDataInjectionImpl(enable);
+        return initDataInjectionImpl(enable);
     }
 
     /**
@@ -1846,9 +1853,9 @@
         }
         int expectedNumValues = Sensor.getMaxLengthValuesArray(sensor, Build.VERSION_CODES.M);
         if (values.length != expectedNumValues) {
-            throw new  IllegalArgumentException ("Wrong number of values for sensor " +
-                    sensor.getName() + " actual=" + values.length + " expected=" +
-                                                  expectedNumValues);
+            throw new  IllegalArgumentException("Wrong number of values for sensor "
+                    + sensor.getName() + " actual=" + values.length + " expected="
+                    + expectedNumValues);
         }
         if (accuracy < SENSOR_STATUS_NO_CONTACT || accuracy > SENSOR_STATUS_ACCURACY_HIGH) {
             throw new IllegalArgumentException("Invalid sensor accuracy");
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 607788d..1174cb6 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -28,10 +28,11 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
-import dalvik.system.CloseGuard;
 
 import com.android.internal.annotations.GuardedBy;
 
+import dalvik.system.CloseGuard;
+
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.lang.ref.WeakReference;
@@ -40,7 +41,6 @@
 import java.util.List;
 import java.util.Map;
 
-
 /**
  * Sensor manager implementation that communicates with the built-in
  * system sensors.
@@ -101,7 +101,7 @@
 
     /** {@hide} */
     public SystemSensorManager(Context context, Looper mainLooper) {
-        synchronized(sLock) {
+        synchronized (sLock) {
             if (!sNativeClassInited) {
                 sNativeClassInited = true;
                 nativeClassInit();
@@ -114,7 +114,7 @@
         mNativeInstance = nativeCreate(context.getOpPackageName());
 
         // initialize the sensor list
-        for (int index = 0;;++index) {
+        for (int index = 0;; ++index) {
             Sensor sensor = new Sensor();
             if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
             mFullSensorsList.add(sensor);
@@ -157,9 +157,9 @@
             return false;
         }
         if (mSensorListeners.size() >= MAX_LISTENER_COUNT) {
-            throw new IllegalStateException("register failed, " +
-                "the sensor listeners size has exceeded the maximum limit " +
-                MAX_LISTENER_COUNT);
+            throw new IllegalStateException("register failed, "
+                + "the sensor listeners size has exceeded the maximum limit "
+                + MAX_LISTENER_COUNT);
         }
 
         // Invariants to preserve:
@@ -170,9 +170,10 @@
             SensorEventQueue queue = mSensorListeners.get(listener);
             if (queue == null) {
                 Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
-                final String fullClassName = listener.getClass().getEnclosingClass() != null ?
-                    listener.getClass().getEnclosingClass().getName() :
-                    listener.getClass().getName();
+                final String fullClassName =
+                        listener.getClass().getEnclosingClass() != null
+                            ? listener.getClass().getEnclosingClass().getName()
+                            : listener.getClass().getName();
                 queue = new SensorEventQueue(listener, looper, this, fullClassName);
                 if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
                     queue.dispose();
@@ -221,17 +222,18 @@
         if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) return false;
 
         if (mTriggerListeners.size() >= MAX_LISTENER_COUNT) {
-            throw new IllegalStateException("request failed, " +
-                    "the trigger listeners size has exceeded the maximum limit " +
-                    MAX_LISTENER_COUNT);
+            throw new IllegalStateException("request failed, "
+                    + "the trigger listeners size has exceeded the maximum limit "
+                    + MAX_LISTENER_COUNT);
         }
 
         synchronized (mTriggerListeners) {
             TriggerEventQueue queue = mTriggerListeners.get(listener);
             if (queue == null) {
-                final String fullClassName = listener.getClass().getEnclosingClass() != null ?
-                    listener.getClass().getEnclosingClass().getName() :
-                    listener.getClass().getName();
+                final String fullClassName =
+                        listener.getClass().getEnclosingClass() != null
+                            ? listener.getClass().getEnclosingClass().getName()
+                            : listener.getClass().getName();
                 queue = new TriggerEventQueue(listener, mMainLooper, this, fullClassName);
                 if (!queue.addSensor(sensor, 0, 0)) {
                     queue.dispose();
@@ -336,27 +338,27 @@
         mHandleToSensor.remove(sensor.getHandle());
 
         if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
-            synchronized(mTriggerListeners) {
+            synchronized (mTriggerListeners) {
                 HashMap<TriggerEventListener, TriggerEventQueue> triggerListeners =
-                    new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
+                        new HashMap<TriggerEventListener, TriggerEventQueue>(mTriggerListeners);
 
-                for (TriggerEventListener l: triggerListeners.keySet()) {
-                    if (DEBUG_DYNAMIC_SENSOR){
-                        Log.i(TAG, "removed trigger listener" + l.toString() +
-                                   " due to sensor disconnection");
+                for (TriggerEventListener l : triggerListeners.keySet()) {
+                    if (DEBUG_DYNAMIC_SENSOR) {
+                        Log.i(TAG, "removed trigger listener" + l.toString()
+                                + " due to sensor disconnection");
                     }
                     cancelTriggerSensorImpl(l, sensor, true);
                 }
             }
         } else {
-            synchronized(mSensorListeners) {
+            synchronized (mSensorListeners) {
                 HashMap<SensorEventListener, SensorEventQueue> sensorListeners =
-                    new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
+                        new HashMap<SensorEventListener, SensorEventQueue>(mSensorListeners);
 
                 for (SensorEventListener l: sensorListeners.keySet()) {
-                    if (DEBUG_DYNAMIC_SENSOR){
-                        Log.i(TAG, "removed event listener" + l.toString() +
-                                   " due to sensor disconnection");
+                    if (DEBUG_DYNAMIC_SENSOR) {
+                        Log.i(TAG, "removed event listener" + l.toString()
+                                + " due to sensor disconnection");
                     }
                     unregisterListenerImpl(l, sensor);
                 }
@@ -365,7 +367,7 @@
     }
 
     private void updateDynamicSensorList() {
-        synchronized(mFullDynamicSensorsList) {
+        synchronized (mFullDynamicSensorsList) {
             if (mDynamicSensorListDirty) {
                 List<Sensor> list = new ArrayList<>();
                 nativeGetDynamicSensors(mNativeInstance, list);
@@ -488,15 +490,15 @@
 
         int i = 0, j = 0;
         while (true) {
-            if (j < oldList.size() && ( i >= newList.size() ||
-                    newList.get(i).getHandle() > oldList.get(j).getHandle()) ) {
+            if (j < oldList.size() && (i >= newList.size()
+                    || newList.get(i).getHandle() > oldList.get(j).getHandle())) {
                 changed = true;
                 if (removed != null) {
                     removed.add(oldList.get(j));
                 }
                 ++j;
-            } else if (i < newList.size() && ( j >= oldList.size() ||
-                    newList.get(i).getHandle() < oldList.get(j).getHandle())) {
+            } else if (i < newList.size() && (j >= oldList.size()
+                    || newList.get(i).getHandle() < oldList.get(j).getHandle())) {
                 changed = true;
                 if (added != null) {
                     added.add(newList.get(i));
@@ -505,8 +507,8 @@
                     updated.add(newList.get(i));
                 }
                 ++i;
-            } else if (i < newList.size() && j < oldList.size() &&
-                    newList.get(i).getHandle() == oldList.get(j).getHandle()) {
+            } else if (i < newList.size() && j < oldList.size()
+                    && newList.get(i).getHandle() == oldList.get(j).getHandle()) {
                 if (updated != null) {
                     updated.add(oldList.get(j));
                 }
@@ -623,7 +625,7 @@
      * associated with any listener and there is one InjectEventQueue associated with a
      * SensorManager instance.
      */
-    private static abstract class BaseEventQueue {
+    private abstract static class BaseEventQueue {
         private static native long nativeInitBaseEventQueue(long nativeManager,
                 WeakReference<BaseEventQueue> eventQWeak, MessageQueue msgQ,
                 String packageName, int mode, String opPackageName);
@@ -633,9 +635,9 @@
         private static native void nativeDestroySensorEventQueue(long eventQ);
         private static native int nativeFlushSensor(long eventQ);
         private static native int nativeInjectSensorData(long eventQ, int handle,
-                float[] values,int accuracy, long timestamp);
+                float[] values, int accuracy, long timestamp);
 
-        private long nSensorEventQueue;
+        private long mNativeSensorEventQueue;
         private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
         protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
         private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -646,7 +648,7 @@
 
         BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
             if (packageName == null) packageName = "";
-            nSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
+            mNativeSensorEventQueue = nativeInitBaseEventQueue(manager.mNativeInstance,
                     new WeakReference<>(this), looper.getQueue(),
                     packageName, mode, manager.mContext.getOpPackageName());
             mCloseGuard.open("dispose");
@@ -668,17 +670,17 @@
             addSensorEvent(sensor);
             if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
                 // Try continuous mode if batching fails.
-                if (maxBatchReportLatencyUs == 0 ||
-                    maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
-                  removeSensor(sensor, false);
-                  return false;
+                if (maxBatchReportLatencyUs == 0
+                        || maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
+                    removeSensor(sensor, false);
+                    return false;
                 }
             }
             return true;
         }
 
         public boolean removeAllSensors() {
-            for (int i=0 ; i<mActiveSensors.size(); i++) {
+            for (int i = 0; i < mActiveSensors.size(); i++) {
                 if (mActiveSensors.valueAt(i) == true) {
                     int handle = mActiveSensors.keyAt(i);
                     Sensor sensor = mManager.mHandleToSensor.get(handle);
@@ -706,8 +708,8 @@
         }
 
         public int flush() {
-            if (nSensorEventQueue == 0) throw new NullPointerException();
-            return nativeFlushSensor(nSensorEventQueue);
+            if (mNativeSensorEventQueue == 0) throw new NullPointerException();
+            return nativeFlushSensor(mNativeSensorEventQueue);
         }
 
         public boolean hasSensors() {
@@ -731,29 +733,30 @@
                 }
                 mCloseGuard.close();
             }
-            if (nSensorEventQueue != 0) {
-                nativeDestroySensorEventQueue(nSensorEventQueue);
-                nSensorEventQueue = 0;
+            if (mNativeSensorEventQueue != 0) {
+                nativeDestroySensorEventQueue(mNativeSensorEventQueue);
+                mNativeSensorEventQueue = 0;
             }
         }
 
         private int enableSensor(
                 Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
-            if (nSensorEventQueue == 0) throw new NullPointerException();
+            if (mNativeSensorEventQueue == 0) throw new NullPointerException();
             if (sensor == null) throw new NullPointerException();
-            return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs,
+            return nativeEnableSensor(mNativeSensorEventQueue, sensor.getHandle(), rateUs,
                     maxBatchReportLatencyUs);
         }
 
         protected int injectSensorDataBase(int handle, float[] values, int accuracy,
                                            long timestamp) {
-            return nativeInjectSensorData(nSensorEventQueue, handle, values, accuracy, timestamp);
+            return nativeInjectSensorData(
+                    mNativeSensorEventQueue, handle, values, accuracy, timestamp);
         }
 
         private int disableSensor(Sensor sensor) {
-            if (nSensorEventQueue == 0) throw new NullPointerException();
+            if (mNativeSensorEventQueue == 0) throw new NullPointerException();
             if (sensor == null) throw new NullPointerException();
-            return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
+            return nativeDisableSensor(mNativeSensorEventQueue, sensor.getHandle());
         }
         protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
                 long timestamp);
@@ -840,7 +843,7 @@
                     // sensor disconnected
                     return;
                 }
-                ((SensorEventListener2)mListener).onFlushCompleted(sensor);
+                ((SensorEventListener2) mListener).onFlushCompleted(sensor);
             }
             return;
         }
@@ -858,7 +861,7 @@
                 }
                 SensorAdditionalInfo info =
                         new SensorAdditionalInfo(sensor, type, serial, intValues, floatValues);
-                ((SensorEventCallback)mListener).onSensorAdditionalInfo(info);
+                ((SensorEventCallback) mListener).onSensorAdditionalInfo(info);
             }
         }
     }
@@ -930,8 +933,8 @@
             super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
         }
 
-        int injectSensorData(int handle, float[] values,int accuracy, long timestamp) {
-             return injectSensorDataBase(handle, values, accuracy, timestamp);
+        int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
+            return injectSensorDataBase(handle, values, accuracy, timestamp);
         }
 
         @SuppressWarnings("unused")
@@ -959,6 +962,7 @@
         int handle = -1;
         if (parameter.sensor != null) handle = parameter.sensor.getHandle();
         return nativeSetOperationParameter(
-                mNativeInstance, handle, parameter.type, parameter.floatValues, parameter.intValues) == 0;
+                mNativeInstance, handle,
+                parameter.type, parameter.floatValues, parameter.intValues) == 0;
     }
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 6fbacaf..b2af44e 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -278,6 +278,19 @@
      */
     public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
 
+    /**
+     * Virtual display flag: Indicates that the contents will be destroyed once
+     * the display is removed.
+     *
+     * Public virtual displays without this flag will move their content to main display
+     * stack once they're removed. Private vistual displays will always destroy their
+     * content on removal even without this flag.
+     *
+     * @see #createVirtualDisplay
+     * @hide
+     */
+    public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
new file mode 100644
index 0000000..b7e353a
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.annotation.RequiresPermission;
+import android.os.Handler;
+
+import java.io.Closeable;
+
+/**
+ * A class describing a client of the Context Hub Service.
+ *
+ * Clients can send messages to nanoapps at a Context Hub through this object.
+ *
+ * @hide
+ */
+public class ContextHubClient implements Closeable {
+    /*
+     * The ContextHubClient interface associated with this client.
+     */
+    // TODO: Implement this interface and associate with ContextHubClient object
+    // private final IContextHubClient mClientInterface;
+
+    /*
+     * The listening callback associated with this client.
+     */
+    private ContextHubClientCallback mCallback;
+
+    /*
+     * The Context Hub that this client is attached to.
+     */
+    private ContextHubInfo mAttachedHub;
+
+    /*
+     * The handler to invoke mCallback.
+     */
+    private Handler mCallbackHandler;
+
+    ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
+        mCallback = callback;
+        mCallbackHandler = handler;
+        mAttachedHub = hubInfo;
+    }
+
+    /**
+     * Returns the hub that this client is attached to.
+     *
+     * @return the ContextHubInfo of the attached hub
+     */
+    public ContextHubInfo getAttachedHub() {
+        return mAttachedHub;
+    }
+
+    /**
+     * Closes the connection for this client and the Context Hub Service.
+     *
+     * When this function is invoked, the messaging associated with this client is invalidated.
+     * All futures messages targeted for this client are dropped at the service.
+     */
+    public void close() {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Sends a message to a nanoapp through the Context Hub Service.
+     *
+     * This function returns TRANSACTION_SUCCESS if the message has reached the HAL, but
+     * does not guarantee delivery of the message to the target nanoapp.
+     *
+     * @param message the message object to send
+     *
+     * @return the result of sending the message defined as in ContextHubTransaction.Result
+     *
+     * @see NanoAppMessage
+     * @see ContextHubTransaction.Result
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    @ContextHubTransaction.Result
+    public int sendMessageToNanoApp(NanoAppMessage message) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+}
diff --git a/core/java/android/hardware/location/ContextHubClientCallback.java b/core/java/android/hardware/location/ContextHubClientCallback.java
new file mode 100644
index 0000000..ab19d54
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubClientCallback.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 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.hardware.location;
+
+/**
+ * A class for {@link android.hardware.location.ContextHubClient ContextHubClient} to
+ * receive messages and life-cycle events from nanoapps in the Context Hub at which the client is
+ * attached to.
+ *
+ * This callback is registered through the
+ * {@link android.hardware.location.ContextHubManager#createClient() creation} of
+ * {@link android.hardware.location.ContextHubClient ContextHubClient}. Callbacks are
+ * invoked in the following ways:
+ * 1) Messages from nanoapps delivered through onMessageFromNanoApp may either be broadcasted
+ *    or targeted to a specific client.
+ * 2) Nanoapp or Context Hub events (the remaining callbacks) are broadcasted to all clients, and
+ *    the client can choose to ignore the event by filtering through the parameters.
+ *
+ * @hide
+ */
+public class ContextHubClientCallback {
+    /**
+     * Callback invoked when receiving a message from a nanoapp.
+     *
+     * The message contents of this callback may either be broadcasted or targeted to the
+     * client receiving the invocation.
+     *
+     * @param message the message sent by the nanoapp
+     */
+    public void onMessageFromNanoApp(NanoAppMessage message) {}
+
+    /**
+     * Callback invoked when the attached Context Hub has reset.
+     */
+    public void onHubReset() {}
+
+    /**
+     * Callback invoked when a nanoapp aborts at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had aborted
+     * @param abortCode the reason for nanoapp's abort, specific to each nanoapp
+     */
+    public void onNanoAppAborted(long nanoAppId, int abortCode) {}
+
+    /**
+     * Callback invoked when a nanoapp is loaded at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been loaded
+     */
+    public void onNanoAppLoaded(long nanoAppId) {}
+
+    /**
+     * Callback invoked when a nanoapp is unloaded from the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been unloaded
+     */
+    public void onNanoAppUnloaded(long nanoAppId) {}
+
+    /**
+     * Callback invoked when a nanoapp is enabled at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been enabled
+     */
+    public void onNanoAppEnabled(long nanoAppId) {}
+
+    /**
+     * Callback invoked when a nanoapp is disabled at the attached Context Hub.
+     *
+     * @param nanoAppId the ID of the nanoapp that had been disabled
+     */
+    public void onNanoAppDisabled(long nanoAppId) {}
+}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6050046..7cbb436 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -15,6 +15,7 @@
  */
 package android.hardware.location;
 
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -27,6 +28,8 @@
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.Log;
 
+import java.util.List;
+
 /**
  * A class that exposes the Context hubs on a device to applications.
  *
@@ -38,7 +41,6 @@
 @SystemApi
 @SystemService(Context.CONTEXTHUB_SERVICE)
 public final class ContextHubManager {
-
     private static final String TAG = "ContextHubManager";
 
     private final Looper mMainLooper;
@@ -256,6 +258,100 @@
     }
 
     /**
+     * Returns the list of context hubs in the system.
+     *
+     * @return the list of context hub informations
+     *
+     * @see ContextHubInfo
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public List<ContextHubInfo> getContextHubs() {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Loads a nanoapp at the specified Context Hub.
+     *
+     * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in
+     * the enabled state.
+     *
+     * @param hubInfo the hub to load the nanoapp on
+     * @param appBinary The app binary to load
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @see NanoAppBinary
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> loadNanoApp(
+            ContextHubInfo hubInfo, NanoAppBinary appBinary) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Unloads a nanoapp at the specified Context Hub.
+     *
+     * @param hubInfo the hub to unload the nanoapp from
+     * @param nanoAppId the app to unload
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> unloadNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Enables a nanoapp at the specified Context Hub.
+     *
+     * @param hubInfo the hub to enable the nanoapp on
+     * @param nanoAppId the app to enable
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> enableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Disables a nanoapp at the specified Context Hub.
+     *
+     * @param hubInfo the hub to disable the nanoapp on
+     * @param nanoAppId the app to disable
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<Void> disableNanoApp(ContextHubInfo hubInfo, long nanoAppId) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Requests a query for nanoapps loaded at the specified Context Hub.
+     *
+     * @param hubInfo the hub to query a list of nanoapps from
+     *
+     * @return the ContextHubTransaction of the request
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+    public ContextHubTransaction<List<NanoAppState>> queryNanoApps(ContextHubInfo hubInfo) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
      * Set a callback to receive messages from the context hub
      *
      * @param callback Callback object
@@ -307,6 +403,29 @@
     }
 
     /**
+     * Creates and registers a client and its callback with the Context Hub Service.
+     *
+     * A client is registered with the Context Hub Service for a specified Context Hub. When the
+     * registration succeeds, the client can send messages to nanoapps through the returned
+     * {@link ContextHubClient} object, and receive notifications through the provided callback.
+     *
+     * @param callback the notification callback to register
+     * @param hubInfo the hub to attach this client to
+     * @param handler the handler to invoke the callback, if null uses the current thread Looper
+     *
+     * @return the registered client object
+     *
+     * @see ContextHubClientCallback
+     *
+     * @hide
+     */
+    public ContextHubClient createClient(
+            ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
+        throw new UnsupportedOperationException(
+                "TODO: Implement this, and throw an exception on error");
+    }
+
+    /**
      * Unregister a callback for receive messages from the context hub.
      *
      * @see Callback
diff --git a/core/java/android/hardware/location/ContextHubTransaction.java b/core/java/android/hardware/location/ContextHubTransaction.java
new file mode 100644
index 0000000..4877d38
--- /dev/null
+++ b/core/java/android/hardware/location/ContextHubTransaction.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class describing a request sent to the Context Hub Service.
+ *
+ * This object is generated as a result of an asynchronous request sent to the Context Hub
+ * through the ContextHubManager APIs. The caller can either retrieve the result
+ * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
+ * asynchronously through a user-defined callback
+ * ({@link #onComplete(ContextHubTransaction.Callback<T>, Handler)}).
+ *
+ * A transaction can be invalidated if the caller of the transaction is no longer active
+ * and the reference to this object is lost, or if timeout period has passed in
+ * {@link #waitForResponse(long, TimeUnit)}.
+ *
+ * @param <T> the type of the contents in the transaction response
+ *
+ * @hide
+ */
+public class ContextHubTransaction<T> {
+    /**
+     * Constants describing the type of a transaction through the Context Hub Service.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            TYPE_LOAD_NANOAPP,
+            TYPE_UNLOAD_NANOAPP,
+            TYPE_ENABLE_NANOAPP,
+            TYPE_DISABLE_NANOAPP,
+            TYPE_QUERY_NANOAPPS})
+    public @interface Type {}
+    public static final int TYPE_LOAD_NANOAPP = 0;
+    public static final int TYPE_UNLOAD_NANOAPP = 1;
+    public static final int TYPE_ENABLE_NANOAPP = 2;
+    public static final int TYPE_DISABLE_NANOAPP = 3;
+    public static final int TYPE_QUERY_NANOAPPS = 4;
+
+    /**
+     * Constants describing the result of a transaction or request through the Context Hub Service.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            TRANSACTION_SUCCESS,
+            TRANSACTION_FAILED_UNKNOWN,
+            TRANSACTION_FAILED_BAD_PARAMS,
+            TRANSACTION_FAILED_UNINITIALIZED,
+            TRANSACTION_FAILED_PENDING,
+            TRANSACTION_FAILED_AT_HUB,
+            TRANSACTION_FAILED_TIMEOUT})
+    public @interface Result {}
+    public static final int TRANSACTION_SUCCESS = 0;
+    /**
+     * Generic failure mode.
+     */
+    public static final int TRANSACTION_FAILED_UNKNOWN = 1;
+    /**
+     * Failure mode when the request parameters were not valid.
+     */
+    public static final int TRANSACTION_FAILED_BAD_PARAMS = 2;
+    /**
+     * Failure mode when the Context Hub is not initialized.
+     */
+    public static final int TRANSACTION_FAILED_UNINITIALIZED = 3;
+    /**
+     * Failure mode when there are too many transactions pending.
+     */
+    public static final int TRANSACTION_FAILED_PENDING = 4;
+    /**
+     * Failure mode when the request went through, but failed asynchronously at the hub.
+     */
+    public static final int TRANSACTION_FAILED_AT_HUB = 5;
+    /**
+     * Failure mode when the transaction has timed out.
+     */
+    public static final int TRANSACTION_FAILED_TIMEOUT = 6;
+
+    /**
+     * A class describing the response for a ContextHubTransaction.
+     *
+     * @param <R> the type of the contents in the response
+     */
+    public static class Response<R> {
+        /*
+         * The result of the transaction.
+         */
+        @ContextHubTransaction.Result
+        private int mResult;
+
+        /*
+         * The contents of the response from the Context Hub.
+         */
+        private R mContents;
+
+        Response(@ContextHubTransaction.Result int result, R contents) {
+            mResult = result;
+            mContents = contents;
+        }
+
+        @ContextHubTransaction.Result
+        public int getResult() {
+            return mResult;
+        }
+
+        public R getContents() {
+            return mContents;
+        }
+    }
+
+    /**
+     * An interface describing the callback to be invoked when a transaction completes.
+     *
+     * @param <C> the type of the contents in the transaction response
+     */
+    @FunctionalInterface
+    public interface Callback<C> {
+        /**
+         * The callback to invoke when the transaction completes.
+         *
+         * @param transaction the transaction that this callback was attached to.
+         * @param response the response of the transaction.
+         */
+        void onComplete(
+                ContextHubTransaction<C> transaction, ContextHubTransaction.Response<C> response);
+    }
+
+    /*
+     * The unique identifier representing the transaction.
+     */
+    private int mTransactionId;
+
+    /*
+     * The type of the transaction.
+     */
+    @Type
+    private int mTransactionType;
+
+    /*
+     * The response of the transaction.
+     */
+    private ContextHubTransaction.Response<T> mResponse;
+
+    /*
+     * The handler to invoke the aynsc response supplied by onComplete.
+     */
+    private Handler mHandler = null;
+
+    /*
+     * The callback to invoke when the transaction completes.
+     */
+    private ContextHubTransaction.Callback<T> mCallback = null;
+
+    ContextHubTransaction(int id, @Type int type) {
+        mTransactionId = id;
+        mTransactionType = type;
+    }
+
+    /**
+     * @return the type of the transaction
+     */
+    @Type
+    public int getType() {
+        return mTransactionType;
+    }
+
+    /**
+     * Waits to receive the asynchronous transaction result.
+     *
+     * This function blocks until the Context Hub Service has received a response
+     * for the transaction represented by this object by the Context Hub, or a
+     * specified timeout period has elapsed.
+     *
+     * If the specified timeout has passed, the transaction represented by this object
+     * is invalidated by the Context Hub Service (resulting in a timeout failure in the
+     * response).
+     *
+     * @param timeout the timeout duration
+     * @param unit the unit of the timeout
+     *
+     * @return the transaction response
+     */
+    public ContextHubTransaction.Response<T> waitForResponse(long timeout, TimeUnit unit) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    /**
+     * Sets a callback to be invoked when the transaction completes.
+     *
+     * This function provides an asynchronous approach to retrieve the result of the
+     * transaction. When the transaction response has been provided by the Context Hub,
+     * the given callback will be posted by the provided handler.
+     *
+     * If the transaction has already completed at the time of invocation, the callback
+     * will be immediately posted by the handler. If the transaction has been invalidated,
+     * the callback will never be invoked.
+     *
+     * @param callback the callback to be invoked upon completion
+     * @param handler the handler to post the callback
+     */
+    public void onComplete(ContextHubTransaction.Callback<T> callback, Handler handler) {
+        throw new UnsupportedOperationException("TODO: Implement this");
+    }
+
+    private void setResponse(ContextHubTransaction.Response<T> response) {
+        mResponse = response;
+        throw new UnsupportedOperationException("TODO: Unblock waitForResponse");
+    }
+}
diff --git a/core/java/android/hardware/location/NanoAppBinary.aidl b/core/java/android/hardware/location/NanoAppBinary.aidl
new file mode 100644
index 0000000..ff50c93
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppBinary.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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.hardware.location;
+
+/**
+ * @hide
+ */
+parcelable NanoAppBinary;
diff --git a/core/java/android/hardware/location/NanoAppBinary.java b/core/java/android/hardware/location/NanoAppBinary.java
new file mode 100644
index 0000000..5454227
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppBinary.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @hide
+ */
+public final class NanoAppBinary implements Parcelable {
+    private static final String TAG = "NanoAppBinary";
+
+    /*
+     * The contents of the app binary.
+     */
+    private byte[] mNanoAppBinary;
+
+    /*
+     * Contents of the nanoapp binary header.
+     *
+     * Only valid if mHasValidHeader is true.
+     * See nano_app_binary_t in context_hub.h for details.
+     */
+    private int mHeaderVersion;
+    private int mMagic;
+    private long mNanoAppId;
+    private int mNanoAppVersion;
+    private int mFlags;
+    private long mHwHubType;
+    private byte mTargetChreApiMajorVersion;
+    private byte mTargetChreApiMinorVersion;
+
+    private boolean mHasValidHeader = false;
+
+    /*
+     * The header version used to parse the binary in parseBinaryHeader().
+     */
+    private static final int EXPECTED_HEADER_VERSION = 1;
+
+    /*
+     * The magic value expected in the header.
+     */
+    private static final int EXPECTED_MAGIC_VALUE =
+            (((int) 'N' <<  0) | ((int) 'A' <<  8) | ((int) 'N' << 16) | ((int) 'O' << 24));
+
+    /*
+     * Byte order established in context_hub.h
+     */
+    private static final ByteOrder HEADER_ORDER = ByteOrder.LITTLE_ENDIAN;
+
+    public NanoAppBinary(byte[] appBinary) {
+        mNanoAppBinary = appBinary;
+        parseBinaryHeader();
+    }
+
+    /*
+     * Parses the binary header and populates its field using mNanoAppBinary.
+     */
+    private void parseBinaryHeader() {
+        ByteBuffer buf = ByteBuffer.wrap(mNanoAppBinary).order(HEADER_ORDER);
+
+        mHasValidHeader = false;
+        try {
+            mHeaderVersion = buf.getInt();
+            if (mHeaderVersion != EXPECTED_HEADER_VERSION) {
+                Log.e(TAG, "Unexpected header version " + mHeaderVersion + " while parsing header"
+                        + " (expected " + EXPECTED_HEADER_VERSION + ")");
+                return;
+            }
+
+            mMagic = buf.getInt();
+            mNanoAppId = buf.getLong();
+            mNanoAppVersion = buf.getInt();
+            mFlags = buf.getInt();
+            mHwHubType = buf.getLong();
+            mTargetChreApiMajorVersion = buf.get();
+            mTargetChreApiMinorVersion = buf.get();
+        } catch (BufferUnderflowException e) {
+            Log.e(TAG, "Not enough contents in nanoapp header");
+            return;
+        }
+
+        if (mMagic != EXPECTED_MAGIC_VALUE) {
+            Log.e(TAG, "Unexpected magic value " + String.format("0x%08X", mMagic)
+                    + "while parsing header (expected "
+                    + String.format("0x%08X", EXPECTED_MAGIC_VALUE) + ")");
+        } else {
+            mHasValidHeader = true;
+        }
+    }
+
+    /**
+     * @return the app binary byte array
+     */
+    public byte[] getNanoAppBinary() {
+        return mNanoAppBinary;
+    }
+
+    /**
+     * @return {@code true} if the header is valid, {@code false} otherwise
+     */
+    public boolean hasValidHeader() {
+        return mHasValidHeader;
+    }
+
+    /**
+     * @return the header version
+     */
+    public int getHeaderVersion() {
+        return mHeaderVersion;
+    }
+
+    /**
+     * @return the app ID parsed from the nanoapp header
+     */
+    public long getNanoAppId() {
+        return mNanoAppId;
+    }
+
+    /**
+     * @return the app version parsed from the nanoapp header
+     */
+    public int getNanoAppVersion() {
+        return mNanoAppVersion;
+    }
+
+    /**
+     * @return the compile target hub type parsed from the nanoapp header
+     */
+    public long getHwHubType() {
+        return mHwHubType;
+    }
+
+    /**
+     * @return the target CHRE API major version parsed from the nanoapp header
+     */
+    public byte getTargetChreApiMajorVersion() {
+        return mTargetChreApiMajorVersion;
+    }
+
+    /**
+     * @return the target CHRE API minor version parsed from the nanoapp header
+     */
+    public byte getTargetChreApiMinorVersion() {
+        return mTargetChreApiMinorVersion;
+    }
+
+    private NanoAppBinary(Parcel in) {
+        int binaryLength = in.readInt();
+        mNanoAppBinary = new byte[binaryLength];
+        in.readByteArray(mNanoAppBinary);
+
+        parseBinaryHeader();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mNanoAppBinary.length);
+        out.writeByteArray(mNanoAppBinary);
+    }
+
+    public static final Creator<NanoAppBinary> CREATOR =
+            new Creator<NanoAppBinary>() {
+                @Override
+                public NanoAppBinary createFromParcel(Parcel in) {
+                    return new NanoAppBinary(in);
+                }
+
+                @Override
+                public NanoAppBinary[] newArray(int size) {
+                    return new NanoAppBinary[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/location/NanoAppMessage.aidl b/core/java/android/hardware/location/NanoAppMessage.aidl
new file mode 100644
index 0000000..f1f4ff6
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppMessage.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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.hardware.location;
+
+/**
+ * @hide
+ */
+parcelable NanoAppMessage;
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
new file mode 100644
index 0000000..2028674
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing messages send to or from nanoapps through the Context Hub Service.
+ *
+ * The basis of the class is in the IContextHub.hal ContextHubMsg definition.
+ *
+ * @hide
+ */
+public final class NanoAppMessage implements Parcelable {
+    private long mNanoAppId;
+    private int mMessageType;
+    private byte[] mMessageBody;
+    private boolean mIsBroadcasted;
+
+    private NanoAppMessage(
+            long nanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+        mNanoAppId = nanoAppId;
+        mMessageType = messageType;
+        mMessageBody = messageBody;
+        mIsBroadcasted = broadcasted;
+    }
+
+    /**
+     * Creates a NanoAppMessage object to send to a nanoapp.
+     *
+     * This factory method can be used to generate a NanoAppMessage object to be used in
+     * the ContextHubClient.sendMessageToNanoApp API.
+     *
+     * @param targetNanoAppId the ID of the nanoapp to send the message to
+     * @param messageType the nanoapp-dependent message type
+     * @param messageBody the byte array message contents
+     *
+     * @return the NanoAppMessage object
+     */
+    public static NanoAppMessage createMessageToNanoApp(
+            long targetNanoAppId, int messageType, byte[] messageBody) {
+        return new NanoAppMessage(
+                targetNanoAppId, messageType, messageBody, false /* broadcasted */);
+    }
+
+    /**
+     * Creates a NanoAppMessage object sent from a nanoapp.
+     *
+     * This factory method is intended only to be used by the Context Hub Service when delivering
+     * messages from a nanoapp to clients.
+     *
+     * @param sourceNanoAppId the ID of the nanoapp that the message was sent from
+     * @param messageType the nanoapp-dependent message type
+     * @param messageBody the byte array message contents
+     * @param broadcasted {@code true} if the message was broadcasted, {@code false} otherwise
+     *
+     * @return the NanoAppMessage object
+     */
+    public static NanoAppMessage createMessageFromNanoApp(
+            long sourceNanoAppId, int messageType, byte[] messageBody, boolean broadcasted) {
+        return new NanoAppMessage(sourceNanoAppId, messageType, messageBody, broadcasted);
+    }
+
+    /**
+     * @return the ID of the source or destination nanoapp
+     */
+    public long getNanoAppId() {
+        return mNanoAppId;
+    }
+
+    /**
+     * @return the type of the message that is nanoapp-dependent
+     */
+    public int getMessageType() {
+        return mMessageType;
+    }
+
+    /**
+     * @return the byte array contents of the message
+     */
+    public byte[] getMessageBody() {
+        return mMessageBody;
+    }
+
+    /**
+     * @return {@code true} if the message is broadcasted, {@code false} otherwise
+     */
+    public boolean isBroadcastMessage() {
+        return mIsBroadcasted;
+    }
+
+    private NanoAppMessage(Parcel in) {
+        mNanoAppId = in.readLong();
+        mIsBroadcasted = (in.readInt() == 1);
+        mMessageType = in.readInt();
+
+        int msgSize = in.readInt();
+        mMessageBody = new byte[msgSize];
+        in.readByteArray(mMessageBody);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mNanoAppId);
+        out.writeInt(mIsBroadcasted ? 1 : 0);
+        out.writeInt(mMessageType);
+
+        out.writeInt(mMessageBody.length);
+        out.writeByteArray(mMessageBody);
+    }
+
+    public static final Creator<NanoAppMessage> CREATOR =
+            new Creator<NanoAppMessage>() {
+                @Override
+                public NanoAppMessage createFromParcel(Parcel in) {
+                    return new NanoAppMessage(in);
+                }
+
+                @Override
+                public NanoAppMessage[] newArray(int size) {
+                    return new NanoAppMessage[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/location/NanoAppState.aidl b/core/java/android/hardware/location/NanoAppState.aidl
new file mode 100644
index 0000000..f80f435
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppState.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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.hardware.location;
+
+/**
+ * @hide
+ */
+parcelable NanoAppState;
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
new file mode 100644
index 0000000..644031b
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 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.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class describing the nanoapp state information resulting from a query to a Context Hub.
+ *
+ * @hide
+ */
+public final class NanoAppState implements Parcelable {
+    private long mNanoAppId;
+    private int mNanoAppVersion;
+    private boolean mIsEnabled;
+
+    public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
+        mNanoAppId = nanoAppId;
+        mNanoAppVersion = appVersion;
+        mIsEnabled = enabled;
+    }
+
+    /**
+     * @return the NanoAppInfo for this app
+     */
+    public long getNanoAppId() {
+        return mNanoAppId;
+    }
+
+    /**
+     * @return the app version
+     */
+    public long getNanoAppVersion() {
+        return mNanoAppVersion;
+    }
+
+    /**
+     * @return {@code true} if the app is enabled at the Context Hub, {@code false} otherwise
+     */
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    private NanoAppState(Parcel in) {
+        mNanoAppId = in.readLong();
+        mNanoAppVersion = in.readInt();
+        mIsEnabled = (in.readInt() == 1);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mNanoAppId);
+        out.writeInt(mNanoAppVersion);
+        out.writeInt(mIsEnabled ? 1 : 0);
+    }
+
+    public static final Creator<NanoAppState> CREATOR =
+            new Creator<NanoAppState>() {
+                @Override
+                public NanoAppState createFromParcel(Parcel in) {
+                    return new NanoAppState(in);
+                }
+
+                @Override
+                public NanoAppState[] newArray(int size) {
+                    return new NanoAppState[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 595d857..6ce9669 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -102,7 +102,7 @@
             "android.hardware.usb.action.USB_PORT_CHANGED";
 
    /**
-     * Broadcast Action:  A broadcast for USB device attached event.
+     * Activity intent sent when user attaches a USB device.
      *
      * This intent is sent when a USB device is attached to the USB bus when in host mode.
      * <ul>
@@ -128,9 +128,8 @@
             "android.hardware.usb.action.USB_DEVICE_DETACHED";
 
    /**
-     * Broadcast Action:  A broadcast for USB accessory attached event.
+     * Activity intent sent when user attaches a USB accessory.
      *
-     * This intent is sent when a USB accessory is attached.
      * <ul>
      * <li> {@link #EXTRA_ACCESSORY} containing the {@link android.hardware.usb.UsbAccessory}
      * for the attached accessory
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 29177b6..185215a 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -16,6 +16,7 @@
 
 package android.inputmethodservice;
 
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.app.Service;
 import android.content.Intent;
@@ -62,6 +63,7 @@
          * back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
          * AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
          */
+        @MainThread
         public void createSession(SessionCallback callback) {
             callback.sessionCreated(onCreateInputMethodSessionInterface());
         }
@@ -71,6 +73,7 @@
          * {@link AbstractInputMethodSessionImpl#revokeSelf()
          * AbstractInputMethodSessionImpl.setEnabled()} method.
          */
+        @MainThread
         public void setSessionEnabled(InputMethodSession session, boolean enabled) {
             ((AbstractInputMethodSessionImpl)session).setEnabled(enabled);
         }
@@ -80,6 +83,7 @@
          * {@link AbstractInputMethodSessionImpl#revokeSelf()
          * AbstractInputMethodSessionImpl.revokeSelf()} method.
          */
+        @MainThread
         public void revokeSession(InputMethodSession session) {
             ((AbstractInputMethodSessionImpl)session).revokeSelf();
         }
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 765aff9..2c7e51a 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -16,14 +16,8 @@
 
 package android.inputmethodservice;
 
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethod;
-import com.android.internal.view.IInputMethodSession;
-import com.android.internal.view.IInputSessionCallback;
-import com.android.internal.view.InputConnectionWrapper;
-
+import android.annotation.BinderThread;
+import android.annotation.MainThread;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -41,11 +35,20 @@
 import android.view.inputmethod.InputMethodSession;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.InputConnectionWrapper;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Implements the internal IInputMethod interface to convert incoming calls
@@ -67,17 +70,27 @@
     private static final int DO_SHOW_SOFT_INPUT = 60;
     private static final int DO_HIDE_SOFT_INPUT = 70;
     private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
-   
+
     final WeakReference<AbstractInputMethodService> mTarget;
     final Context mContext;
     final HandlerCaller mCaller;
     final WeakReference<InputMethod> mInputMethod;
     final int mTargetSdkVersion;
-    
-    static class Notifier {
-        boolean notified;
-    }
-    
+
+    /**
+     * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()}
+     * so that {@link InputConnectionWrapper} can query if {@link #unbindInput()} has already been
+     * called or not, mainly to avoid unnecessary blocking operations.
+     *
+     * <p>This field must be set and cleared only from the binder thread(s), where the system
+     * guarantees that {@link #bindInput(InputBinding)},
+     * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+     * {@link #unbindInput()} are called with the same order as the original calls
+     * in {@link com.android.server.InputMethodManagerService}.  See {@link IBinder#FLAG_ONEWAY}
+     * for detailed semantics.</p>
+     */
+    AtomicBoolean mIsUnbindIssued = null;
+
     // NOTE: we should have a cache of these.
     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
         final Context mContext;
@@ -108,20 +121,16 @@
             }
         }
     }
-    
-    public IInputMethodWrapper(AbstractInputMethodService context,
-            InputMethod inputMethod) {
-        mTarget = new WeakReference<AbstractInputMethodService>(context);
+
+    public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) {
+        mTarget = new WeakReference<>(context);
         mContext = context.getApplicationContext();
         mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
-        mInputMethod = new WeakReference<InputMethod>(inputMethod);
+        mInputMethod = new WeakReference<>(inputMethod);
         mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
     }
 
-    public InputMethod getInternalInputMethod() {
-        return mInputMethod.get();
-    }
-
+    @MainThread
     @Override
     public void executeMessage(Message msg) {
         InputMethod inputMethod = mInputMethod.get();
@@ -169,8 +178,10 @@
                 final IBinder startInputToken = (IBinder) args.arg1;
                 final IInputContext inputContext = (IInputContext) args.arg2;
                 final EditorInfo info = (EditorInfo) args.arg3;
+                final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
                 final InputConnection ic = inputContext != null
-                        ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
+                        ? new InputConnectionWrapper(
+                                mTarget, inputContext, missingMethods, isUnbindIssued) : null;
                 info.makeCompatible(mTargetSdkVersion);
                 inputMethod.dispatchStartInputWithToken(ic, info, restarting /* restarting */,
                         startInputToken);
@@ -205,6 +216,7 @@
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
 
+    @BinderThread
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         AbstractInputMethodService target = mTarget.get();
@@ -232,40 +244,63 @@
         }
     }
 
+    @BinderThread
     @Override
     public void attachToken(IBinder token) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
     }
 
+    @BinderThread
     @Override
     public void bindInput(InputBinding binding) {
+        if (mIsUnbindIssued != null) {
+            Log.e(TAG, "bindInput must be paired with unbindInput.");
+        }
+        mIsUnbindIssued = new AtomicBoolean();
         // This IInputContext is guaranteed to implement all the methods.
         final int missingMethodFlags = 0;
         InputConnection ic = new InputConnectionWrapper(mTarget,
-                IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
+                IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
+                mIsUnbindIssued);
         InputBinding nu = new InputBinding(ic, binding);
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
     }
 
+    @BinderThread
     @Override
     public void unbindInput() {
+        if (mIsUnbindIssued != null) {
+            // Signal the flag then forget it.
+            mIsUnbindIssued.set(true);
+            mIsUnbindIssued = null;
+        } else {
+            Log.e(TAG, "unbindInput must be paired with bindInput.");
+        }
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
     }
 
+    @BinderThread
     @Override
     public void startInput(IBinder startInputToken, IInputContext inputContext,
             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
             EditorInfo attribute, boolean restarting) {
-        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT,
-                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute));
+        if (mIsUnbindIssued == null) {
+            Log.e(TAG, "startInput must be called after bindInput.");
+            mIsUnbindIssued = new AtomicBoolean();
+        }
+        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOOO(DO_START_INPUT,
+                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute,
+                mIsUnbindIssued));
     }
 
+    @BinderThread
     @Override
     public void createSession(InputChannel channel, IInputSessionCallback callback) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
                 channel, callback));
     }
 
+    @BinderThread
     @Override
     public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
         try {
@@ -282,6 +317,7 @@
         }
     }
 
+    @BinderThread
     @Override
     public void revokeSession(IInputMethodSession session) {
         try {
@@ -297,18 +333,21 @@
         }
     }
 
+    @BinderThread
     @Override
     public void showSoftInput(int flags, ResultReceiver resultReceiver) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
                 flags, resultReceiver));
     }
 
+    @BinderThread
     @Override
     public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
                 flags, resultReceiver));
     }
 
+    @BinderThread
     @Override
     public void changeInputMethodSubtype(InputMethodSubtype subtype) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7a20943..223ed73 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -382,8 +382,10 @@
      */
     public class InputMethodImpl extends AbstractInputMethodImpl {
         /**
-         * Take care of attaching the given window token provided by the system.
+         * {@inheritDoc}
          */
+        @MainThread
+        @Override
         public void attachToken(IBinder token) {
             if (mToken == null) {
                 mToken = token;
@@ -392,10 +394,12 @@
         }
         
         /**
-         * Handle a new input binding, calling
-         * {@link InputMethodService#onBindInput InputMethodService.onBindInput()}
-         * when done.
+         * {@inheritDoc}
+         *
+         * <p>Calls {@link InputMethodService#onBindInput()} when done.</p>
          */
+        @MainThread
+        @Override
         public void bindInput(InputBinding binding) {
             mInputBinding = binding;
             mInputConnection = binding.getConnection();
@@ -409,8 +413,12 @@
         }
 
         /**
-         * Clear the current input binding.
+         * {@inheritDoc}
+         *
+         * <p>Calls {@link InputMethodService#onUnbindInput()} when done.</p>
          */
+        @MainThread
+        @Override
         public void unbindInput() {
             if (DEBUG) Log.v(TAG, "unbindInput(): binding=" + mInputBinding
                     + " ic=" + mInputConnection);
@@ -419,11 +427,21 @@
             mInputConnection = null;
         }
 
+        /**
+         * {@inheritDoc}
+         */
+        @MainThread
+        @Override
         public void startInput(InputConnection ic, EditorInfo attribute) {
             if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
             doStartInput(ic, attribute, false);
         }
 
+        /**
+         * {@inheritDoc}
+         */
+        @MainThread
+        @Override
         public void restartInput(InputConnection ic, EditorInfo attribute) {
             if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
             doStartInput(ic, attribute, true);
@@ -433,6 +451,7 @@
          * {@inheritDoc}
          * @hide
          */
+        @MainThread
         @Override
         public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                 @NonNull EditorInfo editorInfo, boolean restarting,
@@ -447,8 +466,10 @@
         }
 
         /**
-         * Handle a request by the system to hide the soft input area.
+         * {@inheritDoc}
          */
+        @MainThread
+        @Override
         public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "hideSoftInput()");
             boolean wasVis = isInputViewShown();
@@ -465,8 +486,10 @@
         }
 
         /**
-         * Handle a request by the system to show the soft input area.
+         * {@inheritDoc}
          */
+        @MainThread
+        @Override
         public void showSoftInput(int flags, ResultReceiver resultReceiver) {
             if (DEBUG) Log.v(TAG, "showSoftInput()");
             boolean wasVis = isInputViewShown();
@@ -495,6 +518,11 @@
             }
         }
 
+        /**
+         * {@inheritDoc}
+         */
+        @MainThread
+        @Override
         public void changeInputMethodSubtype(InputMethodSubtype subtype) {
             onCurrentInputMethodSubtypeChanged(subtype);
         }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 744ee8e..d7ecc81 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2078,16 +2078,30 @@
      * {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
      * due to device configuration.
      *
+     * <p>If this app does not have permission to use this API, it will always
+     * return false rather than throw an exception.</p>
+     *
+     * <p>If the device has a hotspot provisioning app, the caller is required to hold the
+     * {@link android.Manifest.permission.TETHER_PRIVILEGED} permission.</p>
+     *
+     * <p>Otherwise, this method requires the caller to hold the ability to modify system
+     * settings as determined by {@link android.provider.Settings.System#canWrite}.</p>
+     *
      * @return a boolean - {@code true} indicating Tethering is supported.
      *
      * {@hide}
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+    @RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
+            android.Manifest.permission.WRITE_SETTINGS})
     public boolean isTetheringSupported() {
+        String pkgName = mContext.getOpPackageName();
         try {
-            String pkgName = mContext.getOpPackageName();
             return mService.isTetheringSupported(pkgName);
+        } catch (SecurityException e) {
+            // This API is not available to this caller, but for backward-compatibility
+            // this will just return false instead of throwing.
+            return false;
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 5ae3400..16b1452 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -19,18 +19,18 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+
 import com.android.internal.util.HexDump;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 
 /**
  * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
  * RFC 4301.
- *
- * @hide
  */
 public final class IpSecAlgorithm implements Parcelable {
-
     /**
      * AES-CBC Encryption/Ciphering Algorithm.
      *
@@ -67,6 +67,7 @@
      * <p>Valid truncation lengths are multiples of 8 bits from 192 to (default) 384.
      */
     public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
+
     /**
      * SHA512 HMAC Authentication/Integrity Algorithm
      *
@@ -74,13 +75,23 @@
      */
     public static final String AUTH_HMAC_SHA512 = "hmac(sha512)";
 
+    /**
+     * AES-GCM Authentication/Integrity + Encryption/Ciphering Algorithm.
+     *
+     * <p>Valid lengths for this key are {128, 192, 256}.
+     *
+     * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
+     */
+    public static final String AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))";
+
     /** @hide */
     @StringDef({
         CRYPT_AES_CBC,
         AUTH_HMAC_MD5,
         AUTH_HMAC_SHA1,
         AUTH_HMAC_SHA256,
-        AUTH_HMAC_SHA512
+        AUTH_HMAC_SHA512,
+        AUTH_CRYPT_AES_GCM
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AlgorithmName {}
@@ -107,7 +118,7 @@
      * @param algoName precise name of the algorithm to be used.
      * @param key non-null Key padded to a multiple of 8 bits.
      * @param truncLenBits the number of bits of output hash to use; only meaningful for
-     *     Authentication.
+     *     Authentication or Authenticated Encryption (equivalent to ICV length).
      */
     public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
         if (!isTruncationLengthValid(algoName, truncLenBits)) {
@@ -180,6 +191,8 @@
                 return (truncLenBits >= 192 && truncLenBits <= 384);
             case AUTH_HMAC_SHA512:
                 return (truncLenBits >= 256 && truncLenBits <= 512);
+            case AUTH_CRYPT_AES_GCM:
+                return (truncLenBits == 64 || truncLenBits == 96 || truncLenBits == 128);
             default:
                 return false;
         }
@@ -197,4 +210,12 @@
                 .append("}")
                 .toString();
     }
+
+    /** package */
+    static boolean equals(IpSecAlgorithm lhs, IpSecAlgorithm rhs) {
+        if (lhs == null || rhs == null) return (lhs == rhs);
+        return (lhs.mName.equals(rhs.mName)
+                && Arrays.equals(lhs.mKey, rhs.mKey)
+                && lhs.mTruncLenBits == rhs.mTruncLenBits);
+    }
 };
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 5a5c740..61b13a9 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -17,105 +17,184 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
+
+import com.android.internal.annotations.VisibleForTesting;
 
 /** @hide */
 public final class IpSecConfig implements Parcelable {
     private static final String TAG = "IpSecConfig";
 
-    //MODE_TRANSPORT or MODE_TUNNEL
-    int mode;
+    // MODE_TRANSPORT or MODE_TUNNEL
+    private int mMode = IpSecTransform.MODE_TRANSPORT;
 
-    // For tunnel mode
-    InetAddress localAddress;
+    // Needs to be valid only for tunnel mode
+    // Preventing this from being null simplifies Java->Native binder
+    private String mLocalAddress = "";
 
-    InetAddress remoteAddress;
+    // Preventing this from being null simplifies Java->Native binder
+    private String mRemoteAddress = "";
 
-    // Limit selection by network interface
-    Network network;
+    // The underlying Network that represents the "gateway" Network
+    // for outbound packets. It may also be used to select packets.
+    private Network mNetwork;
 
     public static class Flow {
         // Minimum requirements for identifying a transform
         // SPI identifying the IPsec flow in packet processing
         // and a remote IP address
-        int spiResourceId;
+        private int mSpiResourceId = IpSecManager.INVALID_RESOURCE_ID;
 
         // Encryption Algorithm
-        IpSecAlgorithm encryption;
+        private IpSecAlgorithm mEncryption;
 
         // Authentication Algorithm
-        IpSecAlgorithm authentication;
+        private IpSecAlgorithm mAuthentication;
+
+        // Authenticated Encryption Algorithm
+        private IpSecAlgorithm mAuthenticatedEncryption;
 
         @Override
         public String toString() {
             return new StringBuilder()
-                    .append("{spiResourceId=")
-                    .append(spiResourceId)
-                    .append(", encryption=")
-                    .append(encryption)
-                    .append(", authentication=")
-                    .append(authentication)
+                    .append("{mSpiResourceId=")
+                    .append(mSpiResourceId)
+                    .append(", mEncryption=")
+                    .append(mEncryption)
+                    .append(", mAuthentication=")
+                    .append(mAuthentication)
+                    .append(", mAuthenticatedEncryption=")
+                    .append(mAuthenticatedEncryption)
                     .append("}")
                     .toString();
         }
+
+        static boolean equals(IpSecConfig.Flow lhs, IpSecConfig.Flow rhs) {
+            if (lhs == null || rhs == null) return (lhs == rhs);
+            return (lhs.mSpiResourceId == rhs.mSpiResourceId
+                    && IpSecAlgorithm.equals(lhs.mEncryption, rhs.mEncryption)
+                    && IpSecAlgorithm.equals(lhs.mAuthentication, rhs.mAuthentication));
+        }
     }
 
-    final Flow[] flow = new Flow[] {new Flow(), new Flow()};
+    private final Flow[] mFlow = new Flow[] {new Flow(), new Flow()};
 
     // For tunnel mode IPv4 UDP Encapsulation
     // IpSecTransform#ENCAP_ESP_*, such as ENCAP_ESP_OVER_UDP_IKE
-    int encapType;
-    int encapLocalPortResourceId;
-    int encapRemotePort;
+    private int mEncapType = IpSecTransform.ENCAP_NONE;
+    private int mEncapSocketResourceId = IpSecManager.INVALID_RESOURCE_ID;
+    private int mEncapRemotePort;
 
     // An interval, in seconds between the NattKeepalive packets
-    int nattKeepaliveInterval;
+    private int mNattKeepaliveInterval;
+
+    /** Set the mode for this IPsec transform */
+    public void setMode(int mode) {
+        mMode = mode;
+    }
+
+    /** Set the local IP address for Tunnel mode */
+    public void setLocalAddress(String localAddress) {
+        if (localAddress == null) {
+            throw new IllegalArgumentException("localAddress may not be null!");
+        }
+        mLocalAddress = localAddress;
+    }
+
+    /** Set the remote IP address for this IPsec transform */
+    public void setRemoteAddress(String remoteAddress) {
+        if (remoteAddress == null) {
+            throw new IllegalArgumentException("remoteAddress may not be null!");
+        }
+        mRemoteAddress = remoteAddress;
+    }
+
+    /** Set the SPI for a given direction by resource ID */
+    public void setSpiResourceId(int direction, int resourceId) {
+        mFlow[direction].mSpiResourceId = resourceId;
+    }
+
+    /** Set the encryption algorithm for a given direction */
+    public void setEncryption(int direction, IpSecAlgorithm encryption) {
+        mFlow[direction].mEncryption = encryption;
+    }
+
+    /** Set the authentication algorithm for a given direction */
+    public void setAuthentication(int direction, IpSecAlgorithm authentication) {
+        mFlow[direction].mAuthentication = authentication;
+    }
+
+    /** Set the authenticated encryption algorithm for a given direction */
+    public void setAuthenticatedEncryption(int direction, IpSecAlgorithm authenticatedEncryption) {
+        mFlow[direction].mAuthenticatedEncryption = authenticatedEncryption;
+    }
+
+    public void setNetwork(Network network) {
+        mNetwork = network;
+    }
+
+    public void setEncapType(int encapType) {
+        mEncapType = encapType;
+    }
+
+    public void setEncapSocketResourceId(int resourceId) {
+        mEncapSocketResourceId = resourceId;
+    }
+
+    public void setEncapRemotePort(int port) {
+        mEncapRemotePort = port;
+    }
+
+    public void setNattKeepaliveInterval(int interval) {
+        mNattKeepaliveInterval = interval;
+    }
 
     // Transport or Tunnel
     public int getMode() {
-        return mode;
+        return mMode;
     }
 
-    public InetAddress getLocalAddress() {
-        return localAddress;
+    public String getLocalAddress() {
+        return mLocalAddress;
     }
 
     public int getSpiResourceId(int direction) {
-        return flow[direction].spiResourceId;
+        return mFlow[direction].mSpiResourceId;
     }
 
-    public InetAddress getRemoteAddress() {
-        return remoteAddress;
+    public String getRemoteAddress() {
+        return mRemoteAddress;
     }
 
     public IpSecAlgorithm getEncryption(int direction) {
-        return flow[direction].encryption;
+        return mFlow[direction].mEncryption;
     }
 
     public IpSecAlgorithm getAuthentication(int direction) {
-        return flow[direction].authentication;
+        return mFlow[direction].mAuthentication;
+    }
+
+    public IpSecAlgorithm getAuthenticatedEncryption(int direction) {
+        return mFlow[direction].mAuthenticatedEncryption;
     }
 
     public Network getNetwork() {
-        return network;
+        return mNetwork;
     }
 
     public int getEncapType() {
-        return encapType;
+        return mEncapType;
     }
 
-    public int getEncapLocalResourceId() {
-        return encapLocalPortResourceId;
+    public int getEncapSocketResourceId() {
+        return mEncapSocketResourceId;
     }
 
     public int getEncapRemotePort() {
-        return encapRemotePort;
+        return mEncapRemotePort;
     }
 
     public int getNattKeepaliveInterval() {
-        return nattKeepaliveInterval;
+        return mNattKeepaliveInterval;
     }
 
     // Parcelable Methods
@@ -127,82 +206,76 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        // TODO: Use a byte array or other better method for storing IPs that can also include scope
-        out.writeString((localAddress != null) ? localAddress.getHostAddress() : null);
-        // TODO: Use a byte array or other better method for storing IPs that can also include scope
-        out.writeString((remoteAddress != null) ? remoteAddress.getHostAddress() : null);
-        out.writeParcelable(network, flags);
-        out.writeInt(flow[IpSecTransform.DIRECTION_IN].spiResourceId);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].encryption, flags);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_IN].authentication, flags);
-        out.writeInt(flow[IpSecTransform.DIRECTION_OUT].spiResourceId);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].encryption, flags);
-        out.writeParcelable(flow[IpSecTransform.DIRECTION_OUT].authentication, flags);
-        out.writeInt(encapType);
-        out.writeInt(encapLocalPortResourceId);
-        out.writeInt(encapRemotePort);
+        out.writeInt(mMode);
+        out.writeString(mLocalAddress);
+        out.writeString(mRemoteAddress);
+        out.writeParcelable(mNetwork, flags);
+        out.writeInt(mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mEncryption, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthentication, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption, flags);
+        out.writeInt(mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mEncryption, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication, flags);
+        out.writeParcelable(mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption, flags);
+        out.writeInt(mEncapType);
+        out.writeInt(mEncapSocketResourceId);
+        out.writeInt(mEncapRemotePort);
+        out.writeInt(mNattKeepaliveInterval);
     }
 
-    // Package Private: Used by the IpSecTransform.Builder;
-    // there should be no public constructor for this object
-    IpSecConfig() {}
-
-    private static InetAddress readInetAddressFromParcel(Parcel in) {
-        String addrString = in.readString();
-        if (addrString == null) {
-            return null;
-        }
-        try {
-            return InetAddress.getByName(addrString);
-        } catch (UnknownHostException e) {
-            Log.wtf(TAG, "Invalid IpAddress " + addrString);
-            return null;
-        }
-    }
+    @VisibleForTesting
+    public IpSecConfig() {}
 
     private IpSecConfig(Parcel in) {
-        localAddress = readInetAddressFromParcel(in);
-        remoteAddress = readInetAddressFromParcel(in);
-        network = (Network) in.readParcelable(Network.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_IN].spiResourceId = in.readInt();
-        flow[IpSecTransform.DIRECTION_IN].encryption =
+        mMode = in.readInt();
+        mLocalAddress = in.readString();
+        mRemoteAddress = in.readString();
+        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+        mFlow[IpSecTransform.DIRECTION_IN].mSpiResourceId = in.readInt();
+        mFlow[IpSecTransform.DIRECTION_IN].mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_IN].authentication =
+        mFlow[IpSecTransform.DIRECTION_IN].mAuthentication =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_OUT].spiResourceId = in.readInt();
-        flow[IpSecTransform.DIRECTION_OUT].encryption =
+        mFlow[IpSecTransform.DIRECTION_IN].mAuthenticatedEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        flow[IpSecTransform.DIRECTION_OUT].authentication =
+        mFlow[IpSecTransform.DIRECTION_OUT].mSpiResourceId = in.readInt();
+        mFlow[IpSecTransform.DIRECTION_OUT].mEncryption =
                 (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
-        encapType = in.readInt();
-        encapLocalPortResourceId = in.readInt();
-        encapRemotePort = in.readInt();
+        mFlow[IpSecTransform.DIRECTION_OUT].mAuthentication =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mFlow[IpSecTransform.DIRECTION_OUT].mAuthenticatedEncryption =
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+        mEncapType = in.readInt();
+        mEncapSocketResourceId = in.readInt();
+        mEncapRemotePort = in.readInt();
+        mNattKeepaliveInterval = in.readInt();
     }
 
     @Override
     public String toString() {
         StringBuilder strBuilder = new StringBuilder();
         strBuilder
-                .append("{mode=")
-                .append(mode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
-                .append(", localAddress=")
-                .append(localAddress)
-                .append(", remoteAddress=")
-                .append(remoteAddress)
-                .append(", network=")
-                .append(network)
-                .append(", encapType=")
-                .append(encapType)
-                .append(", encapLocalPortResourceId=")
-                .append(encapLocalPortResourceId)
-                .append(", encapRemotePort=")
-                .append(encapRemotePort)
-                .append(", nattKeepaliveInterval=")
-                .append(nattKeepaliveInterval)
-                .append(", flow[OUT]=")
-                .append(flow[IpSecTransform.DIRECTION_OUT])
-                .append(", flow[IN]=")
-                .append(flow[IpSecTransform.DIRECTION_IN])
+                .append("{mMode=")
+                .append(mMode == IpSecTransform.MODE_TUNNEL ? "TUNNEL" : "TRANSPORT")
+                .append(", mLocalAddress=")
+                .append(mLocalAddress)
+                .append(", mRemoteAddress=")
+                .append(mRemoteAddress)
+                .append(", mNetwork=")
+                .append(mNetwork)
+                .append(", mEncapType=")
+                .append(mEncapType)
+                .append(", mEncapSocketResourceId=")
+                .append(mEncapSocketResourceId)
+                .append(", mEncapRemotePort=")
+                .append(mEncapRemotePort)
+                .append(", mNattKeepaliveInterval=")
+                .append(mNattKeepaliveInterval)
+                .append(", mFlow[OUT]=")
+                .append(mFlow[IpSecTransform.DIRECTION_OUT])
+                .append(", mFlow[IN]=")
+                .append(mFlow[IpSecTransform.DIRECTION_IN])
                 .append("}");
 
         return strBuilder.toString();
@@ -218,4 +291,23 @@
                     return new IpSecConfig[size];
                 }
             };
+
+    @VisibleForTesting
+    /** Equals method used for testing */
+    public static boolean equals(IpSecConfig lhs, IpSecConfig rhs) {
+        if (lhs == null || rhs == null) return (lhs == rhs);
+        return (lhs.mMode == rhs.mMode
+                && lhs.mLocalAddress.equals(rhs.mLocalAddress)
+                && lhs.mRemoteAddress.equals(rhs.mRemoteAddress)
+                && ((lhs.mNetwork != null && lhs.mNetwork.equals(rhs.mNetwork))
+                        || (lhs.mNetwork == rhs.mNetwork))
+                && lhs.mEncapType == rhs.mEncapType
+                && lhs.mEncapSocketResourceId == rhs.mEncapSocketResourceId
+                && lhs.mEncapRemotePort == rhs.mEncapRemotePort
+                && lhs.mNattKeepaliveInterval == rhs.mNattKeepaliveInterval
+                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_OUT],
+                        rhs.mFlow[IpSecTransform.DIRECTION_OUT])
+                && IpSecConfig.Flow.equals(lhs.mFlow[IpSecTransform.DIRECTION_IN],
+                        rhs.mFlow[IpSecTransform.DIRECTION_IN]));
+    }
 }
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 2f791e1..d7b3256 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -25,7 +25,11 @@
 import android.os.RemoteException;
 import android.util.AndroidException;
 import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
 import dalvik.system.CloseGuard;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.DatagramSocket;
@@ -36,7 +40,9 @@
  * This class contains methods for managing IPsec sessions, which will perform kernel-space
  * encryption and decryption of socket or Network traffic.
  *
- * @hide
+ * <p>An IpSecManager may be obtained by calling {@link
+ * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
+ * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
  */
 @SystemService(Context.IPSEC_SERVICE)
 public final class IpSecManager {
@@ -184,7 +190,8 @@
         }
 
         /** @hide */
-        int getResourceId() {
+        @VisibleForTesting
+        public int getResourceId() {
             return mResourceId;
         }
     }
@@ -485,7 +492,8 @@
         }
 
         /** @hide */
-        int getResourceId() {
+        @VisibleForTesting
+        public int getResourceId() {
             return mResourceId;
         }
     };
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index cfbac58b..48b5bd5 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -26,9 +26,12 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+
 import dalvik.system.CloseGuard;
+
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -43,8 +46,6 @@
  *
  * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
  * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
- *
- * @hide
  */
 public final class IpSecTransform implements AutoCloseable {
     private static final String TAG = "IpSecTransform";
@@ -67,10 +68,10 @@
     public @interface TransformDirection {}
 
     /** @hide */
-    public static final int MODE_TUNNEL = 0;
+    public static final int MODE_TRANSPORT = 0;
 
     /** @hide */
-    public static final int MODE_TRANSPORT = 1;
+    public static final int MODE_TUNNEL = 1;
 
     /** @hide */
     public static final int ENCAP_NONE = 0;
@@ -112,7 +113,11 @@
         return IIpSecService.Stub.asInterface(b);
     }
 
-    private void checkResultStatusAndThrow(int status)
+    /**
+     * Checks the result status and throws an appropriate exception if
+     * the status is not Status.OK.
+     */
+    private void checkResultStatus(int status)
             throws IOException, IpSecManager.ResourceUnavailableException,
                     IpSecManager.SpiUnavailableException {
         switch (status) {
@@ -140,7 +145,7 @@
                 IpSecTransformResponse result =
                         svc.createTransportModeTransform(mConfig, new Binder());
                 int status = result.status;
-                checkResultStatusAndThrow(status);
+                checkResultStatus(status);
                 mResourceId = result.resourceId;
 
                 /* Keepalive will silently fail if not needed by the config; but, if needed and
@@ -242,61 +247,20 @@
 
     /* Package */
     void startKeepalive(Context c) {
-        // FIXME: NO_KEEPALIVE needs to be a constant
-        if (mConfig.getNattKeepaliveInterval() == 0) {
-            return;
-        }
-
-        ConnectivityManager cm =
-                (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
-
-        if (mKeepalive != null) {
-            Log.wtf(TAG, "Keepalive already started for this IpSecTransform.");
-            return;
-        }
-
-        synchronized (mKeepaliveSyncLock) {
-            mKeepalive =
-                    cm.startNattKeepalive(
-                            mConfig.getNetwork(),
-                            mConfig.getNattKeepaliveInterval(),
-                            mKeepaliveCallback,
-                            mConfig.getLocalAddress(),
-                            0x1234, /* FIXME: get the real port number again,
-                                    which we need to retrieve from the provided
-                                    EncapsulationSocket, and which isn't currently
-                                    stashed in IpSecConfig */
-                            mConfig.getRemoteAddress());
-            try {
-                // FIXME: this is still a horrible way to fudge the synchronous callback
-                mKeepaliveSyncLock.wait(2000);
-            } catch (InterruptedException e) {
-            }
-        }
-        if (mKeepaliveStatus != ConnectivityManager.PacketKeepalive.SUCCESS) {
-            throw new UnsupportedOperationException("Packet Keepalive cannot be started");
+        if (mConfig.getNattKeepaliveInterval() != 0) {
+            Log.wtf(TAG, "Keepalive not yet supported.");
         }
     }
 
-    /* Package */
-    int getResourceId() {
+    /** @hide */
+    @VisibleForTesting
+    public int getResourceId() {
         return mResourceId;
     }
 
     /* Package */
     void stopKeepalive() {
-        if (mKeepalive == null) {
-            return;
-        }
-        mKeepalive.stop();
-        synchronized (mKeepaliveSyncLock) {
-            if (mKeepaliveStatus == ConnectivityManager.PacketKeepalive.SUCCESS) {
-                try {
-                    mKeepaliveSyncLock.wait(2000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
+        return;
     }
 
     /**
@@ -317,12 +281,14 @@
          * <p>If encryption is set for a given direction without also providing an SPI for that
          * direction, creation of an IpSecTransform will fail upon calling a build() method.
          *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
          */
         public IpSecTransform.Builder setEncryption(
                 @TransformDirection int direction, IpSecAlgorithm algo) {
-            mConfig.flow[direction].encryption = algo;
+            mConfig.setEncryption(direction, algo);
             return this;
         }
 
@@ -332,12 +298,37 @@
          * <p>If authentication is set for a given direction without also providing an SPI for that
          * direction, creation of an IpSecTransform will fail upon calling a build() method.
          *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
          * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
          */
         public IpSecTransform.Builder setAuthentication(
                 @TransformDirection int direction, IpSecAlgorithm algo) {
-            mConfig.flow[direction].authentication = algo;
+            mConfig.setAuthentication(direction, algo);
+            return this;
+        }
+
+        /**
+         * Add an authenticated encryption algorithm to the transform for the given direction.
+         *
+         * <p>If an authenticated encryption algorithm is set for a given direction without also
+         * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
+         * a build() method.
+         *
+         * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
+         * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
+         * referred to in RFC 4301)
+         *
+         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         *
+         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
+         *     be applied.
+         */
+        public IpSecTransform.Builder setAuthenticatedEncryption(
+                @TransformDirection int direction, IpSecAlgorithm algo) {
+            mConfig.setAuthenticatedEncryption(direction, algo);
             return this;
         }
 
@@ -360,9 +351,7 @@
          */
         public IpSecTransform.Builder setSpi(
                 @TransformDirection int direction, IpSecManager.SecurityParameterIndex spi) {
-            // TODO: convert to using the resource Id of the SPI. Then build() can validate
-            // the owner in the IpSecService
-            mConfig.flow[direction].spiResourceId = spi.getResourceId();
+            mConfig.setSpiResourceId(direction, spi.getResourceId());
             return this;
         }
 
@@ -377,7 +366,7 @@
          */
         @SystemApi
         public IpSecTransform.Builder setUnderlyingNetwork(Network net) {
-            mConfig.network = net;
+            mConfig.setNetwork(net);
             return this;
         }
 
@@ -394,10 +383,9 @@
          */
         public IpSecTransform.Builder setIpv4Encapsulation(
                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
-            // TODO: check encap type is valid.
-            mConfig.encapType = ENCAP_ESPINUDP;
-            mConfig.encapLocalPortResourceId = localSocket.getResourceId();
-            mConfig.encapRemotePort = remotePort;
+            mConfig.setEncapType(ENCAP_ESPINUDP);
+            mConfig.setEncapSocketResourceId(localSocket.getResourceId());
+            mConfig.setEncapRemotePort(remotePort);
             return this;
         }
 
@@ -415,7 +403,7 @@
          */
         @SystemApi
         public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
-            mConfig.nattKeepaliveInterval = intervalSeconds;
+            mConfig.setNattKeepaliveInterval(intervalSeconds);
             return this;
         }
 
@@ -448,10 +436,8 @@
         public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
                 throws IpSecManager.ResourceUnavailableException,
                         IpSecManager.SpiUnavailableException, IOException {
-            //FIXME: argument validation here
-            //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
-            mConfig.mode = MODE_TRANSPORT;
-            mConfig.remoteAddress = remoteAddress;
+            mConfig.setMode(MODE_TRANSPORT);
+            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
             return new IpSecTransform(mContext, mConfig).activate();
         }
 
@@ -472,9 +458,9 @@
                 InetAddress localAddress, InetAddress remoteAddress) {
             //FIXME: argument validation here
             //throw new IllegalArgumentException("Natt Keepalive requires UDP Encapsulation");
-            mConfig.localAddress = localAddress;
-            mConfig.remoteAddress = remoteAddress;
-            mConfig.mode = MODE_TUNNEL;
+            mConfig.setLocalAddress(localAddress.getHostAddress());
+            mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+            mConfig.setMode(MODE_TUNNEL);
             return new IpSecTransform(mContext, mConfig);
         }
 
@@ -488,14 +474,5 @@
             mContext = context;
             mConfig = new IpSecConfig();
         }
-
-        /**
-         * Return an {@link IpSecConfig} object for testing purposes.
-         * @hide
-         */
-        @VisibleForTesting
-        public IpSecConfig getIpSecConfig() {
-            return mConfig;
-        }
     }
 }
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 2c9fb23..4e474c8 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -683,9 +683,9 @@
      */
     public boolean hasIPv4Address() {
         for (LinkAddress address : mLinkAddresses) {
-          if (address.getAddress() instanceof Inet4Address) {
-            return true;
-          }
+            if (address.getAddress() instanceof Inet4Address) {
+                return true;
+            }
         }
         return false;
     }
@@ -725,9 +725,9 @@
      */
     public boolean hasIPv4DefaultRoute() {
         for (RouteInfo r : mRoutes) {
-          if (r.isIPv4Default()) {
-            return true;
-          }
+            if (r.isIPv4Default()) {
+                return true;
+            }
         }
         return false;
     }
@@ -740,9 +740,9 @@
      */
     public boolean hasIPv6DefaultRoute() {
         for (RouteInfo r : mRoutes) {
-          if (r.isIPv6Default()) {
-            return true;
-          }
+            if (r.isIPv6Default()) {
+                return true;
+            }
         }
         return false;
     }
@@ -755,9 +755,9 @@
      */
     public boolean hasIPv4DnsServer() {
         for (InetAddress ia : mDnses) {
-          if (ia instanceof Inet4Address) {
-            return true;
-          }
+            if (ia instanceof Inet4Address) {
+                return true;
+            }
         }
         return false;
     }
@@ -770,9 +770,9 @@
      */
     public boolean hasIPv6DnsServer() {
         for (InetAddress ia : mDnses) {
-          if (ia instanceof Inet6Address) {
-            return true;
-          }
+            if (ia instanceof Inet6Address) {
+                return true;
+            }
         }
         return false;
     }
diff --git a/core/java/android/net/LocalServerSocket.java b/core/java/android/net/LocalServerSocket.java
index 3fcde330..d1f49d2 100644
--- a/core/java/android/net/LocalServerSocket.java
+++ b/core/java/android/net/LocalServerSocket.java
@@ -16,14 +16,15 @@
 
 package android.net;
 
-import java.io.IOException;
+import java.io.Closeable;
 import java.io.FileDescriptor;
+import java.io.IOException;
 
 /**
  * Non-standard class for creating an inbound UNIX-domain socket
  * in the Linux abstract namespace.
  */
-public class LocalServerSocket {
+public class LocalServerSocket implements Closeable {
     private final LocalSocketImpl impl;
     private final LocalSocketAddress localAddress;
 
@@ -106,7 +107,7 @@
      * 
      * @throws IOException
      */
-    public void close() throws IOException
+    @Override public void close() throws IOException
     {
         impl.close();
     }
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 3c868c3..903b602 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,14 +16,14 @@
 
 package android.net;
 
-import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 
-import libcore.net.http.Dns;
-import libcore.net.http.HttpURLConnectionFactory;
+import com.android.okhttp.internalandroidapi.Dns;
+import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -34,11 +34,12 @@
 import java.net.Socket;
 import java.net.SocketAddress;
 import java.net.SocketException;
-import java.net.UnknownHostException;
 import java.net.URL;
 import java.net.URLConnection;
+import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
+
 import javax.net.SocketFactory;
 
 /**
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4bb8844..db12dd9 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,6 +24,8 @@
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -77,6 +80,31 @@
      */
     private long mNetworkCapabilities;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NET_CAPABILITY_" }, value = {
+            NET_CAPABILITY_MMS,
+            NET_CAPABILITY_SUPL,
+            NET_CAPABILITY_DUN,
+            NET_CAPABILITY_FOTA,
+            NET_CAPABILITY_IMS,
+            NET_CAPABILITY_CBS,
+            NET_CAPABILITY_WIFI_P2P,
+            NET_CAPABILITY_IA,
+            NET_CAPABILITY_RCS,
+            NET_CAPABILITY_XCAP,
+            NET_CAPABILITY_EIMS,
+            NET_CAPABILITY_NOT_METERED,
+            NET_CAPABILITY_INTERNET,
+            NET_CAPABILITY_NOT_RESTRICTED,
+            NET_CAPABILITY_TRUSTED,
+            NET_CAPABILITY_NOT_VPN,
+            NET_CAPABILITY_VALIDATED,
+            NET_CAPABILITY_CAPTIVE_PORTAL,
+            NET_CAPABILITY_FOREGROUND,
+    })
+    public @interface NetCapability { }
+
     /**
      * Indicates this is a network that has the ability to reach the
      * carrier's MMSC for sending and receiving MMS messages.
@@ -260,11 +288,11 @@
      * Multiple capabilities may be applied sequentially.  Note that when searching
      * for a network to satisfy a request, all capabilities requested must be satisfied.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be added.
+     * @param capability the capability to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities addCapability(int capability) {
+    public NetworkCapabilities addCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             throw new IllegalArgumentException("NetworkCapability out of range");
         }
@@ -275,11 +303,11 @@
     /**
      * Removes (if found) the given capability from this {@code NetworkCapability} instance.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILTIY_*} to be removed.
+     * @param capability the capability to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities removeCapability(int capability) {
+    public NetworkCapabilities removeCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             throw new IllegalArgumentException("NetworkCapability out of range");
         }
@@ -290,21 +318,20 @@
     /**
      * Gets all the capabilities set on this {@code NetworkCapability} instance.
      *
-     * @return an array of {@code NetworkCapabilities.NET_CAPABILITY_*} values
-     *         for this instance.
+     * @return an array of capability values for this instance.
      * @hide
      */
-    public int[] getCapabilities() {
+    public @NetCapability int[] getCapabilities() {
         return BitUtils.unpackBits(mNetworkCapabilities);
     }
 
     /**
      * Tests for the presence of a capabilitity on this instance.
      *
-     * @param capability the {@code NetworkCapabilities.NET_CAPABILITY_*} to be tested for.
+     * @param capability the capabilities to be tested for.
      * @return {@code true} if set on this instance.
      */
-    public boolean hasCapability(int capability) {
+    public boolean hasCapability(@NetCapability int capability) {
         if (capability < MIN_NET_CAPABILITY || capability > MAX_NET_CAPABILITY) {
             return false;
         }
@@ -385,6 +412,19 @@
      */
     private long mTransportTypes;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "TRANSPORT_" }, value = {
+            TRANSPORT_CELLULAR,
+            TRANSPORT_WIFI,
+            TRANSPORT_BLUETOOTH,
+            TRANSPORT_ETHERNET,
+            TRANSPORT_VPN,
+            TRANSPORT_WIFI_AWARE,
+            TRANSPORT_LOWPAN,
+    })
+    public @interface Transport { }
+
     /**
      * Indicates this network uses a Cellular transport.
      */
@@ -426,7 +466,7 @@
     public static final int MAX_TRANSPORT = TRANSPORT_LOWPAN;
 
     /** @hide */
-    public static boolean isValidTransport(int transportType) {
+    public static boolean isValidTransport(@Transport int transportType) {
         return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
     }
 
@@ -449,11 +489,11 @@
      * to be selected.  This is logically different than
      * {@code NetworkCapabilities.NET_CAPABILITY_*} listed above.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be added.
+     * @param transportType the transport type to be added.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities addTransportType(int transportType) {
+    public NetworkCapabilities addTransportType(@Transport int transportType) {
         checkValidTransportType(transportType);
         mTransportTypes |= 1 << transportType;
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -463,11 +503,11 @@
     /**
      * Removes (if found) the given transport from this {@code NetworkCapability} instance.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be removed.
+     * @param transportType the transport type to be removed.
      * @return This NetworkCapabilities instance, to facilitate chaining.
      * @hide
      */
-    public NetworkCapabilities removeTransportType(int transportType) {
+    public NetworkCapabilities removeTransportType(@Transport int transportType) {
         checkValidTransportType(transportType);
         mTransportTypes &= ~(1 << transportType);
         setNetworkSpecifier(mNetworkSpecifier); // used for exception checking
@@ -477,21 +517,20 @@
     /**
      * Gets all the transports set on this {@code NetworkCapability} instance.
      *
-     * @return an array of {@code NetworkCapabilities.TRANSPORT_*} values
-     *         for this instance.
+     * @return an array of transport type values for this instance.
      * @hide
      */
-    public int[] getTransportTypes() {
+    public @Transport int[] getTransportTypes() {
         return BitUtils.unpackBits(mTransportTypes);
     }
 
     /**
      * Tests for the presence of a transport on this instance.
      *
-     * @param transportType the {@code NetworkCapabilities.TRANSPORT_*} to be tested for.
+     * @param transportType the transport type to be tested for.
      * @return {@code true} if set on this instance.
      */
-    public boolean hasTransport(int transportType) {
+    public boolean hasTransport(@Transport int transportType) {
         return isValidTransport(transportType) && ((mTransportTypes & (1 << transportType)) != 0);
     }
 
@@ -896,7 +935,7 @@
     /**
      * @hide
      */
-    public static String capabilityNamesOf(int[] capabilities) {
+    public static String capabilityNamesOf(@NetCapability int[] capabilities) {
         StringJoiner joiner = new StringJoiner("|");
         if (capabilities != null) {
             for (int c : capabilities) {
@@ -909,7 +948,7 @@
     /**
      * @hide
      */
-    public static String capabilityNameOf(int capability) {
+    public static String capabilityNameOf(@NetCapability int capability) {
         switch (capability) {
             case NET_CAPABILITY_MMS:            return "MMS";
             case NET_CAPABILITY_SUPL:           return "SUPL";
@@ -937,7 +976,7 @@
     /**
      * @hide
      */
-    public static String transportNamesOf(int[] types) {
+    public static String transportNamesOf(@Transport int[] types) {
         StringJoiner joiner = new StringJoiner("|");
         if (types != null) {
             for (int t : types) {
@@ -950,14 +989,14 @@
     /**
      * @hide
      */
-    public static String transportNameOf(int transport) {
+    public static String transportNameOf(@Transport int transport) {
         if (!isValidTransport(transport)) {
             return "UNKNOWN";
         }
         return TRANSPORT_NAMES[transport];
     }
 
-    private static void checkValidTransportType(int transport) {
+    private static void checkValidTransportType(@Transport int transport) {
         Preconditions.checkArgument(
                 isValidTransport(transport), "Invalid TransportType " + transport);
     }
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 95a8bb4..25b1705 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -155,14 +155,13 @@
          * Add the given capability requirement to this builder.  These represent
          * the requested network's required capabilities.  Note that when searching
          * for a network to satisfy a request, all capabilities requested must be
-         * satisfied.  See {@link NetworkCapabilities} for {@code NET_CAPABILITY_*}
-         * definitions.
+         * satisfied.
          *
-         * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to add.
+         * @param capability The capability to add.
          * @return The builder to facilitate chaining
          *         {@code builder.addCapability(...).addCapability();}.
          */
-        public Builder addCapability(int capability) {
+        public Builder addCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.addCapability(capability);
             return this;
         }
@@ -170,10 +169,10 @@
         /**
          * Removes (if found) the given capability from this builder instance.
          *
-         * @param capability The {@code NetworkCapabilities.NET_CAPABILITY_*} to remove.
+         * @param capability The capability to remove.
          * @return The builder to facilitate chaining.
          */
-        public Builder removeCapability(int capability) {
+        public Builder removeCapability(@NetworkCapabilities.NetCapability int capability) {
             mNetworkCapabilities.removeCapability(capability);
             return this;
         }
@@ -208,13 +207,12 @@
          * Adds the given transport requirement to this builder.  These represent
          * the set of allowed transports for the request.  Only networks using one
          * of these transports will satisfy the request.  If no particular transports
-         * are required, none should be specified here.  See {@link NetworkCapabilities}
-         * for {@code TRANSPORT_*} definitions.
+         * are required, none should be specified here.
          *
-         * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to add.
+         * @param transportType The transport type to add.
          * @return The builder to facilitate chaining.
          */
-        public Builder addTransportType(int transportType) {
+        public Builder addTransportType(@NetworkCapabilities.Transport int transportType) {
             mNetworkCapabilities.addTransportType(transportType);
             return this;
         }
@@ -222,10 +220,10 @@
         /**
          * Removes (if found) the given transport from this builder instance.
          *
-         * @param transportType The {@code NetworkCapabilities.TRANSPORT_*} to remove.
+         * @param transportType The transport type to remove.
          * @return The builder to facilitate chaining.
          */
-        public Builder removeTransportType(int transportType) {
+        public Builder removeTransportType(@NetworkCapabilities.Transport int transportType) {
             mNetworkCapabilities.removeTransportType(transportType);
             return this;
         }
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index 0f1e259..d1ce60e 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -1,6 +1,6 @@
 ek@google.com
 hugobenichi@google.com
-jsharkey@google.com
+jsharkey@android.com
 lorenzo@google.com
 satk@google.com
 silberst@google.com
diff --git a/core/java/android/net/metrics/ConnectStats.java b/core/java/android/net/metrics/ConnectStats.java
index 30b2656..2495cab 100644
--- a/core/java/android/net/metrics/ConnectStats.java
+++ b/core/java/android/net/metrics/ConnectStats.java
@@ -20,6 +20,7 @@
 import android.system.OsConstants;
 import android.util.IntArray;
 import android.util.SparseIntArray;
+
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.TokenBucket;
 
@@ -43,6 +44,8 @@
     public final TokenBucket mLatencyTb;
     /** Maximum number of latency values recorded. */
     public final int mMaxLatencyRecords;
+    /** Total count of events */
+    public int eventCount = 0;
     /** Total count of successful connects. */
     public int connectCount = 0;
     /** Total count of successful connects done in blocking mode. */
@@ -57,12 +60,15 @@
         mMaxLatencyRecords = maxLatencyRecords;
     }
 
-    public void addEvent(int errno, int latencyMs, String ipAddr) {
+    boolean addEvent(int errno, int latencyMs, String ipAddr) {
+        eventCount++;
         if (isSuccess(errno)) {
             countConnect(errno, ipAddr);
             countLatency(errno, latencyMs);
+            return true;
         } else {
             countError(errno);
+            return false;
         }
     }
 
@@ -101,7 +107,7 @@
         return (errno == 0) || isNonBlocking(errno);
     }
 
-    private static boolean isNonBlocking(int errno) {
+    static boolean isNonBlocking(int errno) {
         // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS.
         // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY.
         return (errno == EINPROGRESS) || (errno == EALREADY);
@@ -117,6 +123,7 @@
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
+        builder.append(String.format("%d events, ", eventCount));
         builder.append(String.format("%d success, ", connectCount));
         builder.append(String.format("%d blocking, ", connectBlockingCount));
         builder.append(String.format("%d IPv6 dst", ipv6ConnectCount));
diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java
index 28cf42f..eb61c15 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -16,73 +16,43 @@
 
 package android.net.metrics;
 
+import static android.net.ConnectivityManager.NETID_UNSET;
+
 import android.net.NetworkCapabilities;
-import android.os.Parcel;
-import android.os.Parcelable;
 
 /**
  * An event recorded by ConnectivityService when there is a change in the default network.
  * {@hide}
  */
-public final class DefaultNetworkEvent implements Parcelable {
+public class DefaultNetworkEvent {
+
     // The ID of the network that has become the new default or NETID_UNSET if none.
-    public final int netId;
+    public int netId = NETID_UNSET;
     // The list of transport types of the new default network, for example TRANSPORT_WIFI, as
     // defined in NetworkCapabilities.java.
-    public final int[] transportTypes;
+    public int[] transportTypes = new int[0];
     // The ID of the network that was the default before or NETID_UNSET if none.
-    public final int prevNetId;
+    public int prevNetId = NETID_UNSET;
     // Whether the previous network had IPv4/IPv6 connectivity.
-    public final boolean prevIPv4;
-    public final boolean prevIPv6;
-
-    public DefaultNetworkEvent(int netId, int[] transportTypes,
-                int prevNetId, boolean prevIPv4, boolean prevIPv6) {
-        this.netId = netId;
-        this.transportTypes = transportTypes;
-        this.prevNetId = prevNetId;
-        this.prevIPv4 = prevIPv4;
-        this.prevIPv6 = prevIPv6;
-    }
-
-    private DefaultNetworkEvent(Parcel in) {
-        this.netId = in.readInt();
-        this.transportTypes = in.createIntArray();
-        this.prevNetId = in.readInt();
-        this.prevIPv4 = (in.readByte() > 0);
-        this.prevIPv6 = (in.readByte() > 0);
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(netId);
-        out.writeIntArray(transportTypes);
-        out.writeInt(prevNetId);
-        out.writeByte(prevIPv4 ? (byte) 1 : (byte) 0);
-        out.writeByte(prevIPv6 ? (byte) 1 : (byte) 0);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
+    public boolean prevIPv4;
+    public boolean prevIPv6;
 
     @Override
     public String toString() {
-      String prevNetwork = String.valueOf(prevNetId);
-      String newNetwork = String.valueOf(netId);
-      if (prevNetId != 0) {
-          prevNetwork += ":" + ipSupport();
-      }
-      if (netId != 0) {
-          newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
-      }
-      return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
+        String prevNetwork = String.valueOf(prevNetId);
+        String newNetwork = String.valueOf(netId);
+        if (prevNetId != 0) {
+            prevNetwork += ":" + ipSupport();
+        }
+        if (netId != 0) {
+            newNetwork += ":" + NetworkCapabilities.transportNamesOf(transportTypes);
+        }
+        return String.format("DefaultNetworkEvent(%s -> %s)", prevNetwork, newNetwork);
     }
 
     private String ipSupport() {
         if (prevIPv4 && prevIPv6) {
-            return "DUAL";
+            return "IPv4v6";
         }
         if (prevIPv6) {
             return "IPv6";
@@ -92,15 +62,4 @@
         }
         return "NONE";
     }
-
-    public static final Parcelable.Creator<DefaultNetworkEvent> CREATOR
-        = new Parcelable.Creator<DefaultNetworkEvent>() {
-        public DefaultNetworkEvent createFromParcel(Parcel in) {
-            return new DefaultNetworkEvent(in);
-        }
-
-        public DefaultNetworkEvent[] newArray(int size) {
-            return new DefaultNetworkEvent[size];
-        }
-    };
 }
diff --git a/core/java/android/net/metrics/DnsEvent.java b/core/java/android/net/metrics/DnsEvent.java
index a4970e4..81b098b 100644
--- a/core/java/android/net/metrics/DnsEvent.java
+++ b/core/java/android/net/metrics/DnsEvent.java
@@ -17,11 +17,13 @@
 package android.net.metrics;
 
 import android.net.NetworkCapabilities;
-import java.util.Arrays;
+
 import com.android.internal.util.BitUtils;
 
+import java.util.Arrays;
+
 /**
- * A DNS event recorded by NetdEventListenerService.
+ * A batch of DNS events recorded by NetdEventListenerService for a specific network.
  * {@hide}
  */
 final public class DnsEvent {
@@ -38,6 +40,8 @@
     // the eventTypes, returnCodes, and latenciesMs arrays have the same length and the i-th event
     // is spread across the three array at position i.
     public int eventCount;
+    // The number of successful DNS queries recorded.
+    public int successCount;
     // The types of DNS queries as defined in INetdEventListener.
     public byte[] eventTypes;
     // Current getaddrinfo codes go from 1 to EAI_MAX = 15. gethostbyname returns errno, but there
@@ -54,10 +58,11 @@
         latenciesMs = new int[initialCapacity];
     }
 
-    public void addResult(byte eventType, byte returnCode, int latencyMs) {
+    boolean addResult(byte eventType, byte returnCode, int latencyMs) {
+        boolean isSuccess = (returnCode == 0);
         if (eventCount >= SIZE_LIMIT) {
             // TODO: implement better rate limiting that does not biases metrics.
-            return;
+            return isSuccess;
         }
         if (eventCount == eventTypes.length) {
             resize((int) (1.4 * eventCount));
@@ -66,6 +71,10 @@
         returnCodes[eventCount] = returnCode;
         latenciesMs[eventCount] = latencyMs;
         eventCount++;
+        if (isSuccess) {
+            successCount++;
+        }
+        return isSuccess;
     }
 
     public void resize(int newLength) {
@@ -80,6 +89,8 @@
         for (int t : BitUtils.unpackBits(transports)) {
             builder.append(NetworkCapabilities.transportNameOf(t)).append(", ");
         }
-        return builder.append(eventCount).append(" events)").toString();
+        builder.append(String.format("%d events, ", eventCount));
+        builder.append(String.format("%d success)", successCount));
+        return builder.toString();
     }
 }
diff --git a/core/java/android/net/metrics/NetworkMetrics.java b/core/java/android/net/metrics/NetworkMetrics.java
new file mode 100644
index 0000000..2b662a0
--- /dev/null
+++ b/core/java/android/net/metrics/NetworkMetrics.java
@@ -0,0 +1,168 @@
+/*
+ * 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.net.metrics;
+
+import android.net.NetworkCapabilities;
+
+import com.android.internal.util.BitUtils;
+import com.android.internal.util.TokenBucket;
+
+import java.util.StringJoiner;
+
+/**
+ * A class accumulating network metrics received from Netd regarding dns queries and
+ * connect() calls on a given network.
+ *
+ * This class also accumulates running sums of dns and connect latency stats and
+ * error counts for bug report logging.
+ *
+ * @hide
+ */
+public class NetworkMetrics {
+
+    private static final int INITIAL_DNS_BATCH_SIZE = 100;
+    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+    // The network id of the Android Network.
+    public final int netId;
+    // The transport types bitmap of the Android Network, as defined in NetworkCapabilities.java.
+    public final long transports;
+    // Accumulated metrics for connect events.
+    public final ConnectStats connectMetrics;
+    // Accumulated metrics for dns events.
+    public final DnsEvent dnsMetrics;
+    // Running sums of latencies and error counts for connect and dns events.
+    public final Summary summary;
+    // Running sums of the most recent latencies and error counts for connect and dns events.
+    // Starts null until some events are accumulated.
+    // Allows to collect periodic snapshot of the running summaries for a given network.
+    public Summary pendingSummary;
+
+    public NetworkMetrics(int netId, long transports, TokenBucket tb) {
+        this.netId = netId;
+        this.transports = transports;
+        this.connectMetrics =
+                new ConnectStats(netId, transports, tb, CONNECT_LATENCY_MAXIMUM_RECORDS);
+        this.dnsMetrics = new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
+        this.summary = new Summary(netId, transports);
+    }
+
+    /**
+     * Get currently pending Summary statistics, if any, for this NetworkMetrics, merge them
+     * into the long running Summary statistics of this NetworkMetrics, and also clear them.
+     */
+    public Summary getPendingStats() {
+        Summary s = pendingSummary;
+        pendingSummary = null;
+        if (s != null) {
+            summary.merge(s);
+        }
+        return s;
+    }
+
+    /** Accumulate a dns query result reported by netd. */
+    public void addDnsResult(int eventType, int returnCode, int latencyMs) {
+        if (pendingSummary == null) {
+            pendingSummary = new Summary(netId, transports);
+        }
+        boolean isSuccess = dnsMetrics.addResult((byte) eventType, (byte) returnCode, latencyMs);
+        pendingSummary.dnsLatencies.count(latencyMs);
+        pendingSummary.dnsErrorRate.count(isSuccess ? 0 : 1);
+    }
+
+    /** Accumulate a connect query result reported by netd. */
+    public void addConnectResult(int error, int latencyMs, String ipAddr) {
+        if (pendingSummary == null) {
+            pendingSummary = new Summary(netId, transports);
+        }
+        boolean isSuccess = connectMetrics.addEvent(error, latencyMs, ipAddr);
+        pendingSummary.connectErrorRate.count(isSuccess ? 0 : 1);
+        if (ConnectStats.isNonBlocking(error)) {
+            pendingSummary.connectLatencies.count(latencyMs);
+        }
+    }
+
+    /** Represents running sums for dns and connect average error counts and average latencies. */
+    public static class Summary {
+
+        public final int netId;
+        public final long transports;
+        // DNS latencies measured in milliseconds.
+        public final Metrics dnsLatencies = new Metrics();
+        // DNS error rate measured in percentage points.
+        public final Metrics dnsErrorRate = new Metrics();
+        // Blocking connect latencies measured in milliseconds.
+        public final Metrics connectLatencies = new Metrics();
+        // Blocking and non blocking connect error rate measured in percentage points.
+        public final Metrics connectErrorRate = new Metrics();
+
+        public Summary(int netId, long transports) {
+            this.netId = netId;
+            this.transports = transports;
+        }
+
+        void merge(Summary that) {
+            dnsLatencies.merge(that.dnsLatencies);
+            dnsErrorRate.merge(that.dnsErrorRate);
+            connectLatencies.merge(that.connectLatencies);
+            connectErrorRate.merge(that.connectErrorRate);
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner j = new StringJoiner(", ", "{", "}");
+            j.add("netId=" + netId);
+            for (int t : BitUtils.unpackBits(transports)) {
+                j.add(NetworkCapabilities.transportNameOf(t));
+            }
+            j.add(String.format("dns avg=%dms max=%dms err=%.1f%% tot=%d",
+                    (int) dnsLatencies.average(), (int) dnsLatencies.max,
+                    100 * dnsErrorRate.average(), dnsErrorRate.count));
+            j.add(String.format("connect avg=%dms max=%dms err=%.1f%% tot=%d",
+                    (int) connectLatencies.average(), (int) connectLatencies.max,
+                    100 * connectErrorRate.average(), connectErrorRate.count));
+            return j.toString();
+        }
+    }
+
+    /** Tracks a running sum and returns the average of a metric. */
+    static class Metrics {
+        public double sum;
+        public double max = Double.MIN_VALUE;
+        public int count;
+
+        void merge(Metrics that) {
+            this.count += that.count;
+            this.sum += that.sum;
+            this.max = Math.max(this.max, that.max);
+        }
+
+        void count(double value) {
+            count++;
+            sum += value;
+            max = Math.max(max, value);
+        }
+
+        double average() {
+            double a = sum / (double) count;
+            if (Double.isNaN(a)) {
+                a = 0;
+            }
+            return a;
+        }
+    }
+}
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index 84119bd..b7e7b17 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -43,6 +43,13 @@
         return mValueLong;
     }
 
+    /**
+     * @hide
+     */
+    public void setLong(long val) {
+        mValueLong = val;
+    }
+
     /*
      * Parcel read/write code must be kept in sync with
      * frameworks/native/services/batteryservice/BatteryProperty.cpp
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 66b6b47..a8bd940 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -19,6 +19,7 @@
 import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.service.batterystats.BatteryStatsServiceDumpProto;
 import android.telephony.SignalStrength;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
@@ -29,11 +30,13 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -78,17 +81,17 @@
      * A constant indicating a sensor timer.
      */
     public static final int SENSOR = 3;
-    
+
     /**
      * A constant indicating a a wifi running timer
      */
     public static final int WIFI_RUNNING = 4;
-    
+
     /**
      * A constant indicating a full wifi lock timer
      */
     public static final int FULL_WIFI_LOCK = 5;
-    
+
     /**
      * A constant indicating a wifi scan
      */
@@ -190,7 +193,7 @@
     public static final int STATS_SINCE_UNPLUGGED = 2;
 
     // NOTE: Update this list if you add/change any stats above.
-    // These characters are supposed to represent "total", "last", "current", 
+    // These characters are supposed to represent "total", "last", "current",
     // and "unplugged". They were shortened for efficiency sake.
     private static final String[] STAT_NAMES = { "l", "c", "u" };
 
@@ -217,8 +220,10 @@
      *   - Package wakeup alarms are now on screen-off timebase
      * New in version 26:
      *   - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
+     * New in version 27:
+     *   - Always On Display (screen doze mode) time and power
      */
-    static final String CHECKIN_VERSION = "26";
+    static final int CHECKIN_VERSION = 27;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -442,8 +447,7 @@
 
         /**
          * Returns the max duration if it is being tracked.
-         * Not all Timer subclasses track the max, total, current durations.
-
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getMaxDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
@@ -451,14 +455,14 @@
 
         /**
          * Returns the current time the timer has been active, if it is being tracked.
-         * Not all Timer subclasses track the max, total, current durations.
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getCurrentDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
         }
 
         /**
-         * Returns the current time the timer has been active, if it is being tracked.
+         * Returns the total time the timer has been active, if it is being tracked.
          *
          * Returns the total cumulative duration (i.e. sum of past durations) that this timer has
          * been on since reset.
@@ -466,7 +470,7 @@
          * depending on the Timer, getTotalTimeLocked may represent the total 'blamed' or 'pooled'
          * time, rather than the actual time. By contrast, getTotalDurationMsLocked always gives
          * the actual total time.
-         * Not all Timer subclasses track the max, total, current durations.
+         * Not all Timer subclasses track the max, total, and current durations.
          */
         public long getTotalDurationMsLocked(long elapsedRealtimeMs) {
             return -1;
@@ -595,9 +599,17 @@
         public abstract long getFullWifiLockTime(long elapsedRealtimeUs, int which);
         public abstract long getWifiScanTime(long elapsedRealtimeUs, int which);
         public abstract int getWifiScanCount(int which);
+        /**
+         * Returns the timer keeping track of wifi scans.
+         */
+        public abstract Timer getWifiScanTimer();
         public abstract int getWifiScanBackgroundCount(int which);
         public abstract long getWifiScanActualTime(long elapsedRealtimeUs);
         public abstract long getWifiScanBackgroundTime(long elapsedRealtimeUs);
+        /**
+         * Returns the timer keeping track of background wifi scans.
+         */
+        public abstract Timer getWifiScanBackgroundTimer();
         public abstract long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which);
         public abstract int getWifiBatchedScanCount(int csphBin, int which);
         public abstract long getWifiMulticastTime(long elapsedRealtimeUs, int which);
@@ -1381,12 +1393,12 @@
         public static final int STATE_PHONE_SCANNING_FLAG = 1<<21;
         public static final int STATE_SCREEN_ON_FLAG = 1<<20;       // consider moving to states2
         public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<19; // consider moving to states2
-        // empty slot
+        public static final int STATE_SCREEN_DOZE_FLAG = 1 << 18;
         // empty slot
         public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<16;
 
         public static final int MOST_INTERESTING_STATES =
-            STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG;
+                STATE_BATTERY_PLUGGED_FLAG | STATE_SCREEN_ON_FLAG | STATE_SCREEN_DOZE_FLAG;
 
         public static final int SETTLE_TO_ZERO_STATES = 0xffff0000 & ~MOST_INTERESTING_STATES;
 
@@ -1414,8 +1426,8 @@
         public static final int STATE2_BLUETOOTH_SCAN_FLAG = 1 << 20;
 
         public static final int MOST_INTERESTING_STATES2 =
-            STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
-            | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
+                STATE2_POWER_SAVE_FLAG | STATE2_WIFI_ON_FLAG | STATE2_DEVICE_IDLE_MASK
+                | STATE2_CHARGING_FLAG | STATE2_PHONE_IN_CALL_FLAG | STATE2_BLUETOOTH_ON_FLAG;
 
         public static final int SETTLE_TO_ZERO_STATES2 = 0xffff0000 & ~MOST_INTERESTING_STATES2;
 
@@ -1863,6 +1875,21 @@
      */
     public abstract int getScreenOnCount(int which);
 
+    /**
+     * Returns the time in microseconds that the screen has been dozing while the device was
+     * running on battery.
+     *
+     * {@hide}
+     */
+    public abstract long getScreenDozeTime(long elapsedRealtimeUs, int which);
+
+    /**
+     * Returns the number of times the screen was turned dozing.
+     *
+     * {@hide}
+     */
+    public abstract int getScreenDozeCount(int which);
+
     public abstract long getInteractiveTime(long elapsedRealtimeUs, int which);
 
     public static final int SCREEN_BRIGHTNESS_DARK = 0;
@@ -1891,6 +1918,13 @@
             long elapsedRealtimeUs, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks the given screen brightness.
+     *
+     * {@hide}
+     */
+    public abstract Timer getScreenBrightnessTimer(int brightnessBin);
+
+    /**
      * Returns the time in microseconds that power save mode has been enabled while the device was
      * running on battery.
      *
@@ -1952,7 +1986,7 @@
     public abstract long getDeviceIdlingTime(int mode, long elapsedRealtimeUs, int which);
 
     /**
-     * Returns the number of times that the devie has started idling.
+     * Returns the number of times that the device has started idling.
      *
      * {@hide}
      */
@@ -1999,6 +2033,14 @@
             long elapsedRealtimeUs, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks how much the phone has been trying to
+     * acquire a signal.
+     *
+     * {@hide}
+     */
+    public abstract Timer getPhoneSignalScanningTimer();
+
+    /**
      * Returns the number of times the phone has entered the given signal strength.
      *
      * {@hide}
@@ -2006,6 +2048,12 @@
     public abstract int getPhoneSignalStrengthCount(int strengthBin, int which);
 
     /**
+     * Return the {@link Timer} object used to track the given signal strength's duration and
+     * counts.
+     */
+    protected abstract Timer getPhoneSignalStrengthTimer(int strengthBin);
+
+    /**
      * Returns the time in microseconds that the mobile network has been active
      * (in a high power state).
      *
@@ -2088,6 +2136,11 @@
      */
     public abstract int getPhoneDataConnectionCount(int dataType, int which);
 
+    /**
+     * Returns the {@link Timer} object that tracks the phone's data connection type stats.
+     */
+    public abstract Timer getPhoneDataConnectionTimer(int dataType);
+
     public static final int WIFI_SUPPL_STATE_INVALID = 0;
     public static final int WIFI_SUPPL_STATE_DISCONNECTED = 1;
     public static final int WIFI_SUPPL_STATE_INTERFACE_DISABLED = 2;
@@ -2116,8 +2169,7 @@
         "group", "compl", "dorm", "uninit"
     };
 
-    public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS
-            = new BitDescription[] {
+    public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS = new BitDescription[] {
         new BitDescription(HistoryItem.STATE_CPU_RUNNING_FLAG, "running", "r"),
         new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock", "w"),
         new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor", "s"),
@@ -2131,6 +2183,7 @@
         new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio", "a"),
         new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen", "S"),
         new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged", "BP"),
+        new BitDescription(HistoryItem.STATE_SCREEN_DOZE_FLAG, "screen_doze", "Sd"),
         new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
                 HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", "Pcn",
                 DATA_CONNECTION_NAMES, DATA_CONNECTION_NAMES),
@@ -2247,6 +2300,13 @@
     public abstract int getWifiStateCount(int wifiState, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks the given WiFi state.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiStateTimer(int wifiState);
+
+    /**
      * Returns the time in microseconds that the wifi supplicant has been
      * in a given state.
      *
@@ -2262,6 +2322,13 @@
      */
     public abstract int getWifiSupplStateCount(int state, int which);
 
+    /**
+     * Returns the {@link Timer} object that tracks the given wifi supplicant state.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiSupplStateTimer(int state);
+
     public static final int NUM_WIFI_SIGNAL_STRENGTH_BINS = 5;
 
     /**
@@ -2281,6 +2348,13 @@
     public abstract int getWifiSignalStrengthCount(int strengthBin, int which);
 
     /**
+     * Returns the {@link Timer} object that tracks the given WIFI signal strength.
+     *
+     * {@hide}
+     */
+    public abstract Timer getWifiSignalStrengthTimer(int strengthBin);
+
+    /**
      * Returns the time in microseconds that the flashlight has been on while the device was
      * running on battery.
      *
@@ -2467,6 +2541,18 @@
     public abstract int getDischargeAmountScreenOffSinceCharge();
 
     /**
+     * Get the amount the battery has discharged while the screen was dozing,
+     * since the last time power was unplugged.
+     */
+    public abstract int getDischargeAmountScreenDoze();
+
+    /**
+     * Get the amount the battery has discharged while the screen was dozing,
+     * since the last time the device was charged.
+     */
+    public abstract int getDischargeAmountScreenDozeSinceCharge();
+
+    /**
      * Returns the total, last, or current battery uptime in microseconds.
      *
      * @param curTime the elapsed realtime in microseconds.
@@ -2483,7 +2569,7 @@
     public abstract long computeBatteryRealtime(long curTime, int which);
 
     /**
-     * Returns the total, last, or current battery screen off uptime in microseconds.
+     * Returns the total, last, or current battery screen off/doze uptime in microseconds.
      *
      * @param curTime the elapsed realtime in microseconds.
      * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -2491,7 +2577,7 @@
     public abstract long computeBatteryScreenOffUptime(long curTime, int which);
 
     /**
-     * Returns the total, last, or current battery screen off realtime in microseconds.
+     * Returns the total, last, or current battery screen off/doze realtime in microseconds.
      *
      * @param curTime the current elapsed realtime in microseconds.
      * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT.
@@ -2590,18 +2676,24 @@
     };
 
     /**
-     * Return the counter keeping track of the amount of battery discharge while the screen was off,
-     * measured in micro-Ampere-hours. This will be non-zero only if the device's battery has
-     * a coulomb counter.
-     */
-    public abstract LongCounter getDischargeScreenOffCoulombCounter();
-
-    /**
-     * Return the counter keeping track of the amount of battery discharge measured in
+     * Return the amount of battery discharge while the screen was off, measured in
      * micro-Ampere-hours. This will be non-zero only if the device's battery has
      * a coulomb counter.
      */
-    public abstract LongCounter getDischargeCoulombCounter();
+    public abstract long getUahDischargeScreenOff(int which);
+
+    /**
+     * Return the amount of battery discharge while the screen was in doze mode, measured in
+     * micro-Ampere-hours. This will be non-zero only if the device's battery has
+     * a coulomb counter.
+     */
+    public abstract long getUahDischargeScreenDoze(int which);
+
+    /**
+     * Return the amount of battery discharge  measured in micro-Ampere-hours. This will be
+     * non-zero only if the device's battery has a coulomb counter.
+     */
+    public abstract long getUahDischarge(int which);
 
     /**
      * Returns the estimated real battery capacity, which may be less than the capacity
@@ -2739,6 +2831,10 @@
         }
     }
 
+    private static long roundUsToMs(long timeUs) {
+        return (timeUs + 500) / 1000;
+    }
+
     private static long computeWakeLock(Timer timer, long elapsedRealtimeUs, int which) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
@@ -2943,16 +3039,54 @@
                                         Timer timer, long rawRealtime, int which) {
         if (timer != null) {
             // Convert from microseconds to milliseconds with rounding
-            final long totalTime = (timer.getTotalTimeLocked(rawRealtime, which) + 500)
-                    / 1000;
+            final long totalTime = roundUsToMs(timer.getTotalTimeLocked(rawRealtime, which));
             final int count = timer.getCountLocked(which);
-            if (totalTime != 0) {
+            if (totalTime != 0 || count != 0) {
                 dumpLine(pw, uid, category, type, totalTime, count);
             }
         }
     }
 
     /**
+     * Dump a given timer stat to the proto stream.
+     *
+     * @param proto the ProtoOutputStream to log to
+     * @param fieldId type of data, the field to save to (e.g. AggregatedBatteryStats.WAKELOCK)
+     * @param timer a {@link Timer} to dump stats for
+     * @param rawRealtimeUs the current elapsed realtime of the system in microseconds
+     * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT
+     */
+    private static void dumpTimer(ProtoOutputStream proto, long fieldId,
+                                        Timer timer, long rawRealtimeUs, int which) {
+        if (timer == null) {
+            return;
+        }
+        // Convert from microseconds to milliseconds with rounding
+        final long timeMs = roundUsToMs(timer.getTotalTimeLocked(rawRealtimeUs, which));
+        final int count = timer.getCountLocked(which);
+        final long maxDurationMs = timer.getMaxDurationMsLocked(rawRealtimeUs / 1000);
+        final long curDurationMs = timer.getCurrentDurationMsLocked(rawRealtimeUs / 1000);
+        final long totalDurationMs = timer.getTotalDurationMsLocked(rawRealtimeUs / 1000);
+        if (timeMs != 0 || count != 0 || maxDurationMs != -1 || curDurationMs != -1
+                || totalDurationMs != -1) {
+            final long token = proto.start(fieldId);
+            proto.write(TimerProto.DURATION_MS, timeMs);
+            proto.write(TimerProto.COUNT, count);
+            // These values will be -1 for timers that don't implement the functionality.
+            if (maxDurationMs != -1) {
+                proto.write(TimerProto.MAX_DURATION_MS, maxDurationMs);
+            }
+            if (curDurationMs != -1) {
+                proto.write(TimerProto.CURRENT_DURATION_MS, curDurationMs);
+            }
+            if (totalDurationMs != -1) {
+                proto.write(TimerProto.TOTAL_DURATION_MS, totalDurationMs);
+            }
+            proto.end(token);
+        }
+    }
+
+    /**
      * Checks if the ControllerActivityCounter has any data worth dumping.
      */
     private static boolean controllerActivityHasData(ControllerActivityCounter counter, int which) {
@@ -3004,6 +3138,38 @@
         pw.println();
     }
 
+    /**
+     * Dumps the ControllerActivityCounter if it has any data worth dumping.
+     */
+    private static void dumpControllerActivityProto(ProtoOutputStream proto, long fieldId,
+                                                    ControllerActivityCounter counter,
+                                                    int which) {
+        if (!controllerActivityHasData(counter, which)) {
+            return;
+        }
+
+        final long cToken = proto.start(fieldId);
+
+        proto.write(ControllerActivityProto.IDLE_DURATION_MS,
+                counter.getIdleTimeCounter().getCountLocked(which));
+        proto.write(ControllerActivityProto.RX_DURATION_MS,
+                counter.getRxTimeCounter().getCountLocked(which));
+        proto.write(ControllerActivityProto.POWER_MAH,
+                counter.getPowerCounter().getCountLocked(which) / (1000 * 60 * 60));
+
+        long tToken;
+        LongCounter[] txCounters = counter.getTxTimeCounters();
+        for (int i = 0; i < txCounters.length; ++i) {
+            LongCounter c = txCounters[i];
+            tToken = proto.start(ControllerActivityProto.TX);
+            proto.write(ControllerActivityProto.TxLevel.LEVEL, i);
+            proto.write(ControllerActivityProto.TxLevel.DURATION_MS, c.getCountLocked(which));
+            proto.end(tToken);
+        }
+
+        proto.end(cToken);
+    }
+
     private final void printControllerActivityIfInteresting(PrintWriter pw, StringBuilder sb,
                                                             String prefix, String controllerName,
                                                             ControllerActivityCounter counter,
@@ -3019,71 +3185,104 @@
         final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
         final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
         final long powerDrainMaMs = counter.getPowerCounter().getCountLocked(which);
+        // Battery real time
+        final long totalControllerActivityTimeMs
+            = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
         long totalTxTimeMs = 0;
         for (LongCounter txState : counter.getTxTimeCounters()) {
             totalTxTimeMs += txState.getCountLocked(which);
         }
-
-        final long totalTimeMs = idleTimeMs + rxTimeMs + totalTxTimeMs;
+        final long sleepTimeMs
+            = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + totalTxTimeMs);
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  ");
+        sb.append("     ");
+        sb.append(controllerName);
+        sb.append(" Sleep time:  ");
+        formatTimeMs(sb, sleepTimeMs);
+        sb.append("(");
+        sb.append(formatRatioLocked(sleepTimeMs, totalControllerActivityTimeMs));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     ");
         sb.append(controllerName);
         sb.append(" Idle time:   ");
         formatTimeMs(sb, idleTimeMs);
         sb.append("(");
-        sb.append(formatRatioLocked(idleTimeMs, totalTimeMs));
+        sb.append(formatRatioLocked(idleTimeMs, totalControllerActivityTimeMs));
         sb.append(")");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  ");
+        sb.append("     ");
         sb.append(controllerName);
         sb.append(" Rx time:     ");
         formatTimeMs(sb, rxTimeMs);
         sb.append("(");
-        sb.append(formatRatioLocked(rxTimeMs, totalTimeMs));
+        sb.append(formatRatioLocked(rxTimeMs, totalControllerActivityTimeMs));
         sb.append(")");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  ");
+        sb.append("     ");
         sb.append(controllerName);
         sb.append(" Tx time:     ");
-        formatTimeMs(sb, totalTxTimeMs);
-        sb.append("(");
-        sb.append(formatRatioLocked(totalTxTimeMs, totalTimeMs));
-        sb.append(")");
-        pw.println(sb.toString());
 
-        final int numTxLvls = counter.getTxTimeCounters().length;
+        String [] powerLevel;
+        switch(controllerName) {
+            case "Cellular":
+                powerLevel = new String[] {
+                    "   less than 0dBm: ",
+                    "   0dBm to 8dBm: ",
+                    "   8dBm to 15dBm: ",
+                    "   15dBm to 20dBm: ",
+                    "   above 20dBm: "};
+                break;
+            default:
+                powerLevel = new String[] {"[0]", "[1]", "[2]", "[3]", "[4]"};
+                break;
+        }
+        final int numTxLvls = Math.min(counter.getTxTimeCounters().length, powerLevel.length);
         if (numTxLvls > 1) {
+            pw.println(sb.toString());
             for (int lvl = 0; lvl < numTxLvls; lvl++) {
                 final long txLvlTimeMs = counter.getTxTimeCounters()[lvl].getCountLocked(which);
                 sb.setLength(0);
                 sb.append(prefix);
-                sb.append("    [");
-                sb.append(lvl);
-                sb.append("] ");
+                sb.append("    ");
+                sb.append(powerLevel[lvl]);
+                sb.append(" ");
                 formatTimeMs(sb, txLvlTimeMs);
                 sb.append("(");
-                sb.append(formatRatioLocked(txLvlTimeMs, totalTxTimeMs));
+                sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
                 sb.append(")");
                 pw.println(sb.toString());
             }
+        } else {
+            final long txLvlTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
+            formatTimeMs(sb, txLvlTimeMs);
+            sb.append("(");
+            sb.append(formatRatioLocked(txLvlTimeMs, totalControllerActivityTimeMs));
+            sb.append(")");
+            pw.println(sb.toString());
         }
 
-        sb.setLength(0);
-        sb.append(prefix);
-        sb.append("  ");
-        sb.append(controllerName);
-        sb.append(" Power drain: ").append(
+        if (powerDrainMaMs > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("     ");
+            sb.append(controllerName);
+            sb.append(" Battery drain: ").append(
                 BatteryStatsHelper.makemAh(powerDrainMaMs / (double) (1000*60*60)));
-        sb.append("mAh");
-        pw.println(sb.toString());
+            sb.append("mAh");
+            pw.println(sb.toString());
+        }
     }
 
     /**
@@ -3096,13 +3295,13 @@
     /**
      * Checkin server version of dump to produce more compact, computer-readable log.
      *
-     * NOTE: all times are expressed in 'ms'.
+     * NOTE: all times are expressed in microseconds, unless specified otherwise.
      */
     public final void dumpCheckinLocked(Context context, PrintWriter pw, int which, int reqUid,
             boolean wifiOnly) {
         final long rawUptime = SystemClock.uptimeMillis() * 1000;
-        final long rawRealtime = SystemClock.elapsedRealtime() * 1000;
-        final long rawRealtimeMs = (rawRealtime + 500) / 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtime = rawRealtimeMs * 1000;
         final long batteryUptime = getBatteryUptime(rawUptime);
         final long whichBatteryUptime = computeBatteryUptime(rawUptime, which);
         final long whichBatteryRealtime = computeBatteryRealtime(rawRealtime, which);
@@ -3112,6 +3311,7 @@
         final long totalRealtime = computeRealtime(rawRealtime, which);
         final long totalUptime = computeUptime(rawUptime, which);
         final long screenOnTime = getScreenOnTime(rawRealtime, which);
+        final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
         final long interactiveTime = getInteractiveTime(rawRealtime, which);
         final long powerSaveModeEnabledTime = getPowerSaveModeEnabledTime(rawRealtime, which);
         final long deviceIdleModeLightTime = getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT,
@@ -3124,9 +3324,9 @@
                 rawRealtime, which);
         final int connChanges = getNumConnectivityChange(which);
         final long phoneOnTime = getPhoneOnTime(rawRealtime, which);
-        final long dischargeCount = getDischargeCoulombCounter().getCountLocked(which);
-        final long dischargeScreenOffCount = getDischargeScreenOffCoulombCounter()
-                .getCountLocked(which);
+        final long dischargeCount = getUahDischarge(which);
+        final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
+        final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
 
         final StringBuilder sb = new StringBuilder(128);
 
@@ -3143,7 +3343,8 @@
                 getStartClockTime(),
                 whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000,
                 getEstimatedBatteryCapacity(),
-                getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity());
+                getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity(),
+                screenDozeTime / 1000);
 
 
         // Calculate wakelock times across all uids.
@@ -3295,13 +3496,15 @@
                     getDischargeStartLevel()-getDischargeCurrentLevel(),
                     getDischargeStartLevel()-getDischargeCurrentLevel(),
                     getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
-                    dischargeCount / 1000, dischargeScreenOffCount / 1000);
+                    dischargeCount / 1000, dischargeScreenOffCount / 1000,
+                    getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
         } else {
             dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
                     getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
                     getDischargeAmountScreenOnSinceCharge(),
                     getDischargeAmountScreenOffSinceCharge(),
-                    dischargeCount / 1000, dischargeScreenOffCount / 1000);
+                    dischargeCount / 1000, dischargeScreenOffCount / 1000,
+                    getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
         }
 
         if (reqUid < 0) {
@@ -3361,9 +3564,9 @@
                     BatteryStatsHelper.makemAh(helper.getComputedPower()),
                     BatteryStatsHelper.makemAh(helper.getMinDrainedPower()),
                     BatteryStatsHelper.makemAh(helper.getMaxDrainedPower()));
+            int uid = 0;
             for (int i=0; i<sippers.size(); i++) {
                 final BatterySipper bs = sippers.get(i);
-                int uid = 0;
                 String label;
                 switch (bs.drainType) {
                     case IDLE:
@@ -3404,6 +3607,9 @@
                     case CAMERA:
                         label = "camera";
                         break;
+                    case MEMORY:
+                        label = "memory";
+                        break;
                     default:
                         label = "???";
                 }
@@ -3424,6 +3630,7 @@
             dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString());
         }
 
+        // Dump stats per UID.
         for (int iu = 0; iu < NU; iu++) {
             final int uid = uidStats.keyAt(iu);
             if (reqUid >= 0 && uid != reqUid) {
@@ -3587,7 +3794,7 @@
                 linePrefix = printWakeLockCheckin(sb, wl.getWakeTime(WAKE_TYPE_WINDOW),
                         rawRealtime, "w", which, linePrefix);
 
-                // Only log if we had at lease one wakelock...
+                // Only log if we had at least one wakelock...
                 if (sb.length() > 0) {
                     String name = wakelocks.keyAt(iw);
                     if (name.indexOf(',') >= 0) {
@@ -3831,6 +4038,7 @@
                 which);
         final long batteryTimeRemaining = computeBatteryTimeRemaining(rawRealtime);
         final long chargeTimeRemaining = computeChargeTimeRemaining(rawRealtime);
+        final long screenDozeTime = getScreenDozeTime(rawRealtime, which);
 
         final StringBuilder sb = new StringBuilder(128);
 
@@ -3868,25 +4076,35 @@
 
         sb.setLength(0);
         sb.append(prefix);
-                sb.append("  Time on battery: ");
-                formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
-                sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
-                sb.append(") realtime, ");
-                formatTimeMs(sb, whichBatteryUptime / 1000);
-                sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, totalRealtime));
-                sb.append(") uptime");
+        sb.append("  Time on battery: ");
+        formatTimeMs(sb, whichBatteryRealtime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryRealtime, totalRealtime));
+        sb.append(") realtime, ");
+        formatTimeMs(sb, whichBatteryUptime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(whichBatteryUptime, whichBatteryRealtime));
+        sb.append(") uptime");
         pw.println(sb.toString());
+
         sb.setLength(0);
         sb.append(prefix);
-                sb.append("  Time on battery screen off: ");
-                formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
-                sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, totalRealtime));
-                sb.append(") realtime, ");
-                formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
-                sb.append("(");
-                sb.append(formatRatioLocked(whichBatteryScreenOffUptime, totalRealtime));
-                sb.append(") uptime");
+        sb.append("  Time on battery screen off: ");
+        formatTimeMs(sb, whichBatteryScreenOffRealtime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryScreenOffRealtime, whichBatteryRealtime));
+        sb.append(") realtime, ");
+        formatTimeMs(sb, whichBatteryScreenOffUptime / 1000);
+        sb.append("(");
+        sb.append(formatRatioLocked(whichBatteryScreenOffUptime, whichBatteryRealtime));
+        sb.append(") uptime");
         pw.println(sb.toString());
+
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Time on battery screen doze: ");
+        formatTimeMs(sb, screenDozeTime / 1000); sb.append("(");
+        sb.append(formatRatioLocked(screenDozeTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
         sb.setLength(0);
         sb.append(prefix);
                 sb.append("  Total run time: ");
@@ -3910,8 +4128,7 @@
             pw.println(sb.toString());
         }
 
-        final LongCounter dischargeCounter = getDischargeCoulombCounter();
-        final long dischargeCount = dischargeCounter.getCountLocked(which);
+        final long dischargeCount = getUahDischarge(which);
         if (dischargeCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -3921,8 +4138,7 @@
             pw.println(sb.toString());
         }
 
-        final LongCounter dischargeScreenOffCounter = getDischargeScreenOffCoulombCounter();
-        final long dischargeScreenOffCount = dischargeScreenOffCounter.getCountLocked(which);
+        final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
         if (dischargeScreenOffCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -3932,7 +4148,18 @@
             pw.println(sb.toString());
         }
 
-        final long dischargeScreenOnCount = dischargeCount - dischargeScreenOffCount;
+        final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+        if (dischargeScreenDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Screen doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeScreenDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeScreenOnCount =
+                dischargeCount - dischargeScreenOffCount - dischargeScreenDozeCount;
         if (dischargeScreenOnCount >= 0) {
             sb.setLength(0);
             sb.append(prefix);
@@ -4127,51 +4354,50 @@
             pw.println(sb.toString());
         }
 
+        pw.println("");
         pw.print(prefix);
-                pw.print("  Mobile total received: "); pw.print(formatBytesLocked(mobileRxTotalBytes));
-                pw.print(", sent: "); pw.print(formatBytesLocked(mobileTxTotalBytes));
-                pw.print(" (packets received "); pw.print(mobileRxTotalPackets);
-                pw.print(", sent "); pw.print(mobileTxTotalPackets); pw.println(")");
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Phone signal levels:");
-        didOne = false;
-        for (int i=0; i<SignalStrength.NUM_SIGNAL_STRENGTH_BINS; i++) {
-            final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
-            if (time == 0) {
-                continue;
-            }
-            sb.append("\n    ");
-            sb.append(prefix);
-            didOne = true;
-            sb.append(SignalStrength.SIGNAL_STRENGTH_NAMES[i]);
-            sb.append(" ");
-            formatTimeMs(sb, time/1000);
-            sb.append("(");
-            sb.append(formatRatioLocked(time, whichBatteryRealtime));
-            sb.append(") ");
-            sb.append(getPhoneSignalStrengthCount(i, which));
-            sb.append("x");
-        }
-        if (!didOne) sb.append(" (no activity)");
+        sb.append("  CONNECTIVITY POWER SUMMARY START");
+        pw.println(sb.toString());
+
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  Logging duration for connectivity statistics: ");
+        formatTimeMs(sb, whichBatteryRealtime / 1000);
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Signal scanning time: ");
-        formatTimeMsNoSpace(sb, getPhoneSignalScanningTime(rawRealtime, which) / 1000);
+        sb.append("  Cellular Statistics:");
         pw.println(sb.toString());
 
+        pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("     Cellular kernel active time: ");
+        final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
+        formatTimeMs(sb, mobileActiveTime / 1000);
+        sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
+        sb.append(")");
+        pw.println(sb.toString());
+
+        pw.print("     Cellular data received: "); pw.println(formatBytesLocked(mobileRxTotalBytes));
+        pw.print("     Cellular data sent: "); pw.println(formatBytesLocked(mobileTxTotalBytes));
+        pw.print("     Cellular packets received: "); pw.println(mobileRxTotalPackets);
+        pw.print("     Cellular packets sent: "); pw.println(mobileTxTotalPackets);
+
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Radio types:");
+        sb.append("     Cellular Radio Access Technology:");
         didOne = false;
         for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
             final long time = getPhoneDataConnectionTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
             }
-            sb.append("\n    ");
+            sb.append("\n       ");
             sb.append(prefix);
             didOne = true;
             sb.append(DATA_CONNECTION_NAMES[i]);
@@ -4180,73 +4406,64 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getPhoneDataConnectionCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Mobile radio active time: ");
-        final long mobileActiveTime = getMobileRadioActiveTime(rawRealtime, which);
-        formatTimeMs(sb, mobileActiveTime / 1000);
-        sb.append("("); sb.append(formatRatioLocked(mobileActiveTime, whichBatteryRealtime));
-        sb.append(") "); sb.append(getMobileRadioActiveCount(which));
-        sb.append("x");
+        sb.append("     Cellular Rx signal strength (RSRP):");
+        final String[] cellularRxSignalStrengthDescription = new String[]{
+            "very poor (less than -128dBm): ",
+            "poor (-128dBm to -118dBm): ",
+            "moderate (-118dBm to -108dBm): ",
+            "good (-108dBm to -98dBm): ",
+            "great (greater than -98dBm): "};
+        didOne = false;
+        final int numCellularRxBins = Math.min(SignalStrength.NUM_SIGNAL_STRENGTH_BINS,
+            cellularRxSignalStrengthDescription.length);
+        for (int i=0; i<numCellularRxBins; i++) {
+            final long time = getPhoneSignalStrengthTime(i, rawRealtime, which);
+            if (time == 0) {
+                continue;
+            }
+            sb.append("\n       ");
+            sb.append(prefix);
+            didOne = true;
+            sb.append(cellularRxSignalStrengthDescription[i]);
+            sb.append(" ");
+            formatTimeMs(sb, time/1000);
+            sb.append("(");
+            sb.append(formatRatioLocked(time, whichBatteryRealtime));
+            sb.append(") ");
+        }
+        if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
-        final long mobileActiveUnknownTime = getMobileRadioActiveUnknownTime(which);
-        if (mobileActiveUnknownTime != 0) {
-            sb.setLength(0);
-            sb.append(prefix);
-            sb.append("  Mobile radio active unknown time: ");
-            formatTimeMs(sb, mobileActiveUnknownTime / 1000);
-            sb.append("(");
-            sb.append(formatRatioLocked(mobileActiveUnknownTime, whichBatteryRealtime));
-            sb.append(") "); sb.append(getMobileRadioActiveUnknownCount(which));
-            sb.append("x");
-            pw.println(sb.toString());
-        }
-
-        final long mobileActiveAdjustedTime = getMobileRadioActiveAdjustedTime(which);
-        if (mobileActiveAdjustedTime != 0) {
-            sb.setLength(0);
-            sb.append(prefix);
-            sb.append("  Mobile radio active adjusted time: ");
-            formatTimeMs(sb, mobileActiveAdjustedTime / 1000);
-            sb.append("(");
-            sb.append(formatRatioLocked(mobileActiveAdjustedTime, whichBatteryRealtime));
-            sb.append(")");
-            pw.println(sb.toString());
-        }
-
-        printControllerActivity(pw, sb, prefix, "Radio", getModemControllerActivity(), which);
+        printControllerActivity(pw, sb, prefix, "Cellular",
+            getModemControllerActivity(), which);
 
         pw.print(prefix);
-                pw.print("  Wi-Fi total received: "); pw.print(formatBytesLocked(wifiRxTotalBytes));
-                pw.print(", sent: "); pw.print(formatBytesLocked(wifiTxTotalBytes));
-                pw.print(" (packets received "); pw.print(wifiRxTotalPackets);
-                pw.print(", sent "); pw.print(wifiTxTotalPackets); pw.println(")");
         sb.setLength(0);
         sb.append(prefix);
-                sb.append("  Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000);
-                sb.append("("); sb.append(formatRatioLocked(wifiOnTime, whichBatteryRealtime));
-                sb.append("), Wifi running: "); formatTimeMs(sb, wifiRunningTime / 1000);
-                sb.append("("); sb.append(formatRatioLocked(wifiRunningTime, whichBatteryRealtime));
-                sb.append(")");
+        sb.append("  Wifi Statistics:");
         pw.println(sb.toString());
 
+        pw.print("     Wifi data received: "); pw.println(formatBytesLocked(wifiRxTotalBytes));
+        pw.print("     Wifi data sent: "); pw.println(formatBytesLocked(wifiTxTotalBytes));
+        pw.print("     Wifi packets received: "); pw.println(wifiRxTotalPackets);
+        pw.print("     Wifi packets sent: "); pw.println(wifiTxTotalPackets);
+
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Wifi states:");
+        sb.append("     Wifi states:");
         didOne = false;
         for (int i=0; i<NUM_WIFI_STATES; i++) {
             final long time = getWifiStateTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
             }
-            sb.append("\n    ");
+            sb.append("\n       ");
             didOne = true;
             sb.append(WIFI_STATE_NAMES[i]);
             sb.append(" ");
@@ -4254,22 +4471,20 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getWifiStateCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Wifi supplicant states:");
+        sb.append("     Wifi supplicant states:");
         didOne = false;
         for (int i=0; i<NUM_WIFI_SUPPL_STATES; i++) {
             final long time = getWifiSupplStateTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
             }
-            sb.append("\n    ");
+            sb.append("\n       ");
             didOne = true;
             sb.append(WIFI_SUPPL_STATE_NAMES[i]);
             sb.append(" ");
@@ -4277,17 +4492,23 @@
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getWifiSupplStateCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
 
         sb.setLength(0);
         sb.append(prefix);
-        sb.append("  Wifi signal levels:");
+        sb.append("     Wifi Rx signal strength (RSSI):");
+        final String[] wifiRxSignalStrengthDescription = new String[]{
+            "very poor (less than -88.75dBm): ",
+            "poor (-88.75 to -77.5dBm): ",
+            "moderate (-77.5dBm to -66.25dBm): ",
+            "good (-66.25dBm to -55dBm): ",
+            "great (greater than -55dBm): "};
         didOne = false;
-        for (int i=0; i<NUM_WIFI_SIGNAL_STRENGTH_BINS; i++) {
+        final int numWifiRxBins = Math.min(NUM_WIFI_SIGNAL_STRENGTH_BINS,
+            wifiRxSignalStrengthDescription.length);
+        for (int i=0; i<numWifiRxBins; i++) {
             final long time = getWifiSignalStrengthTime(i, rawRealtime, which);
             if (time == 0) {
                 continue;
@@ -4295,15 +4516,12 @@
             sb.append("\n    ");
             sb.append(prefix);
             didOne = true;
-            sb.append("level(");
-            sb.append(i);
-            sb.append(") ");
+            sb.append("     ");
+            sb.append(wifiRxSignalStrengthDescription[i]);
             formatTimeMs(sb, time/1000);
             sb.append("(");
             sb.append(formatRatioLocked(time, whichBatteryRealtime));
             sb.append(") ");
-            sb.append(getWifiSignalStrengthCount(i, which));
-            sb.append("x");
         }
         if (!didOne) sb.append(" (no activity)");
         pw.println(sb.toString());
@@ -4311,6 +4529,13 @@
         printControllerActivity(pw, sb, prefix, "WiFi", getWifiControllerActivity(), which);
 
         pw.print(prefix);
+        sb.setLength(0);
+        sb.append(prefix);
+        sb.append("  CONNECTIVITY POWER SUMMARY END");
+        pw.println(sb.toString());
+        pw.println("");
+
+        pw.print(prefix);
         pw.print("  Bluetooth total received: "); pw.print(formatBytesLocked(btRxTotalBytes));
         pw.print(", sent: "); pw.println(formatBytesLocked(btTxTotalBytes));
 
@@ -4340,20 +4565,24 @@
                         pw.println(getDischargeCurrentLevel());
             }
             pw.print(prefix); pw.print("    Amount discharged while screen on: ");
-                    pw.println(getDischargeAmountScreenOn());
+            pw.println(getDischargeAmountScreenOn());
             pw.print(prefix); pw.print("    Amount discharged while screen off: ");
-                    pw.println(getDischargeAmountScreenOff());
+            pw.println(getDischargeAmountScreenOff());
+            pw.print(prefix); pw.print("    Amount discharged while screen doze: ");
+            pw.println(getDischargeAmountScreenDoze());
             pw.println(" ");
         } else {
             pw.print(prefix); pw.println("  Device battery use since last full charge");
             pw.print(prefix); pw.print("    Amount discharged (lower bound): ");
-                    pw.println(getLowDischargeAmountSinceCharge());
+            pw.println(getLowDischargeAmountSinceCharge());
             pw.print(prefix); pw.print("    Amount discharged (upper bound): ");
-                    pw.println(getHighDischargeAmountSinceCharge());
+            pw.println(getHighDischargeAmountSinceCharge());
             pw.print(prefix); pw.print("    Amount discharged while screen on: ");
-                    pw.println(getDischargeAmountScreenOnSinceCharge());
+            pw.println(getDischargeAmountScreenOnSinceCharge());
             pw.print(prefix); pw.print("    Amount discharged while screen off: ");
-                    pw.println(getDischargeAmountScreenOffSinceCharge());
+            pw.println(getDischargeAmountScreenOffSinceCharge());
+            pw.print(prefix); pw.print("    Amount discharged while screen doze: ");
+            pw.println(getDischargeAmountScreenDozeSinceCharge());
             pw.println();
         }
 
@@ -5426,8 +5655,9 @@
             }
         }
     }
-    
+
     public void prepareForDumpLocked() {
+        // We don't need to require subclasses implement this.
     }
 
     public static class HistoryPrinter {
@@ -5914,6 +6144,60 @@
         return true;
     }
 
+    private static void dumpDurationSteps(ProtoOutputStream proto, long fieldId,
+            LevelStepTracker steps) {
+        if (steps == null) {
+            return;
+        }
+        int count = steps.mNumStepDurations;
+        for (int i = 0; i < count; ++i) {
+            long token = proto.start(fieldId);
+            proto.write(SystemProto.BatteryLevelStep.DURATION_MS, steps.getDurationAt(i));
+            proto.write(SystemProto.BatteryLevelStep.LEVEL, steps.getLevelAt(i));
+
+            final long initMode = steps.getInitModeAt(i);
+            final long modMode = steps.getModModeAt(i);
+
+            int ds = SystemProto.BatteryLevelStep.DS_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_SCREEN_STATE) == 0) {
+                switch ((int) (initMode & STEP_LEVEL_MODE_SCREEN_STATE) + 1) {
+                    case Display.STATE_OFF:
+                        ds = SystemProto.BatteryLevelStep.DS_OFF;
+                        break;
+                    case Display.STATE_ON:
+                        ds = SystemProto.BatteryLevelStep.DS_ON;
+                        break;
+                    case Display.STATE_DOZE:
+                        ds = SystemProto.BatteryLevelStep.DS_DOZE;
+                        break;
+                    case Display.STATE_DOZE_SUSPEND:
+                        ds = SystemProto.BatteryLevelStep.DS_DOZE_SUSPEND;
+                        break;
+                    default:
+                        ds = SystemProto.BatteryLevelStep.DS_ERROR;
+                        break;
+                }
+            }
+            proto.write(SystemProto.BatteryLevelStep.DISPLAY_STATE, ds);
+
+            int psm = SystemProto.BatteryLevelStep.PSM_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_POWER_SAVE) == 0) {
+                psm = (initMode & STEP_LEVEL_MODE_POWER_SAVE) != 0
+                    ? SystemProto.BatteryLevelStep.PSM_ON : SystemProto.BatteryLevelStep.PSM_OFF;
+            }
+            proto.write(SystemProto.BatteryLevelStep.POWER_SAVE_MODE, psm);
+
+            int im = SystemProto.BatteryLevelStep.IM_MIXED;
+            if ((modMode & STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
+                im = (initMode & STEP_LEVEL_MODE_DEVICE_IDLE) != 0
+                    ? SystemProto.BatteryLevelStep.IM_ON : SystemProto.BatteryLevelStep.IM_OFF;
+            }
+            proto.write(SystemProto.BatteryLevelStep.IDLE_MODE, im);
+
+            proto.end(token);
+        }
+    }
+
     public static final int DUMP_CHARGED_ONLY = 1<<1;
     public static final int DUMP_DAILY_ONLY = 1<<2;
     public static final int DUMP_HISTORY_ONLY = 1<<3;
@@ -6169,7 +6453,7 @@
                 pw.println();
             }
         }
-        if (!filtering || (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0) {
+        if (!filtering || (flags & DUMP_DAILY_ONLY) != 0) {
             pw.println("Daily stats:");
             pw.print("  Current start time: ");
             pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss",
@@ -6248,7 +6532,8 @@
             pw.println();
         }
     }
-    
+
+    // This is called from BatteryStatsService.
     @SuppressWarnings("unused")
     public void dumpCheckinLocked(Context context, PrintWriter pw,
             List<ApplicationInfo> apps, int flags, long histStart) {
@@ -6260,10 +6545,7 @@
 
         long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
 
-        final boolean filtering = (flags &
-                (DUMP_HISTORY_ONLY|DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) != 0;
-
-        if ((flags&DUMP_INCLUDE_HISTORY) != 0 || (flags&DUMP_HISTORY_ONLY) != 0) {
+        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
             if (startIteratingHistoryLocked()) {
                 try {
                     for (int i=0; i<getHistoryStringPoolSize(); i++) {
@@ -6287,7 +6569,7 @@
             }
         }
 
-        if (filtering && (flags&(DUMP_CHARGED_ONLY|DUMP_DAILY_ONLY)) == 0) {
+        if ((flags & DUMP_HISTORY_ONLY) != 0) {
             return;
         }
 
@@ -6320,7 +6602,7 @@
                 }
             }
         }
-        if (!filtering || (flags&DUMP_CHARGED_ONLY) != 0) {
+        if ((flags & DUMP_DAILY_ONLY) == 0) {
             dumpDurationSteps(pw, "", DISCHARGE_STEP_DATA, getDischargeLevelStepTracker(), true);
             String[] lineArgs = new String[1];
             long timeRemaining = computeBatteryTimeRemaining(SystemClock.elapsedRealtime() * 1000);
@@ -6340,4 +6622,824 @@
                     (flags&DUMP_DEVICE_WIFI_ONLY) != 0);
         }
     }
+
+    /** Dump #STATS_SINCE_CHARGED batterystats data to a proto. @hide */
+    public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps,
+            int flags, long historyStart) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS);
+        prepareForDumpLocked();
+
+        proto.write(BatteryStatsProto.REPORT_VERSION, CHECKIN_VERSION);
+        proto.write(BatteryStatsProto.PARCEL_VERSION, getParcelVersion());
+        proto.write(BatteryStatsProto.START_PLATFORM_VERSION, getStartPlatformVersion());
+        proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion());
+
+        long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+
+        if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
+            if (startIteratingHistoryLocked()) {
+                // TODO: implement dumpProtoHistoryLocked(proto);
+            }
+        }
+
+        if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) {
+            final BatteryStatsHelper helper = new BatteryStatsHelper(context, false,
+                    (flags & DUMP_DEVICE_WIFI_ONLY) != 0);
+            helper.create(this);
+            helper.refreshStats(STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+
+            dumpProtoAppsLocked(proto, helper, apps);
+            dumpProtoSystemLocked(proto, helper);
+        }
+
+        proto.end(bToken);
+        proto.flush();
+    }
+
+    private void dumpProtoAppsLocked(ProtoOutputStream proto, BatteryStatsHelper helper,
+            List<ApplicationInfo> apps) {
+        final int which = STATS_SINCE_CHARGED;
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final long batteryUptimeUs = getBatteryUptime(rawUptimeUs);
+
+        SparseArray<ArrayList<String>> aidToPackages = new SparseArray<>();
+        if (apps != null) {
+            for (int i = 0; i < apps.size(); ++i) {
+                ApplicationInfo ai = apps.get(i);
+                int aid = UserHandle.getAppId(ai.uid);
+                ArrayList<String> pkgs = aidToPackages.get(aid);
+                if (pkgs == null) {
+                    pkgs = new ArrayList<String>();
+                    aidToPackages.put(aid, pkgs);
+                }
+                pkgs.add(ai.packageName);
+            }
+        }
+
+        SparseArray<BatterySipper> uidToSipper = new SparseArray<>();
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                if (bs.drainType != BatterySipper.DrainType.APP) {
+                    // Others are handled by dumpProtoSystemLocked()
+                    continue;
+                }
+                uidToSipper.put(bs.uidObj.getUid(), bs);
+            }
+        }
+
+        SparseArray<? extends Uid> uidStats = getUidStats();
+        final int n = uidStats.size();
+        for (int iu = 0; iu < n; ++iu) {
+            final long uTkn = proto.start(BatteryStatsProto.UIDS);
+            final Uid u = uidStats.valueAt(iu);
+
+            final int uid = uidStats.keyAt(iu);
+            proto.write(UidProto.UID, uid);
+
+            // Print packages and apk stats (UID_DATA & APK_DATA)
+            ArrayList<String> pkgs = aidToPackages.get(UserHandle.getAppId(uid));
+            if (pkgs == null) {
+                pkgs = new ArrayList<String>();
+            }
+            final ArrayMap<String, ? extends BatteryStats.Uid.Pkg> packageStats =
+                    u.getPackageStats();
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                String pkg = packageStats.keyAt(ipkg);
+                final ArrayMap<String, ? extends  Uid.Pkg.Serv> serviceStats =
+                        packageStats.valueAt(ipkg).getServiceStats();
+                if (serviceStats.size() == 0) {
+                    // Due to the way ActivityManagerService logs wakeup alarms, some packages (for
+                    // example, "android") may be included in the packageStats that aren't part of
+                    // the UID. If they don't have any services, then they shouldn't be listed here.
+                    // These packages won't be a part in the pkgs List.
+                    continue;
+                }
+
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, pkg);
+                // Remove from the packages list since we're logging it here.
+                pkgs.remove(pkg);
+
+                for (int isvc = serviceStats.size() - 1; isvc >= 0; --isvc) {
+                    final BatteryStats.Uid.Pkg.Serv ss = serviceStats.valueAt(isvc);
+                    long sToken = proto.start(UidProto.Package.SERVICES);
+
+                    proto.write(UidProto.Package.Service.NAME, serviceStats.keyAt(isvc));
+                    proto.write(UidProto.Package.Service.START_DURATION_MS,
+                            roundUsToMs(ss.getStartTime(batteryUptimeUs, which)));
+                    proto.write(UidProto.Package.Service.START_COUNT, ss.getStarts(which));
+                    proto.write(UidProto.Package.Service.LAUNCH_COUNT, ss.getLaunches(which));
+
+                    proto.end(sToken);
+                }
+                proto.end(pToken);
+            }
+            // Print any remaining packages that weren't in the packageStats map. pkgs is pulled
+            // from PackageManager data. Packages are only included in packageStats if there was
+            // specific data tracked for them (services and wakeup alarms, etc.).
+            for (String p : pkgs) {
+                final long pToken = proto.start(UidProto.PACKAGES);
+                proto.write(UidProto.Package.NAME, p);
+                proto.end(pToken);
+            }
+
+            // Total wakelock data (AGGREGATED_WAKELOCK_DATA)
+            if (u.getAggregatedPartialWakelockTimer() != null) {
+                final Timer timer = u.getAggregatedPartialWakelockTimer();
+                // Times are since reset (regardless of 'which')
+                final long totTimeMs = timer.getTotalDurationMsLocked(rawRealtimeMs);
+                final Timer bgTimer = timer.getSubTimer();
+                final long bgTimeMs = bgTimer != null
+                        ? bgTimer.getTotalDurationMsLocked(rawRealtimeMs) : 0;
+                final long awToken = proto.start(UidProto.AGGREGATED_WAKELOCK);
+                proto.write(UidProto.AggregatedWakelock.PARTIAL_DURATION_MS, totTimeMs);
+                proto.write(UidProto.AggregatedWakelock.BACKGROUND_PARTIAL_DURATION_MS, bgTimeMs);
+                proto.end(awToken);
+            }
+
+            // Audio (AUDIO_DATA)
+            dumpTimer(proto, UidProto.AUDIO, u.getAudioTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Bluetooth Controller (BLUETOOTH_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.BLUETOOTH_CONTROLLER,
+                    u.getBluetoothControllerActivity(), which);
+
+            // BLE scans (BLUETOOTH_MISC_DATA) (uses totalDurationMsLocked and MaxDurationMsLocked)
+            final Timer bleTimer = u.getBluetoothScanTimer();
+            if (bleTimer != null) {
+                final long bmToken = proto.start(UidProto.BLUETOOTH_MISC);
+
+                dumpTimer(proto, UidProto.BluetoothMisc.APPORTIONED_BLE_SCAN, bleTimer,
+                        rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN,
+                        u.getBluetoothScanBackgroundTimer(), rawRealtimeUs, which);
+                // Unoptimized scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanTimer(), rawRealtimeUs, which);
+                // Unoptimized bg scan timer. Unpooled and since reset (regardless of 'which').
+                dumpTimer(proto, UidProto.BluetoothMisc.BACKGROUND_UNOPTIMIZED_BLE_SCAN,
+                        u.getBluetoothUnoptimizedScanBackgroundTimer(), rawRealtimeUs, which);
+                // Result counters
+                proto.write(UidProto.BluetoothMisc.BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultCounter() != null
+                            ? u.getBluetoothScanResultCounter().getCountLocked(which) : 0);
+                proto.write(UidProto.BluetoothMisc.BACKGROUND_BLE_SCAN_RESULT_COUNT,
+                        u.getBluetoothScanResultBgCounter() != null
+                            ? u.getBluetoothScanResultBgCounter().getCountLocked(which) : 0);
+
+                proto.end(bmToken);
+            }
+
+            // Camera (CAMERA_DATA)
+            dumpTimer(proto, UidProto.CAMERA, u.getCameraTurnedOnTimer(), rawRealtimeUs, which);
+
+            // CPU stats (CPU_DATA & CPU_TIMES_AT_FREQ_DATA)
+            final long cpuToken = proto.start(UidProto.CPU);
+            proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which)));
+            proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which)));
+
+            final long[] cpuFreqs = getCpuFreqs();
+            if (cpuFreqs != null) {
+                final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which);
+                // If total cpuFreqTimes is null, then we don't need to check for
+                // screenOffCpuFreqTimes.
+                if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) {
+                    long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which);
+                    if (screenOffCpuFreqTimeMs == null) {
+                        screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length];
+                    }
+                    for (int ic = 0; ic < cpuFreqTimeMs.length; ++ic) {
+                        long cToken = proto.start(UidProto.Cpu.BY_FREQUENCY);
+                        proto.write(UidProto.Cpu.ByFrequency.FREQUENCY_INDEX, ic + 1);
+                        proto.write(UidProto.Cpu.ByFrequency.TOTAL_DURATION_MS,
+                                cpuFreqTimeMs[ic]);
+                        proto.write(UidProto.Cpu.ByFrequency.SCREEN_OFF_DURATION_MS,
+                                screenOffCpuFreqTimeMs[ic]);
+                        proto.end(cToken);
+                    }
+                }
+            }
+            proto.end(cpuToken);
+
+            // Flashlight (FLASHLIGHT_DATA)
+            dumpTimer(proto, UidProto.FLASHLIGHT, u.getFlashlightTurnedOnTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground activity (FOREGROUND_ACTIVITY_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_ACTIVITY, u.getForegroundActivityTimer(),
+                    rawRealtimeUs, which);
+
+            // Foreground service (FOREGROUND_SERVICE_DATA)
+            dumpTimer(proto, UidProto.FOREGROUND_SERVICE, u.getForegroundServiceTimer(),
+                    rawRealtimeUs, which);
+
+            // Job completion (JOB_COMPLETION_DATA)
+            final ArrayMap<String, SparseIntArray> completions = u.getJobCompletionStats();
+            final int[] reasons = new int[]{
+                JobParameters.REASON_CANCELED,
+                JobParameters.REASON_CONSTRAINTS_NOT_SATISFIED,
+                JobParameters.REASON_PREEMPT,
+                JobParameters.REASON_TIMEOUT,
+                JobParameters.REASON_DEVICE_IDLE,
+            };
+            for (int ic = 0; ic < completions.size(); ++ic) {
+                SparseIntArray types = completions.valueAt(ic);
+                if (types != null) {
+                    final long jcToken = proto.start(UidProto.JOB_COMPLETION);
+
+                    proto.write(UidProto.JobCompletion.NAME, completions.keyAt(ic));
+
+                    for (int r : reasons) {
+                        long rToken = proto.start(UidProto.JobCompletion.REASON_COUNT);
+                        proto.write(UidProto.JobCompletion.ReasonCount.NAME, r);
+                        proto.write(UidProto.JobCompletion.ReasonCount.COUNT, types.get(r, 0));
+                        proto.end(rToken);
+                    }
+
+                    proto.end(jcToken);
+                }
+            }
+
+            // Scheduled jobs (JOB_DATA)
+            final ArrayMap<String, ? extends Timer> jobs = u.getJobStats();
+            for (int ij = jobs.size() - 1; ij >= 0; --ij) {
+                final Timer timer = jobs.valueAt(ij);
+                final Timer bgTimer = timer.getSubTimer();
+                final long jToken = proto.start(UidProto.JOBS);
+
+                proto.write(UidProto.Job.NAME, jobs.keyAt(ij));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Job.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Job.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(jToken);
+            }
+
+            // Modem Controller (MODEM_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.MODEM_CONTROLLER,
+                    u.getModemControllerActivity(), which);
+
+            // Network stats (NETWORK_DATA)
+            final long nToken = proto.start(UidProto.NETWORK);
+            proto.write(UidProto.Network.MOBILE_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_RX,
+                    u.getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+            proto.write(UidProto.Network.BT_BYTES_TX,
+                    u.getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_DURATION_MS,
+                    roundUsToMs(u.getMobileRadioActiveTime(which)));
+            proto.write(UidProto.Network.MOBILE_ACTIVE_COUNT,
+                    u.getMobileRadioActiveCount(which));
+            proto.write(UidProto.Network.MOBILE_WAKEUP_COUNT,
+                    u.getMobileRadioApWakeupCount(which));
+            proto.write(UidProto.Network.WIFI_WAKEUP_COUNT,
+                    u.getWifiRadioApWakeupCount(which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_RX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_BYTES_BG_TX,
+                    u.getNetworkActivityBytes(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_RX_DATA, which));
+            proto.write(UidProto.Network.MOBILE_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_MOBILE_BG_TX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_RX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_RX_DATA, which));
+            proto.write(UidProto.Network.WIFI_PACKETS_BG_TX,
+                    u.getNetworkActivityPackets(NETWORK_WIFI_BG_TX_DATA, which));
+            proto.end(nToken);
+
+            // Power use item (POWER_USE_ITEM_DATA)
+            BatterySipper bs = uidToSipper.get(uid);
+            if (bs != null) {
+                final long bsToken = proto.start(UidProto.POWER_USE_ITEM);
+                proto.write(UidProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(UidProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(UidProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(UidProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(bsToken);
+            }
+
+            // Processes (PROCESS_DATA)
+            final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats =
+                    u.getProcessStats();
+            for (int ipr = processStats.size() - 1; ipr >= 0; --ipr) {
+                final Uid.Proc ps = processStats.valueAt(ipr);
+                final long prToken = proto.start(UidProto.PROCESS);
+
+                proto.write(UidProto.Process.NAME, processStats.keyAt(ipr));
+                proto.write(UidProto.Process.USER_DURATION_MS, ps.getUserTime(which));
+                proto.write(UidProto.Process.SYSTEM_DURATION_MS, ps.getSystemTime(which));
+                proto.write(UidProto.Process.FOREGROUND_DURATION_MS, ps.getForegroundTime(which));
+                proto.write(UidProto.Process.START_COUNT, ps.getStarts(which));
+                proto.write(UidProto.Process.ANR_COUNT, ps.getNumAnrs(which));
+                proto.write(UidProto.Process.CRASH_COUNT, ps.getNumCrashes(which));
+
+                proto.end(prToken);
+            }
+
+            // Sensors (SENSOR_DATA)
+            final SparseArray<? extends BatteryStats.Uid.Sensor> sensors = u.getSensorStats();
+            for (int ise = 0; ise < sensors.size(); ++ise) {
+                final Uid.Sensor se = sensors.valueAt(ise);
+                final Timer timer = se.getSensorTime();
+                if (timer == null) {
+                    continue;
+                }
+                final Timer bgTimer = se.getSensorBackgroundTime();
+                final int sensorNumber = sensors.keyAt(ise);
+                final long seToken = proto.start(UidProto.SENSORS);
+
+                proto.write(UidProto.Sensor.ID, sensorNumber);
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sensor.APPORTIONED, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sensor.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(seToken);
+            }
+
+            // State times (STATE_TIME_DATA)
+            for (int ips = 0; ips < Uid.NUM_PROCESS_STATE; ++ips) {
+                long durMs = roundUsToMs(u.getProcessStateTime(ips, rawRealtimeUs, which));
+                if (durMs == 0) {
+                    continue;
+                }
+                final long stToken = proto.start(UidProto.STATES);
+                proto.write(UidProto.StateTime.STATE, ips);
+                proto.write(UidProto.StateTime.DURATION_MS, durMs);
+                proto.end(stToken);
+            }
+
+            // Syncs (SYNC_DATA)
+            final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats();
+            for (int isy = syncs.size() - 1; isy >= 0; --isy) {
+                final Timer timer = syncs.valueAt(isy);
+                final Timer bgTimer = timer.getSubTimer();
+                final long syToken = proto.start(UidProto.SYNCS);
+
+                proto.write(UidProto.Sync.NAME, syncs.keyAt(isy));
+                // Background uses totalDurationMsLocked, while total uses totalTimeLocked
+                dumpTimer(proto, UidProto.Sync.TOTAL, timer, rawRealtimeUs, which);
+                dumpTimer(proto, UidProto.Sync.BACKGROUND, bgTimer, rawRealtimeUs, which);
+
+                proto.end(syToken);
+            }
+
+            // User activity (USER_ACTIVITY_DATA)
+            if (u.hasUserActivity()) {
+                for (int i = 0; i < Uid.NUM_USER_ACTIVITY_TYPES; ++i) {
+                    int val = u.getUserActivityCount(i, which);
+                    if (val != 0) {
+                        final long uaToken = proto.start(UidProto.USER_ACTIVITY);
+                        proto.write(UidProto.UserActivity.NAME, i);
+                        proto.write(UidProto.UserActivity.COUNT, val);
+                        proto.end(uaToken);
+                    }
+                }
+            }
+
+            // Vibrator (VIBRATOR_DATA)
+            dumpTimer(proto, UidProto.VIBRATOR, u.getVibratorOnTimer(), rawRealtimeUs, which);
+
+            // Video (VIDEO_DATA)
+            dumpTimer(proto, UidProto.VIDEO, u.getVideoTurnedOnTimer(), rawRealtimeUs, which);
+
+            // Wakelocks (WAKELOCK_DATA)
+            final ArrayMap<String, ? extends Uid.Wakelock> wakelocks = u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+                final long wToken = proto.start(UidProto.WAKELOCKS);
+                proto.write(UidProto.Wakelock.NAME, wakelocks.keyAt(iw));
+                dumpTimer(proto, UidProto.Wakelock.FULL, wl.getWakeTime(WAKE_TYPE_FULL),
+                        rawRealtimeUs, which);
+                final Timer pTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (pTimer != null) {
+                    dumpTimer(proto, UidProto.Wakelock.PARTIAL, pTimer, rawRealtimeUs, which);
+                    dumpTimer(proto, UidProto.Wakelock.BACKGROUND_PARTIAL, pTimer.getSubTimer(),
+                            rawRealtimeUs, which);
+                }
+                dumpTimer(proto, UidProto.Wakelock.WINDOW, wl.getWakeTime(WAKE_TYPE_WINDOW),
+                        rawRealtimeUs, which);
+                proto.end(wToken);
+            }
+
+            // Wakeup alarms (WAKEUP_ALARM_DATA)
+            for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) {
+                final Uid.Pkg ps = packageStats.valueAt(ipkg);
+                final ArrayMap<String, ? extends Counter> alarms = ps.getWakeupAlarmStats();
+                for (int iwa = alarms.size() - 1; iwa >= 0; --iwa) {
+                    final long waToken = proto.start(UidProto.WAKEUP_ALARM);
+                    proto.write(UidProto.WakeupAlarm.NAME, alarms.keyAt(iwa));
+                    proto.write(UidProto.WakeupAlarm.COUNT,
+                            alarms.valueAt(iwa).getCountLocked(which));
+                    proto.end(waToken);
+                }
+            }
+
+            // Wifi Controller (WIFI_CONTROLLER_DATA)
+            dumpControllerActivityProto(proto, UidProto.WIFI_CONTROLLER,
+                    u.getWifiControllerActivity(), which);
+
+            // Wifi data (WIFI_DATA)
+            final long wToken = proto.start(UidProto.WIFI);
+            proto.write(UidProto.Wifi.FULL_WIFI_LOCK_DURATION_MS,
+                    roundUsToMs(u.getFullWifiLockTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.APPORTIONED_SCAN, u.getWifiScanTimer(),
+                    rawRealtimeUs, which);
+            proto.write(UidProto.Wifi.RUNNING_DURATION_MS,
+                    roundUsToMs(u.getWifiRunningTime(rawRealtimeUs, which)));
+            dumpTimer(proto, UidProto.Wifi.BACKGROUND_SCAN, u.getWifiScanBackgroundTimer(),
+                    rawRealtimeUs, which);
+            proto.end(wToken);
+
+            proto.end(uTkn);
+        }
+    }
+
+    private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) {
+        final long sToken = proto.start(BatteryStatsProto.SYSTEM);
+        final long rawUptimeUs = SystemClock.uptimeMillis() * 1000;
+        final long rawRealtimeMs = SystemClock.elapsedRealtime();
+        final long rawRealtimeUs = rawRealtimeMs * 1000;
+        final int which = STATS_SINCE_CHARGED;
+
+        // Battery data (BATTERY_DATA)
+        final long bToken = proto.start(SystemProto.BATTERY);
+        proto.write(SystemProto.Battery.START_CLOCK_TIME_MS, getStartClockTime());
+        proto.write(SystemProto.Battery.START_COUNT, getStartCount());
+        proto.write(SystemProto.Battery.TOTAL_REALTIME_MS,
+                computeRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.TOTAL_UPTIME_MS,
+                computeUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.BATTERY_REALTIME_MS,
+                computeBatteryRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.BATTERY_UPTIME_MS,
+                computeBatteryUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_OFF_REALTIME_MS,
+                computeBatteryScreenOffRealtime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_OFF_UPTIME_MS,
+                computeBatteryScreenOffUptime(rawUptimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.SCREEN_DOZE_DURATION_MS,
+                getScreenDozeTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Battery.ESTIMATED_BATTERY_CAPACITY_MAH,
+                getEstimatedBatteryCapacity());
+        proto.write(SystemProto.Battery.MIN_LEARNED_BATTERY_CAPACITY_UAH,
+                getMinLearnedBatteryCapacity());
+        proto.write(SystemProto.Battery.MAX_LEARNED_BATTERY_CAPACITY_UAH,
+                getMaxLearnedBatteryCapacity());
+        proto.end(bToken);
+
+        // Battery discharge (BATTERY_DISCHARGE_DATA)
+        final long bdToken = proto.start(SystemProto.BATTERY_DISCHARGE);
+        proto.write(SystemProto.BatteryDischarge.LOWER_BOUND_SINCE_CHARGE,
+                getLowDischargeAmountSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.UPPER_BOUND_SINCE_CHARGE,
+                getHighDischargeAmountSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_ON_SINCE_CHARGE,
+                getDischargeAmountScreenOnSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_OFF_SINCE_CHARGE,
+                getDischargeAmountScreenOffSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.SCREEN_DOZE_SINCE_CHARGE,
+                getDischargeAmountScreenDozeSinceCharge());
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH,
+                getUahDischarge(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_OFF,
+                getUahDischargeScreenOff(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
+                getUahDischargeScreenDoze(which) / 1000);
+        proto.end(bdToken);
+
+        // Time remaining
+        long timeRemainingUs = computeChargeTimeRemaining(rawRealtimeUs);
+        // These are part of a oneof, so we should only set one of them.
+        if (timeRemainingUs >= 0) {
+            // Charge time remaining (CHARGE_TIME_REMAIN_DATA)
+            proto.write(SystemProto.CHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+        } else {
+            timeRemainingUs = computeBatteryTimeRemaining(rawRealtimeUs);
+            // Discharge time remaining (DISCHARGE_TIME_REMAIN_DATA)
+            if (timeRemainingUs >= 0) {
+                proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, timeRemainingUs / 1000);
+            } else {
+                proto.write(SystemProto.DISCHARGE_TIME_REMAINING_MS, -1);
+            }
+        }
+
+        // Charge step (CHARGE_STEP_DATA)
+        dumpDurationSteps(proto, SystemProto.CHARGE_STEP, getChargeLevelStepTracker());
+
+        // Phone data connection (DATA_CONNECTION_TIME_DATA and DATA_CONNECTION_COUNT_DATA)
+        for (int i = 0; i < NUM_DATA_CONNECTION_TYPES; ++i) {
+            final long pdcToken = proto.start(SystemProto.DATA_CONNECTION);
+            proto.write(SystemProto.DataConnection.NAME, i);
+            dumpTimer(proto, SystemProto.DataConnection.TOTAL, getPhoneDataConnectionTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(pdcToken);
+        }
+
+        // Discharge step (DISCHARGE_STEP_DATA)
+        dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker());
+
+        // CPU frequencies (GLOBAL_CPU_FREQ_DATA)
+        final long[] cpuFreqs = getCpuFreqs();
+        if (cpuFreqs != null) {
+            for (long i : cpuFreqs) {
+                proto.write(SystemProto.CPU_FREQUENCY, i);
+            }
+        }
+
+        // Bluetooth controller (GLOBAL_BLUETOOTH_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_BLUETOOTH_CONTROLLER,
+                getBluetoothControllerActivity(), which);
+
+        // Modem controller (GLOBAL_MODEM_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_MODEM_CONTROLLER,
+                getModemControllerActivity(), which);
+
+        // Global network data (GLOBAL_NETWORK_DATA)
+        final long gnToken = proto.start(SystemProto.GLOBAL_NETWORK);
+        proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_MOBILE_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_RX,
+                getNetworkActivityPackets(NETWORK_MOBILE_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.MOBILE_PACKETS_TX,
+                getNetworkActivityPackets(NETWORK_MOBILE_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_WIFI_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_WIFI_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_RX,
+                getNetworkActivityPackets(NETWORK_WIFI_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.WIFI_PACKETS_TX,
+                getNetworkActivityPackets(NETWORK_WIFI_TX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.BT_BYTES_RX,
+                getNetworkActivityBytes(NETWORK_BT_RX_DATA, which));
+        proto.write(SystemProto.GlobalNetwork.BT_BYTES_TX,
+                getNetworkActivityBytes(NETWORK_BT_TX_DATA, which));
+        proto.end(gnToken);
+
+        // Wifi controller (GLOBAL_WIFI_CONTROLLER_DATA)
+        dumpControllerActivityProto(proto, SystemProto.GLOBAL_WIFI_CONTROLLER,
+                getWifiControllerActivity(), which);
+
+
+        // Global wifi (GLOBAL_WIFI_DATA)
+        final long gwToken = proto.start(SystemProto.GLOBAL_WIFI);
+        proto.write(SystemProto.GlobalWifi.ON_DURATION_MS,
+                getWifiOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.GlobalWifi.RUNNING_DURATION_MS,
+                getGlobalWifiRunningTime(rawRealtimeUs, which) / 1000);
+        proto.end(gwToken);
+
+        // Kernel wakelock (KERNEL_WAKELOCK_DATA)
+        final Map<String, ? extends Timer> kernelWakelocks = getKernelWakelockStats();
+        for (Map.Entry<String, ? extends Timer> ent : kernelWakelocks.entrySet()) {
+            final long kwToken = proto.start(SystemProto.KERNEL_WAKELOCK);
+            proto.write(SystemProto.KernelWakelock.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.KernelWakelock.TOTAL, ent.getValue(),
+                    rawRealtimeUs, which);
+            proto.end(kwToken);
+        }
+
+        // Misc (MISC_DATA)
+        // Calculate wakelock times across all uids.
+        long fullWakeLockTimeTotalUs = 0;
+        long partialWakeLockTimeTotalUs = 0;
+
+        final SparseArray<? extends Uid> uidStats = getUidStats();
+        for (int iu = 0; iu < uidStats.size(); iu++) {
+            final Uid u = uidStats.valueAt(iu);
+
+            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
+                    u.getWakelockStats();
+            for (int iw = wakelocks.size() - 1; iw >= 0; --iw) {
+                final Uid.Wakelock wl = wakelocks.valueAt(iw);
+
+                final Timer fullWakeTimer = wl.getWakeTime(WAKE_TYPE_FULL);
+                if (fullWakeTimer != null) {
+                    fullWakeLockTimeTotalUs += fullWakeTimer.getTotalTimeLocked(rawRealtimeUs,
+                            which);
+                }
+
+                final Timer partialWakeTimer = wl.getWakeTime(WAKE_TYPE_PARTIAL);
+                if (partialWakeTimer != null) {
+                    partialWakeLockTimeTotalUs += partialWakeTimer.getTotalTimeLocked(
+                        rawRealtimeUs, which);
+                }
+            }
+        }
+        final long mToken = proto.start(SystemProto.MISC);
+        proto.write(SystemProto.Misc.SCREEN_ON_DURATION_MS,
+                getScreenOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.PHONE_ON_DURATION_MS,
+                getPhoneOnTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.FULL_WAKELOCK_TOTAL_DURATION_MS,
+                fullWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.Misc.PARTIAL_WAKELOCK_TOTAL_DURATION_MS,
+                partialWakeLockTimeTotalUs / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_DURATION_MS,
+                getMobileRadioActiveTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_ADJUSTED_TIME_MS,
+                getMobileRadioActiveAdjustedTime(which) / 1000);
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_COUNT,
+                getMobileRadioActiveCount(which));
+        proto.write(SystemProto.Misc.MOBILE_RADIO_ACTIVE_UNKNOWN_DURATION_MS,
+                getMobileRadioActiveUnknownTime(which) / 1000);
+        proto.write(SystemProto.Misc.INTERACTIVE_DURATION_MS,
+                getInteractiveTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.BATTERY_SAVER_MODE_ENABLED_DURATION_MS,
+                getPowerSaveModeEnabledTime(rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.NUM_CONNECTIVITY_CHANGES,
+                getNumConnectivityChange(which));
+        proto.write(SystemProto.Misc.DEEP_DOZE_ENABLED_DURATION_MS,
+                getDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.DEEP_DOZE_COUNT,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_DEEP, which));
+        proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_DURATION_MS,
+                getDeviceIdlingTime(DEVICE_IDLE_MODE_DEEP, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.DEEP_DOZE_IDLING_COUNT,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_DEEP, which));
+        proto.write(SystemProto.Misc.LONGEST_DEEP_DOZE_DURATION_MS,
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_DEEP));
+        proto.write(SystemProto.Misc.LIGHT_DOZE_ENABLED_DURATION_MS,
+                getDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.LIGHT_DOZE_COUNT,
+                getDeviceIdleModeCount(DEVICE_IDLE_MODE_LIGHT, which));
+        proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_DURATION_MS,
+                getDeviceIdlingTime(DEVICE_IDLE_MODE_LIGHT, rawRealtimeUs, which) / 1000);
+        proto.write(SystemProto.Misc.LIGHT_DOZE_IDLING_COUNT,
+                getDeviceIdlingCount(DEVICE_IDLE_MODE_LIGHT, which));
+        proto.write(SystemProto.Misc.LONGEST_LIGHT_DOZE_DURATION_MS,
+                getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT));
+        proto.end(mToken);
+
+        // Power use item (POWER_USE_ITEM_DATA)
+        final List<BatterySipper> sippers = helper.getUsageList();
+        if (sippers != null) {
+            for (int i = 0; i < sippers.size(); ++i) {
+                final BatterySipper bs = sippers.get(i);
+                int n = SystemProto.PowerUseItem.UNKNOWN_SIPPER;
+                int uid = 0;
+                switch (bs.drainType) {
+                    case IDLE:
+                        n = SystemProto.PowerUseItem.IDLE;
+                        break;
+                    case CELL:
+                        n = SystemProto.PowerUseItem.CELL;
+                        break;
+                    case PHONE:
+                        n = SystemProto.PowerUseItem.PHONE;
+                        break;
+                    case WIFI:
+                        n = SystemProto.PowerUseItem.WIFI;
+                        break;
+                    case BLUETOOTH:
+                        n = SystemProto.PowerUseItem.BLUETOOTH;
+                        break;
+                    case SCREEN:
+                        n = SystemProto.PowerUseItem.SCREEN;
+                        break;
+                    case FLASHLIGHT:
+                        n = SystemProto.PowerUseItem.FLASHLIGHT;
+                        break;
+                    case APP:
+                        // dumpProtoAppsLocked will handle this.
+                        continue;
+                    case USER:
+                        n = SystemProto.PowerUseItem.USER;
+                        uid = UserHandle.getUid(bs.userId, 0);
+                        break;
+                    case UNACCOUNTED:
+                        n = SystemProto.PowerUseItem.UNACCOUNTED;
+                        break;
+                    case OVERCOUNTED:
+                        n = SystemProto.PowerUseItem.OVERCOUNTED;
+                        break;
+                    case CAMERA:
+                        n = SystemProto.PowerUseItem.CAMERA;
+                        break;
+                    case MEMORY:
+                        n = SystemProto.PowerUseItem.MEMORY;
+                        break;
+                }
+                final long puiToken = proto.start(SystemProto.POWER_USE_ITEM);
+                proto.write(SystemProto.PowerUseItem.NAME, n);
+                proto.write(SystemProto.PowerUseItem.UID, uid);
+                proto.write(SystemProto.PowerUseItem.COMPUTED_POWER_MAH, bs.totalPowerMah);
+                proto.write(SystemProto.PowerUseItem.SHOULD_HIDE, bs.shouldHide);
+                proto.write(SystemProto.PowerUseItem.SCREEN_POWER_MAH, bs.screenPowerMah);
+                proto.write(SystemProto.PowerUseItem.PROPORTIONAL_SMEAR_MAH,
+                        bs.proportionalSmearMah);
+                proto.end(puiToken);
+            }
+        }
+
+        // Power use summary (POWER_USE_SUMMARY_DATA)
+        final long pusToken = proto.start(SystemProto.POWER_USE_SUMMARY);
+        proto.write(SystemProto.PowerUseSummary.BATTERY_CAPACITY_MAH,
+                helper.getPowerProfile().getBatteryCapacity());
+        proto.write(SystemProto.PowerUseSummary.COMPUTED_POWER_MAH, helper.getComputedPower());
+        proto.write(SystemProto.PowerUseSummary.MIN_DRAINED_POWER_MAH, helper.getMinDrainedPower());
+        proto.write(SystemProto.PowerUseSummary.MAX_DRAINED_POWER_MAH, helper.getMaxDrainedPower());
+        proto.end(pusToken);
+
+        // RPM stats (RESOURCE_POWER_MANAGER_DATA)
+        final Map<String, ? extends Timer> rpmStats = getRpmStats();
+        final Map<String, ? extends Timer> screenOffRpmStats = getScreenOffRpmStats();
+        for (Map.Entry<String, ? extends Timer> ent : rpmStats.entrySet()) {
+            final long rpmToken = proto.start(SystemProto.RESOURCE_POWER_MANAGER);
+            proto.write(SystemProto.ResourcePowerManager.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.ResourcePowerManager.TOTAL,
+                    ent.getValue(), rawRealtimeUs, which);
+            dumpTimer(proto, SystemProto.ResourcePowerManager.SCREEN_OFF,
+                    screenOffRpmStats.get(ent.getKey()), rawRealtimeUs, which);
+            proto.end(rpmToken);
+        }
+
+        // Screen brightness (SCREEN_BRIGHTNESS_DATA)
+        for (int i = 0; i < NUM_SCREEN_BRIGHTNESS_BINS; ++i) {
+            final long sbToken = proto.start(SystemProto.SCREEN_BRIGHTNESS);
+            proto.write(SystemProto.ScreenBrightness.NAME, i);
+            dumpTimer(proto, SystemProto.ScreenBrightness.TOTAL, getScreenBrightnessTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(sbToken);
+        }
+
+        // Signal scanning time (SIGNAL_SCANNING_TIME_DATA)
+        dumpTimer(proto, SystemProto.SIGNAL_SCANNING, getPhoneSignalScanningTimer(), rawRealtimeUs,
+                which);
+
+        // Phone signal strength (SIGNAL_STRENGTH_TIME_DATA and SIGNAL_STRENGTH_COUNT_DATA)
+        for (int i = 0; i < SignalStrength.NUM_SIGNAL_STRENGTH_BINS; ++i) {
+            final long pssToken = proto.start(SystemProto.PHONE_SIGNAL_STRENGTH);
+            proto.write(SystemProto.PhoneSignalStrength.NAME, i);
+            dumpTimer(proto, SystemProto.PhoneSignalStrength.TOTAL, getPhoneSignalStrengthTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(pssToken);
+        }
+
+        // Wakeup reasons (WAKEUP_REASON_DATA)
+        final Map<String, ? extends Timer> wakeupReasons = getWakeupReasonStats();
+        for (Map.Entry<String, ? extends Timer> ent : wakeupReasons.entrySet()) {
+            final long wrToken = proto.start(SystemProto.WAKEUP_REASON);
+            proto.write(SystemProto.WakeupReason.NAME, ent.getKey());
+            dumpTimer(proto, SystemProto.WakeupReason.TOTAL, ent.getValue(), rawRealtimeUs, which);
+            proto.end(wrToken);
+        }
+
+        // Wifi signal strength (WIFI_SIGNAL_STRENGTH_TIME_DATA and WIFI_SIGNAL_STRENGTH_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_SIGNAL_STRENGTH_BINS; ++i) {
+            final long wssToken = proto.start(SystemProto.WIFI_SIGNAL_STRENGTH);
+            proto.write(SystemProto.WifiSignalStrength.NAME, i);
+            dumpTimer(proto, SystemProto.WifiSignalStrength.TOTAL, getWifiSignalStrengthTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wssToken);
+        }
+
+        // Wifi state (WIFI_STATE_TIME_DATA and WIFI_STATE_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_STATES; ++i) {
+            final long wsToken = proto.start(SystemProto.WIFI_STATE);
+            proto.write(SystemProto.WifiState.NAME, i);
+            dumpTimer(proto, SystemProto.WifiState.TOTAL, getWifiStateTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wsToken);
+        }
+
+        // Wifi supplicant state (WIFI_SUPPL_STATE_TIME_DATA and WIFI_SUPPL_STATE_COUNT_DATA)
+        for (int i = 0; i < NUM_WIFI_SUPPL_STATES; ++i) {
+            final long wssToken = proto.start(SystemProto.WIFI_SUPPLICANT_STATE);
+            proto.write(SystemProto.WifiSupplicantState.NAME, i);
+            dumpTimer(proto, SystemProto.WifiSupplicantState.TOTAL, getWifiSupplStateTimer(i),
+                    rawRealtimeUs, which);
+            proto.end(wssToken);
+        }
+
+        proto.end(sToken);
+    }
 }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 0df6361..2bfb013 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -23,17 +23,18 @@
 import android.util.Slog;
 
 import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 
 import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
 
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 
 /**
  * Base class for a remotable object, the core part of a lightweight
@@ -91,6 +92,20 @@
      */
     private static volatile TransactionTracker sTransactionTracker = null;
 
+    /**
+     * Guestimate of native memory associated with a Binder.
+     */
+    private static final int NATIVE_ALLOCATION_SIZE = 500;
+
+    private static native long getNativeFinalizer();
+
+    // Use a Holder to allow static initialization of Binder in the boot image, and
+    // possibly to avoid some initialization ordering issues.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Binder.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
     // Transaction tracking code.
 
     /**
@@ -168,7 +183,7 @@
         try {
             if (binder instanceof BinderProxy) {
                 ((BinderProxy) binder).mWarnOnBlocking = false;
-            } else if (binder != null
+            } else if (binder != null && binder.getInterfaceDescriptor() != null
                     && binder.queryLocalInterface(binder.getInterfaceDescriptor()) == null) {
                 Log.w(TAG, "Unable to allow blocking on interface " + binder);
             }
@@ -189,8 +204,11 @@
         }
     }
 
-    /* mObject is used by native code, do not remove or rename */
-    private long mObject;
+    /**
+     * Raw native pointer to JavaBBinderHolder object. Owned by this Java object. Not null.
+     */
+    private final long mObject;
+
     private IInterface mOwner;
     private String mDescriptor;
 
@@ -202,7 +220,7 @@
      * then its own pid is returned.
      */
     public static final native int getCallingPid();
-    
+
     /**
      * Return the Linux uid assigned to the process that sent you the
      * current transaction that is being processed.  This uid can be used with
@@ -335,7 +353,7 @@
      * it needs to.
      */
     public static final native void flushPendingCommands();
-    
+
     /**
      * Add the calling thread to the IPC thread pool.  This function does
      * not return until the current process is exiting.
@@ -361,7 +379,8 @@
      * Default constructor initializes the object.
      */
     public Binder() {
-        init();
+        mObject = getNativeBBinderHolder();
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject);
 
         if (FIND_POTENTIAL_LEAKS) {
             final Class<? extends Binder> klass = getClass();
@@ -372,7 +391,7 @@
             }
         }
     }
-    
+
     /**
      * Convenience method for associating a specific interface with the Binder.
      * After calling, queryLocalInterface() will be implemented for you
@@ -383,7 +402,7 @@
         mOwner = owner;
         mDescriptor = descriptor;
     }
-    
+
     /**
      * Default implementation returns an empty interface name.
      */
@@ -408,14 +427,14 @@
     public boolean isBinderAlive() {
         return true;
     }
-    
+
     /**
      * Use information supplied to attachInterface() to return the
      * associated IInterface if it matches the requested
      * descriptor.
      */
     public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
-        if (mDescriptor.equals(descriptor)) {
+        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
             return mOwner;
         }
         return null;
@@ -630,7 +649,7 @@
         }
         return r;
     }
-    
+
     /**
      * Local implementation is a no-op.
      */
@@ -643,14 +662,6 @@
     public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
         return true;
     }
-    
-    protected void finalize() throws Throwable {
-        try {
-            destroyBinder();
-        } finally {
-            super.finalize();
-        }
-    }
 
     static void checkParcel(IBinder obj, int code, Parcel parcel, String msg) {
         if (CHECK_PARCEL_SIZE && parcel.dataSize() >= 800*1024) {
@@ -675,8 +686,8 @@
         }
     }
 
-    private native final void init();
-    private native final void destroyBinder();
+    private static native long getNativeBBinderHolder();
+    private static native long getFinalizer();
 
     // Entry point from android_util_Binder.cpp's onTransact
     private boolean execTransact(int code, long dataObj, long replyObj,
@@ -730,10 +741,214 @@
     }
 }
 
+/**
+ * Java proxy for a native IBinder object.
+ * Allocated and constructed by the native javaObjectforIBinder function. Never allocated
+ * directly from Java code.
+ */
 final class BinderProxy implements IBinder {
+    // See android_util_Binder.cpp for the native half of this.
+
     // Assume the process-wide default value when created
     volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;
 
+    /*
+     * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
+     * We roll our own only because we need to lazily remove WeakReferences during accesses
+     * to avoid accumulating junk WeakReference objects. WeakHashMap isn't easily usable
+     * because we want weak values, not keys.
+     * Our hash table is never resized, but the number of entries is unlimited;
+     * performance degrades as occupancy increases significantly past MAIN_INDEX_SIZE.
+     * Not thread-safe. Client ensures there's a single access at a time.
+     */
+    private static final class ProxyMap {
+        private static final int LOG_MAIN_INDEX_SIZE = 8;
+        private static final int MAIN_INDEX_SIZE = 1 <<  LOG_MAIN_INDEX_SIZE;
+        private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
+
+        /**
+         * We next warn when we exceed this bucket size.
+         */
+        private int mWarnBucketSize = 20;
+
+        /**
+         * Increment mWarnBucketSize by WARN_INCREMENT each time we warn.
+         */
+        private static final int WARN_INCREMENT = 10;
+
+        /**
+         * Hash function tailored to native pointers.
+         * Returns a value < MAIN_INDEX_SIZE.
+         */
+        private static int hash(long arg) {
+            return ((int) ((arg >> 2) ^ (arg >> (2 + LOG_MAIN_INDEX_SIZE)))) & MAIN_INDEX_MASK;
+        }
+
+        /**
+         * Return the total number of pairs in the map.
+         */
+        int size() {
+            int size = 0;
+            for (ArrayList<WeakReference<BinderProxy>> a : mMainIndexValues) {
+                if (a != null) {
+                    size += a.size();
+                }
+            }
+            return size;
+        }
+
+        /**
+         * Remove ith entry from the hash bucket indicated by hash.
+         */
+        private void remove(int hash, int index) {
+            Long[] keyArray = mMainIndexKeys[hash];
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[hash];
+            int size = valueArray.size();  // KeyArray may have extra elements.
+            // Move last entry into empty slot, and truncate at end.
+            if (index != size - 1) {
+                keyArray[index] = keyArray[size - 1];
+                valueArray.set(index, valueArray.get(size - 1));
+            }
+            valueArray.remove(size - 1);
+            // Just leave key array entry; it's unused. We only trust the valueArray size.
+        }
+
+        /**
+         * Look up the supplied key. If we have a non-cleared entry for it, return it.
+         */
+        BinderProxy get(long key) {
+            int myHash = hash(key);
+            Long[] keyArray = mMainIndexKeys[myHash];
+            if (keyArray == null) {
+                return null;
+            }
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+            int bucketSize = valueArray.size();
+            for (int i = 0; i < bucketSize; ++i) {
+                long foundKey = keyArray[i];
+                if (key == foundKey) {
+                    WeakReference<BinderProxy> wr = valueArray.get(i);
+                    BinderProxy bp = wr.get();
+                    if (bp != null) {
+                        return bp;
+                    } else {
+                        remove(myHash, i);
+                        return null;
+                    }
+                }
+            }
+            return null;
+        }
+
+        private int mRandom;  // A counter used to generate a "random" index. World's 2nd worst RNG.
+
+        /**
+         * Add the key-value pair to the map.
+         * Requires that the indicated key is not already in the map.
+         */
+        void set(long key, @NonNull BinderProxy value) {
+            int myHash = hash(key);
+            ArrayList<WeakReference<BinderProxy>> valueArray = mMainIndexValues[myHash];
+            if (valueArray == null) {
+                valueArray = mMainIndexValues[myHash] = new ArrayList<>();
+                mMainIndexKeys[myHash] = new Long[1];
+            }
+            int size = valueArray.size();
+            WeakReference<BinderProxy> newWr = new WeakReference<>(value);
+            // First look for a cleared reference.
+            // This ensures that ArrayList size is bounded by the maximum occupancy of
+            // that bucket.
+            for (int i = 0; i < size; ++i) {
+                if (valueArray.get(i).get() == null) {
+                    valueArray.set(i, newWr);
+                    Long[] keyArray = mMainIndexKeys[myHash];
+                    keyArray[i] = key;
+                    if (i < size - 1) {
+                        // "Randomly" check one of the remaining entries in [i+1, size), so that
+                        // needlessly long buckets are eventually pruned.
+                        int rnd = Math.floorMod(++mRandom, size - (i + 1));
+                        if (valueArray.get(i + 1 + rnd).get() == null) {
+                            remove(myHash, i + 1 + rnd);
+                        }
+                    }
+                    return;
+                }
+            }
+            valueArray.add(size, newWr);
+            Long[] keyArray = mMainIndexKeys[myHash];
+            if (keyArray.length == size) {
+                // size >= 1, since we initially allocated one element
+                Long[] newArray = new Long[size + size / 2 + 2];
+                System.arraycopy(keyArray, 0, newArray, 0, size);
+                newArray[size] = key;
+                mMainIndexKeys[myHash] = newArray;
+            } else {
+                keyArray[size] = key;
+            }
+            if (size >= mWarnBucketSize) {
+                Log.v(Binder.TAG, "BinderProxy map growth! bucket size = " + size
+                        + " total = " + size());
+                mWarnBucketSize += WARN_INCREMENT;
+            }
+        }
+
+        // Corresponding ArrayLists in the following two arrays always have the same size.
+        // They contain no empty entries. However WeakReferences in the values ArrayLists
+        // may have been cleared.
+
+        // mMainIndexKeys[i][j] corresponds to mMainIndexValues[i].get(j) .
+        // The values ArrayList has the proper size(), the corresponding keys array
+        // is always at least the same size, but may be larger.
+        // If either a particular keys array, or the corresponding values ArrayList
+        // are null, then they both are.
+        private final Long[][] mMainIndexKeys = new Long[MAIN_INDEX_SIZE][];
+        private final ArrayList<WeakReference<BinderProxy>>[] mMainIndexValues =
+                new ArrayList[MAIN_INDEX_SIZE];
+    }
+
+    private static ProxyMap sProxyMap = new ProxyMap();
+
+    /**
+     * Return a BinderProxy for IBinder.
+     * This method is thread-hostile!  The (native) caller serializes getInstance() calls using
+     * gProxyLock.
+     * If we previously returned a BinderProxy bp for the same iBinder, and bp is still
+     * in use, then we return the same bp.
+     *
+     * @param nativeData C++ pointer to (possibly still empty) BinderProxyNativeData.
+     * Takes ownership of nativeData iff <result>.mNativeData == nativeData.  Caller will usually
+     * delete nativeData if that's not the case.
+     * @param iBinder C++ pointer to IBinder. Does not take ownership of referenced object.
+     */
+    private static BinderProxy getInstance(long nativeData, long iBinder) {
+        BinderProxy result = sProxyMap.get(iBinder);
+        if (result == null) {
+            result = new BinderProxy(nativeData);
+            sProxyMap.set(iBinder, result);
+        }
+        return result;
+    }
+
+    private BinderProxy(long nativeData) {
+        mNativeData = nativeData;
+        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeData);
+    }
+
+    /**
+     * Guestimate of native memory associated with a BinderProxy.
+     * This includes the underlying IBinder, associated DeathRecipientList, and KeyedVector
+     * that points back to us. We guess high since it includes a GlobalRef, which
+     * may be in short supply.
+     */
+    private static final int NATIVE_ALLOCATION_SIZE = 1000;
+
+    // Use a Holder to allow static initialization of BinderProxy in the boot image, and
+    // to avoid some initialization ordering issues.
+    private static class NoImagePreloadHolder {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                BinderProxy.class.getClassLoader(), getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+    }
+
     public native boolean pingBinder();
     public native boolean isBinderAlive();
 
@@ -769,6 +984,7 @@
         }
     }
 
+    private static native long getNativeFinalizer();
     public native String getInterfaceDescriptor() throws RemoteException;
     public native boolean transactNative(int code, Parcel data, Parcel reply,
             int flags) throws RemoteException;
@@ -789,7 +1005,7 @@
             reply.recycle();
         }
     }
-    
+
     public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -823,21 +1039,6 @@
         }
     }
 
-    BinderProxy() {
-        mSelf = new WeakReference(this);
-    }
-    
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            destroy();
-        } finally {
-            super.finalize();
-        }
-    }
-    
-    private native final void destroy();
-    
     private static final void sendDeathNotice(DeathRecipient recipient) {
         if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
         try {
@@ -848,8 +1049,10 @@
                     exc);
         }
     }
-    
-    final private WeakReference mSelf;
-    private long mObject;
-    private long mOrgue;
+
+    /**
+     * C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
+     * native IBinder object, and a DeathRecipientList.
+     */
+    private final long mNativeData;
 }
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b46c6b1..017c213 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1748,22 +1748,26 @@
     public static final int MEMINFO_SHMEM = 4;
     /** @hide */
     public static final int MEMINFO_SLAB = 5;
+     /** @hide */
+    public static final int MEMINFO_SLAB_RECLAIMABLE = 6;
+     /** @hide */
+    public static final int MEMINFO_SLAB_UNRECLAIMABLE = 7;
     /** @hide */
-    public static final int MEMINFO_SWAP_TOTAL = 6;
+    public static final int MEMINFO_SWAP_TOTAL = 8;
     /** @hide */
-    public static final int MEMINFO_SWAP_FREE = 7;
+    public static final int MEMINFO_SWAP_FREE = 9;
     /** @hide */
-    public static final int MEMINFO_ZRAM_TOTAL = 8;
+    public static final int MEMINFO_ZRAM_TOTAL = 10;
     /** @hide */
-    public static final int MEMINFO_MAPPED = 9;
+    public static final int MEMINFO_MAPPED = 11;
     /** @hide */
-    public static final int MEMINFO_VM_ALLOC_USED = 10;
+    public static final int MEMINFO_VM_ALLOC_USED = 12;
     /** @hide */
-    public static final int MEMINFO_PAGE_TABLES = 11;
+    public static final int MEMINFO_PAGE_TABLES = 13;
     /** @hide */
-    public static final int MEMINFO_KERNEL_STACK = 12;
+    public static final int MEMINFO_KERNEL_STACK = 14;
     /** @hide */
-    public static final int MEMINFO_COUNT = 13;
+    public static final int MEMINFO_COUNT = 15;
 
     /**
      * Retrieves /proc/meminfo.  outSizes is filled with fields
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index cc2af21..8271701 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -23,6 +23,11 @@
 interface IDeviceIdleController {
     void addPowerSaveWhitelistApp(String name);
     void removePowerSaveWhitelistApp(String name);
+    /* Removes an app from the system whitelist. Calling restoreSystemPowerWhitelistApp will add
+    the app back into the system whitelist */
+    void removeSystemPowerWhitelistApp(String name);
+    void restoreSystemPowerWhitelistApp(String name);
+    String[] getRemovedSystemPowerWhitelistApps();
     String[] getSystemPowerWhitelistExceptIdle();
     String[] getSystemPowerWhitelist();
     String[] getUserPowerWhitelist();
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 0562716..a474b47 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -104,11 +104,6 @@
     void setIPv6AddrGenMode(String iface, int mode);
 
     /**
-     * Enables or enables IPv6 ND offload.
-     */
-    void setInterfaceIpv6NdOffload(String iface, boolean enable);
-
-    /**
      * Add the specified route to the interface.
      */
     void addRoute(int netId, in RouteInfo route);
diff --git a/core/java/android/os/IServiceManager.java b/core/java/android/os/IServiceManager.java
index 7b11c28..2176a78 100644
--- a/core/java/android/os/IServiceManager.java
+++ b/core/java/android/os/IServiceManager.java
@@ -18,12 +18,12 @@
 
 /**
  * Basic interface for finding and publishing system services.
- * 
+ *
  * An implementation of this interface is usually published as the
  * global context object, which can be retrieved via
  * BinderNative.getContextObject().  An easy way to retrieve this
  * is with the static method BnServiceManager.getDefault().
- * 
+ *
  * @hide
  */
 public interface IServiceManager extends IInterface
@@ -33,33 +33,33 @@
      * service manager.  Blocks for a few seconds waiting for it to be
      * published if it does not already exist.
      */
-    public IBinder getService(String name) throws RemoteException;
-    
+    IBinder getService(String name) throws RemoteException;
+
     /**
      * Retrieve an existing service called @a name from the
      * service manager.  Non-blocking.
      */
-    public IBinder checkService(String name) throws RemoteException;
+    IBinder checkService(String name) throws RemoteException;
 
     /**
      * Place a new @a service called @a name into the service
      * manager.
      */
-    public void addService(String name, IBinder service, boolean allowIsolated)
-                throws RemoteException;
+    void addService(String name, IBinder service, boolean allowIsolated, int dumpFlags)
+            throws RemoteException;
 
     /**
      * Return a list of all currently running services.
      */
-    public String[] listServices() throws RemoteException;
+    String[] listServices(int dumpFlags) throws RemoteException;
 
     /**
      * Assign a permission controller to the service manager.  After set, this
      * interface is checked before any services are added.
      */
-    public void setPermissionController(IPermissionController controller)
+    void setPermissionController(IPermissionController controller)
             throws RemoteException;
-    
+
     static final String descriptor = "android.os.IServiceManager";
 
     int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
@@ -68,4 +68,17 @@
     int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;
     int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;
     int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;
+
+    /*
+     * Must update values in IServiceManager.h
+     */
+    /* Allows services to dump sections according to priorities. */
+    int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
+    int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
+    int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
+    int DUMP_FLAG_PRIORITY_ALL = DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
+            | DUMP_FLAG_PRIORITY_NORMAL;
+    /* Allows services to dump sections in protobuf format. */
+    int DUMP_FLAG_PROTO = 1 << 3;
+
 }
diff --git a/core/java/android/os/IStatsCallbacks.aidl b/core/java/android/os/IStatsCallbacks.aidl
new file mode 100644
index 0000000..02e7cd3
--- /dev/null
+++ b/core/java/android/os/IStatsCallbacks.aidl
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2016, 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.os;
+
+/**
+  * Callback for Statsd to allow binder calls to clients.
+  * {@hide}
+  */
+interface IStatsCallbacks {
+    void onReceiveLogs(out byte[] log);
+}
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index a83d313..20f6c8e 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -16,15 +16,17 @@
 
 package android.os;
 
+import android.os.StatsLogEventWrapper;
+
 /**
   * Binder interface to communicate with the Java-based statistics service helper.
   * {@hide}
   */
-oneway interface IStatsCompanionService {
+interface IStatsCompanionService {
     /**
      * Tell statscompanion that stastd is up and running.
      */
-    void statsdReady();
+    oneway void statsdReady();
 
     /**
     * Register an alarm for anomaly detection to fire at the given timestamp (ms since epoch).
@@ -32,10 +34,10 @@
     * Uses AlarmManager.set API, so  if the timestamp is in the past, alarm fires immediately, and
     * alarm is inexact.
     */
-    void setAnomalyAlarm(long timestampMs);
+    oneway void setAnomalyAlarm(long timestampMs);
 
     /** Cancel any anomaly detection alarm. */
-    void cancelAnomalyAlarm();
+    oneway void cancelAnomalyAlarm();
 
     /**
       * Register a repeating alarm for polling to fire at the given timestamp and every
@@ -44,8 +46,11 @@
       * Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately,
       * and alarm is inexact.
       */
-    void setPollingAlarms(long timestampMs, long intervalMs);
+    oneway void setPollingAlarms(long timestampMs, long intervalMs);
 
     /** Cancel any repeating polling alarm. */
-    void cancelPollingAlarms();
+    oneway void cancelPollingAlarms();
+
+    /** Pull the specified data. Results will be sent to statsd when complete. */
+    StatsLogEventWrapper[] pullData(int pullCode);
 }
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index f8f2813..480296c 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.os.IStatsCallbacks;
+
 /**
   * Binder interface to communicate with the statistics management service.
   * {@hide}
@@ -45,4 +47,31 @@
      * Two-way binder call so that caller's method (and corresponding wakelocks) will linger.
      */
     void informPollAlarmFired();
+
+    /**
+     * Inform statsd what the version and package are for each uid. Note that each array should
+     * have the same number of elements, and version[i] and package[i] correspond to uid[i].
+     */
+    oneway void informAllUidData(in int[] uid, in int[] version, in String[] app);
+
+    /**
+     * Inform statsd what the uid and version are for one app that was updated.
+     */
+    oneway void informOnePackage(in String app, in int uid, in int version);
+
+    /**
+     * Inform stats that an app was removed.
+     */
+    oneway void informOnePackageRemoved(in String app, in int uid);
+
+    /**
+     * Trigger pushLog to force push stats log entries from statsd on client side.
+     */
+    void requestPush();
+
+    /**
+     * Listen to statsd to send stats log entries.
+     * TODO: Limit callbacks with specific configurations.
+     */
+    void subscribeStatsLog(IStatsCallbacks callbacks);
 }
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 2dc3beb..ca9cbec 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -295,7 +295,11 @@
         return STRING_EN_XA.equals(locale) || STRING_AR_XB.equals(locale);
     }
 
-    private static boolean isPseudoLocale(Locale locale) {
+    /**
+     * Returns true if locale is a pseudo-locale, false otherwise.
+     * {@hide}
+     */
+    public static boolean isPseudoLocale(Locale locale) {
         return LOCALE_EN_XA.equals(locale) || LOCALE_AR_XB.equals(locale);
     }
 
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 031ca91..c2cf3967 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -561,6 +561,22 @@
         mClassCookies = from.mClassCookies;
     }
 
+    /** @hide */
+    public Map<Class, Object> copyClassCookies() {
+        return new ArrayMap<>(mClassCookies);
+    }
+
+    /** @hide */
+    public void putClassCookies(Map<Class, Object> cookies) {
+        if (cookies == null) {
+            return;
+        }
+        if (mClassCookies == null) {
+            mClassCookies = new ArrayMap<>();
+        }
+        mClassCookies.putAll(cookies);
+    }
+
     /**
      * Report whether the parcel contains any marshalled file descriptors.
      */
@@ -1340,6 +1356,13 @@
      * @see Parcelable
      */
     public final <T extends Parcelable> void writeTypedList(List<T> val) {
+        writeTypedList(val, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public <T extends Parcelable> void writeTypedList(List<T> val, int parcelableFlags) {
         if (val == null) {
             writeInt(-1);
             return;
@@ -1348,13 +1371,7 @@
         int i=0;
         writeInt(N);
         while (i < N) {
-            T item = val.get(i);
-            if (item != null) {
-                writeInt(1);
-                item.writeToParcel(this, 0);
-            } else {
-                writeInt(0);
-            }
+            writeTypedObject(val.get(i), parcelableFlags);
             i++;
         }
     }
@@ -1456,13 +1473,7 @@
             int N = val.length;
             writeInt(N);
             for (int i = 0; i < N; i++) {
-                T item = val[i];
-                if (item != null) {
-                    writeInt(1);
-                    item.writeToParcel(this, parcelableFlags);
-                } else {
-                    writeInt(0);
-                }
+                writeTypedObject(val[i], parcelableFlags);
             }
         } else {
             writeInt(-1);
@@ -1470,146 +1481,6 @@
     }
 
     /**
-     * Write a uniform (all items are null or the same class) array list of
-     * parcelables.
-     *
-     * @param list The list to write.
-     *
-     * @hide
-     */
-    public final <T extends Parcelable> void writeTypedArrayList(@Nullable ArrayList<T> list,
-            int parcelableFlags) {
-        if (list != null) {
-            int N = list.size();
-            writeInt(N);
-            boolean wroteCreator = false;
-            for (int i = 0; i < N; i++) {
-                T item = list.get(i);
-                if (item != null) {
-                    writeInt(1);
-                    if (!wroteCreator) {
-                        writeParcelableCreator(item);
-                        wroteCreator = true;
-                    }
-                    item.writeToParcel(this, parcelableFlags);
-                } else {
-                    writeInt(0);
-                }
-            }
-        } else {
-            writeInt(-1);
-        }
-    }
-
-    /**
-     * Reads a uniform (all items are null or the same class) array list of
-     * parcelables.
-     *
-     * @return The list or null.
-     *
-     * @hide
-     */
-    public final @Nullable <T> ArrayList<T> readTypedArrayList(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N <= 0) {
-            return null;
-        }
-        Parcelable.Creator<?> creator = null;
-        ArrayList<T> result = new ArrayList<T>(N);
-        for (int i = 0; i < N; i++) {
-            if (readInt() != 0) {
-                if (creator == null) {
-                    creator = readParcelableCreator(loader);
-                    if (creator == null) {
-                        return null;
-                    }
-                }
-                final T parcelable;
-                if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
-                    Parcelable.ClassLoaderCreator<?> classLoaderCreator =
-                            (Parcelable.ClassLoaderCreator<?>) creator;
-                    parcelable = (T) classLoaderCreator.createFromParcel(this, loader);
-                } else {
-                    parcelable = (T) creator.createFromParcel(this);
-                }
-                result.add(parcelable);
-            } else {
-                result.add(null);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Write a uniform (all items are null or the same class) array set of
-     * parcelables.
-     *
-     * @param set The set to write.
-     *
-     * @hide
-     */
-    public final <T extends Parcelable> void writeTypedArraySet(@Nullable ArraySet<T> set,
-            int parcelableFlags) {
-        if (set != null) {
-            int N = set.size();
-            writeInt(N);
-            boolean wroteCreator = false;
-            for (int i = 0; i < N; i++) {
-                T item = set.valueAt(i);
-                if (item != null) {
-                    writeInt(1);
-                    if (!wroteCreator) {
-                        writeParcelableCreator(item);
-                        wroteCreator = true;
-                    }
-                    item.writeToParcel(this, parcelableFlags);
-                } else {
-                    writeInt(0);
-                }
-            }
-        } else {
-            writeInt(-1);
-        }
-    }
-
-    /**
-     * Reads a uniform (all items are null or the same class) array set of
-     * parcelables.
-     *
-     * @return The set or null.
-     *
-     * @hide
-     */
-    public final @Nullable <T> ArraySet<T> readTypedArraySet(@Nullable ClassLoader loader) {
-        int N = readInt();
-        if (N <= 0) {
-            return null;
-        }
-        Parcelable.Creator<?> creator = null;
-        ArraySet<T> result = new ArraySet<T>(N);
-        for (int i = 0; i < N; i++) {
-            T parcelable = null;
-            if (readInt() != 0) {
-                if (creator == null) {
-                    creator = readParcelableCreator(loader);
-                    if (creator == null) {
-                        return null;
-                    }
-                }
-                if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
-                    Parcelable.ClassLoaderCreator<?> classLoaderCreator =
-                            (Parcelable.ClassLoaderCreator<?>) creator;
-                    parcelable = (T) classLoaderCreator.createFromParcel(this, loader);
-                } else {
-                    parcelable = (T) creator.createFromParcel(this);
-                }
-            }
-            result.append(parcelable);
-        }
-        return result;
-    }
-
-    /**
      * Flatten the Parcelable object into the parcel.
      *
      * @param val The Parcelable object to be written.
@@ -2458,11 +2329,7 @@
         }
         ArrayList<T> l = new ArrayList<T>(N);
         while (N > 0) {
-            if (readInt() != 0) {
-                l.add(c.createFromParcel(this));
-            } else {
-                l.add(null);
-            }
+            l.add(readTypedObject(c));
             N--;
         }
         return l;
@@ -2485,18 +2352,10 @@
         int N = readInt();
         int i = 0;
         for (; i < M && i < N; i++) {
-            if (readInt() != 0) {
-                list.set(i, c.createFromParcel(this));
-            } else {
-                list.set(i, null);
-            }
+            list.set(i, readTypedObject(c));
         }
         for (; i<N; i++) {
-            if (readInt() != 0) {
-                list.add(c.createFromParcel(this));
-            } else {
-                list.add(null);
-            }
+            list.add(readTypedObject(c));
         }
         for (; i<M; i++) {
             list.remove(N);
@@ -2641,9 +2500,7 @@
         }
         T[] l = c.newArray(N);
         for (int i=0; i<N; i++) {
-            if (readInt() != 0) {
-                l[i] = c.createFromParcel(this);
-            }
+            l[i] = readTypedObject(c);
         }
         return l;
     }
@@ -2652,11 +2509,7 @@
         int N = readInt();
         if (N == val.length) {
             for (int i=0; i<N; i++) {
-                if (readInt() != 0) {
-                    val[i] = c.createFromParcel(this);
-                } else {
-                    val[i] = null;
-                }
+                val[i] = readTypedObject(c);
             }
         } else {
             throw new RuntimeException("bad array lengths");
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index c091420..7f588ad 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -737,7 +737,9 @@
     private void closeWithStatus(int status, String msg) {
         if (mClosed) return;
         mClosed = true;
-        mGuard.close();
+        if (mGuard != null) {
+            mGuard.close();
+        }
         // Status MUST be sent before closing actual descriptor
         writeCommStatusAndClose(status, msg);
         IoUtils.closeQuietly(mFd);
diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java
index 1f3a1e6..76b2142 100644
--- a/core/java/android/os/PatternMatcher.java
+++ b/core/java/android/os/PatternMatcher.java
@@ -16,7 +16,7 @@
 
 package android.os;
 
-import android.util.Log;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.Arrays;
 
@@ -131,7 +131,17 @@
         }
         return "PatternMatcher{" + type + mPattern + "}";
     }
-    
+
+    /** @hide */
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(PatternMatcherProto.PATTERN, mPattern);
+        proto.write(PatternMatcherProto.TYPE, mType);
+        // PatternMatcherProto.PARSED_PATTERN is too much to dump, but the field is reserved to
+        // match the current data structure.
+        proto.end(token);
+    }
+
     public int describeContents() {
         return 0;
     }
@@ -141,7 +151,7 @@
         dest.writeInt(mType);
         dest.writeIntArray(mParsedPattern);
     }
-    
+
     public PatternMatcher(Parcel src) {
         mPattern = src.readString();
         mType = src.readInt();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 960c9f5..7f4dee6 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -443,6 +443,20 @@
     public static final String SHUTDOWN_USER_REQUESTED = "userrequested";
 
     /**
+     * The value to pass as the 'reason' argument to android_reboot() when battery temperature
+     * is too high.
+     * @hide
+     */
+    public static final String SHUTDOWN_BATTERY_THERMAL_STATE = "thermal,battery";
+
+    /**
+     * The value to pass as the 'reason' argument to android_reboot() when device is running
+     * critically low on battery.
+     * @hide
+     */
+    public static final String SHUTDOWN_LOW_BATTERY = "battery";
+
+    /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -451,7 +465,9 @@
             SHUTDOWN_REASON_SHUTDOWN,
             SHUTDOWN_REASON_REBOOT,
             SHUTDOWN_REASON_USER_REQUESTED,
-            SHUTDOWN_REASON_THERMAL_SHUTDOWN
+            SHUTDOWN_REASON_THERMAL_SHUTDOWN,
+            SHUTDOWN_REASON_LOW_BATTERY,
+            SHUTDOWN_REASON_BATTERY_THERMAL
     })
     public @interface ShutdownReason {}
 
@@ -485,6 +501,18 @@
      */
     public static final int SHUTDOWN_REASON_THERMAL_SHUTDOWN = 4;
 
+    /**
+     * constant for shutdown reason being low battery.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_LOW_BATTERY = 5;
+
+    /**
+     * constant for shutdown reason being critical battery thermal state.
+     * @hide
+     */
+    public static final int SHUTDOWN_REASON_BATTERY_THERMAL = 6;
+
     final Context mContext;
     final IPowerManager mService;
     final Handler mHandler;
@@ -1384,7 +1412,11 @@
          */
         public void release(int flags) {
             synchronized (mToken) {
-                mInternalCount--;
+                if (mInternalCount > 0) {
+                    // internal count must only be decreased if it is > 0 or state of
+                    // the WakeLock object is broken.
+                    mInternalCount--;
+                }
                 if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
                     mExternalCount--;
                 }
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e11494d..42ec315 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -26,7 +26,6 @@
 /** @hide */
 public final class ServiceManager {
     private static final String TAG = "ServiceManager";
-
     private static IServiceManager sServiceManager;
     private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
 
@@ -43,7 +42,7 @@
 
     /**
      * Returns a reference to a service with the given name.
-     * 
+     *
      * @param name the name of the service to get
      * @return a reference to the service, or <code>null</code> if the service doesn't exist
      */
@@ -79,35 +78,46 @@
     /**
      * Place a new @a service called @a name into the service
      * manager.
-     * 
+     *
      * @param name the name of the new service
      * @param service the service object
      */
     public static void addService(String name, IBinder service) {
-        try {
-            getIServiceManager().addService(name, service, false);
-        } catch (RemoteException e) {
-            Log.e(TAG, "error in addService", e);
-        }
+        addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
     }
 
     /**
      * Place a new @a service called @a name into the service
      * manager.
-     * 
+     *
      * @param name the name of the new service
      * @param service the service object
      * @param allowIsolated set to true to allow isolated sandboxed processes
      * to access this service
      */
     public static void addService(String name, IBinder service, boolean allowIsolated) {
+        addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_NORMAL);
+    }
+
+    /**
+     * Place a new @a service called @a name into the service
+     * manager.
+     *
+     * @param name the name of the new service
+     * @param service the service object
+     * @param allowIsolated set to true to allow isolated sandboxed processes
+     * @param dumpPriority supported dump priority levels as a bitmask
+     * to access this service
+     */
+    public static void addService(String name, IBinder service, boolean allowIsolated,
+            int dumpPriority) {
         try {
-            getIServiceManager().addService(name, service, allowIsolated);
+            getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
         } catch (RemoteException e) {
             Log.e(TAG, "error in addService", e);
         }
     }
-    
+
     /**
      * Retrieve an existing service called @a name from the
      * service manager.  Non-blocking.
@@ -133,7 +143,7 @@
      */
     public static String[] listServices() {
         try {
-            return getIServiceManager().listServices();
+            return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
         } catch (RemoteException e) {
             Log.e(TAG, "error in listServices", e);
             return null;
@@ -144,7 +154,7 @@
      * This is only intended to be called when the process is first being brought
      * up and bound by the activity manager. There is only one thread in the process
      * at that time, so no locking is done.
-     * 
+     *
      * @param cache the cache of service references
      * @hide
      */
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index be24426..589b8c4 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -40,63 +40,65 @@
         if (in != null) {
             return in;
         }
-        
+
         return new ServiceManagerProxy(obj);
     }
-    
+
     public ServiceManagerNative()
     {
         attachInterface(this, descriptor);
     }
-    
+
     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
     {
         try {
             switch (code) {
-            case IServiceManager.GET_SERVICE_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String name = data.readString();
-                IBinder service = getService(name);
-                reply.writeStrongBinder(service);
-                return true;
-            }
-    
-            case IServiceManager.CHECK_SERVICE_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String name = data.readString();
-                IBinder service = checkService(name);
-                reply.writeStrongBinder(service);
-                return true;
-            }
-    
-            case IServiceManager.ADD_SERVICE_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String name = data.readString();
-                IBinder service = data.readStrongBinder();
-                boolean allowIsolated = data.readInt() != 0;
-                addService(name, service, allowIsolated);
-                return true;
-            }
-    
-            case IServiceManager.LIST_SERVICES_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                String[] list = listServices();
-                reply.writeStringArray(list);
-                return true;
-            }
-            
-            case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
-                data.enforceInterface(IServiceManager.descriptor);
-                IPermissionController controller
-                        = IPermissionController.Stub.asInterface(
-                                data.readStrongBinder());
-                setPermissionController(controller);
-                return true;
-            }
+                case IServiceManager.GET_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = getService(name);
+                    reply.writeStrongBinder(service);
+                    return true;
+                }
+
+                case IServiceManager.CHECK_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = checkService(name);
+                    reply.writeStrongBinder(service);
+                    return true;
+                }
+
+                case IServiceManager.ADD_SERVICE_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    String name = data.readString();
+                    IBinder service = data.readStrongBinder();
+                    boolean allowIsolated = data.readInt() != 0;
+                    int dumpPriority = data.readInt();
+                    addService(name, service, allowIsolated, dumpPriority);
+                    return true;
+                }
+
+                case IServiceManager.LIST_SERVICES_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    int dumpPriority = data.readInt();
+                    String[] list = listServices(dumpPriority);
+                    reply.writeStringArray(list);
+                    return true;
+                }
+
+                case IServiceManager.SET_PERMISSION_CONTROLLER_TRANSACTION: {
+                    data.enforceInterface(IServiceManager.descriptor);
+                    IPermissionController controller =
+                            IPermissionController.Stub.asInterface(
+                                    data.readStrongBinder());
+                    setPermissionController(controller);
+                    return true;
+                }
             }
         } catch (RemoteException e) {
         }
-        
+
         return false;
     }
 
@@ -110,11 +112,11 @@
     public ServiceManagerProxy(IBinder remote) {
         mRemote = remote;
     }
-    
+
     public IBinder asBinder() {
         return mRemote;
     }
-    
+
     public IBinder getService(String name) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -139,7 +141,7 @@
         return binder;
     }
 
-    public void addService(String name, IBinder service, boolean allowIsolated)
+    public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -147,12 +149,13 @@
         data.writeString(name);
         data.writeStrongBinder(service);
         data.writeInt(allowIsolated ? 1 : 0);
+        data.writeInt(dumpPriority);
         mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
         reply.recycle();
         data.recycle();
     }
-    
-    public String[] listServices() throws RemoteException {
+
+    public String[] listServices(int dumpPriority) throws RemoteException {
         ArrayList<String> services = new ArrayList<String>();
         int n = 0;
         while (true) {
@@ -160,6 +163,7 @@
             Parcel reply = Parcel.obtain();
             data.writeInterfaceToken(IServiceManager.descriptor);
             data.writeInt(n);
+            data.writeInt(dumpPriority);
             n++;
             try {
                 boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0);
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
index e7fe697..ad9fbfb 100644
--- a/core/java/android/os/ShellCallback.java
+++ b/core/java/android/os/ShellCallback.java
@@ -35,8 +35,9 @@
     IShellCallback mShellCallback;
 
     class MyShellCallback extends IShellCallback.Stub {
-        public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
-            return onOpenOutputFile(path, seLinuxContext);
+        public ParcelFileDescriptor openFile(String path, String seLinuxContext,
+                String mode) {
+            return onOpenFile(path, seLinuxContext, mode);
         }
     }
 
@@ -48,23 +49,27 @@
     }
 
     /**
-     * Ask the shell to open a file for writing.  This will truncate the file if it
-     * already exists.  It will create the file if it doesn't exist.
+     * Ask the shell to open a file.  If opening for writing, will truncate the file if it
+     * already exists and will create the file if it doesn't exist.
      * @param path Path of the file to be opened/created.
      * @param seLinuxContext Optional SELinux context that must be allowed to have
      * access to the file; if null, nothing is required.
+     * @param mode Mode to open file in: "r" for input/reading an existing file,
+     * "r+" for reading/writing an existing file, "w" for output/writing a new file (either
+     * creating or truncating an existing one), "w+" for reading/writing a new file (either
+     * creating or truncating an existing one).
      */
-    public ParcelFileDescriptor openOutputFile(String path, String seLinuxContext) {
-        if (DEBUG) Log.d(TAG, "openOutputFile " + this + ": mLocal=" + mLocal
+    public ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode) {
+        if (DEBUG) Log.d(TAG, "openFile " + this + " mode=" + mode + ": mLocal=" + mLocal
                 + " mShellCallback=" + mShellCallback);
 
         if (mLocal) {
-            return onOpenOutputFile(path, seLinuxContext);
+            return onOpenFile(path, seLinuxContext, mode);
         }
 
         if (mShellCallback != null) {
             try {
-                return mShellCallback.openOutputFile(path, seLinuxContext);
+                return mShellCallback.openFile(path, seLinuxContext, mode);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failure opening " + path, e);
             }
@@ -72,7 +77,7 @@
         return null;
     }
 
-    public ParcelFileDescriptor onOpenOutputFile(String path, String seLinuxContext) {
+    public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext, String mode) {
         return null;
     }
 
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index e4a12e8..d75219f 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -17,6 +17,7 @@
 package android.os;
 
 import android.util.Slog;
+
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.BufferedInputStream;
@@ -118,13 +119,33 @@
                 mErrPrintWriter.flush();
             }
             if (DEBUG) Slog.d(TAG, "Sending command result on " + mTarget);
-            mResultReceiver.send(res, null);
+            if (mResultReceiver != null) {
+                mResultReceiver.send(res, null);
+            }
         }
         if (DEBUG) Slog.d(TAG, "Finished command " + mCmd + " on " + mTarget);
         return res;
     }
 
     /**
+     * Adopt the ResultReceiver that was given to this shell command from it, taking
+     * it over.  Primarily used to dispatch to another shell command.  Once called,
+     * this shell command will no longer return its own result when done.
+     */
+    public ResultReceiver adoptResultReceiver() {
+        ResultReceiver rr = mResultReceiver;
+        mResultReceiver = null;
+        return rr;
+    }
+
+    /**
+     * Return the raw FileDescriptor for the output stream.
+     */
+    public FileDescriptor getOutFileDescriptor() {
+        return mOut;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's output data stream.
      */
     public OutputStream getRawOutputStream() {
@@ -145,6 +166,13 @@
     }
 
     /**
+     * Return the raw FileDescriptor for the error stream.
+     */
+    public FileDescriptor getErrFileDescriptor() {
+        return mErr;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's error output data stream.
      */
     public OutputStream getRawErrorStream() {
@@ -168,6 +196,13 @@
     }
 
     /**
+     * Return the raw FileDescriptor for the input stream.
+     */
+    public FileDescriptor getInFileDescriptor() {
+        return mIn;
+    }
+
+    /**
      * Return direct raw access (not buffered) to the command's input data stream.
      */
     public InputStream getRawInputStream() {
@@ -191,10 +226,10 @@
      * Helper for just system services to ask the shell to open an output file.
      * @hide
      */
-    public ParcelFileDescriptor openOutputFileForSystem(String path) {
+    public ParcelFileDescriptor openFileForSystem(String path, String mode) {
         try {
-            ParcelFileDescriptor pfd = getShellCallback().openOutputFile(path,
-                    "u:r:system_server:s0");
+            ParcelFileDescriptor pfd = getShellCallback().openFile(path,
+                    "u:r:system_server:s0", mode);
             if (pfd != null) {
                 return pfd;
             }
diff --git a/core/java/android/os/StatsLogEventWrapper.aidl b/core/java/android/os/StatsLogEventWrapper.aidl
new file mode 100644
index 0000000..766343e
--- /dev/null
+++ b/core/java/android/os/StatsLogEventWrapper.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.os;
+
+/** @hide */
+parcelable StatsLogEventWrapper cpp_header "android/os/StatsLogEventWrapper.h";
\ No newline at end of file
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
new file mode 100644
index 0000000..9491bec
--- /dev/null
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,141 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+    private ByteArrayOutputStream mStorage = new ByteArrayOutputStream();
+
+    // Below are constants copied from log/log.h
+    private static final int EVENT_TYPE_INT = 0;  /* int32_t */
+    private static final int EVENT_TYPE_LONG = 1; /* int64_t */
+    private static final int EVENT_TYPE_STRING = 2;
+    private static final int EVENT_TYPE_LIST = 3;
+    private static final int EVENT_TYPE_FLOAT = 4;
+
+    /**
+     * Creates a log_event that is binary-encoded as implemented in
+     * system/core/liblog/log_event_list.c; this allows us to use the same parsing logic in statsd
+     * for pushed and pulled data. The write* methods must be called in the same order as their
+     * field number. There is no checking that the correct number of write* methods is called.
+     * We also write an END_LIST character before beginning to write to parcel, but this END_LIST
+     * may be unnecessary.
+     *
+     * @param tag    The integer representing the tag for this event.
+     * @param fields The number of fields specified in this event.
+     */
+    public StatsLogEventWrapper(int tag, int fields) {
+        // Write four bytes from tag, starting with least-significant bit.
+        write4Bytes(tag);
+        mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
+        mStorage.write(fields); // Indicate number of elements in this list.
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public static final Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+            Parcelable.Creator<StatsLogEventWrapper>() {
+                public StatsLogEventWrapper createFromParcel(Parcel in) {
+                    return new StatsLogEventWrapper(in);
+                }
+
+                public StatsLogEventWrapper[] newArray(int size) {
+                    return new StatsLogEventWrapper[size];
+                }
+            };
+
+    private void write4Bytes(int val) {
+        mStorage.write(val);
+        mStorage.write(val >>> 8);
+        mStorage.write(val >>> 16);
+        mStorage.write(val >>> 24);
+    }
+
+    private void write8Bytes(long val) {
+        write4Bytes((int) (val & 0xFFFFFFFF)); // keep the lowe 32-bits
+        write4Bytes((int) (val >>> 32)); // Write the high 32-bits.
+    }
+
+    /**
+     * Adds 32-bit integer to output.
+     */
+    public void writeInt(int val) {
+        mStorage.write(EVENT_TYPE_INT);
+        write4Bytes(val);
+    }
+
+    /**
+     * Adds 64-bit long to output.
+     */
+    public void writeLong(long val) {
+        mStorage.write(EVENT_TYPE_LONG);
+        write8Bytes(val);
+    }
+
+    /**
+     * Adds a 4-byte floating point value to output.
+     */
+    public void writeFloat(float val) {
+        int v = Float.floatToIntBits(val);
+        mStorage.write(EVENT_TYPE_FLOAT);
+        write4Bytes(v);
+    }
+
+    /**
+     * Adds a string to the output.
+     */
+    public void writeString(String val) {
+        mStorage.write(EVENT_TYPE_STRING);
+        write4Bytes(val.length());
+        byte[] bytes = val.getBytes(StandardCharsets.UTF_8);
+        mStorage.write(bytes, 0, bytes.length);
+    }
+
+    private StatsLogEventWrapper(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Writes the stored fields to a byte array. Will first write a new-line character to denote
+     * END_LIST before writing contents to byte array.
+     */
+    public void writeToParcel(Parcel out, int flags) {
+        mStorage.write(10); // new-line character is same as END_LIST
+        out.writeByteArray(mStorage.toByteArray());
+    }
+
+    /**
+     * Not implemented.
+     */
+    public void readFromParcel(Parcel in) {
+        // Not needed since this java class is for sending to statsd only.
+    }
+
+    /**
+     * Boilerplate for Parcel.
+     */
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 2528439..6ce12c1 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -20,7 +20,6 @@
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
 import android.app.IActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -35,6 +34,7 @@
 import android.util.Slog;
 import android.view.IWindowManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.os.RuntimeInit;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.HexDump;
@@ -48,8 +48,10 @@
 import java.io.StringWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -154,12 +156,15 @@
     // Byte 1: Thread-policy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_DISK_WRITE = 0x01; // for ThreadPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_DISK_READ = 0x02; // for ThreadPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_NETWORK = 0x04; // for ThreadPolicy
 
     /**
@@ -167,6 +172,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
 
     /**
@@ -174,9 +180,11 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_UNBUFFERED_IO = 0x20; // for ThreadPolicy
 
     private static final int ALL_THREAD_DETECT_BITS =
@@ -194,6 +202,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_VM_CURSOR_LEAKS = 0x01 << 8; // for VmPolicy
 
     /**
@@ -201,6 +210,7 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_VM_CLOSABLE_LEAKS = 0x02 << 8; // for VmPolicy
 
     /**
@@ -208,25 +218,32 @@
      *
      * @hide
      */
+    @TestApi
     public static final int DETECT_VM_ACTIVITY_LEAKS = 0x04 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_INSTANCE_LEAKS = 0x08 << 8; // for VmPolicy
 
     /** @hide */
+    @TestApi
     public static final int DETECT_VM_REGISTRATION_LEAKS = 0x10 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_FILE_URI_EXPOSURE = 0x20 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_CLEARTEXT_NETWORK = 0x40 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_CONTENT_URI_WITHOUT_PERMISSION = 0x80 << 8; // for VmPolicy
 
     /** @hide */
-    private static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
+    @TestApi
+    public static final int DETECT_VM_UNTAGGED_SOCKET = 0x80 << 24; // for VmPolicy
 
     private static final int ALL_VM_DETECT_BITS =
             DETECT_VM_CURSOR_LEAKS
@@ -337,8 +354,8 @@
                 } else {
                     msg = "StrictMode policy violation:";
                 }
-                if (info.crashInfo != null) {
-                    Log.d(TAG, msg + " " + info.crashInfo.stackTrace);
+                if (info.hasStackTrace()) {
+                    Log.d(TAG, msg + " " + info.getStackTrace());
                 } else {
                     Log.d(TAG, msg + " missing stack trace!");
                 }
@@ -1232,28 +1249,6 @@
         }
     }
 
-    /** Like parsePolicyFromMessage(), but returns the violation. */
-    private static int parseViolationFromMessage(String message) {
-        if (message == null) {
-            return 0;
-        }
-        int violationIndex = message.indexOf("violation=");
-        if (violationIndex == -1) {
-            return 0;
-        }
-        int numberStartIndex = violationIndex + "violation=".length();
-        int numberEndIndex = message.indexOf(' ', numberStartIndex);
-        if (numberEndIndex == -1) {
-            numberEndIndex = message.length();
-        }
-        String violationString = message.substring(numberStartIndex, numberEndIndex);
-        try {
-            return Integer.parseInt(violationString);
-        } catch (NumberFormatException e) {
-            return 0;
-        }
-    }
-
     private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
             new ThreadLocal<ArrayList<ViolationInfo>>() {
                 @Override
@@ -1501,7 +1496,7 @@
         // to people who push/pop temporary policy in regions of code,
         // hence the policy being passed around.
         void handleViolation(final ViolationInfo info) {
-            if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) {
+            if (info == null || !info.hasStackTrace()) {
                 Log.wtf(TAG, "unexpected null stacktrace");
                 return;
             }
@@ -1515,7 +1510,7 @@
                     gatheredViolations.set(violations);
                 }
                 for (ViolationInfo previous : violations) {
-                    if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
+                    if (info.getStackTrace().equals(previous.getStackTrace())) {
                         // Duplicate. Don't log.
                         return;
                     }
@@ -1561,8 +1556,7 @@
             }
 
             if (violationMaskSubset != 0) {
-                int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
-                violationMaskSubset |= violationBit;
+                violationMaskSubset |= info.getViolationBit();
                 final int savedPolicyMask = getThreadPolicyMask();
 
                 final boolean justDropBox = (info.policy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
@@ -1607,8 +1601,7 @@
     }
 
     private static void executeDeathPenalty(ViolationInfo info) {
-        int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
-        throw new StrictModeViolation(info.policy, violationBit, null);
+        throw new StrictModeViolation(info.policy, info.getViolationBit(), null);
     }
 
     /**
@@ -1655,7 +1648,7 @@
 
     private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
         public void report(String message, Throwable allocationSite) {
-            onVmPolicyViolation(message, allocationSite);
+            onVmPolicyViolation(allocationSite);
         }
     }
 
@@ -1694,7 +1687,7 @@
             long instances = instanceCounts[i];
             if (instances > limit) {
                 Throwable tr = new InstanceCountViolation(klass, instances, limit);
-                onVmPolicyViolation(tr.getMessage(), tr);
+                onVmPolicyViolation(tr);
             }
         }
     }
@@ -1818,22 +1811,24 @@
 
     /** @hide */
     public static void onSqliteObjectLeaked(String message, Throwable originStack) {
-        onVmPolicyViolation(message, originStack);
+        Throwable t = new Throwable(message);
+        t.setStackTrace(originStack.getStackTrace());
+        onVmPolicyViolation(t);
     }
 
     /** @hide */
     public static void onWebViewMethodCalledOnWrongThread(Throwable originStack) {
-        onVmPolicyViolation(null, originStack);
+        onVmPolicyViolation(originStack);
     }
 
     /** @hide */
     public static void onIntentReceiverLeaked(Throwable originStack) {
-        onVmPolicyViolation(null, originStack);
+        onVmPolicyViolation(originStack);
     }
 
     /** @hide */
     public static void onServiceConnectionLeaked(Throwable originStack) {
-        onVmPolicyViolation(null, originStack);
+        onVmPolicyViolation(originStack);
     }
 
     /** @hide */
@@ -1842,7 +1837,7 @@
         if ((sVmPolicy.mask & PENALTY_DEATH_ON_FILE_URI_EXPOSURE) != 0) {
             throw new FileUriExposedException(message);
         } else {
-            onVmPolicyViolation(null, new Throwable(message));
+            onVmPolicyViolation(new Throwable(message));
         }
     }
 
@@ -1854,10 +1849,14 @@
                         + location
                         + " without permission grant flags; did you forget"
                         + " FLAG_GRANT_READ_URI_PERMISSION?";
-        onVmPolicyViolation(null, new Throwable(message));
+        onVmPolicyViolation(new Throwable(message));
     }
 
     /** @hide */
+    public static final String CLEARTEXT_DETECTED_MSG =
+            "Detected cleartext network traffic from UID ";
+
+    /** @hide */
     public static void onCleartextNetworkDetected(byte[] firstPacket) {
         byte[] rawAddr = null;
         if (firstPacket != null) {
@@ -1873,47 +1872,42 @@
         }
 
         final int uid = android.os.Process.myUid();
-        String msg = "Detected cleartext network traffic from UID " + uid;
+        String msg = CLEARTEXT_DETECTED_MSG + uid;
         if (rawAddr != null) {
             try {
-                msg =
-                        "Detected cleartext network traffic from UID "
-                                + uid
-                                + " to "
-                                + InetAddress.getByAddress(rawAddr);
+                msg += " to " + InetAddress.getByAddress(rawAddr);
             } catch (UnknownHostException ignored) {
             }
         }
-
+        msg += HexDump.dumpHexString(firstPacket).trim() + " ";
         final boolean forceDeath = (sVmPolicy.mask & PENALTY_DEATH_ON_CLEARTEXT_NETWORK) != 0;
-        onVmPolicyViolation(
-                HexDump.dumpHexString(firstPacket).trim(), new Throwable(msg), forceDeath);
+        onVmPolicyViolation(new Throwable(msg), forceDeath);
     }
 
     /** @hide */
+    public static final String UNTAGGED_SOCKET_VIOLATION_MESSAGE =
+            "Untagged socket detected; use"
+                    + " TrafficStats.setThreadSocketTag() to track all network usage";
+
+    /** @hide */
     public static void onUntaggedSocket() {
-        onVmPolicyViolation(
-                null,
-                new Throwable(
-                        "Untagged socket detected; use"
-                                + " TrafficStats.setThreadSocketTag() to track all network usage"));
+        onVmPolicyViolation(new Throwable(UNTAGGED_SOCKET_VIOLATION_MESSAGE));
     }
 
     // Map from VM violation fingerprint to uptime millis.
     private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
 
     /** @hide */
-    public static void onVmPolicyViolation(String message, Throwable originStack) {
-        onVmPolicyViolation(message, originStack, false);
+    public static void onVmPolicyViolation(Throwable originStack) {
+        onVmPolicyViolation(originStack, false);
     }
 
     /** @hide */
-    public static void onVmPolicyViolation(
-            String message, Throwable originStack, boolean forceDeath) {
+    public static void onVmPolicyViolation(Throwable originStack, boolean forceDeath) {
         final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
         final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
         final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
-        final ViolationInfo info = new ViolationInfo(message, originStack, sVmPolicy.mask);
+        final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
 
         // Erase stuff not relevant for process-wide violations
         info.numAnimationsRunning = 0;
@@ -2011,21 +2005,14 @@
      * read back all the encoded violations.
      */
     /* package */ static void readAndHandleBinderCallViolations(Parcel p) {
-        // Our own stack trace to append
-        StringWriter sw = new StringWriter();
-        sw.append("# via Binder call with stack:\n");
-        PrintWriter pw = new FastPrintWriter(sw, false, 256);
-        new LogStackTrace().printStackTrace(pw);
-        pw.flush();
-        String ourStack = sw.toString();
-
+        LogStackTrace localCallSite = new LogStackTrace();
         final int policyMask = getThreadPolicyMask();
         final boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
 
         final int size = p.readInt();
         for (int i = 0; i < size; i++) {
             final ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
-            info.crashInfo.appendStackTrace(ourStack);
+            info.addLocalStack(localCallSite);
             BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
             if (policy instanceof AndroidBlockGuardPolicy) {
                 ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
@@ -2238,7 +2225,7 @@
             // StrictMode not enabled.
             return;
         }
-        ((AndroidBlockGuardPolicy) policy).onUnbufferedIO();
+        policy.onUnbufferedIO();
     }
 
     /** @hide */
@@ -2248,7 +2235,7 @@
             // StrictMode not enabled.
             return;
         }
-        ((AndroidBlockGuardPolicy) policy).onReadFromDisk();
+        policy.onReadFromDisk();
     }
 
     /** @hide */
@@ -2258,12 +2245,11 @@
             // StrictMode not enabled.
             return;
         }
-        ((AndroidBlockGuardPolicy) policy).onWriteToDisk();
+        policy.onWriteToDisk();
     }
 
-    // Guarded by StrictMode.class
-    private static final HashMap<Class, Integer> sExpectedActivityInstanceCount =
-            new HashMap<Class, Integer>();
+    @GuardedBy("StrictMode.class")
+    private static final HashMap<Class, Integer> sExpectedActivityInstanceCount = new HashMap<>();
 
     /**
      * Returns an object that is used to track instances of activites. The activity should store a
@@ -2338,7 +2324,7 @@
         long instances = VMDebug.countInstancesOfClass(klass, false);
         if (instances > limit) {
             Throwable tr = new InstanceCountViolation(klass, instances, limit);
-            onVmPolicyViolation(tr.getMessage(), tr);
+            onVmPolicyViolation(tr);
         }
     }
 
@@ -2350,10 +2336,16 @@
      */
     @TestApi
     public static final class ViolationInfo implements Parcelable {
-        public final String message;
+        /** Stack and violation details. */
+        @Nullable private final Throwable mThrowable;
 
-        /** Stack and other stuff info. */
-        @Nullable public final ApplicationErrorReport.CrashInfo crashInfo;
+        /** Path leading to a violation that occurred across binder. */
+        private final Deque<StackTraceElement[]> mBinderStack = new ArrayDeque<>();
+
+        /** Memoized stack trace of full violation. */
+        @Nullable private String mStackTrace;
+        /** Memoized violation bit. */
+        private int mViolationBit;
 
         /** The strict mode policy mask at the time of violation. */
         public final int policy;
@@ -2388,19 +2380,13 @@
 
         /** Create an uninitialized instance of ViolationInfo */
         public ViolationInfo() {
-            message = null;
-            crashInfo = null;
+            mThrowable = null;
             policy = 0;
         }
 
-        public ViolationInfo(Throwable tr, int policy) {
-            this(null, tr, policy);
-        }
-
         /** Create an instance of ViolationInfo initialized from an exception. */
-        public ViolationInfo(String message, Throwable tr, int policy) {
-            this.message = message;
-            crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+        public ViolationInfo(Throwable tr, int policy) {
+            this.mThrowable = tr;
             violationUptimeMillis = SystemClock.uptimeMillis();
             this.policy = policy;
             this.numAnimationsRunning = ValueAnimator.getCurrentAnimationsCount();
@@ -2430,11 +2416,96 @@
             }
         }
 
+        /** Equivalent output to {@link ApplicationErrorReport.CrashInfo#stackTrace}. */
+        public String getStackTrace() {
+            if (mThrowable != null && mStackTrace == null) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new FastPrintWriter(sw, false, 256);
+                mThrowable.printStackTrace(pw);
+                for (StackTraceElement[] traces : mBinderStack) {
+                    pw.append("# via Binder call with stack:\n");
+                    for (StackTraceElement traceElement : traces) {
+                        pw.append("\tat ");
+                        pw.append(traceElement.toString());
+                        pw.append('\n');
+                    }
+                }
+                pw.flush();
+                pw.close();
+                mStackTrace = sw.toString();
+            }
+            return mStackTrace;
+        }
+
+        /**
+         * Optional message describing this violation.
+         *
+         * @hide
+         */
+        @TestApi
+        public String getViolationDetails() {
+            if (mThrowable != null) {
+                return mThrowable.getMessage();
+            } else {
+                return "";
+            }
+        }
+
+        /**
+         * If this violation has a useful stack trace.
+         *
+         * @hide
+         */
+        public boolean hasStackTrace() {
+            return mThrowable != null;
+        }
+
+        /**
+         * Add a {@link Throwable} from the current process that caused the underlying violation. We
+         * only preserve the stack trace elements.
+         *
+         * @hide
+         */
+        void addLocalStack(Throwable t) {
+            mBinderStack.addFirst(t.getStackTrace());
+        }
+
+        /**
+         * Retrieve the type of StrictMode violation.
+         *
+         * @hide
+         */
+        int getViolationBit() {
+            if (mThrowable == null || mThrowable.getMessage() == null) {
+                return 0;
+            }
+            if (mViolationBit != 0) {
+                return mViolationBit;
+            }
+            String message = mThrowable.getMessage();
+            int violationIndex = message.indexOf("violation=");
+            if (violationIndex == -1) {
+                return 0;
+            }
+            int numberStartIndex = violationIndex + "violation=".length();
+            int numberEndIndex = message.indexOf(' ', numberStartIndex);
+            if (numberEndIndex == -1) {
+                numberEndIndex = message.length();
+            }
+            String violationString = message.substring(numberStartIndex, numberEndIndex);
+            try {
+                mViolationBit = Integer.parseInt(violationString);
+                return mViolationBit;
+            } catch (NumberFormatException e) {
+                return 0;
+            }
+        }
+
         @Override
         public int hashCode() {
             int result = 17;
-            if (crashInfo != null) {
-                result = 37 * result + crashInfo.stackTrace.hashCode();
+            if (mThrowable != null) {
+                result = 37 * result + mThrowable.hashCode();
             }
             if (numAnimationsRunning != 0) {
                 result *= 37;
@@ -2462,11 +2533,20 @@
          *     should be removed.
          */
         public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
-            message = in.readString();
-            if (in.readInt() != 0) {
-                crashInfo = new ApplicationErrorReport.CrashInfo(in);
-            } else {
-                crashInfo = null;
+            mThrowable = (Throwable) in.readSerializable();
+            int binderStackSize = in.readInt();
+            for (int i = 0; i < binderStackSize; i++) {
+                StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
+                for (int j = 0; j < traceElements.length; j++) {
+                    StackTraceElement element =
+                            new StackTraceElement(
+                                    in.readString(),
+                                    in.readString(),
+                                    in.readString(),
+                                    in.readInt());
+                    traceElements[j] = element;
+                }
+                mBinderStack.add(traceElements);
             }
             int rawPolicy = in.readInt();
             if (unsetGatheringBit) {
@@ -2486,12 +2566,16 @@
         /** Save a ViolationInfo instance to a parcel. */
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(message);
-            if (crashInfo != null) {
-                dest.writeInt(1);
-                crashInfo.writeToParcel(dest, flags);
-            } else {
-                dest.writeInt(0);
+            dest.writeSerializable(mThrowable);
+            dest.writeInt(mBinderStack.size());
+            for (StackTraceElement[] traceElements : mBinderStack) {
+                dest.writeInt(traceElements.length);
+                for (StackTraceElement element : traceElements) {
+                    dest.writeString(element.getClassName());
+                    dest.writeString(element.getMethodName());
+                    dest.writeString(element.getFileName());
+                    dest.writeInt(element.getLineNumber());
+                }
             }
             int start = dest.dataPosition();
             dest.writeInt(policy);
@@ -2526,8 +2610,8 @@
 
         /** Dump a ViolationInfo instance to a Printer. */
         public void dump(Printer pw, String prefix) {
-            if (crashInfo != null) {
-                crashInfo.dump(pw, prefix);
+            if (mThrowable != null) {
+                pw.println(prefix + "stackTrace: " + getStackTrace());
             }
             pw.println(prefix + "policy: " + policy);
             if (durationMillis != -1) {
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 560b4b3..4f6d322 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -84,9 +84,6 @@
     /**
      * Get the String value for the given {@code key}.
      *
-     * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
-     * method will crash in native code.
-     *
      * @param key the key to lookup
      * @return an empty string if the {@code key} isn't found
      */
@@ -99,9 +96,6 @@
     /**
      * Get the String value for the given {@code key}.
      *
-     * <b>WARNING:</b> Do not use this method if the value may not be a valid UTF string! This
-     * method will crash in native code.
-     *
      * @param key the key to lookup
      * @param def the default value in case the property is not set or empty
      * @return if the {@code key} isn't found, return {@code def} if it isn't null, or an empty
@@ -163,7 +157,7 @@
      * @throws IllegalArgumentException if the {@code val} exceeds 91 characters
      */
     public static void set(@NonNull String key, @Nullable String val) {
-        if (val != null && val.length() > PROP_VALUE_MAX) {
+        if (val != null && !val.startsWith("ro.") && val.length() > PROP_VALUE_MAX) {
             throw new IllegalArgumentException("value of system property '" + key
                     + "' is longer than " + PROP_VALUE_MAX + " characters: " + val);
         }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 430a5e3..c54b72d 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -319,6 +319,23 @@
     public static final String DISALLOW_CONFIG_VPN = "no_config_vpn";
 
     /**
+     * Specifies if date, time and timezone configuring is disallowed.
+     *
+     * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+     * time and timezone setting on the entire device and all users will be affected. When it's set
+     * by profile owners, it's only applied to the managed user.
+     * <p>The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+
+    /**
      * Specifies if a user is disallowed from configuring Tethering
      * & portable hotspots. This can only be set by device owners and profile owners on the
      * primary user. The default value is <code>false</code>.
@@ -574,6 +591,25 @@
     public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows";
 
     /**
+     * Specifies that system error dialogs for crashed or unresponsive apps should not be shown.
+     * In this case, the system will force-stop the app as if the user chooses the "close app"
+     * option on the UI. No feedback report will be collected as there is no way for the user to
+     * provide explicit consent.
+     *
+     * When this user restriction is set by device owners, it's applied to all users; when it's set
+     * by profile owners, it's only applied to the relevant profiles.
+     * The default value is <code>false</code>.
+     *
+     * <p>This user restriction has no effect on managed profiles.
+     * <p>Key for user restrictions.
+     * <p>Type: Boolean
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+
+    /**
      * Specifies if what is copied in the clipboard of this profile can
      * be pasted in related profiles. Does not restrict if the clipboard of related profiles can be
      * pasted in this profile.
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 17f00c2..9369eeb 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -154,11 +154,21 @@
     public abstract boolean isUserUnlocked(int userId);
 
     /**
-     * Return whether the given user is running
+     * Returns whether the given user is running
      */
     public abstract boolean isUserRunning(int userId);
 
     /**
+     * Returns whether the given user is initialized
+     */
+    public abstract boolean isUserInitialized(int userId);
+
+    /**
+     * Returns whether the given user exists
+     */
+    public abstract boolean exists(int userId);
+
+    /**
      * Set user's running state
      */
     public abstract void setUserState(int userId, int userState);
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 50855bb..e865ed1 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -45,103 +45,11 @@
      */
     void unregisterListener(IStorageEventListener listener) = 1;
     /**
-     * Returns true if a USB mass storage host is connected
-     */
-    boolean isUsbMassStorageConnected() = 2;
-    /**
-     * Enables / disables USB mass storage. The caller should check actual
-     * status of enabling/disabling USB mass storage via StorageEventListener.
-     */
-    void setUsbMassStorageEnabled(boolean enable) = 3;
-    /**
-     * Returns true if a USB mass storage host is enabled (media is shared)
-     */
-    boolean isUsbMassStorageEnabled() = 4;
-    /**
-     * Mount external storage at given mount point. Returns an int consistent
-     * with StorageResultCode
-     */
-    int mountVolume(in String mountPoint) = 5;
-    /**
-     * Safely unmount external storage at given mount point. The unmount is an
-     * asynchronous operation. Applications should register StorageEventListener
-     * for storage related status changes.
-     * @param mountPoint the mount point
-     * @param force whether or not to forcefully unmount it (e.g. even if programs are using this
-     *     data currently)
-     * @param removeEncryption whether or not encryption mapping should be removed from the volume.
-     *     This value implies {@code force}.
-     */
-    void unmountVolume(in String mountPoint, boolean force, boolean removeEncryption) = 6;
-    /**
-     * Format external storage given a mount point. Returns an int consistent
-     * with StorageResultCode
-     */
-    int formatVolume(in String mountPoint) = 7;
-    /**
-     * Returns an array of pids with open files on the specified path.
-     */
-    int[] getStorageUsers(in String path) = 8;
-    /**
-     * Gets the state of a volume via its mountpoint.
-     */
-    String getVolumeState(in String mountPoint) = 9;
-    /*
-     * Creates a secure container with the specified parameters. Returns an int
-     * consistent with StorageResultCode
-     */
-    int createSecureContainer(in String id, int sizeMb, in String fstype, in String key,
-            int ownerUid, boolean external) = 10;
-    /*
-     * Finalize a container which has just been created and populated. After
-     * finalization, the container is immutable. Returns an int consistent with
-     * StorageResultCode
-     */
-    int finalizeSecureContainer(in String id) = 11;
-    /*
-     * Destroy a secure container, and free up all resources associated with it.
-     * NOTE: Ensure all references are released prior to deleting. Returns an
-     * int consistent with StorageResultCode
-     */
-    int destroySecureContainer(in String id, boolean force) = 12;
-    /*
-     * Mount a secure container with the specified key and owner UID. Returns an
-     * int consistent with StorageResultCode
-     */
-    int mountSecureContainer(in String id, in String key, int ownerUid, boolean readOnly) = 13;
-    /*
-     * Unount a secure container. Returns an int consistent with
-     * StorageResultCode
-     */
-    int unmountSecureContainer(in String id, boolean force) = 14;
-    /*
-     * Returns true if the specified container is mounted
-     */
-    boolean isSecureContainerMounted(in String id) = 15;
-    /*
-     * Rename an unmounted secure container. Returns an int consistent with
-     * StorageResultCode
-     */
-    int renameSecureContainer(in String oldId, in String newId) = 16;
-    /*
-     * Returns the filesystem path of a mounted secure container.
-     */
-    String getSecureContainerPath(in String id) = 17;
-    /**
-     * Gets an Array of currently known secure container IDs
-     */
-    String[] getSecureContainerList() = 18;
-    /**
      * Shuts down the StorageManagerService and gracefully unmounts all external media.
      * Invokes call back once the shutdown is complete.
      */
     void shutdown(IStorageShutdownObserver observer) = 19;
     /**
-     * Call into StorageManagerService by PackageManager to notify that its done
-     * processing the media status update request.
-     */
-    void finishMediaUpdate() = 20;
-    /**
      * Mounts an Opaque Binary Blob (OBB) with the specified decryption key and
      * only allows the calling process's UID access to the contents.
      * StorageManagerService will call back to the supplied IObbActionListener to inform
@@ -166,10 +74,6 @@
      */
     String getMountedObbPath(in String rawPath) = 24;
     /**
-     * Returns whether or not the external storage is emulated.
-     */
-    boolean isExternalStorageEmulated() = 25;
-    /**
      * Decrypts any encrypted volumes.
      */
     int decryptStorage(in String password) = 26;
@@ -186,14 +90,6 @@
      */
     StorageVolume[] getVolumeList(int uid, in String packageName, int flags) = 29;
     /**
-     * Gets the path on the filesystem for the ASEC container itself.
-     *
-     * @param cid ASEC container ID
-     * @return path to filesystem or {@code null} if it's not found
-     * @throws RemoteException
-     */
-    String getSecureContainerFilesystemPath(in String cid) = 30;
-    /**
      * Determines the encryption state of the volume.
      * @return a numerical value. See {@code ENCRYPTION_STATE_*} for possible
      * values.
@@ -208,11 +104,6 @@
      * may only be called by the system process.
      */
     int verifyEncryptionPassword(in String password) = 32;
-    /*
-     * Fix permissions in a container which has just been created and populated.
-     * Returns an int consistent with StorageResultCode
-     */
-    int fixPermissionsSecureContainer(in String id, int gid, in String filename) = 33;
     /**
      * Ensure that all directories along given path exist, creating parent
      * directories as needed. Validates that given path is absolute and that it
@@ -247,7 +138,6 @@
      * @return contents of field
      */
     String getField(in String field) = 39;
-    int resizeSecureContainer(in String id, int sizeMb, in String key) = 40;
     /**
      * Report the time of the last maintenance operation such as fstrim.
      * @return Timestamp of the last maintenance operation, in the
@@ -260,7 +150,6 @@
      * @throws RemoteException
      */
     void runMaintenance() = 42;
-    void waitForAsecScan() = 43;
     DiskInfo[] getDisks() = 44;
     VolumeInfo[] getVolumes(int flags) = 45;
     VolumeRecord[] getVolumeRecords(int flags) = 46;
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index ee8eed1..3d2e1d1 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -206,8 +206,7 @@
                     try {
                         mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
                                 .getAudioAttributes())
-                                .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
-                                        AudioAttributes.FLAG_BYPASS_MUTE)
+                                .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
                                 .build());
                         mRingtone.play();
                     } catch (Throwable e) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3c3e466..62f4bf5 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -442,6 +442,18 @@
             "android.settings.ASSIST_GESTURE_SETTINGS";
 
     /**
+     * Activity Action: Show settings to enroll fingerprints, and setup PIN/Pattern/Pass if
+     * necessary.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_FINGERPRINT_ENROLL =
+            "android.settings.FINGERPRINT_ENROLL";
+
+    /**
      * Activity Action: Show settings to allow configuration of cast endpoints.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -3084,6 +3096,12 @@
         private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
 
         /**
+         * The display color mode.
+         * @hide
+         */
+        public static final String DISPLAY_COLOR_MODE = "display_color_mode";
+
+        /**
          * The amount of time in milliseconds before the device goes to sleep or begins
          * to dream after a period of inactivity.  This value is also known as the
          * user activity timeout period since the screen isn't necessarily turned off
@@ -5708,6 +5726,7 @@
          *
          * @hide
          */
+        @TestApi
         public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
                 "accessibility_display_magnification_enabled";
 
@@ -6792,14 +6811,6 @@
                 "lock_screen_show_notifications";
 
         /**
-         * This preference stores the last stack active task time for each user, which affects what
-         * tasks will be visible in Overview.
-         * @hide
-         */
-        public static final String OVERVIEW_LAST_STACK_ACTIVE_TIME =
-                "overview_last_stack_active_time";
-
-        /**
          * List of TV inputs that are currently hidden. This is a string
          * containing the IDs of all hidden TV inputs. Each ID is encoded by
          * {@link android.net.Uri#encode(String)} and separated by ':'.
@@ -7610,6 +7621,13 @@
         public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
 
         /**
+         * An integer representing the Bluetooth Class of Device (CoD).
+         *
+         * @hide
+         */
+        public static final String BLUETOOTH_CLASS_OF_DEVICE = "bluetooth_class_of_device";
+
+        /**
          * A Long representing a bitmap of profiles that should be disabled when bluetooth starts.
          * See {@link android.bluetooth.BluetoothProfile}.
          * {@hide}
@@ -9261,6 +9279,13 @@
          */
         public static final String DEFAULT_DNS_SERVER = "default_dns_server";
 
+        /**
+         * Whether to disable DNS over TLS (boolean)
+         *
+         * @hide
+         */
+        public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+
         /** {@hide} */
         public static final String
                 BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
@@ -9369,16 +9394,6 @@
         public static final String DEVICE_IDLE_CONSTANTS = "device_idle_constants";
 
         /**
-         * Device Idle (Doze) specific settings for watches. See {@code #DEVICE_IDLE_CONSTANTS}
-         *
-         * <p>
-         * Type: string
-         * @hide
-         * @see com.android.server.DeviceIdleController.Constants
-         */
-        public static final String DEVICE_IDLE_CONSTANTS_WATCH = "device_idle_constants_watch";
-
-        /**
          * Battery Saver specific settings
          * This is encoded as a key=value list, separated by commas. Ex:
          *
@@ -9404,9 +9419,12 @@
 
         /**
          * Battery anomaly detection specific settings
-         * This is encoded as a key=value list, separated by commas. Ex:
+         * This is encoded as a key=value list, separated by commas.
+         * wakeup_blacklisted_tags is a string, encoded as a set of tags, encoded via
+         * {@link Uri#encode(String)}, separated by colons. Ex:
          *
-         * "anomaly_detection_enabled=true,wakelock_threshold=2000"
+         * "anomaly_detection_enabled=true,wakelock_threshold=2000,wakeup_alarm_enabled=true,"
+         * "wakeup_alarm_threshold=10,wakeup_blacklisted_tags=tag1:tag2:with%2Ccomma:with%3Acolon"
          *
          * The following keys are supported:
          *
@@ -9414,6 +9432,11 @@
          * anomaly_detection_enabled       (boolean)
          * wakelock_enabled                (boolean)
          * wakelock_threshold              (long)
+         * wakeup_alarm_enabled            (boolean)
+         * wakeup_alarm_threshold          (long)
+         * wakeup_blacklisted_tags         (string)
+         * bluetooth_scan_enabled          (boolean)
+         * bluetooth_scan_threshold        (long)
          * </pre>
          * @hide
          */
@@ -9577,6 +9600,22 @@
         public static final String DEVICE_POLICY_CONSTANTS = "device_policy_constants";
 
         /**
+         * TextClassifier specific settings.
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * <pre>
+         * smart_selection_dark_launch              (boolean)
+         * smart_selection_enabled_for_edit_text    (boolean)
+         * </pre>
+         *
+         * <p>
+         * Type: string
+         * @hide
+         * see also android.view.textclassifier.TextClassifierConstants
+         */
+        public static final String TEXT_CLASSIFIER_CONSTANTS = "text_classifier_constants";
+
+        /**
          * Get the key that retrieves a bluetooth headset's priority.
          * @hide
          */
@@ -9623,7 +9662,7 @@
          * Get the key that retrieves a bluetooth Input Device's priority.
          * @hide
          */
-        public static final String getBluetoothInputDevicePriorityKey(String address) {
+        public static final String getBluetoothHidHostPriorityKey(String address) {
             return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
         }
 
@@ -10894,6 +10933,26 @@
          */
         public static final String ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE =
                 "enable_deletion_helper_no_threshold_toggle";
+
+        /**
+         * The list of snooze options for notifications
+         * This is encoded as a key=value list, separated by commas. Ex:
+         *
+         * "default=60,options_array=15:30:60:120"
+         *
+         * The following keys are supported:
+         *
+         * <pre>
+         * default               (int)
+         * options_array         (string)
+         * </pre>
+         *
+         * All delays in integer minutes. Array order is respected.
+         * Options will be used in order up to the maximum allowed by the UI.
+         * @hide
+         */
+        public static final String NOTIFICATION_SNOOZE_OPTIONS =
+                "notification_snooze_options";
     }
 
     /**
diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java
index 812c956..0c4eeda 100644
--- a/core/java/android/security/NetworkSecurityPolicy.java
+++ b/core/java/android/security/NetworkSecurityPolicy.java
@@ -16,7 +16,6 @@
 
 package android.security;
 
-import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.security.net.config.ApplicationConfig;
@@ -63,7 +62,8 @@
      * traffic from applications is handled by higher-level network stacks/components which can
      * honor this aspect of the policy.
      *
-     * <p>NOTE: {@link android.webkit.WebView} does not honor this flag.
+     * <p>NOTE: {@link android.webkit.WebView} honors this flag for applications targeting API level
+     * 26 and up.
      */
     public boolean isCleartextTrafficPermitted() {
         return libcore.net.NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
index 8fcd5ab..79115a5 100644
--- a/core/java/android/security/net/config/ManifestConfigSource.java
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.util.Log;
 import android.util.Pair;
+
 import java.util.Set;
 
 /** @hide */
@@ -29,21 +30,14 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
-    private final int mApplicationInfoFlags;
-    private final int mTargetSdkVersion;
-    private final int mConfigResourceId;
-    private final int mTargetSandboxVesrsion;
+    private final ApplicationInfo mApplicationInfo;
 
     private ConfigSource mConfigSource;
 
     public ManifestConfigSource(Context context) {
         mContext = context;
-        // Cache values because ApplicationInfo is mutable and apps do modify it :(
-        ApplicationInfo info = context.getApplicationInfo();
-        mApplicationInfoFlags = info.flags;
-        mTargetSdkVersion = info.targetSdkVersion;
-        mConfigResourceId = info.networkSecurityConfigRes;
-        mTargetSandboxVesrsion = info.targetSandboxVersion;
+        // Cache the info because ApplicationInfo is mutable and apps do modify it :(
+        mApplicationInfo = new ApplicationInfo(context.getApplicationInfo());
     }
 
     @Override
@@ -61,17 +55,18 @@
             if (mConfigSource != null) {
                 return mConfigSource;
             }
-
+            int configResource = mApplicationInfo.networkSecurityConfigRes;
             ConfigSource source;
-            if (mConfigResourceId != 0) {
-                boolean debugBuild = (mApplicationInfoFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+            if (configResource != 0) {
+                boolean debugBuild =
+                        (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
                 if (DBG) {
                     Log.d(LOG_TAG, "Using Network Security Config from resource "
-                            + mContext.getResources().getResourceEntryName(mConfigResourceId)
+                            + mContext.getResources()
+                                .getResourceEntryName(configResource)
                             + " debugBuild: " + debugBuild);
                 }
-                source = new XmlConfigSource(mContext, mConfigResourceId, debugBuild,
-                        mTargetSdkVersion, mTargetSandboxVesrsion);
+                source = new XmlConfigSource(mContext, configResource, mApplicationInfo);
             } else {
                 if (DBG) {
                     Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
@@ -79,10 +74,9 @@
                 // the legacy FLAG_USES_CLEARTEXT_TRAFFIC is not supported for Ephemeral apps, they
                 // should use the network security config.
                 boolean usesCleartextTraffic =
-                        (mApplicationInfoFlags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
-                        && mTargetSandboxVesrsion < 2;
-                source = new DefaultConfigSource(usesCleartextTraffic, mTargetSdkVersion,
-                        mTargetSandboxVesrsion);
+                        (mApplicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0
+                        && mApplicationInfo.targetSandboxVersion < 2;
+                source = new DefaultConfigSource(usesCleartextTraffic, mApplicationInfo);
             }
             mConfigSource = source;
             return mConfigSource;
@@ -93,10 +87,8 @@
 
         private final NetworkSecurityConfig mDefaultConfig;
 
-        public DefaultConfigSource(boolean usesCleartextTraffic, int targetSdkVersion,
-                int targetSandboxVesrsion) {
-            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(targetSdkVersion,
-                    targetSandboxVesrsion)
+        DefaultConfigSource(boolean usesCleartextTraffic, ApplicationInfo info) {
+            mDefaultConfig = NetworkSecurityConfig.getDefaultBuilder(info)
                     .setCleartextTrafficPermitted(usesCleartextTraffic)
                     .build();
         }
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 789fc27..52f48ef 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -16,9 +16,11 @@
 
 package android.security.net.config;
 
+import android.content.pm.ApplicationInfo;
 import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -28,8 +30,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import javax.net.ssl.X509TrustManager;
-
 /**
  * @hide
  */
@@ -164,28 +164,32 @@
      * <p>
      * The default configuration has the following properties:
      * <ol>
-     * <li>Cleartext traffic is permitted for non-ephemeral apps.</li>
+     * <li>If the application targets API level 27 (Android O MR1) or lower then cleartext traffic
+     * is allowed by default.</li>
      * <li>Cleartext traffic is not permitted for ephemeral apps.</li>
      * <li>HSTS is not enforced.</li>
      * <li>No certificate pinning is used.</li>
      * <li>The system certificate store is trusted for connections.</li>
      * <li>If the application targets API level 23 (Android M) or lower then the user certificate
-     * store is trusted by default as well.</li>
+     * store is trusted by default as well for non-privileged applications.</li>
+     * <li>Privileged applications do not trust the user certificate store on Android P and higher.
+     * </li>
      * </ol>
      *
      * @hide
      */
-    public static final Builder getDefaultBuilder(int targetSdkVersion, int targetSandboxVesrsion) {
+    public static Builder getDefaultBuilder(ApplicationInfo info) {
         Builder builder = new Builder()
                 .setHstsEnforced(DEFAULT_HSTS_ENFORCED)
                 // System certificate store, does not bypass static pins.
                 .addCertificatesEntryRef(
                         new CertificatesEntryRef(SystemCertificateSource.getInstance(), false));
-        final boolean cleartextTrafficPermitted = targetSandboxVesrsion < 2;
+        final boolean cleartextTrafficPermitted = info.targetSdkVersion < Build.VERSION_CODES.P
+                && info.targetSandboxVersion < 2;
         builder.setCleartextTrafficPermitted(cleartextTrafficPermitted);
         // Applications targeting N and above must opt in into trusting the user added certificate
         // store.
-        if (targetSdkVersion <= Build.VERSION_CODES.M) {
+        if (info.targetSdkVersion <= Build.VERSION_CODES.M && !info.isPrivilegedApp()) {
             // User certificate store, does not bypass static pins.
             builder.addCertificatesEntryRef(
                     new CertificatesEntryRef(UserCertificateSource.getInstance(), false));
diff --git a/core/java/android/security/net/config/XmlConfigSource.java b/core/java/android/security/net/config/XmlConfigSource.java
index a111fbce..02be403 100644
--- a/core/java/android/security/net/config/XmlConfigSource.java
+++ b/core/java/android/security/net/config/XmlConfigSource.java
@@ -1,13 +1,13 @@
 package android.security.net.config;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import android.os.Build;
 import android.util.ArraySet;
 import android.util.Base64;
 import android.util.Pair;
-import com.android.internal.annotations.VisibleForTesting;
+
 import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -36,37 +36,19 @@
     private final Object mLock = new Object();
     private final int mResourceId;
     private final boolean mDebugBuild;
-    private final int mTargetSdkVersion;
-    private final int mTargetSandboxVesrsion;
+    private final ApplicationInfo mApplicationInfo;
 
     private boolean mInitialized;
     private NetworkSecurityConfig mDefaultConfig;
     private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
     private Context mContext;
 
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId) {
-        this(context, resourceId, false);
-    }
-
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
-        this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT);
-    }
-
-    @VisibleForTesting
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
-            int targetSdkVersion) {
-        this(context, resourceId, debugBuild, targetSdkVersion, 1 /*targetSandboxVersion*/);
-    }
-
-    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
-            int targetSdkVersion, int targetSandboxVesrsion) {
-        mResourceId = resourceId;
+    public XmlConfigSource(Context context, int resourceId, ApplicationInfo info) {
         mContext = context;
-        mDebugBuild = debugBuild;
-        mTargetSdkVersion = targetSdkVersion;
-        mTargetSandboxVesrsion = targetSandboxVesrsion;
+        mResourceId = resourceId;
+        mApplicationInfo = new ApplicationInfo(info);
+
+        mDebugBuild = (mApplicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
     }
 
     public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
@@ -365,7 +347,7 @@
         // Use the platform default as the parent of the base config for any values not provided
         // there. If there is no base config use the platform default.
         NetworkSecurityConfig.Builder platformDefaultBuilder =
-                NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion, mTargetSandboxVesrsion);
+                NetworkSecurityConfig.getDefaultBuilder(mApplicationInfo);
         addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
         if (baseConfigBuilder != null) {
             baseConfigBuilder.setParent(platformDefaultBuilder);
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 3e08dcf..953501c 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -65,7 +65,7 @@
  *   <li>The service replies through {@link FillCallback#onSuccess(FillResponse)}.
  *   <li>The Android System calls {@link #onDisconnected()} and unbinds from the
  *       {@code AutofillService}.
- *   <li>The Android System displays an UI affordance with the options sent by the service.
+ *   <li>The Android System displays an autofill UI with the options sent by the service.
  *   <li>The user picks an option.
  *   <li>The proper views are autofilled.
  * </ol>
@@ -187,7 +187,7 @@
  * protect a dataset that contains sensitive information by requiring dataset authentication
  * (see {@link Dataset.Builder#setAuthentication(android.content.IntentSender)}), and to include
  * info about the "primary" field of the partition in the custom presentation for "secondary"
- * fields &mdash; that would prevent a malicious app from getting the "primary" fields without the
+ * fields&mdash;that would prevent a malicious app from getting the "primary" fields without the
  * user realizing they're being released (for example, a malicious app could have fields for a
  * credit card number, verification code, and expiration date crafted in a way that just the latter
  * is visible; by explicitly indicating the expiration date is related to a given credit card
@@ -305,7 +305,7 @@
  *   <li>Use the {@link android.app.assist.AssistStructure.ViewNode#getWebDomain()} to get the
  *       source of the document.
  *   <li>Get the canonical domain using the
- *       <a href="https://publicsuffix.org/>Public Suffix List</a> (see example below).
+ *       <a href="https://publicsuffix.org/">Public Suffix List</a> (see example below).
  *   <li>Use <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a>
  *       to obtain the package name and certificate fingerprint of the package corresponding to
  *       the canonical domain.
@@ -365,6 +365,81 @@
  * <p><b>Note:</b> The autofill service could also whitelist well-known browser apps and skip the
  * verifications above, as long as the service can verify the authenticity of the browser app by
  * checking its signing certificate.
+ *
+ * <a name="MultipleStepsSave"></a>
+ * <h3>Saving when data is split in multiple screens</h3>
+ *
+ * Apps often split the user data in multiple screens in the same activity, specially in
+ * activities used to create a new user account. For example, the first screen asks for a username,
+ * and if the username is available, it moves to a second screen, which asks for a password.
+ *
+ * <p>It's tricky to handle save for autofill in these situations, because the autofill service must
+ * wait until the user enters both fields before the autofill save UI can be shown. But it can be
+ * done by following the steps below:
+ *
+ * <ol>
+ * <li>In the first
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * adds a {@link FillResponse.Builder#setClientState(android.os.Bundle) client state bundle} in
+ * the response, containing the autofill ids of the partial fields present in the screen.
+ * <li>In the second
+ * {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback) fill request}, the service
+ * retrieves the {@link FillRequest#getClientState() client state bundle}, gets the autofill ids
+ * set in the previous request from the client state, and adds these ids and the
+ * {@link SaveInfo#FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} to the {@link SaveInfo} used in the second
+ * response.
+ * <li>In the {@link #onSaveRequest(SaveRequest, SaveCallback) save request}, the service uses the
+ * proper {@link FillContext fill contexts} to get the value of each field (there is one fill
+ * context per fill request).
+ * </ol>
+ *
+ * <p>For example, in an app that uses 2 steps for the username and password fields, the workflow
+ * would be:
+ * <pre class="prettyprint">
+ *  // On first fill request
+ *  AutofillId usernameId = // parse from AssistStructure;
+ *  Bundle clientState = new Bundle();
+ *  clientState.putParcelable("usernameId", usernameId);
+ *  fillCallback.onSuccess(
+ *    new FillResponse.Builder()
+ *        .setClientState(clientState)
+ *        .setSaveInfo(new SaveInfo
+ *             .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME, new AutofillId[] {usernameId})
+ *             .build())
+ *        .build());
+ *
+ *  // On second fill request
+ *  Bundle clientState = fillRequest.getClientState();
+ *  AutofillId usernameId = clientState.getParcelable("usernameId");
+ *  AutofillId passwordId = // parse from AssistStructure
+ *  clientState.putParcelable("passwordId", passwordId);
+ *  fillCallback.onSuccess(
+ *    new FillResponse.Builder()
+ *        .setClientState(clientState)
+ *        .setSaveInfo(new SaveInfo
+ *             .Builder(SaveInfo.SAVE_DATA_TYPE_USERNAME | SaveInfo.SAVE_DATA_TYPE_PASSWORD,
+ *                      new AutofillId[] {usernameId, passwordId})
+ *             .setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
+ *             .build())
+ *        .build());
+ *
+ *  // On save request
+ *  Bundle clientState = saveRequest.getClientState();
+ *  AutofillId usernameId = clientState.getParcelable("usernameId");
+ *  AutofillId passwordId = clientState.getParcelable("passwordId");
+ *  List<FillContext> fillContexts = saveRequest.getFillContexts();
+ *
+ *  FillContext usernameContext = fillContexts.get(0);
+ *  ViewNode usernameNode = findNodeByAutofillId(usernameContext.getStructure(), usernameId);
+ *  AutofillValue username = usernameNode.getAutofillValue().getTextValue().toString();
+ *
+ *  FillContext passwordContext = fillContexts.get(1);
+ *  ViewNode passwordNode = findNodeByAutofillId(passwordContext.getStructure(), passwordId);
+ *  AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
+ *
+ *  save(username, password);
+ *
+ * </pre>
  */
 public abstract class AutofillService extends Service {
     private static final String TAG = "AutofillService";
@@ -503,13 +578,19 @@
             @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
 
     /**
-     * Called when user requests service to save the fields of a screen.
+     * Called when the user requests the service to save the contents of a screen.
      *
      * <p>Service must call one of the {@link SaveCallback} methods (like
      * {@link SaveCallback#onSuccess()} or {@link SaveCallback#onFailure(CharSequence)})
-     * to notify the result of the request.
+     * to notify the Android System of the result of the request.
      *
-     * <p><b>Note:</b> To retrieve the actual value of the field, the service should call
+     * <p>If the service could not handle the request right away&mdash;for example, because it must
+     * launch an activity asking the user to authenticate first or because the network is
+     * down&mdash;the service could keep the {@link SaveRequest request} and reuse it later,
+     * but the service must call {@link SaveCallback#onSuccess()} right away.
+     *
+     * <p><b>Note:</b> To retrieve the actual value of fields input by the user, the service
+     * should call
      * {@link android.app.assist.AssistStructure.ViewNode#getAutofillValue()}; if it calls
      * {@link android.app.assist.AssistStructure.ViewNode#getText()} or other methods, there is no
      * guarantee such method will return the most recent value of the field.
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index f147400..5c7388f 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -146,4 +146,9 @@
     public String getSettingsActivity() {
         return mSettingsActivity;
     }
+
+    @Override
+    public String toString() {
+        return mServiceInfo == null ? "null" : mServiceInfo.toString();
+    }
 }
diff --git a/core/java/android/service/autofill/BatchUpdates.java b/core/java/android/service/autofill/BatchUpdates.java
new file mode 100644
index 0000000..90acc88
--- /dev/null
+++ b/core/java/android/service/autofill/BatchUpdates.java
@@ -0,0 +1,216 @@
+/*
+ * 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.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+import android.widget.RemoteViews;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+
+/**
+ * Defines actions to be applied to a {@link RemoteViews template presentation}.
+ *
+ *
+ * <p>It supports 2 types of actions:
+ *
+ * <ol>
+ *   <li>{@link RemoteViews Actions} to be applied to the template.
+ *   <li>{@link Transformation Transformations} to be applied on child views.
+ * </ol>
+ *
+ * <p>Typically used on {@link CustomDescription custom descriptions} to conditionally display
+ * differents views based on user input - see
+ * {@link CustomDescription.Builder#batchUpdate(Validator, BatchUpdates)} for more information.
+ */
+public final class BatchUpdates implements Parcelable {
+
+    private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+    private final RemoteViews mUpdates;
+
+    private BatchUpdates(Builder builder) {
+        mTransformations = builder.mTransformations;
+        mUpdates = builder.mUpdates;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+        return mTransformations;
+    }
+
+    /** @hide */
+    @Nullable
+    public RemoteViews getUpdates() {
+        return mUpdates;
+    }
+
+    /**
+     * Builder for {@link BatchUpdates} objects.
+     */
+    public static class Builder {
+        private RemoteViews mUpdates;
+
+        private boolean mDestroyed;
+        private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+
+        /**
+         * Applies the {@code updates} in the underlying presentation template.
+         *
+         * <p><b>Note:</b> The updates are applied before the
+         * {@link #transformChild(int, Transformation) transformations} are applied to the children
+         * views.
+         *
+         * @param updates a {@link RemoteViews} with the updated actions to be applied in the
+         * underlying presentation template.
+         *
+         * @return this builder
+         * @throws IllegalArgumentException if {@code condition} is not a class provided
+         * by the Android System.
+         */
+        public Builder updateTemplate(@NonNull RemoteViews updates) {
+            throwIfDestroyed();
+            mUpdates = Preconditions.checkNotNull(updates);
+            return this;
+        }
+
+        /**
+         * Adds a transformation to replace the value of a child view with the fields in the
+         * screen.
+         *
+         * <p>When multiple transformations are added for the same child view, they are applied
+         * in the same order as added.
+         *
+         * <p><b>Note:</b> The transformations are applied after the
+         * {@link #updateTemplate(RemoteViews) updates} are applied to the presentation template.
+         *
+         * @param id view id of the children view.
+         * @param transformation an implementation provided by the Android System.
+         * @return this builder.
+         * @throws IllegalArgumentException if {@code transformation} is not a class provided
+         * by the Android System.
+         */
+        public Builder transformChild(int id, @NonNull Transformation transformation) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((transformation instanceof InternalTransformation),
+                    "not provided by Android System: " + transformation);
+            if (mTransformations == null) {
+                mTransformations = new ArrayList<>();
+            }
+            mTransformations.add(new Pair<>(id, (InternalTransformation) transformation));
+            return this;
+        }
+
+        /**
+         * Creates a new {@link BatchUpdates} instance.
+         *
+         * @throws IllegalStateException if {@link #build()} was already called before or no call
+         * to {@link #updateTemplate(RemoteViews)} or {@link #transformChild(int, Transformation)}
+         * has been made.
+         */
+        public BatchUpdates build() {
+            throwIfDestroyed();
+            Preconditions.checkState(mUpdates != null || mTransformations != null,
+                    "must call either updateTemplate() or transformChild() at least once");
+            mDestroyed = true;
+            return new BatchUpdates(this);
+        }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return new StringBuilder("BatchUpdates: [")
+                .append(", transformations=")
+                    .append(mTransformations == null ? "N/A" : mTransformations.size())
+                .append(", updates=").append(mUpdates)
+                .append("]").toString();
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mTransformations == null) {
+            dest.writeIntArray(null);
+        } else {
+            final int size = mTransformations.size();
+            final int[] ids = new int[size];
+            final InternalTransformation[] values = new InternalTransformation[size];
+            for (int i = 0; i < size; i++) {
+                final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
+                ids[i] = pair.first;
+                values[i] = pair.second;
+            }
+            dest.writeIntArray(ids);
+            dest.writeParcelableArray(values, flags);
+        }
+        dest.writeParcelable(mUpdates, flags);
+    }
+    public static final Parcelable.Creator<BatchUpdates> CREATOR =
+            new Parcelable.Creator<BatchUpdates>() {
+        @Override
+        public BatchUpdates createFromParcel(Parcel parcel) {
+            // Always go through the builder to ensure the data ingested by
+            // the system obeys the contract of the builder to avoid attacks
+            // using specially crafted parcels.
+            final Builder builder = new Builder();
+            final int[] ids = parcel.createIntArray();
+            if (ids != null) {
+                final InternalTransformation[] values =
+                    parcel.readParcelableArray(null, InternalTransformation.class);
+                final int size = ids.length;
+                for (int i = 0; i < size; i++) {
+                    builder.transformChild(ids[i], values[i]);
+                }
+            }
+            final RemoteViews updates = parcel.readParcelable(null);
+            if (updates != null) {
+                builder.updateTemplate(updates);
+            }
+            return builder.build();
+        }
+
+        @Override
+        public BatchUpdates[] newArray(int size) {
+            return new BatchUpdates[size];
+        }
+    };
+}
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
index 9a4cbc4..b8e8b19 100644
--- a/core/java/android/service/autofill/CustomDescription.java
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -19,11 +19,11 @@
 import static android.view.autofill.Helper.sDebug;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 import android.util.Pair;
 import android.widget.RemoteViews;
 
@@ -32,7 +32,7 @@
 import java.util.ArrayList;
 
 /**
- * Defines a custom description for the Save UI affordance.
+ * Defines a custom description for the autofill save UI.
  *
  * <p>This is useful when the autofill service needs to show a detailed view of what would be saved;
  * for example, when the screen contains a credit card, it could display a logo of the credit card
@@ -67,18 +67,18 @@
  * // Image child - different logo for each bank, based on credit card prefix
  * builder.addChild(R.id.templateccLogo,
  *   new ImageTransformation.Builder(ccNumberId)
- *     .addOption(Pattern.compile(""^4815.*$"), R.drawable.ic_credit_card_logo1)
- *     .addOption(Pattern.compile(""^1623.*$"), R.drawable.ic_credit_card_logo2)
- *     .addOption(Pattern.compile(""^42.*$"), R.drawable.ic_credit_card_logo3)
+ *     .addOption(Pattern.compile("^4815.*$"), R.drawable.ic_credit_card_logo1)
+ *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
+ *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
  *     .build();
  * // Masked credit card number (as .....LAST_4_DIGITS)
  * builder.addChild(R.id.templateCcNumber, new CharSequenceTransformation
- *     .Builder(ccNumberId, Pattern.compile(""^.*(\\d\\d\\d\\d)$"), "...$1")
+ *     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
  *     .build();
  * // Expiration date as MM / YYYY:
  * builder.addChild(R.id.templateExpDate, new CharSequenceTransformation
- *     .Builder(ccExpMonthId, Pattern.compile(""^(\\d\\d)$"), "Exp: $1")
- *     .addField(ccExpYearId, Pattern.compile(""^(\\d\\d)$"), "/$1")
+ *     .Builder(ccExpMonthId, Pattern.compile("^(\\d\\d)$"), "Exp: $1")
+ *     .addField(ccExpYearId, Pattern.compile("^(\\d\\d)$"), "/$1")
  *     .build();
  * </pre>
  *
@@ -87,47 +87,43 @@
  */
 public final class CustomDescription implements Parcelable {
 
-    private static final String TAG = "CustomDescription";
-
     private final RemoteViews mPresentation;
     private final ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+    private final ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
 
     private CustomDescription(Builder builder) {
         mPresentation = builder.mPresentation;
         mTransformations = builder.mTransformations;
+        mUpdates = builder.mUpdates;
     }
 
     /** @hide */
-    public RemoteViews getPresentation(ValueFinder finder) {
-        if (mTransformations != null) {
-            final int size = mTransformations.size();
-            if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
-            for (int i = 0; i < size; i++) {
-                final Pair<Integer, InternalTransformation> pair = mTransformations.get(i);
-                final int id = pair.first;
-                final InternalTransformation transformation = pair.second;
-                if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
-
-                try {
-                    transformation.apply(finder, mPresentation, id);
-                } catch (Exception e) {
-                    // Do not log full exception to avoid PII leaking
-                    Log.e(TAG, "Could not apply transformation " + transformation + ": "
-                            + e.getClass());
-                    return null;
-                }
-            }
-        }
+    @Nullable
+    public RemoteViews getPresentation() {
         return mPresentation;
     }
 
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<Integer, InternalTransformation>> getTransformations() {
+        return mTransformations;
+    }
+
+    /** @hide */
+    @Nullable
+    public ArrayList<Pair<InternalValidator, BatchUpdates>> getUpdates() {
+        return mUpdates;
+    }
+
     /**
      * Builder for {@link CustomDescription} objects.
      */
     public static class Builder {
         private final RemoteViews mPresentation;
 
+        private boolean mDestroyed;
         private ArrayList<Pair<Integer, InternalTransformation>> mTransformations;
+        private ArrayList<Pair<InternalValidator, BatchUpdates>> mUpdates;
 
         /**
          * Default constructor.
@@ -135,7 +131,7 @@
          * <p><b>Note:</b> If any child view of presentation triggers a
          * {@link RemoteViews#setOnClickPendingIntent(int, android.app.PendingIntent) pending intent
          * on click}, such {@link PendingIntent} must follow the restrictions below, otherwise
-         * it might not be triggered or the Save affordance might not be shown when its activity
+         * it might not be triggered or the autofill save UI might not be shown when its activity
          * is finished:
          * <ul>
          *   <li>It cannot be created with the {@link PendingIntent#FLAG_IMMUTABLE} flag.
@@ -145,9 +141,11 @@
          * </ul>
          *
          * @param parentPresentation template presentation with (optional) children views.
+         * @throws NullPointerException if {@code parentPresentation} is null (on Android
+         * {@link android.os.Build.VERSION_CODES#P} or higher).
          */
-        public Builder(RemoteViews parentPresentation) {
-            mPresentation = parentPresentation;
+        public Builder(@NonNull RemoteViews parentPresentation) {
+            mPresentation = Preconditions.checkNotNull(parentPresentation);
         }
 
         /**
@@ -164,6 +162,7 @@
          * by the Android System.
          */
         public Builder addChild(int id, @NonNull Transformation transformation) {
+            throwIfDestroyed();
             Preconditions.checkArgument((transformation instanceof InternalTransformation),
                     "not provided by Android System: " + transformation);
             if (mTransformations == null) {
@@ -174,11 +173,109 @@
         }
 
         /**
+         * Updates the {@link RemoteViews presentation template} when a condition is satisfied.
+         *
+         * <p>The updates are applied in the sequence they are added, after the
+         * {@link #addChild(int, Transformation) transformations} are applied to the children
+         * views.
+         *
+         * <p>For example, to make children views visible when fields are not empty:
+         *
+         * <pre class="prettyprint">
+         * RemoteViews template = new RemoteViews(pgkName, R.layout.my_full_template);
+         *
+         * Pattern notEmptyPattern = Pattern.compile(".+");
+         * Validator hasAddress = new RegexValidator(addressAutofillId, notEmptyPattern);
+         * Validator hasCcNumber = new RegexValidator(ccNumberAutofillId, notEmptyPattern);
+         *
+         * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+         * addressUpdates.setViewVisibility(R.id.address, View.VISIBLE);
+         *
+         * // Make address visible
+         * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(addressUpdates)
+         *     .build();
+         *
+         * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_full_template)
+         * ccUpdates.setViewVisibility(R.id.cc_number, View.VISIBLE);
+         *
+         * // Mask credit card number (as .....LAST_4_DIGITS) and make it visible
+         * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(ccUpdates)
+         *     .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+         *                     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+         *                     .build())
+         *     .build();
+         *
+         * CustomDescription customDescription = new CustomDescription.Builder(template)
+         *     .batchUpdate(hasAddress, addressBatchUpdates)
+         *     .batchUpdate(hasCcNumber, ccBatchUpdates)
+         *     .build();
+         * </pre>
+         *
+         * <p>Another approach is to add a child first, then apply the transformations. Example:
+         *
+         * <pre class="prettyprint">
+         * RemoteViews template = new RemoteViews(pgkName, R.layout.my_base_template);
+         *
+         * RemoteViews addressPresentation = new RemoteViews(pgkName, R.layout.address)
+         * RemoteViews addressUpdates = new RemoteViews(pgkName, R.layout.my_template)
+         * addressUpdates.addView(R.id.parentId, addressPresentation);
+         * BatchUpdates addressBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(addressUpdates)
+         *     .build();
+         *
+         * RemoteViews ccPresentation = new RemoteViews(pgkName, R.layout.cc)
+         * RemoteViews ccUpdates = new RemoteViews(pgkName, R.layout.my_template)
+         * ccUpdates.addView(R.id.parentId, ccPresentation);
+         * BatchUpdates ccBatchUpdates = new BatchUpdates.Builder()
+         *     .updateTemplate(ccUpdates)
+         *     .transformChild(R.id.templateCcNumber, new CharSequenceTransformation
+         *                     .Builder(ccNumberId, Pattern.compile("^.*(\\d\\d\\d\\d)$"), "...$1")
+         *                     .build())
+         *     .build();
+         *
+         * CustomDescription customDescription = new CustomDescription.Builder(template)
+         *     .batchUpdate(hasAddress, addressBatchUpdates)
+         *     .batchUpdate(hasCcNumber, ccBatchUpdates)
+         *     .build();
+         * </pre>
+         *
+         * @param condition condition used to trigger the updates.
+         * @param updates actions to be applied to the
+         * {@link #CustomDescription.Builder(RemoteViews) template presentation} when the condition
+         * is satisfied.
+         *
+         * @return this builder
+         * @throws IllegalArgumentException if {@code condition} is not a class provided
+         * by the Android System.
+         */
+        public Builder batchUpdate(@NonNull Validator condition, @NonNull BatchUpdates updates) {
+            throwIfDestroyed();
+            Preconditions.checkArgument((condition instanceof InternalValidator),
+                    "not provided by Android System: " + condition);
+            Preconditions.checkNotNull(updates);
+            if (mUpdates == null) {
+                mUpdates = new ArrayList<>();
+            }
+            mUpdates.add(new Pair<>((InternalValidator) condition, updates));
+            return this;
+        }
+
+        /**
          * Creates a new {@link CustomDescription} instance.
          */
         public CustomDescription build() {
+            throwIfDestroyed();
+            mDestroyed = true;
             return new CustomDescription(this);
         }
+
+        private void throwIfDestroyed() {
+            if (mDestroyed) {
+                throw new IllegalStateException("Already called #build()");
+            }
+        }
     }
 
     /////////////////////////////////////
@@ -190,7 +287,10 @@
 
         return new StringBuilder("CustomDescription: [presentation=")
                 .append(mPresentation)
-                .append(", transformations=").append(mTransformations)
+                .append(", transformations=")
+                    .append(mTransformations == null ? "N/A" : mTransformations.size())
+                .append(", updates=")
+                    .append(mUpdates == null ? "N/A" : mUpdates.size())
                 .append("]").toString();
     }
 
@@ -205,6 +305,8 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(mPresentation, flags);
+        if (mPresentation == null) return;
+
         if (mTransformations == null) {
             dest.writeIntArray(null);
         } else {
@@ -219,6 +321,21 @@
             dest.writeIntArray(ids);
             dest.writeParcelableArray(values, flags);
         }
+        if (mUpdates == null) {
+            dest.writeParcelableArray(null, flags);
+        } else {
+            final int size = mUpdates.size();
+            final InternalValidator[] conditions = new InternalValidator[size];
+            final BatchUpdates[] updates = new BatchUpdates[size];
+
+            for (int i = 0; i < size; i++) {
+                final Pair<InternalValidator, BatchUpdates> pair = mUpdates.get(i);
+                conditions[i] = pair.first;
+                updates[i] = pair.second;
+            }
+            dest.writeParcelableArray(conditions, flags);
+            dest.writeParcelableArray(updates, flags);
+        }
     }
     public static final Parcelable.Creator<CustomDescription> CREATOR =
             new Parcelable.Creator<CustomDescription>() {
@@ -227,7 +344,10 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final Builder builder = new Builder(parcel.readParcelable(null));
+            final RemoteViews parentPresentation = parcel.readParcelable(null);
+            if (parentPresentation == null) return null;
+
+            final Builder builder = new Builder(parentPresentation);
             final int[] ids = parcel.createIntArray();
             if (ids != null) {
                 final InternalTransformation[] values =
@@ -237,6 +357,15 @@
                     builder.addChild(ids[i], values[i]);
                 }
             }
+            final InternalValidator[] conditions =
+                    parcel.readParcelableArray(null, InternalValidator.class);
+            if (conditions != null) {
+                final BatchUpdates[] updates = parcel.readParcelableArray(null, BatchUpdates.class);
+                final int size = conditions.length;
+                for (int i = 0; i < size; i++) {
+                    builder.batchUpdate(conditions[i], updates[i]);
+                }
+            }
             return builder.build();
         }
 
diff --git a/core/java/android/service/autofill/Dataset.aidl b/core/java/android/service/autofill/Dataset.aidl
deleted file mode 100644
index 2342c5f..0000000
--- a/core/java/android/service/autofill/Dataset.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2016, 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.autofill;
-
-parcelable Dataset;
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index cb341b1..331130e 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -29,32 +29,77 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.regex.Pattern;
 
 /**
- * A dataset object represents a group of key/value pairs used to autofill parts of a screen.
+ * A dataset object represents a group of fields (key / value pairs) used to autofill parts of a
+ * screen.
  *
- * <p>In its simplest form, a dataset contains one or more key / value pairs (comprised of
- * {@link AutofillId} and {@link AutofillValue} respectively); and one or more
- * {@link RemoteViews presentation} for these pairs (a pair could have its own
- * {@link RemoteViews presentation}, or use the default {@link RemoteViews presentation} associated
- * with the whole dataset). When an autofill service returns datasets in a {@link FillResponse}
+ * <a name="BasicUsage"></a>
+ * <h3>Basic usage</h3>
+ *
+ * <p>In its simplest form, a dataset contains one or more fields (comprised of
+ * an {@link AutofillId id}, a {@link AutofillValue value}, and an optional filter
+ * {@link Pattern regex}); and one or more {@link RemoteViews presentations} for these fields
+ * (each field could have its own {@link RemoteViews presentation}, or use the default
+ * {@link RemoteViews presentation} associated with the whole dataset).
+ *
+ * <p>When an autofill service returns datasets in a {@link FillResponse}
  * and the screen input is focused in a view that is present in at least one of these datasets,
- * the Android System displays a UI affordance containing the {@link RemoteViews presentation} of
+ * the Android System displays a UI containing the {@link RemoteViews presentation} of
  * all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
- * dataset from the affordance, all views in that dataset are autofilled.
+ * dataset from the UI, all views in that dataset are autofilled.
  *
- * <p>In a more sophisticated form, the dataset value can be protected until the user authenticates
- * the dataset - see {@link Dataset.Builder#setAuthentication(IntentSender)}.
+ * <a name="Authentication"></a>
+ * <h3>Dataset authentication</h3>
  *
- * @see android.service.autofill.AutofillService for more information and examples about the
- * role of datasets in the autofill workflow.
+ * <p>In a more sophisticated form, the dataset values can be protected until the user authenticates
+ * the dataset&mdash;in that case, when a dataset is selected by the user, the Android System
+ * launches an intent set by the service to "unlock" the dataset.
+ *
+ * <p>For example, when a data set contains credit card information (such as number,
+ * expiration date, and verification code), you could provide a dataset presentation saying
+ * "Tap to authenticate". Then when the user taps that option, you would launch an activity asking
+ * the user to enter the credit card code, and if the user enters a valid code, you could then
+ * "unlock" the dataset.
+ *
+ * <p>You can also use authenticated datasets to offer an interactive UI for the user. For example,
+ * if the activity being autofilled is an account creation screen, you could use an authenticated
+ * dataset to automatically generate a random password for the user.
+ *
+ * <p>See {@link Dataset.Builder#setAuthentication(IntentSender)} for more details about the dataset
+ * authentication mechanism.
+ *
+ * <a name="Filtering"></a>
+ * <h3>Filtering</h3>
+ * <p>The autofill UI automatically changes which values are shown based on value of the view
+ * anchoring it, following the rules below:
+ * <ol>
+ *   <li>If the view's {@link android.view.View#getAutofillValue() autofill value} is not
+ * {@link AutofillValue#isText() text} or is empty, all datasets are shown.
+ *   <li>Datasets that have a filter regex (set through
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern)} or
+ * {@link Dataset.Builder#setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}) and whose
+ * regex matches the view's text value converted to lower case are shown.
+ *   <li>Datasets that do not require authentication, have a field value that is
+ * {@link AutofillValue#isText() text} and whose {@link AutofillValue#getTextValue() value} starts
+ * with the lower case value of the view's text are shown.
+ *   <li>All other datasets are hidden.
+ * </ol>
+ *
+ * <a name="MoreInfo"></a>
+ * <h3>More information</h3>
+ * <p>See {@link android.service.autofill.AutofillService} for more information and examples about
+ * the role of datasets in the autofill workflow.
  */
 public final class Dataset implements Parcelable {
 
     private final ArrayList<AutofillId> mFieldIds;
     private final ArrayList<AutofillValue> mFieldValues;
     private final ArrayList<RemoteViews> mFieldPresentations;
+    private final ArrayList<Pattern> mFieldFilters;
     private final RemoteViews mPresentation;
     private final IntentSender mAuthentication;
     @Nullable String mId;
@@ -63,6 +108,7 @@
         mFieldIds = builder.mFieldIds;
         mFieldValues = builder.mFieldValues;
         mFieldPresentations = builder.mFieldPresentations;
+        mFieldFilters = builder.mFieldFilters;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
         mId = builder.mId;
@@ -85,6 +131,12 @@
     }
 
     /** @hide */
+    @Nullable
+    public Pattern getFilter(int index) {
+        return mFieldFilters.get(index);
+    }
+
+    /** @hide */
     public @Nullable IntentSender getAuthentication() {
         return mAuthentication;
     }
@@ -98,11 +150,21 @@
     public String toString() {
         if (!sDebug) return super.toString();
 
-        return new StringBuilder("Dataset " + mId + " [")
-                .append("fieldIds=").append(mFieldIds)
+        final StringBuilder builder = new StringBuilder("Dataset[id=");
+        if (mId == null) {
+            builder.append("null");
+        } else {
+            // Cannot disclose id because it could contain PII.
+            builder.append(mId.length()).append("_chars");
+        }
+
+        return builder
+                .append(", fieldIds=").append(mFieldIds)
                 .append(", fieldValues=").append(mFieldValues)
                 .append(", fieldPresentations=")
                 .append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
+                .append(", fieldFilters=")
+                .append(mFieldFilters == null ? 0 : mFieldFilters.size())
                 .append(", hasPresentation=").append(mPresentation != null)
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(']').toString();
@@ -127,6 +189,7 @@
         private ArrayList<AutofillId> mFieldIds;
         private ArrayList<AutofillValue> mFieldValues;
         private ArrayList<RemoteViews> mFieldPresentations;
+        private ArrayList<Pattern> mFieldFilters;
         private RemoteViews mPresentation;
         private IntentSender mAuthentication;
         private boolean mDestroyed;
@@ -146,7 +209,8 @@
          * Creates a new builder for a dataset where each field will be visualized independently.
          *
          * <p>When using this constructor, fields must be set through
-         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}.
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+         * {@link #setValue(AutofillId, AutofillValue, Pattern, RemoteViews)}.
          */
         public Builder() {
         }
@@ -182,12 +246,12 @@
          * credit card information without the CVV for the data set in the {@link FillResponse
          * response} then the returned data set should contain the CVV entry.
          *
-         * <p><b>NOTE:</b> Do not make the provided pending intent
+         * <p><b>Note:</b> Do not make the provided pending intent
          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
          * platform needs to fill in the authentication arguments.
          *
          * @param authentication Intent to an activity with your authentication flow.
-         * @return This builder.
+         * @return this builder.
          *
          * @see android.app.PendingIntent
          */
@@ -214,11 +278,10 @@
          *
          * @param id id for this dataset or {@code null} to unset.
          *
-         * @return This builder.
+         * @return this builder.
          */
         public @NonNull Builder setId(@Nullable String id) {
             throwIfDestroyed();
-
             mId = id;
             return this;
         }
@@ -226,21 +289,25 @@
         /**
          * Sets the value of a field.
          *
+         * <b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, this method would
+         * throw an {@link IllegalStateException} if this builder was constructed without a
+         * {@link RemoteViews presentation}. Android {@link android.os.Build.VERSION_CODES#P} and
+         * higher removed this restriction because datasets used as an
+         * {@link android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT
+         * authentication result} do not need a presentation. But if you don't set the presentation
+         * in the constructor in a dataset that is meant to be shown to the user, the autofill UI
+         * for this field will not be displayed.
+         *
          * @param id id returned by {@link
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
          * @param value value to be autofilled. Pass {@code null} if you do not have the value
          *        but the target view is a logical part of the dataset. For example, if
-         *        the dataset needs an authentication and you have no access to the value.
-         * @return This builder.
-         * @throws IllegalStateException if the builder was constructed without a
-         * {@link RemoteViews presentation}.
+         *        the dataset needs authentication and you have no access to the value.
+         * @return this builder.
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
             throwIfDestroyed();
-            if (mPresentation == null) {
-                throw new IllegalStateException("Dataset presentation not set on constructor");
-            }
-            setValueAndPresentation(id, value, null);
+            setLifeTheUniverseAndEverything(id, value, null, null);
             return this;
         }
 
@@ -250,23 +317,81 @@
          *
          * @param id id returned by {@link
          *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
-         * @param value value to be auto filled. Pass {@code null} if you do not have the value
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
          *        but the target view is a logical part of the dataset. For example, if
-         *        the dataset needs an authentication and you have no access to the value.
-         *        Filtering matches any user typed string to {@code null} values.
-         * @param presentation The presentation used to visualize this field.
-         * @return This builder.
+         *        the dataset needs authentication and you have no access to the value.
+         * @param presentation the presentation used to visualize this field.
+         * @return this builder.
+         *
          */
         public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
                 @NonNull RemoteViews presentation) {
             throwIfDestroyed();
             Preconditions.checkNotNull(presentation, "presentation cannot be null");
-            setValueAndPresentation(id, value, presentation);
+            setLifeTheUniverseAndEverything(id, value, presentation, null);
             return this;
         }
 
-        private void setValueAndPresentation(AutofillId id, AutofillValue value,
-                RemoteViews presentation) {
+        /**
+         * Sets the value of a field using an <a href="#Filtering">explicit filter</a>.
+         *
+         * <p>This method is typically used when the dataset is not authenticated and the field
+         * value is not {@link AutofillValue#isText() text} but the service still wants to allow
+         * the user to filter it out.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI.
+         *
+         * @return this builder.
+         * @throws IllegalStateException if the builder was constructed without a
+         *         {@link RemoteViews presentation}.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @NonNull Pattern filter) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(filter, "filter cannot be null");
+            Preconditions.checkState(mPresentation != null,
+                    "Dataset presentation not set on constructor");
+            setLifeTheUniverseAndEverything(id, value, null, filter);
+            return this;
+        }
+
+        /**
+         * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+         * visualize it and a <a href="#Filtering">explicit filter</a>.
+         *
+         * <p>Typically used to allow filtering on
+         * {@link Dataset.Builder#setAuthentication(IntentSender) authenticated datasets}. For
+         * example, if the dataset represents a credit card number and the service does not want to
+         * show the "Tap to authenticate" message until the user tapped 4 digits, in which case
+         * the filter would be {@code Pattern.compile("\\d.{4,}")}.
+         *
+         * @param id id returned by {@link
+         *         android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+         * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+         *        but the target view is a logical part of the dataset. For example, if
+         *        the dataset needs authentication and you have no access to the value.
+         * @param presentation the presentation used to visualize this field.
+         * @param filter regex used to determine if the dataset should be shown in the autofill UI.
+         *
+         * @return this builder.
+         */
+        public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+                @NonNull Pattern filter, @NonNull RemoteViews presentation) {
+            throwIfDestroyed();
+            Preconditions.checkNotNull(filter, "filter cannot be null");
+            Preconditions.checkNotNull(presentation, "presentation cannot be null");
+            setLifeTheUniverseAndEverything(id, value, presentation, filter);
+            return this;
+        }
+
+        private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
+                @Nullable AutofillValue value, @Nullable RemoteViews presentation,
+                @Nullable Pattern filter) {
             Preconditions.checkNotNull(id, "id cannot be null");
             if (mFieldIds != null) {
                 final int existingIdx = mFieldIds.indexOf(id);
@@ -279,10 +404,12 @@
                 mFieldIds = new ArrayList<>();
                 mFieldValues = new ArrayList<>();
                 mFieldPresentations = new ArrayList<>();
+                mFieldFilters = new ArrayList<>();
             }
             mFieldIds.add(id);
             mFieldValues.add(value);
             mFieldPresentations.add(presentation);
+            mFieldFilters.add(filter);
         }
 
         /**
@@ -290,8 +417,9 @@
          *
          * <p>You should not interact with this builder once this method is called.
          *
-         * <p>It is required that you specify at least one field before calling this method. It's
-         * also mandatory to provide a presentation view to visualize the data set in the UI.
+         * @throws IllegalStateException if no field was set (through
+         * {@link #setValue(AutofillId, AutofillValue)} or
+         * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
          *
          * @return The built dataset.
          */
@@ -299,7 +427,7 @@
             throwIfDestroyed();
             mDestroyed = true;
             if (mFieldIds == null) {
-                throw new IllegalArgumentException("at least one value must be set");
+                throw new IllegalStateException("at least one value must be set");
             }
             return new Dataset(this);
         }
@@ -323,9 +451,10 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeParcelable(mPresentation, flags);
-        parcel.writeTypedArrayList(mFieldIds, flags);
-        parcel.writeTypedArrayList(mFieldValues, flags);
+        parcel.writeTypedList(mFieldIds, flags);
+        parcel.writeTypedList(mFieldValues, flags);
         parcel.writeParcelableList(mFieldPresentations, flags);
+        parcel.writeSerializable(mFieldFilters);
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeString(mId);
     }
@@ -340,10 +469,14 @@
             final Builder builder = (presentation == null)
                     ? new Builder()
                     : new Builder(presentation);
-            final ArrayList<AutofillId> ids = parcel.readTypedArrayList(null);
-            final ArrayList<AutofillValue> values = parcel.readTypedArrayList(null);
+            final ArrayList<AutofillId> ids = parcel.createTypedArrayList(AutofillId.CREATOR);
+            final ArrayList<AutofillValue> values =
+                    parcel.createTypedArrayList(AutofillValue.CREATOR);
             final ArrayList<RemoteViews> presentations = new ArrayList<>();
             parcel.readParcelableList(presentations, null);
+            @SuppressWarnings("unchecked")
+            final ArrayList<Serializable> filters =
+                    (ArrayList<Serializable>) parcel.readSerializable();
             final int idCount = (ids != null) ? ids.size() : 0;
             final int valueCount = (values != null) ? values.size() : 0;
             for (int i = 0; i < idCount; i++) {
@@ -351,7 +484,8 @@
                 final AutofillValue value = (valueCount > i) ? values.get(i) : null;
                 final RemoteViews fieldPresentation = presentations.isEmpty() ? null
                         : presentations.get(i);
-                builder.setValueAndPresentation(id, value, fieldPresentation);
+                final Pattern filter = (Pattern) filters.get(i);
+                builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
             }
             builder.setAuthentication(parcel.readParcelable(null));
             builder.setId(parcel.readString());
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 3b719ac..b1857b3 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -17,19 +17,27 @@
 package android.service.autofill;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Describes what happened after the last
@@ -121,6 +129,11 @@
     }
 
     @Override
+    public String toString() {
+        return mEvents == null ? "no events" : mEvents.toString();
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -136,9 +149,21 @@
             int numEvents = mEvents.size();
             for (int i = 0; i < numEvents; i++) {
                 Event event = mEvents.get(i);
-                dest.writeInt(event.getType());
-                dest.writeString(event.getDatasetId());
-                dest.writeBundle(event.getClientState());
+                dest.writeInt(event.mEventType);
+                dest.writeString(event.mDatasetId);
+                dest.writeBundle(event.mClientState);
+                dest.writeStringList(event.mSelectedDatasetIds);
+                dest.writeArraySet(event.mIgnoredDatasetIds);
+                dest.writeTypedList(event.mChangedFieldIds);
+                dest.writeStringList(event.mChangedDatasetIds);
+
+                dest.writeTypedList(event.mManuallyFilledFieldIds);
+                if (event.mManuallyFilledFieldIds != null) {
+                    final int size = event.mManuallyFilledFieldIds.size();
+                    for (int j = 0; j < size; j++) {
+                        dest.writeStringList(event.mManuallyFilledDatasetIds.get(j));
+                    }
+                }
             }
         }
     }
@@ -176,12 +201,40 @@
         /** A save UI was shown. */
         public static final int TYPE_SAVE_SHOWN = 3;
 
+        /**
+         * A committed autofill context for which the autofill service provided datasets.
+         *
+         * <p>This event is useful to track:
+         * <ul>
+         *   <li>Which datasets (if any) were selected by the user
+         *       ({@link #getSelectedDatasetIds()}).
+         *   <li>Which datasets (if any) were NOT selected by the user
+         *       ({@link #getIgnoredDatasetIds()}).
+         *   <li>Which fields in the selected datasets were changed by the user after the dataset
+         *       was selected ({@link #getChangedFields()}.
+         * </ul>
+         *
+         * <p><b>Note: </b>This event is only generated when:
+         * <ul>
+         *   <li>The autofill context is committed.
+         *   <li>The service provides at least one dataset in the
+         *       {@link FillResponse fill responses} associated with the context.
+         *   <li>The last {@link FillResponse fill responses} associated with the context has the
+         *       {@link FillResponse#FLAG_TRACK_CONTEXT_COMMITED} flag.
+         * </ul>
+         *
+         * <p>See {@link android.view.autofill.AutofillManager} for more information about autofill
+         * contexts.
+         */
+        public static final int TYPE_CONTEXT_COMMITTED = 4;
+
         /** @hide */
         @IntDef(
                 value = {TYPE_DATASET_SELECTED,
                         TYPE_DATASET_AUTHENTICATION_SELECTED,
                         TYPE_AUTHENTICATION_SELECTED,
-                        TYPE_SAVE_SHOWN})
+                        TYPE_SAVE_SHOWN,
+                        TYPE_CONTEXT_COMMITTED})
         @Retention(RetentionPolicy.SOURCE)
         @interface EventIds{}
 
@@ -189,6 +242,17 @@
         @Nullable private final String mDatasetId;
         @Nullable private final Bundle mClientState;
 
+        // Note: mSelectedDatasetIds is stored as List<> instead of Set because Session already
+        // stores it as List
+        @Nullable private final List<String> mSelectedDatasetIds;
+        @Nullable private final ArraySet<String> mIgnoredDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mChangedFieldIds;
+        @Nullable private final ArrayList<String> mChangedDatasetIds;
+
+        @Nullable private final ArrayList<AutofillId> mManuallyFilledFieldIds;
+        @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds;
+
         /**
          * Returns the type of the event.
          *
@@ -220,25 +284,202 @@
         }
 
         /**
+         * Returns which datasets were selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getSelectedDatasetIds() {
+            return mSelectedDatasetIds == null ? Collections.emptySet()
+                    : new ArraySet<>(mSelectedDatasetIds);
+        }
+
+        /**
+         * Returns which datasets were NOT selected by the user.
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         */
+        @NonNull public Set<String> getIgnoredDatasetIds() {
+            return mIgnoredDatasetIds == null ? Collections.emptySet() : mIgnoredDatasetIds;
+        }
+
+        /**
+         * Returns which fields in the selected datasets were changed by the user after the dataset
+         * was selected.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User select both datasets (for username and password) but after the fields are
+         * autofilled, user changes them to:
+         *
+         * <pre class="prettyprint">
+         *   username = "ElBarto";
+         *   password = "AyCaramba";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => "4815"
+         *   passwordId => "162342"
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the change fields, and value is the id of
+         * dataset that has that field and was selected by the user.
+         */
+        @NonNull public Map<AutofillId, String> getChangedFields() {
+            if (mChangedFieldIds == null || mChangedDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mChangedFieldIds.size();
+            final ArrayMap<AutofillId, String> changedFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                changedFields.put(mChangedFieldIds.get(i), mChangedDatasetIds.get(i));
+            }
+            return changedFields;
+        }
+
+        /**
+         * Returns which fields were available on datasets provided by the service but manually
+         * entered by the user.
+         *
+         * <p>For example, server provides:
+         *
+         * <pre class="prettyprint">
+         *  FillResponse response = new FillResponse.Builder()
+         *      .addDataset(new Dataset.Builder(presentation1)
+         *          .setId("4815")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("AyCaramba"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation2)
+         *          .setId("162342")
+         *          .setValue(usernameId, AutofillValue.forText("ElBarto"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .addDataset(new Dataset.Builder(presentation3)
+         *          .setId("108")
+         *          .setValue(usernameId, AutofillValue.forText("MrPlow"))
+         *          .setValue(passwordId, AutofillValue.forText("D'OH"))
+         *          .build())
+         *      .build();
+         * </pre>
+         *
+         * <p>User doesn't select a dataset but manually enters:
+         *
+         * <pre class="prettyprint">
+         *   username = "MrPlow";
+         *   password = "D'OH";
+         * </pre>
+         *
+         * <p>Then the result is the following map:
+         *
+         * <pre class="prettyprint">
+         *   usernameId => { "4815", "108"}
+         *   passwordId => { "162342", "108" }
+         * </pre>
+         *
+         * <p><b>Note: </b>Only set on events of type {@link #TYPE_CONTEXT_COMMITTED}.
+         *
+         * @return map map whose key is the id of the manually-entered field, and value is the
+         * ids of the datasets that have that value but were not selected by the user.
+         */
+        @Nullable public Map<AutofillId, Set<String>> getManuallyEnteredField() {
+            if (mManuallyFilledFieldIds == null || mManuallyFilledDatasetIds == null) {
+                return Collections.emptyMap();
+            }
+
+            final int size = mManuallyFilledFieldIds.size();
+            final Map<AutofillId, Set<String>> manuallyFilledFields = new ArrayMap<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = mManuallyFilledFieldIds.get(i);
+                final ArrayList<String> datasetIds = mManuallyFilledDatasetIds.get(i);
+                manuallyFilledFields.put(fieldId, new ArraySet<>(datasetIds));
+            }
+            return manuallyFilledFields;
+        }
+
+        /**
          * Creates a new event.
          *
          * @param eventType The type of the event
          * @param datasetId The dataset the event was on, or {@code null} if the event was on the
          *                  whole response.
          * @param clientState The client state associated with the event.
+         * @param selectedDatasetIds The ids of datasets selected by the user.
+         * @param ignoredDatasetIds The ids of datasets NOT select by the user.
+         * @param changedFieldIds The ids of fields changed by the user.
+         * @param changedDatasetIds The ids of the datasets that havd values matching the
+         * respective entry on {@code changedFieldIds}.
+         * @param manuallyFilledFieldIds The ids of fields that were manually entered by the user
+         * and belonged to datasets.
+         * @param manuallyFilledDatasetIds The ids of datasets that had values matching the
+         * respective entry on {@code manuallyFilledFieldIds}.
+         *
+         * @throws IllegalArgumentException If the length of {@code changedFieldIds} and
+         * {@code changedDatasetIds} doesn't match.
+         * @throws IllegalArgumentException If the length of {@code manuallyFilledFieldIds} and
+         * {@code manuallyFilledDatasetIds} doesn't match.
          *
          * @hide
          */
-        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState) {
-            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_SAVE_SHOWN,
+        public Event(int eventType, @Nullable String datasetId, @Nullable Bundle clientState,
+                @Nullable List<String> selectedDatasetIds,
+                @Nullable ArraySet<String> ignoredDatasetIds,
+                @Nullable ArrayList<AutofillId> changedFieldIds,
+                @Nullable ArrayList<String> changedDatasetIds,
+                @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+                @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+            mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED,
                     "eventType");
             mDatasetId = datasetId;
             mClientState = clientState;
+            mSelectedDatasetIds = selectedDatasetIds;
+            mIgnoredDatasetIds = ignoredDatasetIds;
+            if (changedFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(changedFieldIds)
+                        && changedDatasetIds != null
+                        && changedFieldIds.size() == changedDatasetIds.size(),
+                        "changed ids must have same length and not be empty");
+            }
+            mChangedFieldIds = changedFieldIds;
+            mChangedDatasetIds = changedDatasetIds;
+            if (manuallyFilledFieldIds != null) {
+                Preconditions.checkArgument(!ArrayUtils.isEmpty(manuallyFilledFieldIds)
+                        && manuallyFilledDatasetIds != null
+                        && manuallyFilledFieldIds.size() == manuallyFilledDatasetIds.size(),
+                        "manually filled ids must have same length and not be empty");
+            }
+            mManuallyFilledFieldIds = manuallyFilledFieldIds;
+            mManuallyFilledDatasetIds = manuallyFilledDatasetIds;
         }
 
         @Override
         public String toString() {
-            return "FillEvent [datasetId=" + mDatasetId + ", type=" + mEventType + "]";
+            return "FillEvent [datasetId=" + mDatasetId
+                    + ", type=" + mEventType
+                    + ", selectedDatasets=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + mIgnoredDatasetIds
+                    + ", changedFieldIds=" + mChangedFieldIds
+                    + ", changedDatasetsIds=" + mChangedDatasetIds
+                    + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds
+                    + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds
+                    + "]";
         }
     }
 
@@ -248,12 +489,37 @@
                 public FillEventHistory createFromParcel(Parcel parcel) {
                     FillEventHistory selection = new FillEventHistory(0, 0, parcel.readBundle());
 
-                    int numEvents = parcel.readInt();
+                    final int numEvents = parcel.readInt();
                     for (int i = 0; i < numEvents; i++) {
-                        selection.addEvent(new Event(parcel.readInt(), parcel.readString(),
-                                parcel.readBundle()));
-                    }
+                        final int eventType = parcel.readInt();
+                        final String datasetId = parcel.readString();
+                        final Bundle clientState = parcel.readBundle();
+                        final ArrayList<String> selectedDatasetIds = parcel.createStringArrayList();
+                        @SuppressWarnings("unchecked")
+                        final ArraySet<String> ignoredDatasets =
+                                (ArraySet<String>) parcel.readArraySet(null);
+                        final ArrayList<AutofillId> changedFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<String> changedDatasetIds = parcel.createStringArrayList();
 
+                        final ArrayList<AutofillId> manuallyFilledFieldIds =
+                                parcel.createTypedArrayList(AutofillId.CREATOR);
+                        final ArrayList<ArrayList<String>> manuallyFilledDatasetIds;
+                        if (manuallyFilledFieldIds != null) {
+                            final int size = manuallyFilledFieldIds.size();
+                            manuallyFilledDatasetIds = new ArrayList<>(size);
+                            for (int j = 0; j < size; j++) {
+                                manuallyFilledDatasetIds.add(parcel.createStringArrayList());
+                            }
+                        } else {
+                            manuallyFilledDatasetIds = null;
+                        }
+
+                        selection.addEvent(new Event(eventType, datasetId, clientState,
+                                selectedDatasetIds, ignoredDatasets,
+                                changedFieldIds, changedDatasetIds,
+                                manuallyFilledFieldIds, manuallyFilledDatasetIds));
+                    }
                     return selection;
                 }
 
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 6d8a959..2f6342a 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
 import static android.view.autofill.Helper.sDebug;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
@@ -30,6 +31,10 @@
 import android.view.autofill.AutofillId;
 import android.widget.RemoteViews;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -42,6 +47,26 @@
  */
 public final class FillResponse implements Parcelable {
 
+    /**
+     * Must be set in the last response to generate
+     * {@link FillEventHistory.Event#TYPE_CONTEXT_COMMITTED} events.
+     */
+    public static final int FLAG_TRACK_CONTEXT_COMMITED = 0x1;
+
+    /**
+     * Used in conjunction to {@link FillResponse.Builder#disableAutofill(long)} to disable autofill
+     * only for the activiy associated with the {@link FillResponse}, instead of the whole app.
+     */
+    public static final int FLAG_DISABLE_ACTIVITY_ONLY = 0x2;
+
+    /** @hide */
+    @IntDef(flag = true, value = {
+            FLAG_TRACK_CONTEXT_COMMITED,
+            FLAG_DISABLE_ACTIVITY_ONLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface FillResponseFlags {}
+
     private final @Nullable ParceledListSlice<Dataset> mDatasets;
     private final @Nullable SaveInfo mSaveInfo;
     private final @Nullable Bundle mClientState;
@@ -49,16 +74,20 @@
     private final @Nullable IntentSender mAuthentication;
     private final @Nullable AutofillId[] mAuthenticationIds;
     private final @Nullable AutofillId[] mIgnoredIds;
+    private final long mDisableDuration;
+    private final int mFlags;
     private int mRequestId;
 
     private FillResponse(@NonNull Builder builder) {
         mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
         mSaveInfo = builder.mSaveInfo;
-        mClientState = builder.mCLientState;
+        mClientState = builder.mClientState;
         mPresentation = builder.mPresentation;
         mAuthentication = builder.mAuthentication;
         mAuthenticationIds = builder.mAuthenticationIds;
         mIgnoredIds = builder.mIgnoredIds;
+        mDisableDuration = builder.mDisableDuration;
+        mFlags = builder.mFlags;
         mRequestId = INVALID_REQUEST_ID;
     }
 
@@ -97,6 +126,16 @@
         return mIgnoredIds;
     }
 
+    /** @hide */
+    public long getDisableDuration() {
+        return mDisableDuration;
+    }
+
+    /** @hide */
+    public int getFlags() {
+        return mFlags;
+    }
+
     /**
      * Associates a {@link FillResponse} to a request.
      *
@@ -122,11 +161,13 @@
     public static final class Builder {
         private ArrayList<Dataset> mDatasets;
         private SaveInfo mSaveInfo;
-        private Bundle mCLientState;
+        private Bundle mClientState;
         private RemoteViews mPresentation;
         private IntentSender mAuthentication;
         private AutofillId[] mAuthenticationIds;
         private AutofillId[] mIgnoredIds;
+        private long mDisableDuration;
+        private int mFlags;
         private boolean mDestroyed;
 
         /**
@@ -163,23 +204,25 @@
          * which is used to visualize visualize the response for triggering the authentication
          * flow.
          *
-         * <p></><strong>Note:</strong> Do not make the provided pending intent
+         * <p><b>Note:</b> Do not make the provided pending intent
          * immutable by using {@link android.app.PendingIntent#FLAG_IMMUTABLE} as the
          * platform needs to fill in the authentication arguments.
          *
          * @param authentication Intent to an activity with your authentication flow.
          * @param presentation The presentation to visualize the response.
-         * @param ids id of Views that when focused will display the authentication UI affordance.
+         * @param ids id of Views that when focused will display the authentication UI.
          *
          * @return This builder.
          * @throws IllegalArgumentException if {@code ids} is {@code null} or empty, or if
-         * neither {@code authentication} nor {@code presentation} is non-{@code null}.
+         * both {@code authentication} and {@code presentation} are {@code null}, or if
+         * both {@code authentication} and {@code presentation} are non-{@code null}
          *
          * @see android.app.PendingIntent#getIntentSender()
          */
         public @NonNull Builder setAuthentication(@NonNull AutofillId[] ids,
                 @Nullable IntentSender authentication, @Nullable RemoteViews presentation) {
             throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             if (ids == null || ids.length == 0) {
                 throw new IllegalArgumentException("ids cannot be null or empry");
             }
@@ -202,6 +245,7 @@
          * text field representing the result of a Captcha challenge.
          */
         public Builder setIgnoredIds(AutofillId...ids) {
+            throwIfDestroyed();
             mIgnoredIds = ids;
             return this;
         }
@@ -222,6 +266,7 @@
          */
         public @NonNull Builder addDataset(@Nullable Dataset dataset) {
             throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             if (dataset == null) {
                 return this;
             }
@@ -241,6 +286,7 @@
          */
         public @NonNull Builder setSaveInfo(@NonNull SaveInfo saveInfo) {
             throwIfDestroyed();
+            throwIfDisableAutofillCalled();
             mSaveInfo = saveInfo;
             return this;
         }
@@ -264,24 +310,89 @@
          */
         public Builder setClientState(@Nullable Bundle clientState) {
             throwIfDestroyed();
-            mCLientState = clientState;
+            mClientState = clientState;
+            return this;
+        }
+
+        /**
+         * Sets flags changing the response behavior.
+         *
+         * @param flags a combination of {@link #FLAG_TRACK_CONTEXT_COMMITED} and
+         * {@link #FLAG_DISABLE_ACTIVITY_ONLY}, or {@code 0}.
+         *
+         * @return This builder.
+         */
+        public Builder setFlags(@FillResponseFlags int flags) {
+            throwIfDestroyed();
+            mFlags = Preconditions.checkFlagsArgument(flags,
+                    FLAG_TRACK_CONTEXT_COMMITED | FLAG_DISABLE_ACTIVITY_ONLY);
+            return this;
+        }
+
+        /**
+         * Disables autofill for the app or activity.
+         *
+         * <p>This method is useful to optimize performance in cases where the service knows it
+         * can not autofill an app&mdash;for example, when the service has a list of "blacklisted"
+         * apps such as office suites.
+         *
+         * <p>By default, it disables autofill for all activities in the app, unless the response is
+         * {@link #setFlags(int) flagged} with {@link #FLAG_DISABLE_ACTIVITY_ONLY}.
+         *
+         * <p>Autofill for the app or activity is automatically re-enabled after any of the
+         * following conditions:
+         *
+         * <ol>
+         *   <li>{@code duration} milliseconds have passed.
+         *   <li>The autofill service for the user has changed.
+         *   <li>The device has rebooted.
+         * </ol>
+         *
+         * <p><b>Note:</b> Activities that are running when autofill is re-enabled remain
+         * disabled for autofill until they finish and restart.
+         *
+         * @param duration duration to disable autofill, in milliseconds.
+         *
+         * @return this builder
+         *
+         * @throws IllegalArgumentException if {@code duration} is not a positive number.
+         * @throws IllegalStateException if either {@link #addDataset(Dataset)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)}, or
+         *       {@link #setSaveInfo(SaveInfo)} was already called.
+         */
+        public Builder disableAutofill(long duration) {
+            throwIfDestroyed();
+            if (duration <= 0) {
+                throw new IllegalArgumentException("duration must be greater than 0");
+            }
+            if (mAuthentication != null || mDatasets != null || mSaveInfo != null) {
+                throw new IllegalStateException("disableAutofill() must be the only method called");
+            }
+
+            mDisableDuration = duration;
             return this;
         }
 
         /**
          * Builds a new {@link FillResponse} instance.
          *
-         * <p>You must provide at least one dataset or some savable ids or an authentication with a
-         * presentation view.
+         * @throws IllegalStateException if any of the following conditions occur:
+         * <ol>
+         *   <li>{@link #build()} was already called.
+         *   <li>No call was made to {@link #addDataset(Dataset)},
+         *       {@link #setAuthentication(AutofillId[], IntentSender, RemoteViews)},
+         *       {@link #setSaveInfo(SaveInfo)}, or {@link #disableAutofill(long)}.
+         * </ol>
          *
          * @return A built response.
          */
         public FillResponse build() {
             throwIfDestroyed();
 
-            if (mAuthentication == null && mDatasets == null && mSaveInfo == null) {
-                throw new IllegalArgumentException("need to provide at least one DataSet or a "
-                        + "SaveInfo or an authentication with a presentation");
+            if (mAuthentication == null && mDatasets == null && mSaveInfo == null
+                    && mDisableDuration == 0) {
+                throw new IllegalStateException("need to provide at least one DataSet or a "
+                        + "SaveInfo or an authentication with a presentation or disable autofill");
             }
             mDestroyed = true;
             return new FillResponse(this);
@@ -292,6 +403,12 @@
                 throw new IllegalStateException("Already called #build()");
             }
         }
+
+        private void throwIfDisableAutofillCalled() {
+            if (mDisableDuration > 0) {
+                throw new IllegalStateException("Already called #disableAutofill()");
+            }
+        }
     }
 
     /////////////////////////////////////
@@ -311,6 +428,8 @@
                 .append(", hasAuthentication=").append(mAuthentication != null)
                 .append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds))
                 .append(", ignoredIds=").append(Arrays.toString(mIgnoredIds))
+                .append(", disableDuration=").append(mDisableDuration)
+                .append(", flags=").append(mFlags)
                 .append("]")
                 .toString();
     }
@@ -333,6 +452,8 @@
         parcel.writeParcelable(mAuthentication, flags);
         parcel.writeParcelable(mPresentation, flags);
         parcel.writeParcelableArray(mIgnoredIds, flags);
+        parcel.writeLong(mDisableDuration);
+        parcel.writeInt(mFlags);
         parcel.writeInt(mRequestId);
     }
 
@@ -363,8 +484,13 @@
             }
 
             builder.setIgnoredIds(parcel.readParcelableArray(null, AutofillId.class));
-            final FillResponse response = builder.build();
+            final long disableDuration = parcel.readLong();
+            if (disableDuration > 0) {
+                builder.disableAutofill(disableDuration);
+            }
+            builder.setFlags(parcel.readInt());
 
+            final FillResponse response = builder.build();
             response.setRequestId(parcel.readInt());
 
             return response;
diff --git a/core/java/android/service/autofill/ImageTransformation.java b/core/java/android/service/autofill/ImageTransformation.java
index 2151f74..4afda24 100644
--- a/core/java/android/service/autofill/ImageTransformation.java
+++ b/core/java/android/service/autofill/ImageTransformation.java
@@ -20,11 +20,12 @@
 
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.view.autofill.AutofillId;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
@@ -43,9 +44,9 @@
  *
  * <pre class="prettyprint">
  *   new ImageTransformation.Builder(ccNumberId, Pattern.compile("^4815.*$"),
- *                                   R.drawable.ic_credit_card_logo1)
- *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2)
- *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3)
+ *                                   R.drawable.ic_credit_card_logo1, "Brand 1")
+ *     .addOption(Pattern.compile("^1623.*$"), R.drawable.ic_credit_card_logo2, "Brand 2")
+ *     .addOption(Pattern.compile("^42.*$"), R.drawable.ic_credit_card_logo3, "Brand 3")
  *     .build();
  * </pre>
  *
@@ -59,7 +60,7 @@
     private static final String TAG = "ImageTransformation";
 
     private final AutofillId mId;
-    private final ArrayList<Pair<Pattern, Integer>> mOptions;
+    private final ArrayList<Option> mOptions;
 
     private ImageTransformation(Builder builder) {
         mId = builder.mId;
@@ -82,17 +83,21 @@
         }
 
         for (int i = 0; i < size; i++) {
-            final Pair<Pattern, Integer> option = mOptions.get(i);
+            final Option option = mOptions.get(i);
             try {
-                if (option.first.matcher(value).matches()) {
+                if (option.pattern.matcher(value).matches()) {
                     Log.d(TAG, "Found match at " + i + ": " + option);
-                    parentTemplate.setImageViewResource(childViewId, option.second);
+                    parentTemplate.setImageViewResource(childViewId, option.resId);
+                    if (option.contentDescription != null) {
+                        parentTemplate.setContentDescription(childViewId,
+                                option.contentDescription);
+                    }
                     return;
                 }
             } catch (Exception e) {
                 // Do not log full exception to avoid PII leaking
-                Log.w(TAG, "Error matching regex #" + i + "(" + option.first.pattern() + ") on id "
-                        + option.second + ": " + e.getClass());
+                Log.w(TAG, "Error matching regex #" + i + "(" + option.pattern + ") on id "
+                        + option.resId + ": " + e.getClass());
                 throw e;
 
             }
@@ -105,25 +110,44 @@
      */
     public static class Builder {
         private final AutofillId mId;
-        private final ArrayList<Pair<Pattern, Integer>> mOptions = new ArrayList<>();
+        private final ArrayList<Option> mOptions = new ArrayList<>();
         private boolean mDestroyed;
 
         /**
-         * Create a new builder for a autofill id and add a first option.
+         * Creates a new builder for a autofill id and add a first option.
          *
          * @param id id of the screen field that will be used to evaluate whether the image should
          * be used.
          * @param regex regular expression defining what should be matched to use this image.
          * @param resId resource id of the image (in the autofill service's package). The
          * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         *
+         * @deprecated use
+         * {@link #ImageTransformation.Builder(AutofillId, Pattern, int, CharSequence)} instead.
          */
+        @Deprecated
         public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId) {
             mId = Preconditions.checkNotNull(id);
-
             addOption(regex, resId);
         }
 
         /**
+         * Creates a new builder for a autofill id and add a first option.
+         *
+         * @param id id of the screen field that will be used to evaluate whether the image should
+         * be used.
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         * @param contentDescription content description to be applied in the child view.
+         */
+        public Builder(@NonNull AutofillId id, @NonNull Pattern regex, @DrawableRes int resId,
+                @NonNull CharSequence contentDescription) {
+            mId = Preconditions.checkNotNull(id);
+            addOption(regex, resId, contentDescription);
+        }
+
+        /**
          * Adds an option to replace the child view with a different image when the regex matches.
          *
          * @param regex regular expression defining what should be matched to use this image.
@@ -131,17 +155,43 @@
          * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
          *
          * @return this build
+         *
+         * @deprecated use {@link #addOption(Pattern, int, CharSequence)} instead.
          */
+        @Deprecated
         public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId) {
+            addOptionInternal(regex, resId, null);
+            return this;
+        }
+
+        /**
+         * Adds an option to replace the child view with a different image and content description
+         * when the regex matches.
+         *
+         * @param regex regular expression defining what should be matched to use this image.
+         * @param resId resource id of the image (in the autofill service's package). The
+         * {@link RemoteViews presentation} must contain a {@link ImageView} child with that id.
+         * @param contentDescription content description to be applied in the child view.
+         *
+         * @return this build
+         */
+        public Builder addOption(@NonNull Pattern regex, @DrawableRes int resId,
+                @NonNull CharSequence contentDescription) {
+            addOptionInternal(regex, resId, Preconditions.checkNotNull(contentDescription));
+            return this;
+        }
+
+        private void addOptionInternal(@NonNull Pattern regex, @DrawableRes int resId,
+                @Nullable CharSequence contentDescription) {
             throwIfDestroyed();
 
             Preconditions.checkNotNull(regex);
             Preconditions.checkArgument(resId != 0);
 
-            mOptions.add(new Pair<>(regex, resId));
-            return this;
+            mOptions.add(new Option(regex, resId, contentDescription));
         }
 
+
         /**
          * Creates a new {@link ImageTransformation} instance.
          */
@@ -178,15 +228,18 @@
         parcel.writeParcelable(mId, flags);
 
         final int size = mOptions.size();
-        final Pattern[] regexs = new Pattern[size];
+        final Pattern[] patterns = new Pattern[size];
         final int[] resIds = new int[size];
+        final CharSequence[] contentDescriptions = new String[size];
         for (int i = 0; i < size; i++) {
-            Pair<Pattern, Integer> regex = mOptions.get(i);
-            regexs[i] = regex.first;
-            resIds[i] = regex.second;
+            final Option option = mOptions.get(i);
+            patterns[i] = option.pattern;
+            resIds[i] = option.resId;
+            contentDescriptions[i] = option.contentDescription;
         }
-        parcel.writeSerializable(regexs);
+        parcel.writeSerializable(patterns);
         parcel.writeIntArray(resIds);
+        parcel.writeCharSequenceArray(contentDescriptions);
     }
 
     public static final Parcelable.Creator<ImageTransformation> CREATOR =
@@ -197,15 +250,22 @@
 
             final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
             final int[] resIds = parcel.createIntArray();
+            final CharSequence[] contentDescriptions = parcel.readCharSequenceArray();
 
             // Always go through the builder to ensure the data ingested by the system obeys the
             // contract of the builder to avoid attacks using specially crafted parcels.
-            final ImageTransformation.Builder builder = new ImageTransformation.Builder(id,
-                    regexs[0], resIds[0]);
+            final CharSequence contentDescription = contentDescriptions[0];
+            final ImageTransformation.Builder builder = (contentDescription != null)
+                    ? new ImageTransformation.Builder(id, regexs[0], resIds[0], contentDescription)
+                    : new ImageTransformation.Builder(id, regexs[0], resIds[0]);
 
             final int size = regexs.length;
             for (int i = 1; i < size; i++) {
-                builder.addOption(regexs[i], resIds[i]);
+                if (contentDescriptions[i] != null) {
+                    builder.addOption(regexs[i], resIds[i], contentDescriptions[i]);
+                } else {
+                    builder.addOption(regexs[i], resIds[i]);
+                }
             }
 
             return builder.build();
@@ -216,4 +276,16 @@
             return new ImageTransformation[size];
         }
     };
+
+    private static final class Option {
+        public final Pattern pattern;
+        public final int resId;
+        public final CharSequence contentDescription;
+
+        Option(Pattern pattern, int resId, CharSequence contentDescription) {
+            this.pattern = pattern;
+            this.resId = resId;
+            this.contentDescription = TextUtils.trimNoCopySpans(contentDescription);
+        }
+    }
 }
diff --git a/core/java/android/service/autofill/InternalSanitizer.java b/core/java/android/service/autofill/InternalSanitizer.java
new file mode 100644
index 0000000..95d2f66
--- /dev/null
+++ b/core/java/android/service/autofill/InternalSanitizer.java
@@ -0,0 +1,38 @@
+/*
+ * 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.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcelable;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Superclass of all sanitizers the system understands. As this is not public all public subclasses
+ * have to implement {@link Sanitizer} again.
+ *
+ * @hide
+ */
+@TestApi
+public abstract class InternalSanitizer implements Sanitizer, Parcelable {
+
+    /**
+     * Sanitizes an {@link AutofillValue}.
+     *
+     * @hide
+     */
+    public abstract AutofillValue sanitize(@NonNull AutofillValue value);
+}
diff --git a/core/java/android/service/autofill/InternalTransformation.java b/core/java/android/service/autofill/InternalTransformation.java
index 974397b..c9864a0 100644
--- a/core/java/android/service/autofill/InternalTransformation.java
+++ b/core/java/android/service/autofill/InternalTransformation.java
@@ -15,17 +15,27 @@
  */
 package android.service.autofill;
 
+import static android.view.autofill.Helper.sDebug;
+
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.os.Parcelable;
+import android.util.Log;
+import android.util.Pair;
 import android.widget.RemoteViews;
 
+import java.util.ArrayList;
+
 /**
  * Superclass of all transformation the system understands. As this is not public all
  * subclasses have to implement {@link Transformation} again.
  *
  * @hide
  */
-abstract class InternalTransformation implements Transformation, Parcelable {
+@TestApi
+public abstract class InternalTransformation implements Transformation, Parcelable {
+
+    private static final String TAG = "InternalTransformation";
 
     /**
      * Applies this transformation to a child view of a {@link android.widget.RemoteViews
@@ -39,4 +49,37 @@
      */
     abstract void apply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
             int childViewId) throws Exception;
+
+    /**
+     * Applies multiple transformations to the children views of a
+     * {@link android.widget.RemoteViews presentation template}.
+     *
+     * @param finder object used to find the value of a field in the screen.
+     * @param template the {@link RemoteViews presentation template}.
+     * @param transformations map of resource id of the child view inside the template to
+     * transformation.
+     *
+     * @hide
+     */
+    public static boolean batchApply(@NonNull ValueFinder finder, @NonNull RemoteViews template,
+            @NonNull ArrayList<Pair<Integer, InternalTransformation>> transformations) {
+        final int size = transformations.size();
+        if (sDebug) Log.d(TAG, "getPresentation(): applying " + size + " transformations");
+        for (int i = 0; i < size; i++) {
+            final Pair<Integer, InternalTransformation> pair = transformations.get(i);
+            final int id = pair.first;
+            final InternalTransformation transformation = pair.second;
+            if (sDebug) Log.d(TAG, "#" + i + ": " + transformation);
+
+            try {
+                transformation.apply(finder, template, id);
+            } catch (Exception e) {
+                // Do not log full exception to avoid PII leaking
+                Log.e(TAG, "Could not apply transformation " + transformation + ": "
+                        + e.getClass());
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/core/java/android/service/autofill/InternalValidator.java b/core/java/android/service/autofill/InternalValidator.java
index e11cf6a..e08bb6c 100644
--- a/core/java/android/service/autofill/InternalValidator.java
+++ b/core/java/android/service/autofill/InternalValidator.java
@@ -16,6 +16,7 @@
 package android.service.autofill;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.os.Parcelable;
 
 /**
@@ -24,6 +25,7 @@
  *
  * @hide
  */
+@TestApi
 public abstract class InternalValidator implements Validator, Parcelable {
 
     /**
@@ -34,5 +36,6 @@
      *
      * @hide
      */
+    @TestApi
     public abstract boolean isValid(@NonNull ValueFinder finder);
 }
diff --git a/core/java/android/service/autofill/LuhnChecksumValidator.java b/core/java/android/service/autofill/LuhnChecksumValidator.java
index 0b5930d..c56ae84 100644
--- a/core/java/android/service/autofill/LuhnChecksumValidator.java
+++ b/core/java/android/service/autofill/LuhnChecksumValidator.java
@@ -27,6 +27,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Arrays;
+
 /**
  * Validator that returns {@code true} if the number created by concatenating all given fields
  * pass a Luhn algorithm checksum. All non-digits are ignored.
@@ -86,17 +88,27 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         if (mIds == null || mIds.length == 0) return false;
 
-        final StringBuilder number = new StringBuilder();
+        final StringBuilder builder = new StringBuilder();
         for (AutofillId id : mIds) {
             final String partialNumber = finder.findByAutofillId(id);
             if (partialNumber == null) {
                 if (sDebug) Log.d(TAG, "No partial number for id " + id);
                 return false;
             }
-            number.append(partialNumber);
+            builder.append(partialNumber);
         }
 
-        return isLuhnChecksumValid(number.toString());
+        final String number = builder.toString();
+        boolean valid = isLuhnChecksumValid(number);
+        if (sDebug) Log.d(TAG, "isValid(" + number.length() + " chars): " + valid);
+        return valid;
+    }
+
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "LuhnChecksumValidator: [ids=" + Arrays.toString(mIds) + "]";
     }
 
     /////////////////////////////////////
diff --git a/core/java/android/service/autofill/OptionalValidators.java b/core/java/android/service/autofill/OptionalValidators.java
index f7edd6e..7aec59f 100644
--- a/core/java/android/service/autofill/OptionalValidators.java
+++ b/core/java/android/service/autofill/OptionalValidators.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,6 +35,8 @@
  */
 final class OptionalValidators extends InternalValidator {
 
+    private static final String TAG = "OptionalValidators";
+
     @NonNull private final InternalValidator[] mValidators;
 
     OptionalValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         for (InternalValidator validator : mValidators) {
             final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
             if (valid) return true;
         }
 
diff --git a/core/java/android/service/autofill/RequiredValidators.java b/core/java/android/service/autofill/RequiredValidators.java
index ac85c28..9e1db2b 100644
--- a/core/java/android/service/autofill/RequiredValidators.java
+++ b/core/java/android/service/autofill/RequiredValidators.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 
 import com.android.internal.util.Preconditions;
 
@@ -34,6 +35,8 @@
  */
 final class RequiredValidators extends InternalValidator {
 
+    private static final String TAG = "RequiredValidators";
+
     @NonNull private final InternalValidator[] mValidators;
 
     RequiredValidators(@NonNull InternalValidator[] validators) {
@@ -44,6 +47,7 @@
     public boolean isValid(@NonNull ValueFinder finder) {
         for (InternalValidator validator : mValidators) {
             final boolean valid = validator.isValid(finder);
+            if (sDebug) Log.d(TAG, "isValid(" + validator + "): " + valid);
             if (!valid) return false;
         }
         return true;
diff --git a/core/java/android/service/autofill/Sanitizer.java b/core/java/android/service/autofill/Sanitizer.java
new file mode 100644
index 0000000..38757ac
--- /dev/null
+++ b/core/java/android/service/autofill/Sanitizer.java
@@ -0,0 +1,26 @@
+/*
+ * 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.autofill;
+
+/**
+ * Helper class used to sanitize user input before using it in a save request.
+ *
+ * <p>Typically used to avoid displaying the save UI for values that are autofilled but reformatted
+ * by the app&mdash;for example, if the autofill service sends a credit card number
+ * value as "004815162342108" and the app automatically changes it to "0048 1516 2342 108".
+ */
+public interface Sanitizer {
+}
diff --git a/core/java/android/service/autofill/SaveCallback.java b/core/java/android/service/autofill/SaveCallback.java
index 3a70138..7207f1d 100644
--- a/core/java/android/service/autofill/SaveCallback.java
+++ b/core/java/android/service/autofill/SaveCallback.java
@@ -34,9 +34,13 @@
 
     /**
      * Notifies the Android System that an
-     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully fulfilled
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} was successfully handled
      * by the service.
      *
+     * <p>If the service could not handle the request right away&mdash;for example, because it must
+     * launch an activity asking the user to authenticate first or because the network is
+     * down&mdash;it should still call {@link #onSuccess()}.
+     *
      * @throws RuntimeException if an error occurred while calling the Android System.
      */
     public void onSuccess() {
@@ -51,9 +55,16 @@
 
     /**
      * Notifies the Android System that an
-     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be fulfilled
+     * {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} could not be handled
      * by the service.
      *
+     * <p>This method should only be called when the service could not handle the request right away
+     * and could not recover or retry it. If the service could retry or recover, it could keep
+     * the {@link SaveRequest} and call {@link #onSuccess()} instead.
+     *
+     * <p><b>Note:</b> The Android System displays an UI with the supplied error message; if
+     * you prefer to show your own message, call {@link #onSuccess()} instead.
+     *
      * @param message error message to be displayed to the user.
      *
      * @throws RuntimeException if an error occurred while calling the Android System.
diff --git a/core/java/android/service/autofill/SaveInfo.aidl b/core/java/android/service/autofill/SaveInfo.aidl
deleted file mode 100644
index 8cda608..0000000
--- a/core/java/android/service/autofill/SaveInfo.aidl
+++ /dev/null
@@ -1,19 +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 android.service.autofill;
-
-parcelable SaveInfo;
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index e0a0730..9a1dcbb 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -25,6 +25,8 @@
 import android.content.IntentSender;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
@@ -66,7 +68,7 @@
  *       .build();
  * </pre>
  *
- * <p>The save type flags are used to display the appropriate strings in the save UI affordance.
+ * <p>The save type flags are used to display the appropriate strings in the autofill save UI.
  * You can pass multiple values, but try to keep it short if possible. In the above example, just
  * {@code SaveInfo.SAVE_DATA_TYPE_PASSWORD} would be enough.
  *
@@ -101,13 +103,17 @@
  *       .build();
  * </pre>
  *
+ * <a name="TriggeringSaveRequest"></a>
+ * <h3>Triggering a save request</h3>
+ *
  * <p>The {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)} can be triggered after
  * any of the following events:
  * <ul>
  *   <li>The {@link Activity} finishes.
- *   <li>The app explicitly called {@link AutofillManager#commit()}.
- *   <li>All required views became invisible (if the {@link SaveInfo} was created with the
+ *   <li>The app explicitly calls {@link AutofillManager#commit()}.
+ *   <li>All required views become invisible (if the {@link SaveInfo} was created with the
  *       {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} flag).
+ *   <li>The user clicks a specific view (defined by {@link Builder#setTriggerId(AutofillId)}.
  * </ul>
  *
  * <p>But it is only triggered when all conditions below are met:
@@ -121,10 +127,13 @@
  *   <li>There is no {@link Dataset} in the last {@link FillResponse} that completely matches the
  *       screen state (i.e., all required and optional fields in the dataset have the same value as
  *       the fields in the screen).
- *   <li>The user explicitly tapped the UI affordance asking to save data for autofill.
+ *   <li>The user explicitly tapped the autofill save UI asking to save data for autofill.
  * </ul>
  *
- * <p>The service can also customize some aspects of the save UI affordance:
+ * <a name="CustomizingSaveUI"></a>
+ * <h3>Customizing the autofill save UI</h3>
+ *
+ * <p>The service can also customize some aspects of the autofill save UI:
  * <ul>
  *   <li>Add a simple subtitle by calling {@link Builder#setDescription(CharSequence)}.
  *   <li>Add a customized subtitle by calling
@@ -210,16 +219,25 @@
     @interface SaveDataType{}
 
     /**
-     * Usually {@link AutofillService#onSaveRequest(SaveRequest, SaveCallback)}
-     * is called once the {@link Activity} finishes. If this flag is set it is called once all
-     * saved views become invisible.
+     * Usually, a save request is only automatically <a href="#TriggeringSaveRequest">triggered</a>
+     * once the {@link Activity} finishes. If this flag is set, it is triggered once all saved views
+     * become invisible.
      */
     public static final int FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE = 0x1;
 
+    /**
+     * By default, a save request is automatically <a href="#TriggeringSaveRequest">triggered</a>
+     * once the {@link Activity} finishes. If this flag is set, finishing the activity doesn't
+     * trigger a save request.
+     *
+     * <p>This flag is typically used in conjunction with {@link Builder#setTriggerId(AutofillId)}.
+     */
+    public static final int FLAG_DONT_SAVE_ON_FINISH = 0x2;
+
     /** @hide */
     @IntDef(
             flag = true,
-            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE})
+            value = {FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE, FLAG_DONT_SAVE_ON_FINISH})
     @Retention(RetentionPolicy.SOURCE)
     @interface SaveInfoFlags{}
 
@@ -232,6 +250,9 @@
     private final int mFlags;
     private final CustomDescription mCustomDescription;
     private final InternalValidator mValidator;
+    private final InternalSanitizer[] mSanitizerKeys;
+    private final AutofillId[][] mSanitizerValues;
+    private final AutofillId mTriggerId;
 
     private SaveInfo(Builder builder) {
         mType = builder.mType;
@@ -243,6 +264,19 @@
         mFlags = builder.mFlags;
         mCustomDescription = builder.mCustomDescription;
         mValidator = builder.mValidator;
+        if (builder.mSanitizers == null) {
+            mSanitizerKeys = null;
+            mSanitizerValues = null;
+        } else {
+            final int size = builder.mSanitizers.size();
+            mSanitizerKeys = new InternalSanitizer[size];
+            mSanitizerValues = new AutofillId[size][];
+            for (int i = 0; i < size; i++) {
+                mSanitizerKeys[i] = builder.mSanitizers.keyAt(i);
+                mSanitizerValues[i] = builder.mSanitizers.valueAt(i);
+            }
+        }
+        mTriggerId = builder.mTriggerId;
     }
 
     /** @hide */
@@ -292,6 +326,24 @@
         return mValidator;
     }
 
+    /** @hide */
+    @Nullable
+    public InternalSanitizer[] getSanitizerKeys() {
+        return mSanitizerKeys;
+    }
+
+    /** @hide */
+    @Nullable
+    public AutofillId[][] getSanitizerValues() {
+        return mSanitizerValues;
+    }
+
+    /** @hide */
+    @Nullable
+    public AutofillId getTriggerId() {
+        return mTriggerId;
+    }
+
     /**
      * A builder for {@link SaveInfo} objects.
      */
@@ -307,6 +359,10 @@
         private int mFlags;
         private CustomDescription mCustomDescription;
         private InternalValidator mValidator;
+        private ArrayMap<InternalSanitizer, AutofillId[]> mSanitizers;
+        // Set used to validate against duplicate ids.
+        private ArraySet<AutofillId> mSanitizerIds;
+        private AutofillId mTriggerId;
 
         /**
          * Creates a new builder.
@@ -363,13 +419,15 @@
         /**
          * Sets flags changing the save behavior.
          *
-         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE} or {@code 0}.
+         * @param flags {@link #FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE},
+         * {@link #FLAG_DONT_SAVE_ON_FINISH}, or {@code 0}.
          * @return This builder.
          */
         public @NonNull Builder setFlags(@SaveInfoFlags int flags) {
             throwIfDestroyed();
 
-            mFlags = Preconditions.checkFlagsArgument(flags, FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
+            mFlags = Preconditions.checkFlagsArgument(flags,
+                    FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DONT_SAVE_ON_FINISH);
             return this;
         }
 
@@ -462,8 +520,8 @@
         }
 
         /**
-         * Sets an object used to validate the user input - if the input is not valid, the Save UI
-         * affordance is not shown.
+         * Sets an object used to validate the user input - if the input is not valid, the
+         * autofill save UI is not shown.
          *
          * <p>Typically used to validate credit card numbers. Examples:
          *
@@ -477,19 +535,20 @@
          * 16 digits, or 15 digits starting with 108:
          *
          * <pre class="prettyprint">
-         * import android.service.autofill.Validators;
+         * import static android.service.autofill.Validators.and;
+         * import static android.service.autofill.Validators.or;
          *
          * Validator validator =
          *   and(
          *     new LuhnChecksumValidator(ccNumberId),
          *     or(
-         *       new RegexValidator(ccNumberId, Pattern.compile(""^\\d{16}$")),
-         *       new RegexValidator(ccNumberId, Pattern.compile(""^108\\d{12}$"))
+         *       new RegexValidator(ccNumberId, Pattern.compile("^\\d{16}$")),
+         *       new RegexValidator(ccNumberId, Pattern.compile("^108\\d{12}$"))
          *     )
          *   );
          * </pre>
          *
-         * <p><b>NOTE: </b>the example above is just for illustrative purposes; the same validator
+         * <p><b>Note:</b> the example above is just for illustrative purposes; the same validator
          * could be created using a single regex for the {@code OR} part:
          *
          * <pre class="prettyprint">
@@ -504,14 +563,14 @@
          * 4 digits on each field:
          *
          * <pre class="prettyprint">
-         * import android.service.autofill.Validators;
+         * import static android.service.autofill.Validators.and;
          *
          * Validator validator =
          *   and(
-         *     new RegexValidator(ccNumberId1, Pattern.compile(""^\\d{4}$")),
-         *     new RegexValidator(ccNumberId2, Pattern.compile(""^\\d{4}$")),
-         *     new RegexValidator(ccNumberId3, Pattern.compile(""^\\d{4}$")),
-         *     new RegexValidator(ccNumberId4, Pattern.compile(""^\\d{4}$"))
+         *     new RegexValidator(ccNumberId1, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId2, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId3, Pattern.compile("^\\d{4}$")),
+         *     new RegexValidator(ccNumberId4, Pattern.compile("^\\d{4}$"))
          *   );
          * </pre>
          *
@@ -530,6 +589,82 @@
         }
 
         /**
+         * Adds a sanitizer for one or more field.
+         *
+         * <p>When a sanitizer is set for a field, the {@link AutofillValue} is sent to the
+         * sanitizer before a save request is <a href="#TriggeringSaveRequest">triggered</a>.
+         *
+         * <p>Typically used to avoid displaying the save UI for values that are autofilled but
+         * reformattedby the app. For example, to remove spaces between every 4 digits of a
+         * credit card number:
+         *
+         * <pre class="prettyprint">
+         * builder.addSanitizer(
+         *     new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"),
+         *         "$1$2$3$4"), ccNumberId);
+         * </pre>
+         *
+         * <p>The same sanitizer can be reused to sanitize multiple fields. For example, to trim
+         * both the username and password fields:
+         *
+         * <pre class="prettyprint">
+         * builder.addSanitizer(
+         *     new TextValueSanitizer(Pattern.compile("^\\s*(.*)\\s*$"), "$1"),
+         *         usernameId, passwordId);
+         * </pre>
+         *
+         * @param sanitizer an implementation provided by the Android System.
+         * @param ids id of fields whose value will be sanitized.
+         * @return this builder.
+         *
+         * @throws IllegalArgumentException if a sanitizer for any of the {@code ids} has already
+         * been added or if {@code ids} is empty.
+         */
+        public @NonNull Builder addSanitizer(@NonNull Sanitizer sanitizer,
+                @NonNull AutofillId... ids) {
+            throwIfDestroyed();
+            Preconditions.checkArgument(!ArrayUtils.isEmpty(ids), "ids cannot be empty or null");
+            Preconditions.checkArgument((sanitizer instanceof InternalSanitizer),
+                    "not provided by Android System: " + sanitizer);
+
+            if (mSanitizers == null) {
+                mSanitizers = new ArrayMap<>();
+                mSanitizerIds = new ArraySet<>(ids.length);
+            }
+
+            // Check for duplicates first.
+            for (AutofillId id : ids) {
+                Preconditions.checkArgument(!mSanitizerIds.contains(id), "already added %s", id);
+                mSanitizerIds.add(id);
+            }
+
+            mSanitizers.put((InternalSanitizer) sanitizer, ids);
+
+            return this;
+        }
+
+       /**
+         * Explicitly defines the view that should commit the autofill context when clicked.
+         *
+         * <p>Usually, the save request is only automatically
+         * <a href="#TriggeringSaveRequest">triggered</a> after the activity is
+         * finished or all relevant views become invisible, but there are scenarios where the
+         * autofill context is automatically commited too late
+         * &mdash;for example, when the activity manually clears the autofillable views when a
+         * button is tapped. This method can be used to trigger the autofill save UI earlier in
+         * these scenarios.
+         *
+         * <p><b>Note:</b> This method should only be used in scenarios where the automatic workflow
+         * is not enough, otherwise it could trigger the autofill save UI when it should not&mdash;
+         * for example, when the user entered invalid credentials for the autofillable views.
+         */
+        public @NonNull Builder setTriggerId(@NonNull AutofillId id) {
+            throwIfDestroyed();
+            mTriggerId = Preconditions.checkNotNull(id);
+            return this;
+        }
+
+        /**
          * Builds a new {@link SaveInfo} instance.
          *
          * @throws IllegalStateException if no
@@ -566,9 +701,14 @@
                 .append(", description=").append(mDescription)
                 .append(DebugUtils.flagsToString(SaveInfo.class, "NEGATIVE_BUTTON_STYLE_",
                         mNegativeButtonStyle))
-                .append(", mFlags=").append(mFlags)
-                .append(", mCustomDescription=").append(mCustomDescription)
-                .append(", validation=").append(mValidator)
+                .append(", flags=").append(mFlags)
+                .append(", customDescription=").append(mCustomDescription)
+                .append(", validator=").append(mValidator)
+                .append(", sanitizerKeys=")
+                    .append(mSanitizerKeys == null ? "N/A:" : mSanitizerKeys.length)
+                .append(", sanitizerValues=")
+                    .append(mSanitizerValues == null ? "N/A:" : mSanitizerValues.length)
+                .append(", triggerId=").append(mTriggerId)
                 .append("]").toString();
     }
 
@@ -591,6 +731,13 @@
         parcel.writeCharSequence(mDescription);
         parcel.writeParcelable(mCustomDescription, flags);
         parcel.writeParcelable(mValidator, flags);
+        parcel.writeParcelableArray(mSanitizerKeys, flags);
+        if (mSanitizerKeys != null) {
+            for (int i = 0; i < mSanitizerValues.length; i++) {
+                parcel.writeParcelableArray(mSanitizerValues[i], flags);
+            }
+        }
+        parcel.writeParcelable(mTriggerId, flags);
         parcel.writeInt(mFlags);
     }
 
@@ -621,6 +768,20 @@
             if (validator != null) {
                 builder.setValidator(validator);
             }
+            final InternalSanitizer[] sanitizers =
+                    parcel.readParcelableArray(null, InternalSanitizer.class);
+            if (sanitizers != null) {
+                final int size = sanitizers.length;
+                for (int i = 0; i < size; i++) {
+                    final AutofillId[] autofillIds =
+                            parcel.readParcelableArray(null, AutofillId.class);
+                    builder.addSanitizer(sanitizers[i], autofillIds);
+                }
+            }
+            final AutofillId triggerId = parcel.readParcelable(null);
+            if (triggerId != null) {
+                builder.setTriggerId(triggerId);
+            }
             builder.setFlags(parcel.readInt());
             return builder.build();
         }
diff --git a/core/java/android/service/autofill/SaveRequest.java b/core/java/android/service/autofill/SaveRequest.java
index 1a6c5b0..f53967b 100644
--- a/core/java/android/service/autofill/SaveRequest.java
+++ b/core/java/android/service/autofill/SaveRequest.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
-import android.os.CancellationSignal;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -48,7 +47,8 @@
     }
 
     private SaveRequest(@NonNull Parcel parcel) {
-        this(parcel.readTypedArrayList(null), parcel.readBundle(), parcel.createStringArrayList());
+        this(parcel.createTypedArrayList(FillContext.CREATOR),
+                parcel.readBundle(), parcel.createStringArrayList());
     }
 
     /**
@@ -59,9 +59,14 @@
     }
 
     /**
-     * Gets the extra client state returned from the last {@link
-     * AutofillService#onFillRequest(FillRequest, CancellationSignal, FillCallback)}
-     * fill request}.
+     * Gets the latest client state extra returned from the service.
+     *
+     * <p><b>Note:</b> Prior to Android {@link android.os.Build.VERSION_CODES#P}, only client state
+     * bundles set by {@link FillResponse.Builder#setClientState(Bundle)} where considered. On
+     * Android {@link android.os.Build.VERSION_CODES#P} and higher, bundles set in the result of
+     * an authenticated request through the
+     * {@link android.view.autofill.AutofillManager#EXTRA_CLIENT_STATE} extra are
+     * also considered (and take precedence when set).
      *
      * @return The client state.
      */
@@ -84,7 +89,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeTypedArrayList(mFillContexts, flags);
+        parcel.writeTypedList(mFillContexts, flags);
         parcel.writeBundle(mClientState);
         parcel.writeStringList(mDatasetIds);
     }
diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java
new file mode 100644
index 0000000..12e85b1
--- /dev/null
+++ b/core/java/android/service/autofill/TextValueSanitizer.java
@@ -0,0 +1,122 @@
+/*
+ * 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.autofill;
+
+import static android.view.autofill.Helper.sDebug;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.view.autofill.AutofillValue;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Sanitizes a text {@link AutofillValue} using a regular expression (regex) substitution.
+ *
+ * <p>For example, to remove spaces from groups of 4-digits in a credit card:
+ *
+ * <pre class="prettyprint">
+ * new TextValueSanitizer(Pattern.compile("^(\\d{4}\s?\\d{4}\s?\\d{4}\s?\\d{4})$"), "$1$2$3$4")
+ * </pre>
+ */
+public final class TextValueSanitizer extends InternalSanitizer implements
+        Sanitizer, Parcelable {
+    private static final String TAG = "TextValueSanitizer";
+
+    private final Pattern mRegex;
+    private final String mSubst;
+
+    /**
+     * Default constructor.
+     *
+     * @param regex regular expression with groups (delimited by {@code (} and {@code (}) that
+     * are used to substitute parts of the {@link AutofillValue#getTextValue() text value}.
+     * @param subst the string that substitutes the matched regex, using {@code $} for
+     * group substitution ({@code $1} for 1st group match, {@code $2} for 2nd, etc).
+     */
+    public TextValueSanitizer(@NonNull Pattern regex, @NonNull String subst) {
+        mRegex = Preconditions.checkNotNull(regex);
+        mSubst = Preconditions.checkNotNull(subst);
+    }
+
+    /** @hide */
+    @Override
+    @TestApi
+    public AutofillValue sanitize(@NonNull AutofillValue value) {
+        if (value == null) {
+            Slog.w(TAG, "sanitize() called with null value");
+            return null;
+        }
+        if (!value.isText()) return value;
+
+        final CharSequence text = value.getTextValue();
+
+        try {
+            final Matcher matcher = mRegex.matcher(text);
+            if (!matcher.matches()) return value;
+
+            final CharSequence sanitized = matcher.replaceAll(mSubst);
+            return AutofillValue.forText(sanitized);
+        } catch (Exception e) {
+            Slog.w(TAG, "Exception evaluating " + mRegex + "/" + mSubst + ": " + e);
+            return value;
+        }
+    }
+
+    /////////////////////////////////////
+    // Object "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public String toString() {
+        if (!sDebug) return super.toString();
+
+        return "TextValueSanitizer: [regex=" + mRegex + ", subst=" + mSubst + "]";
+    }
+
+    /////////////////////////////////////
+    // Parcelable "contract" methods. //
+    /////////////////////////////////////
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeSerializable(mRegex);
+        parcel.writeString(mSubst);
+    }
+
+    public static final Parcelable.Creator<TextValueSanitizer> CREATOR =
+            new Parcelable.Creator<TextValueSanitizer>() {
+        @Override
+        public TextValueSanitizer createFromParcel(Parcel parcel) {
+            return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
+        }
+
+        @Override
+        public TextValueSanitizer[] newArray(int size) {
+            return new TextValueSanitizer[size];
+        }
+    };
+}
diff --git a/core/java/android/service/autofill/Transformation.java b/core/java/android/service/autofill/Transformation.java
index 4cef261..aa8bc9b 100644
--- a/core/java/android/service/autofill/Transformation.java
+++ b/core/java/android/service/autofill/Transformation.java
@@ -19,7 +19,7 @@
  * Helper class used to change a child view of a {@link android.widget.RemoteViews presentation
  * template} at runtime, using the values of fields contained in the screen.
  *
- * <p>Typically used by {@link CustomDescription} to provide a customized Save UI affordance.
+ * <p>Typically used by {@link CustomDescription} to provide a customized autofill save UI.
  */
 public interface Transformation {
 }
diff --git a/core/java/android/service/autofill/Validator.java b/core/java/android/service/autofill/Validator.java
index 854aa1e..a4036f2 100644
--- a/core/java/android/service/autofill/Validator.java
+++ b/core/java/android/service/autofill/Validator.java
@@ -16,9 +16,9 @@
 package android.service.autofill;
 
 /**
- * Helper class used to define whether the contents of a screen are valid.
+ * Class used to define whether a condition is satisfied.
  *
- * <p>Typically used to avoid displaying the Save UI affordance when the user input is invalid.
+ * <p>Typically used to avoid displaying the save UI when the user input is invalid.
  */
 public interface Validator {
 }
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index ce678fc..7348cf6 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -56,6 +56,15 @@
     public static final String KEY_GROUP_KEY = "key_group_key";
 
     /**
+     * Data type: int, one of {@link NotificationListenerService.Ranking#USER_SENTIMENT_POSITIVE},
+     * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEUTRAL},
+     * {@link NotificationListenerService.Ranking#USER_SENTIMENT_NEGATIVE}. Used to express how
+     * a user feels about notifications in the same {@link android.app.NotificationChannel} as
+     * the notification represented by {@link #getKey()}.
+     */
+    public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index ed44f25..c388367 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -19,6 +19,7 @@
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
 import android.os.UserHandle;
+import android.service.notification.NotificationStats;
 import android.service.notification.IStatusBarNotificationHolder;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.NotificationRankingUpdate;
@@ -26,12 +27,13 @@
 /** @hide */
 oneway interface INotificationListener
 {
-    // listeners and rankers
+    // listeners and assistant
     void onListenerConnected(in NotificationRankingUpdate update);
     void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder,
             in NotificationRankingUpdate update);
+    // stats only for assistant
     void onNotificationRemoved(in IStatusBarNotificationHolder notificationHolder,
-            in NotificationRankingUpdate update, int reason);
+            in NotificationRankingUpdate update, in NotificationStats stats, int reason);
     void onNotificationRankingUpdate(in NotificationRankingUpdate update);
     void onListenerHintsChanged(int hints);
     void onInterruptionFilterChanged(int interruptionFilter);
@@ -40,7 +42,7 @@
     void onNotificationChannelModification(String pkgName, in UserHandle user, in NotificationChannel channel, int modificationType);
     void onNotificationChannelGroupModification(String pkgName, in UserHandle user, in NotificationChannelGroup group, int modificationType);
 
-    // rankers only
+    // assistants only
     void onNotificationEnqueued(in IStatusBarNotificationHolder notificationHolder);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index d94017c..8e52bfa 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -16,12 +16,9 @@
 
 package android.service.notification;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.app.NotificationChannel;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
@@ -30,9 +27,9 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+
 import com.android.internal.os.SomeArgs;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -79,7 +76,7 @@
             String snoozeCriterionId);
 
     /**
-     * A notification was posted by an app. Called before alert.
+     * A notification was posted by an app. Called before post.
      *
      * @param sbn the new notification
      * @return an adjustment or null to take no action, within 100ms.
@@ -87,6 +84,34 @@
     abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn);
 
     /**
+     * Implement this method to learn when notifications are removed, how they were interacted with
+     * before removal, and why they were removed.
+     * <p>
+     * This might occur because the user has dismissed the notification using system UI (or another
+     * notification listener) or because the app has withdrawn the notification.
+     * <p>
+     * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
+     * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
+     * fields such as {@link android.app.Notification#contentView} and
+     * {@link android.app.Notification#largeIcon}. However, all other fields on
+     * {@link StatusBarNotification}, sufficient to match this call with a prior call to
+     * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
+     *
+     ** @param sbn A data structure encapsulating at least the original information (tag and id)
+     *            and source (package name) used to post the {@link android.app.Notification} that
+     *            was just removed.
+     * @param rankingMap The current ranking map that can be used to retrieve ranking information
+     *                   for active notifications.
+     * @param stats Stats about how the user interacted with the notification before it was removed.
+     * @param reason see {@link #REASON_LISTENER_CANCEL}, etc.
+     */
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            NotificationStats stats, int reason) {
+        onNotificationRemoved(sbn, rankingMap, reason);
+    }
+
+    /**
      * Updates a notification.  N.B. this won’t cause
      * an existing notification to alert, but might allow a future update to
      * this notification to alert.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index a5223fd..08d3118 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.app.INotificationManager;
 import android.app.Notification;
 import android.app.Notification.Builder;
@@ -265,7 +266,10 @@
     @GuardedBy("mLock")
     private RankingMap mRankingMap;
 
-    private INotificationManager mNoMan;
+    /**
+     * @hide
+     */
+    protected INotificationManager mNoMan;
 
     /**
      * Only valid after a successful call to (@link registerAsService}.
@@ -389,6 +393,18 @@
     }
 
     /**
+     * NotificationStats are not populated for notification listeners, so fall back to
+     * {@link #onNotificationRemoved(StatusBarNotification, RankingMap, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            NotificationStats stats, int reason) {
+        onNotificationRemoved(sbn, rankingMap, reason);
+    }
+
+    /**
      * Implement this method to learn about when the listener is enabled and connected to
      * the notification manager.  You are safe to call {@link #getActiveNotifications()}
      * at this time.
@@ -1200,7 +1216,7 @@
 
         @Override
         public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
-                NotificationRankingUpdate update, int reason) {
+                NotificationRankingUpdate update, NotificationStats stats, int reason) {
             StatusBarNotification sbn;
             try {
                 sbn = sbnHolder.get();
@@ -1215,6 +1231,7 @@
                 args.arg1 = sbn;
                 args.arg2 = mRankingMap;
                 args.arg3 = reason;
+                args.arg4 = stats;
                 mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_REMOVED,
                         args).sendToTarget();
             }
@@ -1324,6 +1341,26 @@
          * @hide */
         public static final int VISIBILITY_NO_OVERRIDE = NotificationManager.VISIBILITY_NO_OVERRIDE;
 
+        /**
+         * The user is likely to have a negative reaction to this notification.
+         */
+        public static final int USER_SENTIMENT_NEGATIVE = -1;
+        /**
+         * It is not known how the user will react to this notification.
+         */
+        public static final int USER_SENTIMENT_NEUTRAL = 0;
+        /**
+         * The user is likely to have a positive reaction to this notification.
+         */
+        public static final int USER_SENTIMENT_POSITIVE = 1;
+
+        /** @hide */
+        @IntDef(prefix = { "USER_SENTIMENT_" }, value = {
+                USER_SENTIMENT_NEGATIVE, USER_SENTIMENT_NEUTRAL, USER_SENTIMENT_POSITIVE
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface UserSentiment {}
+
         private String mKey;
         private int mRank = -1;
         private boolean mIsAmbient;
@@ -1341,6 +1378,7 @@
         // Notification assistant snooze criteria.
         private ArrayList<SnoozeCriterion> mSnoozeCriteria;
         private boolean mShowBadge;
+        private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
 
         public Ranking() {}
 
@@ -1436,6 +1474,17 @@
         }
 
         /**
+         * Returns how the system thinks the user feels about notifications from the
+         * channel provided by {@link #getChannel()}. You can use this information to expose
+         * controls to help the user block this channel's notifications, if the sentiment is
+         * {@link #USER_SENTIMENT_NEGATIVE}, or emphasize this notification if the sentiment is
+         * {@link #USER_SENTIMENT_POSITIVE}.
+         */
+        public int getUserSentiment() {
+            return mUserSentiment;
+        }
+
+        /**
          * If the {@link NotificationAssistantService} has added people to this notification, then
          * this will be non-null.
          * @hide
@@ -1471,7 +1520,8 @@
                 int visibilityOverride, int suppressedVisualEffects, int importance,
                 CharSequence explanation, String overrideGroupKey,
                 NotificationChannel channel, ArrayList<String> overridePeople,
-                ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge) {
+                ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
+                int userSentiment) {
             mKey = key;
             mRank = rank;
             mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1485,6 +1535,7 @@
             mOverridePeople = overridePeople;
             mSnoozeCriteria = snoozeCriteria;
             mShowBadge = showBadge;
+            mUserSentiment = userSentiment;
         }
 
         /**
@@ -1532,6 +1583,7 @@
         private ArrayMap<String, ArrayList<String>> mOverridePeople;
         private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
         private ArrayMap<String, Boolean> mShowBadge;
+        private ArrayMap<String, Integer> mUserSentiment;
 
         private RankingMap(NotificationRankingUpdate rankingUpdate) {
             mRankingUpdate = rankingUpdate;
@@ -1560,7 +1612,7 @@
                     getVisibilityOverride(key), getSuppressedVisualEffects(key),
                     getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
                     getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
-                    getShowBadge(key));
+                    getShowBadge(key), getUserSentiment(key));
             return rank >= 0;
         }
 
@@ -1677,6 +1729,17 @@
             return showBadge == null ? false : showBadge.booleanValue();
         }
 
+        private int getUserSentiment(String key) {
+            synchronized (this) {
+                if (mUserSentiment == null) {
+                    buildUserSentimentLocked();
+                }
+            }
+            Integer userSentiment = mUserSentiment.get(key);
+            return userSentiment == null
+                    ? Ranking.USER_SENTIMENT_NEUTRAL : userSentiment.intValue();
+        }
+
         // Locked by 'this'
         private void buildRanksLocked() {
             String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1776,6 +1839,15 @@
             }
         }
 
+        // Locked by 'this'
+        private void buildUserSentimentLocked() {
+            Bundle userSentiment = mRankingUpdate.getUserSentiment();
+            mUserSentiment = new ArrayMap<>(userSentiment.size());
+            for (String key : userSentiment.keySet()) {
+                mUserSentiment.put(key, userSentiment.getInt(key));
+            }
+        }
+
         // ----------- Parcelable
 
         @Override
@@ -1835,8 +1907,9 @@
                     StatusBarNotification sbn = (StatusBarNotification) args.arg1;
                     RankingMap rankingMap = (RankingMap) args.arg2;
                     int reason = (int) args.arg3;
+                    NotificationStats stats = (NotificationStats) args.arg4;
                     args.recycle();
-                    onNotificationRemoved(sbn, rankingMap, reason);
+                    onNotificationRemoved(sbn, rankingMap, stats, reason);
                 } break;
 
                 case MSG_ON_LISTENER_CONNECTED: {
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 326b212..6d51db0 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -35,12 +35,13 @@
     private final Bundle mOverridePeople;
     private final Bundle mSnoozeCriteria;
     private final Bundle mShowBadge;
+    private final Bundle mUserSentiment;
 
     public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
             Bundle visibilityOverrides, Bundle suppressedVisualEffects,
             int[] importance, Bundle explanation, Bundle overrideGroupKeys,
             Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
-            Bundle showBadge) {
+            Bundle showBadge, Bundle userSentiment) {
         mKeys = keys;
         mInterceptedKeys = interceptedKeys;
         mVisibilityOverrides = visibilityOverrides;
@@ -52,6 +53,7 @@
         mOverridePeople = overridePeople;
         mSnoozeCriteria = snoozeCriteria;
         mShowBadge = showBadge;
+        mUserSentiment = userSentiment;
     }
 
     public NotificationRankingUpdate(Parcel in) {
@@ -67,6 +69,7 @@
         mOverridePeople = in.readBundle();
         mSnoozeCriteria = in.readBundle();
         mShowBadge = in.readBundle();
+        mUserSentiment = in.readBundle();
     }
 
     @Override
@@ -87,6 +90,7 @@
         out.writeBundle(mOverridePeople);
         out.writeBundle(mSnoozeCriteria);
         out.writeBundle(mShowBadge);
+        out.writeBundle(mUserSentiment);
     }
 
     public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -143,4 +147,8 @@
     public Bundle getShowBadge() {
         return mShowBadge;
     }
+
+    public Bundle getUserSentiment() {
+        return mUserSentiment;
+    }
 }
diff --git a/core/java/android/service/notification/NotificationStats.aidl b/core/java/android/service/notification/NotificationStats.aidl
new file mode 100644
index 0000000..40f5548
--- /dev/null
+++ b/core/java/android/service/notification/NotificationStats.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.notification;
+
+parcelable NotificationStats;
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java
new file mode 100644
index 0000000..76d5328
--- /dev/null
+++ b/core/java/android/service/notification/NotificationStats.java
@@ -0,0 +1,256 @@
+/**
+ * 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.notification;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.app.RemoteInput;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+@TestApi
+@SystemApi
+public final class NotificationStats implements Parcelable {
+
+    private boolean mSeen;
+    private boolean mExpanded;
+    private boolean mDirectReplied;
+    private boolean mSnoozed;
+    private boolean mViewedSettings;
+    private boolean mInteracted;
+
+    /** @hide */
+    @IntDef(prefix = { "DISMISSAL_SURFACE_" }, value = {
+            DISMISSAL_NOT_DISMISSED, DISMISSAL_OTHER, DISMISSAL_PEEK, DISMISSAL_AOD, DISMISSAL_SHADE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DismissalSurface {}
+
+
+    private @DismissalSurface int mDismissalSurface = DISMISSAL_NOT_DISMISSED;
+
+    /**
+     * Notification has not been dismissed yet.
+     */
+    public static final int DISMISSAL_NOT_DISMISSED = -1;
+    /**
+     * Notification has been dismissed from a {@link NotificationListenerService} or the app
+     * itself.
+     */
+    public static final int DISMISSAL_OTHER = 0;
+    /**
+     * Notification has been dismissed while peeking.
+     */
+    public static final int DISMISSAL_PEEK = 1;
+    /**
+     * Notification has been dismissed from always on display.
+     */
+    public static final int DISMISSAL_AOD = 2;
+    /**
+     * Notification has been dismissed from the notification shade.
+     */
+    public static final int DISMISSAL_SHADE = 3;
+
+    public NotificationStats() {
+    }
+
+    protected NotificationStats(Parcel in) {
+        mSeen = in.readByte() != 0;
+        mExpanded = in.readByte() != 0;
+        mDirectReplied = in.readByte() != 0;
+        mSnoozed = in.readByte() != 0;
+        mViewedSettings = in.readByte() != 0;
+        mInteracted = in.readByte() != 0;
+        mDismissalSurface = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByte((byte) (mSeen ? 1 : 0));
+        dest.writeByte((byte) (mExpanded ? 1 : 0));
+        dest.writeByte((byte) (mDirectReplied ? 1 : 0));
+        dest.writeByte((byte) (mSnoozed ? 1 : 0));
+        dest.writeByte((byte) (mViewedSettings ? 1 : 0));
+        dest.writeByte((byte) (mInteracted ? 1 : 0));
+        dest.writeInt(mDismissalSurface);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<NotificationStats> CREATOR = new Creator<NotificationStats>() {
+        @Override
+        public NotificationStats createFromParcel(Parcel in) {
+            return new NotificationStats(in);
+        }
+
+        @Override
+        public NotificationStats[] newArray(int size) {
+            return new NotificationStats[size];
+        }
+    };
+
+    /**
+     * Returns whether the user has seen this notification at least once.
+     */
+    public boolean hasSeen() {
+        return mSeen;
+    }
+
+    /**
+     * Records that the user as seen this notification at least once.
+     */
+    public void setSeen() {
+        mSeen = true;
+    }
+
+    /**
+     * Returns whether the user has expanded this notification at least once.
+     */
+    public boolean hasExpanded() {
+        return mExpanded;
+    }
+
+    /**
+     * Records that the user has expanded this notification at least once.
+     */
+    public void setExpanded() {
+        mExpanded = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has replied to a notification that has a
+     * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply} at
+     * least once.
+     */
+    public boolean hasDirectReplied() {
+        return mDirectReplied;
+    }
+
+    /**
+     * Records that the user has replied to a notification that has a
+     * {@link android.app.Notification.Action.Builder#addRemoteInput(RemoteInput) direct reply}
+     * at least once.
+     */
+    public void setDirectReplied() {
+        mDirectReplied = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has snoozed this notification at least once.
+     */
+    public boolean hasSnoozed() {
+        return mSnoozed;
+    }
+
+    /**
+     * Records that the user has snoozed this notification at least once.
+     */
+    public void setSnoozed() {
+        mSnoozed = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has viewed the in-shade settings for this notification at least
+     * once.
+     */
+    public boolean hasViewedSettings() {
+        return mViewedSettings;
+    }
+
+    /**
+     * Records that the user has viewed the in-shade settings for this notification at least once.
+     */
+    public void setViewedSettings() {
+        mViewedSettings = true;
+        mInteracted = true;
+    }
+
+    /**
+     * Returns whether the user has interacted with this notification beyond having viewed it.
+     */
+    public boolean hasInteracted() {
+        return mInteracted;
+    }
+
+    /**
+     * Returns from which surface the notification was dismissed.
+     */
+    public @DismissalSurface int getDismissalSurface() {
+        return mDismissalSurface;
+    }
+
+    /**
+     * Returns from which surface the notification was dismissed.
+     */
+    public void setDismissalSurface(@DismissalSurface int dismissalSurface) {
+        mDismissalSurface = dismissalSurface;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        NotificationStats that = (NotificationStats) o;
+
+        if (mSeen != that.mSeen) return false;
+        if (mExpanded != that.mExpanded) return false;
+        if (mDirectReplied != that.mDirectReplied) return false;
+        if (mSnoozed != that.mSnoozed) return false;
+        if (mViewedSettings != that.mViewedSettings) return false;
+        if (mInteracted != that.mInteracted) return false;
+        return mDismissalSurface == that.mDismissalSurface;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = (mSeen ? 1 : 0);
+        result = 31 * result + (mExpanded ? 1 : 0);
+        result = 31 * result + (mDirectReplied ? 1 : 0);
+        result = 31 * result + (mSnoozed ? 1 : 0);
+        result = 31 * result + (mViewedSettings ? 1 : 0);
+        result = 31 * result + (mInteracted ? 1 : 0);
+        result = 31 * result + mDismissalSurface;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("NotificationStats{");
+        sb.append("mSeen=").append(mSeen);
+        sb.append(", mExpanded=").append(mExpanded);
+        sb.append(", mDirectReplied=").append(mDirectReplied);
+        sb.append(", mSnoozed=").append(mSnoozed);
+        sb.append(", mViewedSettings=").append(mViewedSettings);
+        sb.append(", mInteracted=").append(mInteracted);
+        sb.append(", mDismissalSurface=").append(mDismissalSurface);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 7bec898..735b822 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -76,10 +76,13 @@
     private static final int DAY_MINUTES = 24 * 60;
     private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
 
-    private static final boolean DEFAULT_ALLOW_CALLS = true;
+    // Default allow categories set in readXml() from default_zen_mode_config.xml, fallback values:
+    private static final boolean DEFAULT_ALLOW_ALARMS = true;
+    private static final boolean DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER = true;
+    private static final boolean DEFAULT_ALLOW_CALLS = false;
     private static final boolean DEFAULT_ALLOW_MESSAGES = false;
-    private static final boolean DEFAULT_ALLOW_REMINDERS = true;
-    private static final boolean DEFAULT_ALLOW_EVENTS = true;
+    private static final boolean DEFAULT_ALLOW_REMINDERS = false;
+    private static final boolean DEFAULT_ALLOW_EVENTS = false;
     private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
     private static final boolean DEFAULT_ALLOW_SCREEN_OFF = true;
     private static final boolean DEFAULT_ALLOW_SCREEN_ON = true;
@@ -89,6 +92,8 @@
     private static final String ZEN_ATT_VERSION = "version";
     private static final String ZEN_ATT_USER = "user";
     private static final String ALLOW_TAG = "allow";
+    private static final String ALLOW_ATT_ALARMS = "alarms";
+    private static final String ALLOW_ATT_MEDIA_SYSTEM_OTHER = "media_system_other";
     private static final String ALLOW_ATT_CALLS = "calls";
     private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
     private static final String ALLOW_ATT_MESSAGES = "messages";
@@ -100,8 +105,6 @@
     private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
     private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
 
-    private static final String CONDITION_TAG = "condition";
-    private static final String CONDITION_ATT_COMPONENT = "component";
     private static final String CONDITION_ATT_ID = "id";
     private static final String CONDITION_ATT_SUMMARY = "summary";
     private static final String CONDITION_ATT_LINE1 = "line1";
@@ -123,6 +126,8 @@
     private static final String RULE_ATT_CREATION_TIME = "creationTime";
     private static final String RULE_ATT_ENABLER = "enabler";
 
+    public boolean allowAlarms = DEFAULT_ALLOW_ALARMS;
+    public boolean allowMediaSystemOther = DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER;
     public boolean allowCalls = DEFAULT_ALLOW_CALLS;
     public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
     public boolean allowMessages = DEFAULT_ALLOW_MESSAGES;
@@ -161,6 +166,8 @@
         }
         allowWhenScreenOff = source.readInt() == 1;
         allowWhenScreenOn = source.readInt() == 1;
+        allowAlarms = source.readInt() == 1;
+        allowMediaSystemOther = source.readInt() == 1;
     }
 
     @Override
@@ -190,19 +197,23 @@
         }
         dest.writeInt(allowWhenScreenOff ? 1 : 0);
         dest.writeInt(allowWhenScreenOn ? 1 : 0);
+        dest.writeInt(allowAlarms ? 1 : 0);
+        dest.writeInt(allowMediaSystemOther ? 1 : 0);
     }
 
     @Override
     public String toString() {
         return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
                 .append("user=").append(user)
+                .append(",allowAlarms=").append(allowAlarms)
+                .append(",allowMediaSystemOther=").append(allowMediaSystemOther)
+                .append(",allowReminders=").append(allowReminders)
+                .append(",allowEvents=").append(allowEvents)
                 .append(",allowCalls=").append(allowCalls)
                 .append(",allowRepeatCallers=").append(allowRepeatCallers)
                 .append(",allowMessages=").append(allowMessages)
                 .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom))
                 .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom))
-                .append(",allowReminders=").append(allowReminders)
-                .append(",allowEvents=").append(allowEvents)
                 .append(",allowWhenScreenOff=").append(allowWhenScreenOff)
                 .append(",allowWhenScreenOn=").append(allowWhenScreenOn)
                 .append(",automaticRules=").append(automaticRules)
@@ -218,9 +229,21 @@
         if (user != to.user) {
             d.addLine("user", user, to.user);
         }
+        if (allowAlarms != to.allowAlarms) {
+            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
+        }
+        if (allowMediaSystemOther != to.allowMediaSystemOther) {
+            d.addLine("allowMediaSystemOther", allowMediaSystemOther, to.allowMediaSystemOther);
+        }
         if (allowCalls != to.allowCalls) {
             d.addLine("allowCalls", allowCalls, to.allowCalls);
         }
+        if (allowReminders != to.allowReminders) {
+            d.addLine("allowReminders", allowReminders, to.allowReminders);
+        }
+        if (allowEvents != to.allowEvents) {
+            d.addLine("allowEvents", allowEvents, to.allowEvents);
+        }
         if (allowRepeatCallers != to.allowRepeatCallers) {
             d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
         }
@@ -233,12 +256,6 @@
         if (allowMessagesFrom != to.allowMessagesFrom) {
             d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
         }
-        if (allowReminders != to.allowReminders) {
-            d.addLine("allowReminders", allowReminders, to.allowReminders);
-        }
-        if (allowEvents != to.allowEvents) {
-            d.addLine("allowEvents", allowEvents, to.allowEvents);
-        }
         if (allowWhenScreenOff != to.allowWhenScreenOff) {
             d.addLine("allowWhenScreenOff", allowWhenScreenOff, to.allowWhenScreenOff);
         }
@@ -335,7 +352,9 @@
         if (!(o instanceof ZenModeConfig)) return false;
         if (o == this) return true;
         final ZenModeConfig other = (ZenModeConfig) o;
-        return other.allowCalls == allowCalls
+        return other.allowAlarms == allowAlarms
+                && other.allowMediaSystemOther == allowMediaSystemOther
+                && other.allowCalls == allowCalls
                 && other.allowRepeatCallers == allowRepeatCallers
                 && other.allowMessages == allowMessages
                 && other.allowCallsFrom == allowCallsFrom
@@ -351,10 +370,10 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom,
-                allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff,
-                allowWhenScreenOn,
-                user, automaticRules, manualRule);
+        return Objects.hash(allowAlarms, allowMediaSystemOther, allowCalls,
+                allowRepeatCallers, allowMessages,
+                allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents,
+                allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule);
     }
 
     private static String toDayList(int[] days) {
@@ -413,10 +432,12 @@
             }
             if (type == XmlPullParser.START_TAG) {
                 if (ALLOW_TAG.equals(tag)) {
-                    rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+                    rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS,
+                            DEFAULT_ALLOW_CALLS);
                     rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
                             DEFAULT_ALLOW_REPEAT_CALLERS);
-                    rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+                    rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES,
+                            DEFAULT_ALLOW_MESSAGES);
                     rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
                             DEFAULT_ALLOW_REMINDERS);
                     rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
@@ -438,6 +459,9 @@
                             safeBoolean(parser, ALLOW_ATT_SCREEN_OFF, DEFAULT_ALLOW_SCREEN_OFF);
                     rt.allowWhenScreenOn =
                             safeBoolean(parser, ALLOW_ATT_SCREEN_ON, DEFAULT_ALLOW_SCREEN_ON);
+                    rt.allowAlarms = safeBoolean(parser, ALLOW_ATT_ALARMS, DEFAULT_ALLOW_ALARMS);
+                    rt.allowMediaSystemOther = safeBoolean(parser, ALLOW_ATT_MEDIA_SYSTEM_OTHER,
+                            DEFAULT_ALLOW_MEDIA_SYSTEM_OTHER);
                 } else if (MANUAL_TAG.equals(tag)) {
                     rt.manualRule = readRuleXml(parser);
                 } else if (AUTOMATIC_TAG.equals(tag)) {
@@ -468,6 +492,8 @@
         out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom));
         out.attribute(null, ALLOW_ATT_SCREEN_OFF, Boolean.toString(allowWhenScreenOff));
         out.attribute(null, ALLOW_ATT_SCREEN_ON, Boolean.toString(allowWhenScreenOn));
+        out.attribute(null, ALLOW_ATT_ALARMS, Boolean.toString(allowAlarms));
+        out.attribute(null, ALLOW_ATT_MEDIA_SYSTEM_OTHER, Boolean.toString(allowMediaSystemOther));
         out.endTag(null, ALLOW_TAG);
 
         if (manualRule != null) {
@@ -654,6 +680,12 @@
         if (!allowWhenScreenOn) {
             suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_SCREEN_ON;
         }
+        if (allowAlarms) {
+            priorityCategories |= Policy.PRIORITY_CATEGORY_ALARMS;
+        }
+        if (allowMediaSystemOther) {
+            priorityCategories |= Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER;
+        }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
         return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders,
@@ -680,10 +712,13 @@
 
     public void applyNotificationPolicy(Policy policy) {
         if (policy == null) return;
-        allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
-        allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
+        allowAlarms = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS) != 0;
+        allowMediaSystemOther = (policy.priorityCategories
+                & Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0;
         allowEvents = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_EVENTS) != 0;
         allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0;
+        allowCalls = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_CALLS) != 0;
+        allowMessages = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_MESSAGES) != 0;
         allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS)
                 != 0;
         allowCallsFrom = prioritySendersToSource(policy.priorityCallSenders, allowCallsFrom);
diff --git a/core/java/android/service/settings/suggestions/ISuggestionService.aidl b/core/java/android/service/settings/suggestions/ISuggestionService.aidl
index 423c507..8dfa9c3 100644
--- a/core/java/android/service/settings/suggestions/ISuggestionService.aidl
+++ b/core/java/android/service/settings/suggestions/ISuggestionService.aidl
@@ -17,4 +17,10 @@
      * calls.
      */
     void dismissSuggestion(in Suggestion suggestion) = 2;
+
+    /**
+     * This is the opposite signal to {@link #dismissSuggestion}, indicating a suggestion has been
+     * launched.
+     */
+    void launchSuggestion(in Suggestion suggestion) = 3;
 }
\ No newline at end of file
diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java
index f27cc2e..cfeb7fc 100644
--- a/core/java/android/service/settings/suggestions/Suggestion.java
+++ b/core/java/android/service/settings/suggestions/Suggestion.java
@@ -16,12 +16,17 @@
 
 package android.service.settings.suggestions;
 
+import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Data object that has information about a device suggestion.
  *
@@ -30,9 +35,27 @@
 @SystemApi
 public final class Suggestion implements Parcelable {
 
+    /**
+     * @hide
+     */
+    @IntDef(flag = true, value = {
+            FLAG_HAS_BUTTON,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Flags {
+    }
+
+    /**
+     * Flag for suggestion type with a single button
+     */
+    public static final int FLAG_HAS_BUTTON = 1 << 0;
+
     private final String mId;
     private final CharSequence mTitle;
     private final CharSequence mSummary;
+    private final Icon mIcon;
+    @Flags
+    private final int mFlags;
     private final PendingIntent mPendingIntent;
 
     /**
@@ -57,6 +80,22 @@
     }
 
     /**
+     * Optional icon for this suggestion.
+     */
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Optional flags for this suggestion. This will influence UI when rendering suggestion in
+     * different style.
+     */
+    @Flags
+    public int getFlags() {
+        return mFlags;
+    }
+
+    /**
      * The Intent to launch when the suggestion is activated.
      */
     public PendingIntent getPendingIntent() {
@@ -67,6 +106,8 @@
         mId = builder.mId;
         mTitle = builder.mTitle;
         mSummary = builder.mSummary;
+        mIcon = builder.mIcon;
+        mFlags = builder.mFlags;
         mPendingIntent = builder.mPendingIntent;
     }
 
@@ -74,6 +115,8 @@
         mId = in.readString();
         mTitle = in.readCharSequence();
         mSummary = in.readCharSequence();
+        mIcon = in.readParcelable(Icon.class.getClassLoader());
+        mFlags = in.readInt();
         mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
     }
 
@@ -99,6 +142,8 @@
         dest.writeString(mId);
         dest.writeCharSequence(mTitle);
         dest.writeCharSequence(mSummary);
+        dest.writeParcelable(mIcon, flags);
+        dest.writeInt(mFlags);
         dest.writeParcelable(mPendingIntent, flags);
     }
 
@@ -109,6 +154,9 @@
         private final String mId;
         private CharSequence mTitle;
         private CharSequence mSummary;
+        private Icon mIcon;
+        @Flags
+        private int mFlags;
         private PendingIntent mPendingIntent;
 
         public Builder(String id) {
@@ -135,6 +183,23 @@
         }
 
         /**
+         * Sets icon for the suggestion.
+         */
+        public Builder setIcon(Icon icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Sets a UI type for this suggestion. This will influence UI when rendering suggestion in
+         * different style.
+         */
+        public Builder setFlags(@Flags int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        /**
          * Sets suggestion intent
          */
         public Builder setPendingIntent(PendingIntent pendingIntent) {
diff --git a/core/java/android/service/settings/suggestions/SuggestionService.java b/core/java/android/service/settings/suggestions/SuggestionService.java
index 2a4c84c..ce9501d 100644
--- a/core/java/android/service/settings/suggestions/SuggestionService.java
+++ b/core/java/android/service/settings/suggestions/SuggestionService.java
@@ -48,12 +48,20 @@
             }
 
             @Override
-            public  void dismissSuggestion(Suggestion suggestion) {
+            public void dismissSuggestion(Suggestion suggestion) {
                 if (DEBUG) {
                     Log.d(TAG, "dismissSuggestion() " + getPackageName());
                 }
                 onSuggestionDismissed(suggestion);
             }
+
+            @Override
+            public void launchSuggestion(Suggestion suggestion) {
+                if (DEBUG) {
+                    Log.d(TAG, "launchSuggestion() " + getPackageName());
+                }
+                onSuggestionLaunched(suggestion);
+            }
         };
     }
 
@@ -65,7 +73,12 @@
     /**
      * Dismiss a suggestion. The suggestion will not be included in future
      * {@link #onGetSuggestions()} calls.
-     * @param suggestion
      */
     public abstract void onSuggestionDismissed(Suggestion suggestion);
+
+    /**
+     * This is the opposite signal to {@link #onSuggestionDismissed}, indicating a suggestion has
+     * been launched.
+     */
+    public abstract void onSuggestionLaunched(Suggestion suggestion);
 }
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 625dd9e..cd177c4 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,8 @@
 
 package android.service.voice;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.Dialog;
@@ -46,7 +48,6 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
@@ -63,8 +64,6 @@
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 /**
  * An active voice interaction session, providing a facility for the implementation
  * to interact with the user in the voice interaction layer.  The user interface is
@@ -110,16 +109,6 @@
      */
     public static final int SHOW_SOURCE_ACTIVITY = 1<<4;
 
-    // Keys for Bundle values
-    /** @hide */
-    public static final String KEY_DATA = "data";
-    /** @hide */
-    public static final String KEY_STRUCTURE = "structure";
-    /** @hide */
-    public static final String KEY_CONTENT = "content";
-    /** @hide */
-    public static final String KEY_RECEIVER_EXTRAS = "receiverExtras";
-
     final Context mContext;
     final HandlerCaller mHandlerCaller;
 
@@ -1423,9 +1412,7 @@
     public void setContentView(View view) {
         ensureWindowCreated();
         mContentFrame.removeAllViews();
-        mContentFrame.addView(view, new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT));
+        mContentFrame.addView(view, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         mContentFrame.requestApplyInsets();
     }
 
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index c38fab1..fef9223 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -93,5 +93,13 @@
      * currently, else return the display id of the virtual display
      */
     int getVr2dDisplayId();
+
+    /**
+     * Set the component name of the compositor service to bind.
+     *
+     * @param componentName flattened string representing a ComponentName of a Service in the
+     * application's compositor process to bind to, or null to clear the current binding.
+     */
+    void setAndBindCompositor(in String componentName);
 }
 
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index bbe1523..179d545 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -16,6 +16,11 @@
 
 package android.text;
 
+import android.icu.lang.UCharacter;
+import android.icu.lang.UCharacterDirection;
+import android.icu.lang.UProperty;
+import android.icu.text.Bidi;
+import android.icu.text.BidiClassifier;
 import android.text.Layout.Directions;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -27,26 +32,57 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class AndroidBidi {
 
-    public static int bidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo) {
+    private static class EmojiBidiOverride extends BidiClassifier {
+        EmojiBidiOverride() {
+            super(null /* No persisting object needed */);
+        }
+
+        // Tells ICU to use the standard Unicode value.
+        private static final int NO_OVERRIDE =
+                UCharacter.getIntPropertyMaxValue(UProperty.BIDI_CLASS) + 1;
+
+        @Override
+        public int classify(int c) {
+            if (Emoji.isNewEmoji(c)) {
+                // All new emoji characters in Unicode 10.0 are of the bidi class ON.
+                return UCharacterDirection.OTHER_NEUTRAL;
+            } else {
+                return NO_OVERRIDE;
+            }
+        }
+    }
+
+    private static final EmojiBidiOverride sEmojiBidiOverride = new EmojiBidiOverride();
+
+    /**
+     * Runs the bidi algorithm on input text.
+     */
+    public static int bidi(int dir, char[] chs, byte[] chInfo) {
         if (chs == null || chInfo == null) {
             throw new NullPointerException();
         }
 
-        if (n < 0 || chs.length < n || chInfo.length < n) {
+        final int length = chs.length;
+        if (chInfo.length < length) {
             throw new IndexOutOfBoundsException();
         }
 
-        switch(dir) {
-            case Layout.DIR_REQUEST_LTR: dir = 0; break;
-            case Layout.DIR_REQUEST_RTL: dir = 1; break;
-            case Layout.DIR_REQUEST_DEFAULT_LTR: dir = -2; break;
-            case Layout.DIR_REQUEST_DEFAULT_RTL: dir = -1; break;
-            default: dir = 0; break;
+        final byte paraLevel;
+        switch (dir) {
+            case Layout.DIR_REQUEST_LTR: paraLevel = Bidi.LTR; break;
+            case Layout.DIR_REQUEST_RTL: paraLevel = Bidi.RTL; break;
+            case Layout.DIR_REQUEST_DEFAULT_LTR: paraLevel = Bidi.LEVEL_DEFAULT_LTR; break;
+            case Layout.DIR_REQUEST_DEFAULT_RTL: paraLevel = Bidi.LEVEL_DEFAULT_RTL; break;
+            default: paraLevel = Bidi.LTR; break;
         }
-
-        int result = runBidi(dir, chs, chInfo, n, haveInfo);
-        result = (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
-        return result;
+        final Bidi icuBidi = new Bidi(length /* maxLength */, 0 /* maxRunCount */);
+        icuBidi.setCustomClassifier(sEmojiBidiOverride);
+        icuBidi.setPara(chs, paraLevel, null /* embeddingLevels */);
+        for (int i = 0; i < length; i++) {
+            chInfo[i] = icuBidi.getLevelAt(i);
+        }
+        final byte result = icuBidi.getParaLevel();
+        return (result & 0x1) == 0 ? Layout.DIR_LEFT_TO_RIGHT : Layout.DIR_RIGHT_TO_LEFT;
     }
 
     /**
@@ -178,6 +214,4 @@
         }
         return new Directions(ld);
     }
-
-    private native static int runBidi(int dir, char[] chs, byte[] chInfo, int n, boolean haveInfo);
 }
\ No newline at end of file
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 24260c4..fba358c 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -299,7 +299,7 @@
 
         private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 
-        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
     /**
@@ -440,7 +440,7 @@
             mEllipsizeAt = null;
         }
 
-        mObjects = new PackedObjectVector<Directions>(1);
+        mObjects = new PackedObjectVector<>(1);
 
         // Initial state is a single line with 0 characters (0 to 0), with top at 0 and bottom at
         // whatever is natural, and undefined ellipsis.
@@ -1050,7 +1050,7 @@
 
     private static class ChangeWatcher implements TextWatcher, SpanWatcher {
         public ChangeWatcher(DynamicLayout layout) {
-            mLayout = new WeakReference<DynamicLayout>(layout);
+            mLayout = new WeakReference<>(layout);
         }
 
         private void reflow(CharSequence s, int where, int before, int after) {
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index ea1100e..4f1488e 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -16,254 +16,15 @@
 
 package android.text;
 
-import android.annotation.Nullable;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.HashMap;
-import java.util.Locale;
-
 /**
- * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * Hyphenator just initializes the native implementation of automatic hyphenation,
  * in essence finding valid hyphenation opportunities in a word.
  *
  * @hide
  */
 public class Hyphenator {
-    // This class has deliberately simple lifetime management (no finalizer) because in
-    // the common case a process will use a very small number of locales.
-
-    private static String TAG = "Hyphenator";
-
-    // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but
-    // that appears too small.
-    private static final int INDIC_MIN_PREFIX = 2;
-    private static final int INDIC_MIN_SUFFIX = 2;
-
-    private final static Object sLock = new Object();
-
-    @GuardedBy("sLock")
-    final static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
-
-    // Reasonable enough values for cases where we have no hyphenation patterns but may be able to
-    // do some automatic hyphenation based on characters. These values would be used very rarely.
-    private static final int DEFAULT_MIN_PREFIX = 2;
-    private static final int DEFAULT_MIN_SUFFIX = 2;
-    final static Hyphenator sEmptyHyphenator =
-            new Hyphenator(StaticLayout.nLoadHyphenator(
-                                   null, 0, DEFAULT_MIN_PREFIX, DEFAULT_MIN_SUFFIX),
-                           null);
-
-    final private long mNativePtr;
-
-    // We retain a reference to the buffer to keep the memory mapping valid
-    @SuppressWarnings("unused")
-    final private ByteBuffer mBuffer;
-
-    private Hyphenator(long nativePtr, ByteBuffer b) {
-        mNativePtr = nativePtr;
-        mBuffer = b;
-    }
-
-    public long getNativePtr() {
-        return mNativePtr;
-    }
-
-    public static Hyphenator get(@Nullable Locale locale) {
-        synchronized (sLock) {
-            Hyphenator result = sMap.get(locale);
-            if (result != null) {
-                return result;
-            }
-
-            // If there's a variant, fall back to language+variant only, if available
-            final String variant = locale.getVariant();
-            if (!variant.isEmpty()) {
-                final Locale languageAndVariantOnlyLocale =
-                        new Locale(locale.getLanguage(), "", variant);
-                result = sMap.get(languageAndVariantOnlyLocale);
-                if (result != null) {
-                    sMap.put(locale, result);
-                    return result;
-                }
-            }
-
-            // Fall back to language-only, if available
-            final Locale languageOnlyLocale = new Locale(locale.getLanguage());
-            result = sMap.get(languageOnlyLocale);
-            if (result != null) {
-                sMap.put(locale, result);
-                return result;
-            }
-
-            // Fall back to script-only, if available
-            final String script = locale.getScript();
-            if (!script.equals("")) {
-                final Locale scriptOnlyLocale = new Locale.Builder()
-                        .setLanguage("und")
-                        .setScript(script)
-                        .build();
-                result = sMap.get(scriptOnlyLocale);
-                if (result != null) {
-                    sMap.put(locale, result);
-                    return result;
-                }
-            }
-
-            sMap.put(locale, sEmptyHyphenator);  // To remember we found nothing.
-        }
-        return sEmptyHyphenator;
-    }
-
-    private static class HyphenationData {
-        final String mLanguageTag;
-        final int mMinPrefix, mMinSuffix;
-        HyphenationData(String languageTag, int minPrefix, int minSuffix) {
-            this.mLanguageTag = languageTag;
-            this.mMinPrefix = minPrefix;
-            this.mMinSuffix = minSuffix;
-        }
-    }
-
-    private static Hyphenator loadHyphenator(HyphenationData data) {
-        String patternFilename = "hyph-" + data.mLanguageTag.toLowerCase(Locale.US) + ".hyb";
-        File patternFile = new File(getSystemHyphenatorLocation(), patternFilename);
-        if (!patternFile.canRead()) {
-            Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable");
-            return null;
-        }
-        try {
-            RandomAccessFile f = new RandomAccessFile(patternFile, "r");
-            try {
-                FileChannel fc = f.getChannel();
-                MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
-                long nativePtr = StaticLayout.nLoadHyphenator(
-                        buf, 0, data.mMinPrefix, data.mMinSuffix);
-                return new Hyphenator(nativePtr, buf);
-            } finally {
-                f.close();
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "error loading hyphenation " + patternFile, e);
-            return null;
-        }
-    }
-
-    private static File getSystemHyphenatorLocation() {
-        return new File("/system/usr/hyphen-data");
-    }
-
-    // This array holds pairs of language tags that are used to prefill the map from locale to
-    // hyphenation data: The hyphenation data for the first field will be prefilled from the
-    // hyphenation data for the second field.
-    //
-    // The aliases that are computable by the get() method above are not included.
-    private static final String[][] LOCALE_FALLBACK_DATA = {
-        // English locales that fall back to en-US. The data is
-        // from CLDR. It's all English locales, minus the locales whose
-        // parent is en-001 (from supplementalData.xml, under <parentLocales>).
-        // TODO: Figure out how to get this from ICU.
-        {"en-AS", "en-US"}, // English (American Samoa)
-        {"en-GU", "en-US"}, // English (Guam)
-        {"en-MH", "en-US"}, // English (Marshall Islands)
-        {"en-MP", "en-US"}, // English (Northern Mariana Islands)
-        {"en-PR", "en-US"}, // English (Puerto Rico)
-        {"en-UM", "en-US"}, // English (United States Minor Outlying Islands)
-        {"en-VI", "en-US"}, // English (Virgin Islands)
-
-        // All English locales other than those falling back to en-US are mapped to en-GB.
-        {"en", "en-GB"},
-
-        // For German, we're assuming the 1996 (and later) orthography by default.
-        {"de", "de-1996"},
-        // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
-        {"de-LI-1901", "de-CH-1901"},
-
-        // Norwegian is very probably Norwegian Bokmål.
-        {"no", "nb"},
-
-        // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
-        {"mn", "mn-Cyrl"}, // Mongolian
-
-        // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
-        // Data is from CLDR's likelySubtags.xml.
-        // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
-        {"am", "und-Ethi"}, // Amharic
-        {"byn", "und-Ethi"}, // Blin
-        {"gez", "und-Ethi"}, // Geʻez
-        {"ti", "und-Ethi"}, // Tigrinya
-        {"wal", "und-Ethi"}, // Wolaytta
-    };
-
-    private static final HyphenationData[] AVAILABLE_LANGUAGES = {
-        new HyphenationData("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Assamese
-        new HyphenationData("bg", 2, 2), // Bulgarian
-        new HyphenationData("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Bengali
-        new HyphenationData("cu", 1, 2), // Church Slavonic
-        new HyphenationData("cy", 2, 3), // Welsh
-        new HyphenationData("da", 2, 2), // Danish
-        new HyphenationData("de-1901", 2, 2), // German 1901 orthography
-        new HyphenationData("de-1996", 2, 2), // German 1996 orthography
-        new HyphenationData("de-CH-1901", 2, 2), // Swiss High German 1901 orthography
-        new HyphenationData("en-GB", 2, 3), // British English
-        new HyphenationData("en-US", 2, 3), // American English
-        new HyphenationData("es", 2, 2), // Spanish
-        new HyphenationData("et", 2, 3), // Estonian
-        new HyphenationData("eu", 2, 2), // Basque
-        new HyphenationData("fr", 2, 3), // French
-        new HyphenationData("ga", 2, 3), // Irish
-        new HyphenationData("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Gujarati
-        new HyphenationData("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Hindi
-        new HyphenationData("hr", 2, 2), // Croatian
-        new HyphenationData("hu", 2, 2), // Hungarian
-        // texhyphen sources say Armenian may be (1, 2), but that it needs confirmation.
-        // Going with a more conservative value of (2, 2) for now.
-        new HyphenationData("hy", 2, 2), // Armenian
-        new HyphenationData("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Kannada
-        new HyphenationData("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Malayalam
-        new HyphenationData("mn-Cyrl", 2, 2), // Mongolian in Cyrillic script
-        new HyphenationData("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Marathi
-        new HyphenationData("nb", 2, 2), // Norwegian Bokmål
-        new HyphenationData("nn", 2, 2), // Norwegian Nynorsk
-        new HyphenationData("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Oriya
-        new HyphenationData("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Punjabi
-        new HyphenationData("pt", 2, 3), // Portuguese
-        new HyphenationData("sl", 2, 2), // Slovenian
-        new HyphenationData("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Tamil
-        new HyphenationData("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX), // Telugu
-        new HyphenationData("tk", 2, 2), // Turkmen
-        new HyphenationData("und-Ethi", 1, 1), // Any language in Ethiopic script
-    };
-
-    /**
-     * Load hyphenation patterns at initialization time. We want to have patterns
-     * for all locales loaded and ready to use so we don't have to do any file IO
-     * on the UI thread when drawing text in different locales.
-     *
-     * @hide
-     */
     public static void init() {
-        sMap.put(null, null);
-
-        for (int i = 0; i < AVAILABLE_LANGUAGES.length; i++) {
-            HyphenationData data = AVAILABLE_LANGUAGES[i];
-            Hyphenator h = loadHyphenator(data);
-            if (h != null) {
-                sMap.put(Locale.forLanguageTag(data.mLanguageTag), h);
-            }
-        }
-
-        for (int i = 0; i < LOCALE_FALLBACK_DATA.length; i++) {
-            String language = LOCALE_FALLBACK_DATA[i][0];
-            String fallback = LOCALE_FALLBACK_DATA[i][1];
-            sMap.put(Locale.forLanguageTag(language), sMap.get(Locale.forLanguageTag(fallback)));
-        }
+        nInit();
     }
+    private static native void nInit();
 }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 25f791b..4d2a962 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -319,8 +319,6 @@
 
     private float getJustifyWidth(int lineNum) {
         Alignment paraAlign = mAlignment;
-        TabStops tabStops = null;
-        boolean tabStopsIsInitialized = false;
 
         int left = 0;
         int right = mWidth;
@@ -371,10 +369,6 @@
             }
         }
 
-        if (getLineContainsTab(lineNum)) {
-            tabStops = new TabStops(TAB_INCREMENT, spans);
-        }
-
         final Alignment align;
         if (paraAlign == Alignment.ALIGN_LEFT) {
             align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
@@ -1423,7 +1417,6 @@
         float dist = Math.abs(getHorizontal(max, primary) - horiz);
 
         if (dist <= bestdist) {
-            bestdist = dist;
             best = max;
         }
 
@@ -1570,7 +1563,7 @@
         // XXX: we don't care about tabs
         tl.set(mPaint, mText, lineStart, lineEnd, lineDir, directions, false, null);
         caret = lineStart + tl.getOffsetToLeftRightOf(caret - lineStart, toLeft);
-        tl = TextLine.recycle(tl);
+        TextLine.recycle(tl);
         return caret;
     }
 
@@ -1894,10 +1887,7 @@
 
         int margin = 0;
 
-        boolean isFirstParaLine = lineStart == 0 ||
-            spanned.charAt(lineStart - 1) == '\n';
-
-        boolean useFirstLineMargin = isFirstParaLine;
+        boolean useFirstLineMargin = lineStart == 0 || spanned.charAt(lineStart - 1) == '\n';
         for (int i = 0; i < spans.length; i++) {
             if (spans[i] instanceof LeadingMarginSpan2) {
                 int spStart = spanned.getSpanStart(spans[i]);
@@ -1915,13 +1905,12 @@
         return margin;
     }
 
-    /* package */
-    static float measurePara(TextPaint paint, CharSequence text, int start, int end,
+    private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
             TextDirectionHeuristic textDir) {
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
         try {
-            mt.setPara(text, start, end, textDir, null);
+            mt.setPara(text, start, end, textDir);
             Directions directions;
             int dir;
             if (mt.mEasy) {
@@ -2146,18 +2135,14 @@
      * text within the layout of a line.
      */
     public static class Directions {
-        // Directions represents directional runs within a line of text.
-        // Runs are pairs of ints listed in visual order, starting from the
-        // leading margin.  The first int of each pair is the offset from
-        // the first character of the line to the start of the run.  The
-        // second int represents both the length and level of the run.
-        // The length is in the lower bits, accessed by masking with
-        // DIR_LENGTH_MASK.  The level is in the higher bits, accessed
-        // by shifting by DIR_LEVEL_SHIFT and masking by DIR_LEVEL_MASK.
-        // To simply test for an RTL direction, test the bit using
-        // DIR_RTL_FLAG, if set then the direction is rtl.
-
         /**
+         * Directions represents directional runs within a line of text. Runs are pairs of ints
+         * listed in visual order, starting from the leading margin.  The first int of each pair is
+         * the offset from the first character of the line to the start of the run.  The second int
+         * represents both the length and level of the run. The length is in the lower bits,
+         * accessed by masking with RUN_LENGTH_MASK.  The level is in the higher bits, accessed by
+         * shifting by RUN_LEVEL_SHIFT and masking by RUN_LEVEL_MASK. To simply test for an RTL
+         * direction, test the bit using RUN_RTL_FLAG, if set then the direction is rtl.
          * @hide
          */
         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index ce3e282..3d9fba7 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -39,7 +39,6 @@
 
     private int mPos;
     private TextPaint mWorkPaint;
-    private StaticLayout.Builder mBuilder;
 
     private MeasuredText() {
         mWorkPaint = new TextPaint();
@@ -82,7 +81,6 @@
 
     void finish() {
         mText = null;
-        mBuilder = null;
         if (mLen > 1000) {
             mWidths = null;
             mChars = null;
@@ -90,16 +88,10 @@
         }
     }
 
-    void setPos(int pos) {
-        mPos = pos - mTextStart;
-    }
-
     /**
      * Analyzes text for bidirectional runs.  Allocates working buffers.
      */
-    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir,
-            StaticLayout.Builder builder) {
-        mBuilder = builder;
+    void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
         mText = text;
         mTextStart = start;
 
@@ -110,8 +102,8 @@
         if (mWidths == null || mWidths.length < len) {
             mWidths = ArrayUtils.newUnpaddedFloatArray(len);
         }
-        if (mChars == null || mChars.length < len) {
-            mChars = ArrayUtils.newUnpaddedCharArray(len);
+        if (mChars == null || mChars.length != len) {
+            mChars = new char[len];
         }
         TextUtils.getChars(text, start, end, mChars, 0);
 
@@ -155,52 +147,48 @@
                 boolean isRtl = textDir.isRtl(mChars, 0, len);
                 bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
             }
-            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false);
+            mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels);
             mEasy = false;
         }
     }
 
-    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+    /**
+     * Apply the style.
+     *
+     * If nativeStaticLayoutPtr is 0, this method measures the styled text width.
+     * If nativeStaticLayoutPtr is not 0, this method just passes the style information to native
+     * code by calling StaticLayout.addstyleRun() and returns 0.
+     */
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm,
+            long nativeStaticLayoutPtr) {
         if (fm != null) {
             paint.getFontMetricsInt(fm);
         }
 
-        int p = mPos;
+        final int p = mPos;
         mPos = p + len;
 
-        // try to do widths measurement in native code, but use Java if paint has been subclassed
-        // FIXME: may want to eliminate special case for subclass
-        float[] widths = null;
-        if (mBuilder == null || paint.getClass() != TextPaint.class) {
-            widths = mWidths;
-        }
         if (mEasy) {
-            boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
-            float width = 0;
-            if (widths != null) {
-                width = paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, widths, p);
-                if (mBuilder != null) {
-                    mBuilder.addMeasuredRun(p, p + len, widths);
-                }
+            final boolean isRtl = mDir != Layout.DIR_LEFT_TO_RIGHT;
+            if (nativeStaticLayoutPtr == 0) {
+                return paint.getTextRunAdvances(mChars, p, len, p, len, isRtl, mWidths, p);
             } else {
-                width = mBuilder.addStyleRun(paint, p, p + len, isRtl);
+                StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, p, p + len, isRtl);
+                return 0.0f;  // Builder.addStyleRun doesn't return the width.
             }
-            return width;
         }
 
         float totalAdvance = 0;
         int level = mLevels[p];
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
-                boolean isRtl = (level & 0x1) != 0;
-                if (widths != null) {
+                final boolean isRtl = (level & 0x1) != 0;
+                if (nativeStaticLayoutPtr == 0) {
                     totalAdvance +=
-                            paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, widths, q);
-                    if (mBuilder != null) {
-                        mBuilder.addMeasuredRun(q, i, widths);
-                    }
+                            paint.getTextRunAdvances(mChars, q, i - q, q, i - q, isRtl, mWidths, q);
                 } else {
-                    totalAdvance += mBuilder.addStyleRun(paint, q, i, isRtl);
+                    // Builder.addStyleRun doesn't return the width.
+                    StaticLayout.addStyleRun(nativeStaticLayoutPtr, paint, q, i, isRtl);
                 }
                 if (i == e) {
                     break;
@@ -209,11 +197,15 @@
                 level = mLevels[i];
             }
         }
-        return totalAdvance;
+        return totalAdvance;  // If nativeStaticLayoutPtr is 0, the result is zero.
+    }
+
+    float addStyleRun(TextPaint paint, int len, Paint.FontMetricsInt fm) {
+        return addStyleRun(paint, len, fm, 0 /* native ptr */);
     }
 
     float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
-            Paint.FontMetricsInt fm) {
+            Paint.FontMetricsInt fm, long nativeStaticLayoutPtr) {
 
         TextPaint workPaint = mWorkPaint;
         workPaint.set(paint);
@@ -232,18 +224,18 @@
 
         float wid;
         if (replacement == null) {
-            wid = addStyleRun(workPaint, len, fm);
+            wid = addStyleRun(workPaint, len, fm, nativeStaticLayoutPtr);
         } else {
             // Use original text.  Shouldn't matter.
             wid = replacement.getSize(workPaint, mText, mTextStart + mPos,
                     mTextStart + mPos + len, fm);
-            if (mBuilder == null) {
+            if (nativeStaticLayoutPtr == 0) {
                 float[] w = mWidths;
                 w[mPos] = wid;
                 for (int i = mPos + 1, e = mPos + len; i < e; i++)
                     w[i] = 0;
             } else {
-                mBuilder.addReplacementRun(mPos, mPos + len, wid);
+                StaticLayout.addReplacementRun(nativeStaticLayoutPtr, paint, mPos, mPos + len, wid);
             }
             mPos += len;
         }
@@ -261,6 +253,11 @@
         return wid;
     }
 
+    float addStyleRun(TextPaint paint, MetricAffectingSpan[] spans, int len,
+            Paint.FontMetricsInt fm) {
+        return addStyleRun(paint, spans, len, fm, 0 /* native ptr */);
+    }
+
     int breakText(int limit, boolean forwards, float width) {
         float[] w = mWidths;
         if (forwards) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index c124c7f..c0fc44f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Paint;
-import android.os.LocaleList;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.text.style.LineHeightSpan;
@@ -33,9 +32,10 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
-import java.nio.ByteBuffer;
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
 import java.util.Arrays;
-import java.util.Locale;
 
 /**
  * StaticLayout is a Layout for text that will not be edited after it
@@ -60,9 +60,7 @@
      * default values.
      */
     public final static class Builder {
-        private Builder() {
-            mNativePtr = nNewBuilder();
-        }
+        private Builder() {}
 
         /**
          * Obtain a builder for constructing StaticLayout objects.
@@ -117,17 +115,19 @@
             b.mMeasuredText = null;
             b.mLeftIndents = null;
             b.mRightIndents = null;
-            nFinishBuilder(b.mNativePtr);
+            b.mLeftPaddings = null;
+            b.mRightPaddings = null;
             sPool.release(b);
         }
 
         // release any expensive state
         /* package */ void finish() {
-            nFinishBuilder(mNativePtr);
             mText = null;
             mPaint = null;
             mLeftIndents = null;
             mRightIndents = null;
+            mLeftPaddings = null;
+            mRightPaddings = null;
             mMeasuredText.finish();
         }
 
@@ -356,6 +356,28 @@
         }
 
         /**
+         * Set available paddings to draw overhanging text on. Arguments are arrays holding the
+         * amount of padding available, one per line, measured in pixels. For lines past the last
+         * element in the array, the last element repeats.
+         *
+         * The individual padding amounts should be non-negative. The result of passing negative
+         * paddings is undefined.
+         *
+         * @param leftPaddings array of amounts of available padding for left margin, in pixels
+         * @param rightPaddings array of amounts of available padding for right margin, in pixels
+         * @return this builder, useful for chaining
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setAvailablePaddings(@Nullable int[] leftPaddings,
+                @Nullable int[] rightPaddings) {
+            mLeftPaddings = leftPaddings;
+            mRightPaddings = rightPaddings;
+            return this;
+        }
+
+        /**
          * Set paragraph justification mode. The default value is
          * {@link Layout#JUSTIFICATION_MODE_NONE}. If the last line is too short for justification,
          * the last line will be displayed with the alignment set by {@link #setAlignment}.
@@ -381,58 +403,6 @@
             return this;
         }
 
-        @NonNull
-        private long[] getHyphenators(@NonNull LocaleList locales) {
-            final int length = locales.size();
-            final long[] result = new long[length];
-            for (int i = 0; i < length; i++) {
-                final Locale locale = locales.get(i);
-                result[i] = Hyphenator.get(locale).getNativePtr();
-            }
-            return result;
-        }
-
-        /**
-         * Measurement and break iteration is done in native code. The protocol for using
-         * the native code is as follows.
-         *
-         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
-         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
-         * future).
-         *
-         * Then, for each run within the paragraph:
-         *  - setLocales (this must be done at least for the first run, optional afterwards)
-         *  - one of the following, depending on the type of run:
-         *    + addStyleRun (a text run, to be measured in native code)
-         *    + addMeasuredRun (a run already measured in Java, passed into native code)
-         *    + addReplacementRun (a replacement run, width is given)
-         *
-         * After measurement, nGetWidths() is valid if the widths are needed (eg for ellipsis).
-         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
-         *
-         * After all paragraphs, call finish() to release expensive buffers.
-         */
-
-        private void setLocales(LocaleList locales) {
-            if (!locales.equals(mLocales)) {
-                nSetLocales(mNativePtr, locales.toLanguageTags(), getHyphenators(locales));
-                mLocales = locales;
-            }
-        }
-
-        /* package */ float addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            setLocales(paint.getTextLocales());
-            return nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
-        }
-
-        /* package */ void addMeasuredRun(int start, int end, float[] widths) {
-            nAddMeasuredRun(mNativePtr, start, end, widths);
-        }
-
-        /* package */ void addReplacementRun(int start, int end, float width) {
-            nAddReplacementRun(mNativePtr, start, end, width);
-        }
-
         /**
          * Build the {@link StaticLayout} after options have been set.
          *
@@ -449,17 +419,6 @@
             return result;
         }
 
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                nFreeBuilder(mNativePtr);
-            } finally {
-                super.finalize();
-            }
-        }
-
-        /* package */ long mNativePtr;
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
@@ -478,6 +437,8 @@
         private int mHyphenationFrequency;
         @Nullable private int[] mLeftIndents;
         @Nullable private int[] mRightIndents;
+        @Nullable private int[] mLeftPaddings;
+        @Nullable private int[] mRightPaddings;
         private int mJustificationMode;
         private boolean mAddLastLineLineSpacing;
 
@@ -486,9 +447,7 @@
         // This will go away and be subsumed by native builder code
         private MeasuredText mMeasuredText;
 
-        private LocaleList mLocales;
-
-        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
+        private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
     public StaticLayout(CharSequence source, TextPaint paint,
@@ -616,7 +575,7 @@
                 : (b.mText instanceof Spanned)
                     ? new SpannedEllipsizer(b.mText)
                     : new Ellipsizer(b.mText),
-                b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+                b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd);
 
         if (b.mEllipsize != null) {
             Ellipsizer e = (Ellipsizer) getText();
@@ -638,6 +597,8 @@
 
         mLeftIndents = b.mLeftIndents;
         mRightIndents = b.mRightIndents;
+        mLeftPaddings = b.mLeftPaddings;
+        mRightPaddings = b.mRightPaddings;
         setJustificationMode(b.mJustificationMode);
 
         generate(b, b.mIncludePad, b.mIncludePad);
@@ -662,7 +623,6 @@
         // store fontMetrics per span range
         // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
         int[] fmCache = new int[4 * 4];
-        b.setLocales(paint.getTextLocales());
 
         mLineCount = 0;
         mEllipsized = false;
@@ -696,268 +656,294 @@
             indents = null;
         }
 
-        int paraEnd;
-        for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
-            if (paraEnd < 0)
-                paraEnd = bufEnd;
-            else
-                paraEnd++;
+        final long nativePtr = nInit(
+                b.mBreakStrategy, b.mHyphenationFrequency,
+                // TODO: Support more justification mode, e.g. letter spacing, stretching.
+                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+                indents, mLeftPaddings, mRightPaddings);
 
-            int firstWidthLineCount = 1;
-            int firstWidth = outerWidth;
-            int restWidth = outerWidth;
+        try {
+            int paraEnd;
+            for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+                paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+                if (paraEnd < 0) {
+                    paraEnd = bufEnd;
+                } else {
+                    paraEnd++;
+                }
 
-            LineHeightSpan[] chooseHt = null;
+                int firstWidthLineCount = 1;
+                int firstWidth = outerWidth;
+                int restWidth = outerWidth;
 
-            if (spanned != null) {
-                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
-                        LeadingMarginSpan.class);
-                for (int i = 0; i < sp.length; i++) {
-                    LeadingMarginSpan lms = sp[i];
-                    firstWidth -= sp[i].getLeadingMargin(true);
-                    restWidth -= sp[i].getLeadingMargin(false);
+                LineHeightSpan[] chooseHt = null;
 
-                    // LeadingMarginSpan2 is odd.  The count affects all
-                    // leading margin spans, not just this particular one
-                    if (lms instanceof LeadingMarginSpan2) {
-                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
-                        firstWidthLineCount = Math.max(firstWidthLineCount,
-                                lms2.getLeadingMarginLineCount());
+                if (spanned != null) {
+                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+                            LeadingMarginSpan.class);
+                    for (int i = 0; i < sp.length; i++) {
+                        LeadingMarginSpan lms = sp[i];
+                        firstWidth -= sp[i].getLeadingMargin(true);
+                        restWidth -= sp[i].getLeadingMargin(false);
+
+                        // LeadingMarginSpan2 is odd.  The count affects all
+                        // leading margin spans, not just this particular one
+                        if (lms instanceof LeadingMarginSpan2) {
+                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                            firstWidthLineCount = Math.max(firstWidthLineCount,
+                                    lms2.getLeadingMarginLineCount());
+                        }
+                    }
+
+                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+                    if (chooseHt.length == 0) {
+                        chooseHt = null; // So that out() would not assume it has any contents
+                    } else {
+                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                        }
+
+                        for (int i = 0; i < chooseHt.length; i++) {
+                            int o = spanned.getSpanStart(chooseHt[i]);
+
+                            if (o < paraStart) {
+                                // starts in this layout, before the
+                                // current paragraph
+
+                                chooseHtv[i] = getLineTop(getLineForOffset(o));
+                            } else {
+                                // starts in this paragraph
+
+                                chooseHtv[i] = v;
+                            }
+                        }
                     }
                 }
 
-                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+                measured.setPara(source, paraStart, paraEnd, textDir);
+                char[] chs = measured.mChars;
+                float[] widths = measured.mWidths;
+                byte[] chdirs = measured.mLevels;
+                int dir = measured.mDir;
+                boolean easy = measured.mEasy;
 
-                if (chooseHt.length == 0) {
-                    chooseHt = null; // So that out() would not assume it has any contents
-                } else {
-                    if (chooseHtv == null ||
-                        chooseHtv.length < chooseHt.length) {
-                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                // tab stop locations
+                int[] variableTabStops = null;
+                if (spanned != null) {
+                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+                            paraEnd, TabStopSpan.class);
+                    if (spans.length > 0) {
+                        int[] stops = new int[spans.length];
+                        for (int i = 0; i < spans.length; i++) {
+                            stops[i] = spans[i].getTabStop();
+                        }
+                        Arrays.sort(stops, 0, stops.length);
+                        variableTabStops = stops;
+                    }
+                }
+
+                // measurement has to be done before performing line breaking
+                // but we don't want to recompute fontmetrics or span ranges the
+                // second time, so we cache those and then use those stored values
+                int fmCacheCount = 0;
+                int spanEndCacheCount = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    if (fmCacheCount * 4 >= fmCache.length) {
+                        int[] grow = new int[fmCacheCount * 4 * 2];
+                        System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+                        fmCache = grow;
                     }
 
-                    for (int i = 0; i < chooseHt.length; i++) {
-                        int o = spanned.getSpanStart(chooseHt[i]);
+                    if (spanEndCacheCount >= spanEndCache.length) {
+                        int[] grow = new int[spanEndCacheCount * 2];
+                        System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+                        spanEndCache = grow;
+                    }
 
-                        if (o < paraStart) {
-                            // starts in this layout, before the
-                            // current paragraph
+                    if (spanned == null) {
+                        spanEnd = paraEnd;
+                        int spanLen = spanEnd - spanStart;
+                        measured.addStyleRun(paint, spanLen, fm, nativePtr);
+                    } else {
+                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+                                MetricAffectingSpan.class);
+                        int spanLen = spanEnd - spanStart;
+                        MetricAffectingSpan[] spans =
+                                spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+                        spans = TextUtils.removeEmptySpans(spans, spanned,
+                                MetricAffectingSpan.class);
+                        measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+                    }
 
-                            chooseHtv[i] = getLineTop(getLineForOffset(o));
+                    // the order of storage here (top, bottom, ascent, descent) has to match the
+                    // code below where these values are retrieved
+                    fmCache[fmCacheCount * 4 + 0] = fm.top;
+                    fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+                    fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+                    fmCache[fmCacheCount * 4 + 3] = fm.descent;
+                    fmCacheCount++;
+
+                    spanEndCache[spanEndCacheCount] = spanEnd;
+                    spanEndCacheCount++;
+                }
+
+                int breakCount = nComputeLineBreaks(
+                        nativePtr,
+
+                        // Inputs
+                        chs,
+                        paraEnd - paraStart,
+                        firstWidth,
+                        firstWidthLineCount,
+                        restWidth,
+                        variableTabStops,
+                        TAB_INCREMENT,
+                        mLineCount,
+
+                        // Outputs
+                        lineBreaks,
+                        lineBreaks.breaks.length,
+                        lineBreaks.breaks,
+                        lineBreaks.widths,
+                        lineBreaks.ascents,
+                        lineBreaks.descents,
+                        lineBreaks.flags,
+                        widths);
+
+                final int[] breaks = lineBreaks.breaks;
+                final float[] lineWidths = lineBreaks.widths;
+                final float[] ascents = lineBreaks.ascents;
+                final float[] descents = lineBreaks.descents;
+                final int[] flags = lineBreaks.flags;
+
+                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+                final boolean ellipsisMayBeApplied = ellipsize != null
+                        && (ellipsize == TextUtils.TruncateAt.END
+                            || (mMaximumVisibleLineCount == 1
+                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
+                if (0 < remainingLineCount && remainingLineCount < breakCount
+                        && ellipsisMayBeApplied) {
+                    // Calculate width and flag.
+                    float width = 0;
+                    int flag = 0; // XXX May need to also have starting hyphen edit
+                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
+                        if (i == breakCount - 1) {
+                            width += lineWidths[i];
                         } else {
-                            // starts in this paragraph
-
-                            chooseHtv[i] = v;
+                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+                                width += widths[j];
+                            }
                         }
+                        flag |= flags[i] & TAB_MASK;
                     }
-                }
-            }
+                    // Treat the last line and overflowed lines as a single line.
+                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+                    lineWidths[remainingLineCount - 1] = width;
+                    flags[remainingLineCount - 1] = flag;
 
-            measured.setPara(source, paraStart, paraEnd, textDir, b);
-            char[] chs = measured.mChars;
-            float[] widths = measured.mWidths;
-            byte[] chdirs = measured.mLevels;
-            int dir = measured.mDir;
-            boolean easy = measured.mEasy;
-
-            // tab stop locations
-            int[] variableTabStops = null;
-            if (spanned != null) {
-                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
-                        paraEnd, TabStopSpan.class);
-                if (spans.length > 0) {
-                    int[] stops = new int[spans.length];
-                    for (int i = 0; i < spans.length; i++) {
-                        stops[i] = spans[i].getTabStop();
-                    }
-                    Arrays.sort(stops, 0, stops.length);
-                    variableTabStops = stops;
-                }
-            }
-
-            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
-                    firstWidth, firstWidthLineCount, restWidth,
-                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
-                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
-                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLineCount);
-
-            // measurement has to be done before performing line breaking
-            // but we don't want to recompute fontmetrics or span ranges the
-            // second time, so we cache those and then use those stored values
-            int fmCacheCount = 0;
-            int spanEndCacheCount = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                if (fmCacheCount * 4 >= fmCache.length) {
-                    int[] grow = new int[fmCacheCount * 4 * 2];
-                    System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
-                    fmCache = grow;
+                    breakCount = remainingLineCount;
                 }
 
-                if (spanEndCacheCount >= spanEndCache.length) {
-                    int[] grow = new int[spanEndCacheCount * 2];
-                    System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
-                    spanEndCache = grow;
-                }
+                // here is the offset of the starting character of the line we are currently
+                // measuring
+                int here = paraStart;
 
-                if (spanned == null) {
-                    spanEnd = paraEnd;
-                    int spanLen = spanEnd - spanStart;
-                    measured.addStyleRun(paint, spanLen, fm);
-                } else {
-                    spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
-                            MetricAffectingSpan.class);
-                    int spanLen = spanEnd - spanStart;
-                    MetricAffectingSpan[] spans =
-                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
-                    measured.addStyleRun(paint, spans, spanLen, fm);
-                }
+                int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+                int fmCacheIndex = 0;
+                int spanEndCacheIndex = 0;
+                int breakIndex = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    // retrieve end of span
+                    spanEnd = spanEndCache[spanEndCacheIndex++];
 
-                // the order of storage here (top, bottom, ascent, descent) has to match the code below
-                // where these values are retrieved
-                fmCache[fmCacheCount * 4 + 0] = fm.top;
-                fmCache[fmCacheCount * 4 + 1] = fm.bottom;
-                fmCache[fmCacheCount * 4 + 2] = fm.ascent;
-                fmCache[fmCacheCount * 4 + 3] = fm.descent;
-                fmCacheCount++;
+                    // retrieve cached metrics, order matches above
+                    fm.top = fmCache[fmCacheIndex * 4 + 0];
+                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
+                    fmCacheIndex++;
 
-                spanEndCache[spanEndCacheCount] = spanEnd;
-                spanEndCacheCount++;
-            }
-
-            nGetWidths(b.mNativePtr, widths);
-            int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
-                    lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
-                    lineBreaks.breaks.length);
-
-            final int[] breaks = lineBreaks.breaks;
-            final float[] lineWidths = lineBreaks.widths;
-            final float[] ascents = lineBreaks.ascents;
-            final float[] descents = lineBreaks.descents;
-            final int[] flags = lineBreaks.flags;
-
-            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
-            final boolean ellipsisMayBeApplied = ellipsize != null
-                    && (ellipsize == TextUtils.TruncateAt.END
-                        || (mMaximumVisibleLineCount == 1
-                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
-            if (0 < remainingLineCount && remainingLineCount < breakCount
-                    && ellipsisMayBeApplied) {
-                // Calculate width and flag.
-                float width = 0;
-                int flag = 0; // XXX May need to also have starting hyphen edit
-                for (int i = remainingLineCount - 1; i < breakCount; i++) {
-                    if (i == breakCount - 1) {
-                        width += lineWidths[i];
-                    } else {
-                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
-                            width += widths[j];
-                        }
-                    }
-                    flag |= flags[i] & TAB_MASK;
-                }
-                // Treat the last line and overflowed lines as a single line.
-                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
-                lineWidths[remainingLineCount - 1] = width;
-                flags[remainingLineCount - 1] = flag;
-
-                breakCount = remainingLineCount;
-            }
-
-            // here is the offset of the starting character of the line we are currently measuring
-            int here = paraStart;
-
-            int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
-            int fmCacheIndex = 0;
-            int spanEndCacheIndex = 0;
-            int breakIndex = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                // retrieve end of span
-                spanEnd = spanEndCache[spanEndCacheIndex++];
-
-                // retrieve cached metrics, order matches above
-                fm.top = fmCache[fmCacheIndex * 4 + 0];
-                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
-                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
-                fm.descent = fmCache[fmCacheIndex * 4 + 3];
-                fmCacheIndex++;
-
-                if (fm.top < fmTop) {
-                    fmTop = fm.top;
-                }
-                if (fm.ascent < fmAscent) {
-                    fmAscent = fm.ascent;
-                }
-                if (fm.descent > fmDescent) {
-                    fmDescent = fm.descent;
-                }
-                if (fm.bottom > fmBottom) {
-                    fmBottom = fm.bottom;
-                }
-
-                // skip breaks ending before current span range
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
-                    breakIndex++;
-                }
-
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
-                    int endPos = paraStart + breaks[breakIndex];
-
-                    boolean moreChars = (endPos < bufEnd);
-
-                    final int ascent = fallbackLineSpacing
-                            ? Math.min(fmAscent, (int) Math.round(ascents[breakIndex]))
-                            : fmAscent;
-                    final int descent = fallbackLineSpacing
-                            ? Math.max(fmDescent, (int) Math.round(descents[breakIndex]))
-                            : fmDescent;
-                    v = out(source, here, endPos,
-                            ascent, descent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
-                            needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
-                            addLastLineSpacing, chs, widths, paraStart, ellipsize,
-                            ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
-                    if (endPos < spanEnd) {
-                        // preserve metrics for current span
+                    if (fm.top < fmTop) {
                         fmTop = fm.top;
-                        fmBottom = fm.bottom;
+                    }
+                    if (fm.ascent < fmAscent) {
                         fmAscent = fm.ascent;
+                    }
+                    if (fm.descent > fmDescent) {
                         fmDescent = fm.descent;
-                    } else {
-                        fmTop = fmBottom = fmAscent = fmDescent = 0;
+                    }
+                    if (fm.bottom > fmBottom) {
+                        fmBottom = fm.bottom;
                     }
 
-                    here = endPos;
-                    breakIndex++;
-
-                    if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
-                        return;
+                    // skip breaks ending before current span range
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+                        breakIndex++;
                     }
+
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+                        int endPos = paraStart + breaks[breakIndex];
+
+                        boolean moreChars = (endPos < bufEnd);
+
+                        final int ascent = fallbackLineSpacing
+                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+                                : fmAscent;
+                        final int descent = fallbackLineSpacing
+                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+                                : fmDescent;
+                        v = out(source, here, endPos,
+                                ascent, descent, fmTop, fmBottom,
+                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+                                flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+                                includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+                                ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+                                moreChars);
+
+                        if (endPos < spanEnd) {
+                            // preserve metrics for current span
+                            fmTop = fm.top;
+                            fmBottom = fm.bottom;
+                            fmAscent = fm.ascent;
+                            fmDescent = fm.descent;
+                        } else {
+                            fmTop = fmBottom = fmAscent = fmDescent = 0;
+                        }
+
+                        here = endPos;
+                        breakIndex++;
+
+                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+                            return;
+                        }
+                    }
+                }
+
+                if (paraEnd == bufEnd) {
+                    break;
                 }
             }
 
-            if (paraEnd == bufEnd)
-                break;
-        }
+            if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+                    && mLineCount < mMaximumVisibleLineCount) {
+                measured.setPara(source, bufEnd, bufEnd, textDir);
 
-        if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
-                mLineCount < mMaximumVisibleLineCount) {
-            measured.setPara(source, bufEnd, bufEnd, textDir, b);
+                paint.getFontMetricsInt(fm);
 
-            paint.getFontMetricsInt(fm);
-
-            v = out(source,
-                    bufEnd, bufEnd, fm.ascent, fm.descent,
-                    fm.top, fm.bottom,
-                    v,
-                    spacingmult, spacingadd, null,
-                    null, fm, 0,
-                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
-                    includepad, trackpad, addLastLineSpacing, null,
-                    null, bufStart, ellipsize,
-                    ellipsizedWidth, 0, paint, false);
+                v = out(source,
+                        bufEnd, bufEnd, fm.ascent, fm.descent,
+                        fm.top, fm.bottom,
+                        v,
+                        spacingmult, spacingadd, null,
+                        null, fm, 0,
+                        needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+                        includepad, trackpad, addLastLineSpacing, null,
+                        null, bufStart, ellipsize,
+                        ellipsizedWidth, 0, paint, false);
+            }
+        } finally {
+            nFinish(nativePtr);
         }
     }
 
@@ -1137,7 +1123,7 @@
         mWorkPaint.set(paint);
         do {
             final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths,
-                    widthStart, tempAvail, where, line, textWidth, mWorkPaint, forceEllipsis, dir);
+                    widthStart, tempAvail, where, line, mWorkPaint, forceEllipsis, dir);
             if (ellipsizedWidth <= avail) {
                 lineFits = true;
             } else {
@@ -1167,7 +1153,7 @@
     // This method temporarily modifies the TextPaint passed to it, so the TextPaint passed to it
     // should not be accessed while the method is running.
     private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths,
-            int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth,
+            int widthStart, float avail, TextUtils.TruncateAt where, int line,
             TextPaint paint, boolean forceEllipsis, int dir) {
         final int savedHyphenEdit = paint.getHyphenEdit();
         paint.setHyphenEdit(0);
@@ -1487,43 +1473,85 @@
                 mMaxLineHeight : super.getHeight();
     }
 
-    private static native long nNewBuilder();
-    private static native void nFreeBuilder(long nativePtr);
-    private static native void nFinishBuilder(long nativePtr);
+    /**
+     * Measurement and break iteration is done in native code. The protocol for using
+     * the native code is as follows.
+     *
+     * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+     * following:
+     *
+     *   - Call one of the following methods for each run within the paragraph depending on the type
+     *     of run:
+     *     + addStyleRun (a text run, to be measured in native code)
+     *     + addReplacementRun (a replacement run, width is given)
+     *
+     *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+     *
+     * After all paragraphs, call finish() to release expensive buffers.
+     */
 
-    /* package */ static native long nLoadHyphenator(ByteBuffer buf, int offset,
-            int minPrefix, int minSuffix);
+    /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+            boolean isRtl) {
+        nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+    }
 
-    private static native void nSetLocales(long nativePtr, String locales,
-            long[] nativeHyphenators);
+    /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
+            float width) {
+        nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
+    }
 
-    // Set up paragraph text and settings; done as one big method to minimize jni crossings
-    private static native void nSetupParagraph(
-            @NonNull long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
-            @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
-            @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
-            int defaultTabStop, @BreakStrategy int breakStrategy,
-            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
-            @Nullable int[] indents, @IntRange(from = 0) int indentsOffset);
+    @FastNative
+    private static native long nInit(
+            @BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency,
+            boolean isJustified,
+            @Nullable int[] indents,
+            @Nullable int[] leftPaddings,
+            @Nullable int[] rightPaddings);
 
-    private static native float nAddStyleRun(long nativePtr, long nativePaint, int start, int end,
-            boolean isRtl);
+    @CriticalNative
+    private static native void nFinish(long nativePtr);
 
-    private static native void nAddMeasuredRun(long nativePtr,
-            int start, int end, float[] widths);
+    @CriticalNative
+    private static native void nAddStyleRun(
+            /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
+            @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
 
-    private static native void nAddReplacementRun(long nativePtr, int start, int end, float width);
-
-    private static native void nGetWidths(long nativePtr, float[] widths);
+    @CriticalNative
+    private static native void nAddReplacementRun(
+            /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
+            @IntRange(from = 0) int start, @IntRange(from = 0) int end,
+            @FloatRange(from = 0.0f) float width);
 
     // populates LineBreaks and returns the number of breaks found
     //
     // the arrays inside the LineBreaks objects are passed in as well
     // to reduce the number of JNI calls in the common case where the
     // arrays do not have to be resized
-    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
-            int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength);
+    // The individual character widths will be returned in charWidths. The length of charWidths must
+    // be at least the length of the text.
+    private static native int nComputeLineBreaks(
+            /* non zero */ long nativePtr,
+
+            // Inputs
+            @NonNull char[] text,
+            @IntRange(from = 0) int length,
+            @FloatRange(from = 0.0f) float firstWidth,
+            @IntRange(from = 0) int firstWidthLineCount,
+            @FloatRange(from = 0.0f) float restWidth,
+            @Nullable int[] variableTabStops,
+            int defaultTabStop,
+            @IntRange(from = 0) int indentsOffset,
+
+            // Outputs
+            @NonNull LineBreaks recycle,
+            @IntRange(from  = 0) int recycleLength,
+            @NonNull int[] recycleBreaks,
+            @NonNull float[] recycleWidths,
+            @NonNull float[] recycleAscents,
+            @NonNull float[] recycleDescents,
+            @NonNull int[] recycleFlags,
+            @NonNull float[] charWidths);
 
     private int mLineCount;
     private int mTopPadding, mBottomPadding;
@@ -1590,4 +1618,6 @@
 
     @Nullable private int[] mLeftIndents;
     @Nullable private int[] mRightIndents;
+    @Nullable private int[] mLeftPaddings;
+    @Nullable private int[] mRightPaddings;
 }
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 2dbff10..86cc0141 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -28,6 +28,7 @@
 import android.text.style.ReplacementSpan;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.util.ArrayList;
@@ -44,7 +45,8 @@
  *
  * @hide
  */
-class TextLine {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class TextLine {
     private static final boolean DEBUG = false;
 
     private TextPaint mPaint;
@@ -73,7 +75,7 @@
             new SpanSet<ReplacementSpan>(ReplacementSpan.class);
 
     private final DecorationInfo mDecorationInfo = new DecorationInfo();
-    private final ArrayList<DecorationInfo> mDecorations = new ArrayList();
+    private final ArrayList<DecorationInfo> mDecorations = new ArrayList<>();
 
     private static final TextLine[] sCached = new TextLine[3];
 
@@ -82,7 +84,8 @@
      *
      * @return an uninitialized TextLine
      */
-    static TextLine obtain() {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static TextLine obtain() {
         TextLine tl;
         synchronized (sCached) {
             for (int i = sCached.length; --i >= 0;) {
@@ -107,7 +110,8 @@
      * @return null, as a convenience from clearing references to the provided
      * TextLine
      */
-    static TextLine recycle(TextLine tl) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static TextLine recycle(TextLine tl) {
         tl.mText = null;
         tl.mPaint = null;
         tl.mDirections = null;
@@ -142,7 +146,8 @@
      * @param hasTabs true if the line might contain tabs
      * @param tabStops the tabStops. Can be null.
      */
-    void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
             Directions directions, boolean hasTabs, TabStops tabStops) {
         mPaint = paint;
         mText = text;
@@ -196,7 +201,8 @@
     /**
      * Justify the line to the given width.
      */
-    void justify(float justifyWidth) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void justify(float justifyWidth) {
         int end = mLen;
         while (end > 0 && isLineEndSpace(mText.charAt(mStart + end - 1))) {
             end--;
@@ -277,7 +283,8 @@
      * @param fmi receives font metrics information, can be null
      * @return the signed width of the line
      */
-    float metrics(FontMetricsInt fmi) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public float metrics(FontMetricsInt fmi) {
         return measure(mLen, false, fmi);
     }
 
@@ -340,14 +347,14 @@
 
                     boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
                     if (inSegment && advance) {
-                        return h += measureRun(segstart, offset, j, runIsRtl, fmi);
+                        return h + measureRun(segstart, offset, j, runIsRtl, fmi);
                     }
 
                     float w = measureRun(segstart, j, j, runIsRtl, fmi);
                     h += advance ? w : -w;
 
                     if (inSegment) {
-                        return h += measureRun(segstart, offset, j, runIsRtl, null);
+                        return h + measureRun(segstart, offset, j, runIsRtl, null);
                     }
 
                     if (codept == '\t') {
@@ -828,14 +835,14 @@
                     }
                     if (info.isUnderlineText) {
                         final float thickness =
-                                Math.max(((Paint) wp).getUnderlineThickness(), 1.0f);
+                                Math.max(wp.getUnderlineThickness(), 1.0f);
                         drawStroke(wp, c, wp.getColor(), wp.getUnderlinePosition(), thickness,
                                 decorationXLeft, decorationXRight, y);
                     }
 
                     if (info.isStrikeThruText) {
                         final float thickness =
-                                Math.max(((Paint) wp).getStrikeThruThickness(), 1.0f);
+                                Math.max(wp.getStrikeThruThickness(), 1.0f);
                         drawStroke(wp, c, wp.getColor(), wp.getStrikeThruPosition(), thickness,
                                 decorationXLeft, decorationXRight, y);
                     }
@@ -1165,23 +1172,18 @@
     }
 
     private boolean isStretchableWhitespace(int ch) {
-        // TODO: Support other stretchable whitespace. (Bug: 34013491)
-        return ch == 0x0020 || ch == 0x00A0;
-    }
-
-    private int nextStretchableSpace(int start, int end) {
-        for (int i = start; i < end; i++) {
-            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
-            if (isStretchableWhitespace(c)) return i;
-        }
-        return end;
+        // TODO: Support NBSP and other stretchable whitespace (b/34013491 and b/68204709).
+        return ch == 0x0020;
     }
 
     /* Return the number of spaces in the text line, for the purpose of justification */
     private int countStretchableSpaces(int start, int end) {
         int count = 0;
-        for (int i = start; i < end; i = nextStretchableSpace(i + 1, end)) {
-            count++;
+        for (int i = start; i < end; i++) {
+            final char c = mCharsValid ? mChars[i] : mText.charAt(i + mStart);
+            if (isStretchableWhitespace(c)) {
+                count++;
+            }
         }
         return count;
     }
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 68afeec..cbdaa69 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1519,7 +1519,7 @@
                     }
 
                     // XXX this is probably ok, but need to look at it more
-                    tempMt.setPara(format, 0, format.length(), textDir, null);
+                    tempMt.setPara(format, 0, format.length(), textDir);
                     float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
 
                     if (w + moreWid <= avail) {
@@ -1541,7 +1541,7 @@
     private static float setPara(MeasuredText mt, TextPaint paint,
             CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
 
-        mt.setPara(text, start, end, textDir, null);
+        mt.setPara(text, start, end, textDir);
 
         float width;
         Spanned sp = text instanceof Spanned ? (Spanned) text : null;
diff --git a/core/java/android/transition/TransitionUtils.java b/core/java/android/transition/TransitionUtils.java
index 4951237..084b79d 100644
--- a/core/java/android/transition/TransitionUtils.java
+++ b/core/java/android/transition/TransitionUtils.java
@@ -101,7 +101,7 @@
 
         ImageView copy = new ImageView(view.getContext());
         copy.setScaleType(ImageView.ScaleType.CENTER_CROP);
-        Bitmap bitmap = createViewBitmap(view, matrix, bounds);
+        Bitmap bitmap = createViewBitmap(view, matrix, bounds, sceneRoot);
         if (bitmap != null) {
             copy.setImageBitmap(bitmap);
         }
@@ -115,7 +115,7 @@
     /**
      * Get a copy of bitmap of given drawable, return null if intrinsic size is zero
      */
-    public static Bitmap createDrawableBitmap(Drawable drawable) {
+    public static Bitmap createDrawableBitmap(Drawable drawable, View hostView) {
         int width = drawable.getIntrinsicWidth();
         int height = drawable.getIntrinsicHeight();
         if (width <= 0 || height <= 0) {
@@ -128,7 +128,7 @@
         }
         int bitmapWidth = (int) (width * scale);
         int bitmapHeight = (int) (height * scale);
-        final RenderNode node = RenderNode.create("TransitionUtils", null);
+        final RenderNode node = RenderNode.create("TransitionUtils", hostView);
         node.setLeftTopRightBottom(0, 0, width, height);
         node.setClipToBounds(false);
         final DisplayListCanvas canvas = node.start(width, height);
@@ -156,20 +156,30 @@
      *               returning.
      * @param bounds The bounds of the bitmap in the destination coordinate system (where the
      *               view should be presented. Typically, this is matrix.mapRect(viewBounds);
+     * @param sceneRoot A ViewGroup that is attached to the window to temporarily contain the view
+     *                  if it isn't attached to the window.
      * @return A bitmap of the given view or null if bounds has no width or height.
      */
-    public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) {
+    public static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds,
+            ViewGroup sceneRoot) {
+        final boolean addToOverlay = !view.isAttachedToWindow();
+        if (addToOverlay) {
+            if (sceneRoot == null || !sceneRoot.isAttachedToWindow()) {
+                return null;
+            }
+            sceneRoot.getOverlay().add(view);
+        }
         Bitmap bitmap = null;
         int bitmapWidth = Math.round(bounds.width());
         int bitmapHeight = Math.round(bounds.height());
         if (bitmapWidth > 0 && bitmapHeight > 0) {
-            float scale = Math.min(1f, ((float)MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
+            float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
             bitmapWidth *= scale;
             bitmapHeight *= scale;
             matrix.postTranslate(-bounds.left, -bounds.top);
             matrix.postScale(scale, scale);
 
-            final RenderNode node = RenderNode.create("TransitionUtils", null);
+            final RenderNode node = RenderNode.create("TransitionUtils", view);
             node.setLeftTopRightBottom(0, 0, bitmapWidth, bitmapHeight);
             node.setClipToBounds(false);
             final DisplayListCanvas canvas = node.start(bitmapWidth, bitmapHeight);
@@ -178,6 +188,9 @@
             node.end(canvas);
             bitmap = ThreadedRenderer.createHardwareBitmap(node, bitmapWidth, bitmapHeight);
         }
+        if (addToOverlay) {
+            sceneRoot.getOverlay().remove(view);
+        }
         return bitmap;
     }
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 5838f95..fc1d487 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -50,6 +50,13 @@
     }
 
     /**
+     * Override feature flag to new state.
+     */
+    public static void setEnabled(String feature, boolean enabled) {
+        SystemProperties.set(FFLAG_OVERRIDE_PREFIX + feature, enabled ? "true" : "false");
+    }
+
+    /**
      * Returns all feature flags in their raw form.
      */
     public static Map<String, String> getAllFeatureFlags() {
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 8691136..0299865 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -392,7 +392,7 @@
         // and the length of the tag.
         // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
         //       is too expensive to compute that ahead of time.
-        int bufferSize = NoPreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD  // Base.
+        int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD    // Base.
                 - 2                                                // Two terminators.
                 - (tag != null ? tag.length() : 0)                 // Tag length.
                 - 32;                                              // Some slack.
@@ -429,10 +429,10 @@
     }
 
     /**
-     * NoPreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
+     * PreloadHelper class. Caches the LOGGER_ENTRY_MAX_PAYLOAD value to avoid
      * a JNI call during logging.
      */
-    static class NoPreloadHolder {
+    static class PreloadHolder {
         public final static int LOGGER_ENTRY_MAX_PAYLOAD =
                 logger_entry_max_payload_native();
     }
diff --git a/core/java/android/util/MutableBoolean.java b/core/java/android/util/MutableBoolean.java
new file mode 100644
index 0000000..ed837ab
--- /dev/null
+++ b/core/java/android/util/MutableBoolean.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ */
+public final class MutableBoolean {
+    public boolean value;
+
+    public MutableBoolean(boolean value) {
+        this.value = value;
+    }
+}
diff --git a/core/java/android/util/MutableByte.java b/core/java/android/util/MutableByte.java
new file mode 100644
index 0000000..cc6b00a
--- /dev/null
+++ b/core/java/android/util/MutableByte.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ */
+public final class MutableByte {
+    public byte value;
+
+    public MutableByte(byte value) {
+        this.value = value;
+    }
+}
diff --git a/core/java/android/util/MutableChar.java b/core/java/android/util/MutableChar.java
new file mode 100644
index 0000000..9a2e2bc
--- /dev/null
+++ b/core/java/android/util/MutableChar.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ */
+public final class MutableChar {
+    public char value;
+
+    public MutableChar(char value) {
+        this.value = value;
+    }
+}
diff --git a/core/java/android/util/MutableDouble.java b/core/java/android/util/MutableDouble.java
new file mode 100644
index 0000000..bd7329a
--- /dev/null
+++ b/core/java/android/util/MutableDouble.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ */
+public final class MutableDouble {
+    public double value;
+
+    public MutableDouble(double value) {
+        this.value = value;
+    }
+}
diff --git a/core/java/android/util/MutableFloat.java b/core/java/android/util/MutableFloat.java
new file mode 100644
index 0000000..e6f2d7d
--- /dev/null
+++ b/core/java/android/util/MutableFloat.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ */
+public final class MutableFloat {
+    public float value;
+
+    public MutableFloat(float value) {
+        this.value = value;
+    }
+}
diff --git a/core/java/android/util/MutableShort.java b/core/java/android/util/MutableShort.java
new file mode 100644
index 0000000..48fb232
--- /dev/null
+++ b/core/java/android/util/MutableShort.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ */
+public final class MutableShort {
+    public short value;
+
+    public MutableShort(short value) {
+        this.value = value;
+    }
+}
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
deleted file mode 100644
index 0be1a8c..0000000
--- a/core/java/android/util/StatsLog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007 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;
-
-/**
- * Logging access for platform metrics.
- *
- * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
- * These diagnostic stats are for system integrators, not application authors.
- *
- * <p>Stats use integer tag codes.
- * They carry a payload of one or more int, long, or String values.
- * @hide
- */
-public class StatsLog {
-    /** @hide */ public StatsLog() {}
-
-    private static final String TAG = "StatsLog";
-
-    // We assume that the native methods deal with any concurrency issues.
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param value A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeInt(int tag, int value);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param value A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeLong(int tag, long value);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param value A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeFloat(int tag, float value);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param str A value to log
-     * @return The number of bytes written
-     */
-    public static native int writeString(int tag, String str);
-
-    /**
-     * Records an stats log message.
-     * @param tag The stats type tag code
-     * @param list A list of values to log. All values should
-     * be of type int, long, float or String.
-     * @return The number of bytes written
-     */
-    public static native int writeArray(int tag, Object... list);
-}
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 0216a07..a9ccae1 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -17,6 +17,7 @@
 package android.util.apk;
 
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.ArrayMap;
 import android.util.Pair;
@@ -59,9 +60,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import libcore.io.Libcore;
-import libcore.io.Os;
-
 /**
  * APK Signature Scheme v2 verifier.
  *
@@ -994,8 +992,7 @@
      * {@link DataSource#feedIntoMessageDigests(MessageDigest[], long, int) feedIntoMessageDigests}.
      */
     private static final class MemoryMappedFileDataSource implements DataSource {
-        private static final Os OS = Libcore.os;
-        private static final long MEMORY_PAGE_SIZE_BYTES = OS.sysconf(OsConstants._SC_PAGESIZE);
+        private static final long MEMORY_PAGE_SIZE_BYTES = Os.sysconf(OsConstants._SC_PAGESIZE);
 
         private final FileDescriptor mFd;
         private final long mFilePosition;
@@ -1041,7 +1038,7 @@
             long mmapRegionSize = size + dataStartOffsetInMmapRegion;
             long mmapPtr = 0;
             try {
-                mmapPtr = OS.mmap(
+                mmapPtr = Os.mmap(
                         0, // let the OS choose the start address of the region in memory
                         mmapRegionSize,
                         OsConstants.PROT_READ,
@@ -1066,7 +1063,7 @@
             } finally {
                 if (mmapPtr != 0) {
                     try {
-                        OS.munmap(mmapPtr, mmapRegionSize);
+                        Os.munmap(mmapPtr, mmapRegionSize);
                     } catch (ErrnoException ignored) {}
                 }
             }
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 9afa56d..43a9789 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -29,8 +29,8 @@
  * Class to write to a protobuf stream.
  *
  * Each write method takes an ID code from the protoc generated classes
- * and the value to write.  To make a nested object, call startObject
- * and then endObject when you are done.
+ * and the value to write.  To make a nested object, call #start
+ * and then #end when you are done.
  *
  * The ID codes have type information embedded into them, so if you call
  * the incorrect function you will get an IllegalArgumentException.
@@ -60,16 +60,16 @@
  * Message objects. We need to find another way.
  *
  * So what we do here is to let the calling code write the data into a
- * byte[] (actually a collection of them wrapped in the EncodedBuffer) class,
+ * byte[] (actually a collection of them wrapped in the EncodedBuffer class),
  * but not do the varint encoding of the sub-message sizes.  Then, we do a
  * recursive traversal of the buffer itself, calculating the sizes (which are
  * then knowable, although still not the actual sizes in the buffer because of
  * possible further nesting).  Then we do a third pass, compacting the
  * buffer and varint encoding the sizes.
  *
- * This gets us a relatively small number number of fixed-size allocations,
+ * This gets us a relatively small number of fixed-size allocations,
  * which is less likely to cause memory fragmentation or churn the GC, and
- * the same number of data copies as would have gotten with setting it
+ * the same number of data copies as we would have gotten with setting it
  * field-by-field in generated code, and no code bloat from generated code.
  * The final data copy is also done with System.arraycopy, which will be
  * more efficient, in general, than doing the individual fields twice (as in
@@ -77,26 +77,26 @@
  *
  * To accomplish the multiple passes, whenever we write a
  * WIRE_TYPE_LENGTH_DELIMITED field, we write the size occupied in our
- * buffer as a fixed 32 bit int (called childRawSize), not variable length
+ * buffer as a fixed 32 bit int (called childRawSize), not a variable length
  * one. We reserve another 32 bit slot for the computed size (called
  * childEncodedSize).  If we know the size up front, as we do for strings
  * and byte[], then we also put that into childEncodedSize, if we don't, we
- * write the negative of childRawSize, as a sentiel that we need to
+ * write the negative of childRawSize, as a sentinel that we need to
  * compute it during the second pass and recursively compact it during the
  * third pass.
  *
- * Unsgigned size varints can be up to five bytes long, but we reserve eight
+ * Unsigned size varints can be up to five bytes long, but we reserve eight
  * bytes for overhead, so we know that when we compact the buffer, there
  * will always be space for the encoded varint.
  *
  * When we can figure out the size ahead of time, we do, in order
  * to save overhead with recalculating it, and with the later arraycopy.
  *
- * During the period between when the caller has called startObject, but
- * not yet called endObject, we maintain a linked list of the tokens
- * returned by startObject, stored in those 8 bytes of size storage space.
+ * During the period between when the caller has called #start, but
+ * not yet called #end, we maintain a linked list of the tokens
+ * returned by #start, stored in those 8 bytes of size storage space.
  * We use that linked list of tokens to ensure that the caller has
- * correctly matched pairs of startObject and endObject calls, and issue
+ * correctly matched pairs of #start and #end calls, and issue
  * errors if they are not matched.
  */
 @TestApi
@@ -2375,6 +2375,9 @@
         if (countString == null) {
             countString = "fieldCount=" + fieldCount;
         }
+        if (countString.length() > 0) {
+            countString += " ";
+        }
 
         final long fieldType = fieldId & FIELD_TYPE_MASK;
         String typeString = getFieldTypeString(fieldType);
@@ -2382,7 +2385,7 @@
             typeString = "fieldType=" + fieldType;
         }
 
-        return fieldCount + " " + typeString + " tag=" + ((int)fieldId)
+        return countString + typeString + " tag=" + ((int) fieldId)
                 + " fieldId=0x" + Long.toHexString(fieldId);
     }
 
diff --git a/core/java/android/util/proto/ProtoUtils.java b/core/java/android/util/proto/ProtoUtils.java
new file mode 100644
index 0000000..449baca
--- /dev/null
+++ b/core/java/android/util/proto/ProtoUtils.java
@@ -0,0 +1,39 @@
+/*
+ * 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.util.proto;
+
+import android.util.AggStats;
+
+/**
+ * This class contains a list of helper functions to write common proto in
+ * //frameworks/base/core/proto/android/base directory
+ */
+public class ProtoUtils {
+
+    /**
+     * Dump AggStats to ProtoOutputStream
+     * @hide
+     */
+    public static void toAggStatsProto(ProtoOutputStream proto, long fieldId,
+            long min, long average, long max) {
+        final long aggStatsToken = proto.start(fieldId);
+        proto.write(AggStats.MIN, min);
+        proto.write(AggStats.AVERAGE, average);
+        proto.write(AggStats.MAX, max);
+        proto.end(aggStatsToken);
+    }
+}
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 16f2d7d..2c9f871 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -103,7 +103,7 @@
  *  <tr>
  *      <td>ACTION_DRAG_ENDED</td>
  *      <td style="text-align: center;">&nbsp;</td>
- *      <td style="text-align: center;">&nbsp;</td>
+ *      <td style="text-align: center;">X</td>
  *      <td style="text-align: center;">&nbsp;</td>
  *      <td style="text-align: center;">&nbsp;</td>
  *      <td style="text-align: center;">&nbsp;</td>
@@ -112,6 +112,7 @@
  * </table>
  * <p>
  *  The {@link android.view.DragEvent#getAction()},
+ *  {@link android.view.DragEvent#getLocalState()}
  *  {@link android.view.DragEvent#describeContents()},
  *  {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
  *  {@link android.view.DragEvent#toString()} methods always return valid data.
@@ -397,7 +398,7 @@
      * operation. In all other activities this method will return null
      * </p>
      * <p>
-     *  This method returns valid data for all event actions except for {@link #ACTION_DRAG_ENDED}.
+     *  This method returns valid data for all event actions.
      * </p>
      * @return The local state object sent to the system by startDragAndDrop().
      */
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index 324a1ae..232ff25 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -440,4 +440,57 @@
         }
         return result;
     }
+
+    /**
+     * @hide
+     */
+    public static String toString(int gravity) {
+        final StringBuilder result = new StringBuilder();
+        if ((gravity & FILL) != 0) {
+            result.append("FILL").append(' ');
+        } else {
+            if ((gravity & FILL_VERTICAL) != 0) {
+                result.append("FILL_VERTICAL").append(' ');
+            } else {
+                if ((gravity & TOP) != 0) {
+                    result.append("TOP").append(' ');
+                }
+                if ((gravity & BOTTOM) != 0) {
+                    result.append("BOTTOM").append(' ');
+                }
+            }
+            if ((gravity & FILL_HORIZONTAL) != 0) {
+                result.append("FILL_HORIZONTAL").append(' ');
+            } else {
+                if ((gravity & START) != 0) {
+                    result.append("START").append(' ');
+                } else if ((gravity & LEFT) != 0) {
+                    result.append("LEFT").append(' ');
+                }
+                if ((gravity & END) != 0) {
+                    result.append("END").append(' ');
+                } else if ((gravity & RIGHT) != 0) {
+                    result.append("RIGHT").append(' ');
+                }
+            }
+        }
+        if ((gravity & CENTER) != 0) {
+            result.append("CENTER").append(' ');
+        } else {
+            if ((gravity & CENTER_VERTICAL) != 0) {
+                result.append("CENTER_VERTICAL").append(' ');
+            }
+            if ((gravity & CENTER_HORIZONTAL) != 0) {
+                result.append("CENTER_HORIZONTAL").append(' ');
+            }
+        }
+        if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+            result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+        }
+        if ((gravity & DISPLAY_CLIP_VERTICAL) != 0) {
+            result.append("DISPLAY_CLIP_VERTICAL").append(' ');
+        }
+        result.deleteCharAt(result.length() - 1);
+        return result.toString();
+    }
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e576a0f..65bde49 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -16,7 +16,7 @@
 
 package android.view;
 
-import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
@@ -272,7 +272,7 @@
     /**
      * Used only for assist -- request a screenshot of the current application.
      */
-    boolean requestAssistScreenshot(IAssistScreenshotReceiver receiver);
+    boolean requestAssistScreenshot(IAssistDataReceiver receiver);
 
     /**
      * Called by the status bar to notify Views of changes to System UI visiblity.
@@ -358,10 +358,10 @@
      * Updates the dim layer used while resizing.
      *
      * @param visible Whether the dim layer should be visible.
-     * @param targetStackId The id of the task stack the dim layer should be placed on.
+     * @param targetWindowingMode The windowing mode of the stack the dim layer should be placed on.
      * @param alpha The translucency of the dim layer, between 0 and 1.
      */
-    void setResizeDimLayer(boolean visible, int targetStackId, float alpha);
+    void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha);
 
     /**
      * Requests Keyboard Shortcuts from the displayed window.
@@ -385,7 +385,7 @@
     /**
      * Create an input consumer by name.
      */
-    void createInputConsumer(String name, out InputChannel inputChannel);
+    void createInputConsumer(IBinder token, String name, out InputChannel inputChannel);
 
     /**
      * Destroy an input consumer by name.  This method will also dispose the input channels
diff --git a/core/java/android/view/MenuItem.java b/core/java/android/view/MenuItem.java
index 88b9c0d..ad160cb 100644
--- a/core/java/android/view/MenuItem.java
+++ b/core/java/android/view/MenuItem.java
@@ -72,11 +72,6 @@
     public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
 
     /**
-     * @hide
-     */
-    int SHOW_AS_OVERFLOW_ALWAYS = 1 << 31;
-
-    /**
      * Interface definition for a callback to be invoked when a menu item is
      * clicked.
      *
@@ -806,12 +801,22 @@
     }
 
     /**
-     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_OVERFLOW_ALWAYS}.
-     * Default value if {@code false}.
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_ALWAYS}.
+     * Default value is {@code false}.
+     *
+     * @hide
+     */
+    default boolean requiresActionButton() {
+        return false;
+    }
+
+    /**
+     * Returns true if {@link #setShowAsAction(int)} was set to {@link #SHOW_AS_ACTION_NEVER}.
+     * Default value is {@code true}.
      *
      * @hide
      */
     default boolean requiresOverflow() {
-        return false;
+        return true;
     }
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 7198f37..6f8315a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -16,18 +16,21 @@
 
 package android.view;
 
-import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-
+import android.annotation.Size;
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
-import android.os.Binder;
 import android.os.IBinder;
+import android.os.Process;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Surface.OutOfResourcesException;
-
 import dalvik.system.CloseGuard;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.Closeable;
 
 /**
  * SurfaceControl
@@ -52,25 +55,37 @@
     private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
             boolean allLayers, boolean useIdentityTransform);
+    private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
+            int rotation);
 
-    private static native void nativeOpenTransaction();
-    private static native void nativeCloseTransaction(boolean sync);
-    private static native void nativeSetAnimationTransaction();
+    private static native long nativeCreateTransaction();
+    private static native long nativeGetNativeTransactionFinalizer();
+    private static native void nativeApplyTransaction(long transactionObj, boolean sync);
+    private static native void nativeSetAnimationTransaction(long transactionObj);
 
-    private static native void nativeSetLayer(long nativeObject, int zorder);
-    private static native void nativeSetRelativeLayer(long nativeObject, IBinder relativeTo,
-            int zorder);
-    private static native void nativeSetPosition(long nativeObject, float x, float y);
-    private static native void nativeSetGeometryAppliesWithResize(long nativeObject);
-    private static native void nativeSetSize(long nativeObject, int w, int h);
-    private static native void nativeSetTransparentRegionHint(long nativeObject, Region region);
-    private static native void nativeSetAlpha(long nativeObject, float alpha);
-    private static native void nativeSetMatrix(long nativeObject, float dsdx, float dtdx,
+    private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
+    private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
+            IBinder relativeTo, int zorder);
+    private static native void nativeSetPosition(long transactionObj, long nativeObject,
+            float x, float y);
+    private static native void nativeSetGeometryAppliesWithResize(long transactionObj,
+            long nativeObject);
+    private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
+    private static native void nativeSetTransparentRegionHint(long transactionObj,
+            long nativeObject, Region region);
+    private static native void nativeSetAlpha(long transactionObj, long nativeObject, float alpha);
+    private static native void nativeSetMatrix(long transactionObj, long nativeObject,
+            float dsdx, float dtdx,
             float dtdy, float dsdy);
-    private static native void nativeSetFlags(long nativeObject, int flags, int mask);
-    private static native void nativeSetWindowCrop(long nativeObject, int l, int t, int r, int b);
-    private static native void nativeSetFinalCrop(long nativeObject, int l, int t, int r, int b);
-    private static native void nativeSetLayerStack(long nativeObject, int layerStack);
+    private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
+    private static native void nativeSetFlags(long transactionObj, long nativeObject,
+            int flags, int mask);
+    private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
+            int l, int t, int r, int b);
+    private static native void nativeSetFinalCrop(long transactionObj, long nativeObject,
+            int l, int t, int r, int b);
+    private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
+            int layerStack);
 
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -80,15 +95,16 @@
     private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
     private static native IBinder nativeCreateDisplay(String name, boolean secure);
     private static native void nativeDestroyDisplay(IBinder displayToken);
-    private static native void nativeSetDisplaySurface(
+    private static native void nativeSetDisplaySurface(long transactionObj,
             IBinder displayToken, long nativeSurfaceObject);
-    private static native void nativeSetDisplayLayerStack(
+    private static native void nativeSetDisplayLayerStack(long transactionObj,
             IBinder displayToken, int layerStack);
-    private static native void nativeSetDisplayProjection(
+    private static native void nativeSetDisplayProjection(long transactionObj,
             IBinder displayToken, int orientation,
             int l, int t, int r, int b,
             int L, int T, int R, int B);
-    private static native void nativeSetDisplaySize(IBinder displayToken, int width, int height);
+    private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
+            int width, int height);
     private static native SurfaceControl.PhysicalDisplayInfo[] nativeGetDisplayConfigs(
             IBinder displayToken);
     private static native int nativeGetActiveConfig(IBinder displayToken);
@@ -99,16 +115,17 @@
             int colorMode);
     private static native void nativeSetDisplayPowerMode(
             IBinder displayToken, int mode);
-    private static native void nativeDeferTransactionUntil(long nativeObject,
+    private static native void nativeDeferTransactionUntil(long transactionObj, long nativeObject,
             IBinder handle, long frame);
-    private static native void nativeDeferTransactionUntilSurface(long nativeObject,
+    private static native void nativeDeferTransactionUntilSurface(long transactionObj,
+            long nativeObject,
             long surfaceObject, long frame);
-    private static native void nativeReparentChildren(long nativeObject,
+    private static native void nativeReparentChildren(long transactionObj, long nativeObject,
             IBinder handle);
-    private static native void nativeReparent(long nativeObject,
+    private static native void nativeReparent(long transactionObj, long nativeObject,
             IBinder parentHandle);
-    private static native void nativeSeverChildren(long nativeObject);
-    private static native void nativeSetOverrideScalingMode(long nativeObject,
+    private static native void nativeSeverChildren(long transactionObj, long nativeObject);
+    private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
             int scalingMode);
     private static native IBinder nativeGetHandle(long nativeObject);
     private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
@@ -120,6 +137,9 @@
     private final String mName;
     long mNativeObject; // package visibility only for Surface.java access
 
+    static Transaction sGlobalTransaction;
+    static long sTransactionNestCount = 0;
+
     /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
 
     /**
@@ -163,7 +183,7 @@
 
     /**
      * Surface creation flag: Indicates that the surface must be considered opaque,
-     * even if its pixel format is set to translucent. This can be useful if an
+     * even if its pixel format contains an alpha channel. This can be useful if an
      * application needs full RGBA 8888 support for instance but will
      * still draw every pixel opaque.
      * <p>
@@ -284,6 +304,203 @@
     public static final int WINDOW_TYPE_DONT_SCREENSHOT = 441731;
 
     /**
+     * Builder class for {@link SurfaceControl} objects.
+     */
+    public static class Builder {
+        private SurfaceSession mSession;
+        private int mFlags = HIDDEN;
+        private int mWidth;
+        private int mHeight;
+        private int mFormat = PixelFormat.OPAQUE;
+        private String mName;
+        private SurfaceControl mParent;
+        private int mWindowType;
+        private int mOwnerUid;
+
+        /**
+         * Begin building a SurfaceControl with a given {@link SurfaceSession}.
+         *
+         * @param session The {@link SurfaceSession} with which to eventually construct the surface.
+         */
+        public Builder(SurfaceSession session) {
+            mSession = session;
+        }
+
+        /**
+         * Construct a new {@link SurfaceControl} with the set parameters.
+         */
+        public SurfaceControl build() {
+            if (mWidth <= 0 || mHeight <= 0) {
+                throw new IllegalArgumentException(
+                        "width and height must be set");
+            }
+            return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
+                    mFlags, mParent, mWindowType, mOwnerUid);
+        }
+
+        /**
+         * Set a debugging-name for the SurfaceControl.
+         *
+         * @param name A name to identify the Surface in debugging.
+         */
+        public Builder setName(String name) {
+            mName = name;
+            return this;
+        }
+
+        /**
+         * Set the initial size of the controlled surface's buffers in pixels.
+         *
+         * @param width The buffer width in pixels.
+         * @param height The buffer height in pixels.
+         */
+        public Builder setSize(int width, int height) {
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException(
+                        "width and height must be positive");
+            }
+            mWidth = width;
+            mHeight = height;
+            return this;
+        }
+
+        /**
+         * Set the pixel format of the controlled surface's buffers, using constants from
+         * {@link android.graphics.PixelFormat}.
+         */
+        public Builder setFormat(@PixelFormat.Format int format) {
+            mFormat = format;
+            return this;
+        }
+
+        /**
+         * Specify if the app requires a hardware-protected path to
+         * an external display sync. If protected content is enabled, but
+         * such a path is not available, then the controlled Surface will
+         * not be displayed.
+         *
+         * @param protectedContent Whether to require a protected sink.
+         */
+        public Builder setProtected(boolean protectedContent) {
+            if (protectedContent) {
+                mFlags |= PROTECTED_APP;
+            } else {
+                mFlags &= ~PROTECTED_APP;
+            }
+            return this;
+        }
+
+        /**
+         * Specify whether the Surface contains secure content. If true, the system
+         * will prevent the surfaces content from being copied by another process. In
+         * particular screenshots and VNC servers will be disabled. This is however
+         * not a complete prevention of readback as {@link #setProtected}.
+         */
+        public Builder setSecure(boolean secure) {
+            if (secure) {
+                mFlags |= SECURE;
+            } else {
+                mFlags &= ~SECURE;
+            }
+            return this;
+        }
+
+        /**
+         * Indicates whether the surface must be considered opaque,
+         * even if its pixel format is set to translucent. This can be useful if an
+         * application needs full RGBA 8888 support for instance but will
+         * still draw every pixel opaque.
+         * <p>
+         * This flag only determines whether opacity will be sampled from the alpha channel.
+         * Plane-alpha from calls to setAlpha() can still result in blended composition
+         * regardless of the opaque setting.
+         *
+         * Combined effects are (assuming a buffer format with an alpha channel):
+         * <ul>
+         * <li>OPAQUE + alpha(1.0) == opaque composition
+         * <li>OPAQUE + alpha(0.x) == blended composition
+         * <li>OPAQUE + alpha(0.0) == no composition
+         * <li>!OPAQUE + alpha(1.0) == blended composition
+         * <li>!OPAQUE + alpha(0.x) == blended composition
+         * <li>!OPAQUE + alpha(0.0) == no composition
+         * </ul>
+         * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+         * were set automatically.
+         * @param opaque Whether the Surface is OPAQUE.
+         */
+        public Builder setOpaque(boolean opaque) {
+            if (opaque) {
+                mFlags |= OPAQUE;
+            } else {
+                mFlags &= ~OPAQUE;
+            }
+            return this;
+        }
+
+        /**
+         * Set a parent surface for our new SurfaceControl.
+         *
+         * Child surfaces are constrained to the onscreen region of their parent.
+         * Furthermore they stack relatively in Z order, and inherit the transformation
+         * of the parent.
+         *
+         * @param parent The parent control.
+         */
+        public Builder setParent(SurfaceControl parent) {
+            mParent = parent;
+            return this;
+        }
+
+        /**
+         * Set surface metadata.
+         *
+         * Currently these are window-types as per {@link WindowManager.LayoutParams} and
+         * owner UIDs. Child surfaces inherit their parents
+         * metadata so only the WindowManager needs to set this on root Surfaces.
+         *
+         * @param windowType A window-type
+         * @param ownerUid UID of the window owner.
+         */
+        public Builder setMetadata(int windowType, int ownerUid) {
+            if (UserHandle.getAppId(Process.myUid()) != Process.SYSTEM_UID) {
+                throw new UnsupportedOperationException(
+                        "It only makes sense to set Surface metadata from the WindowManager");
+            }
+            mWindowType = windowType;
+            mOwnerUid = ownerUid;
+            return this;
+        }
+
+        /**
+         * Indicate whether a 'ColorLayer' is to be constructed.
+         *
+         * Color layers will not have an associated BufferQueue and will instead always render a
+         * solid color (that is, solid before plane alpha). Currently that color is black.
+         *
+         * @param isColorLayer Whether to create a color layer.
+         */
+        public Builder setColorLayer(boolean isColorLayer) {
+            if (isColorLayer) {
+                mFlags |= FX_SURFACE_DIM;
+            } else {
+                mFlags &= ~FX_SURFACE_DIM;
+            }
+            return this;
+        }
+
+        /**
+         * Set 'Surface creation flags' such as {@link HIDDEN}, {@link SECURE}.
+         *
+         * TODO: Finish conversion to individual builder methods?
+         * @param flags The combined flags
+         */
+        public Builder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+    }
+
+    /**
      * Create a surface with a name.
      * <p>
      * The surface creation flags specify what kind of surface to create and
@@ -308,19 +525,7 @@
      *
      * @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
      */
-    public SurfaceControl(SurfaceSession session,
-            String name, int w, int h, int format, int flags, int windowType, int ownerUid)
-                    throws OutOfResourcesException {
-        this(session, name, w, h, format, flags, null, windowType, ownerUid);
-    }
-
-    public SurfaceControl(SurfaceSession session,
-            String name, int w, int h, int format, int flags)
-                    throws OutOfResourcesException {
-        this(session, name, w, h, format, flags, null, INVALID_WINDOW_TYPE, Binder.getCallingUid());
-    }
-
-    public SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
+    private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
             SurfaceControl parent, int windowType, int ownerUid)
                     throws OutOfResourcesException {
         if (session == null) {
@@ -375,11 +580,6 @@
         }
     }
 
-    @Override
-    public String toString() {
-        return "Surface(name=" + mName + ")";
-    }
-
     /**
      * Release the local reference to the server-side surface.
      * Always call release() when you're done with a Surface.
@@ -427,102 +627,141 @@
 
     /** start a transaction */
     public static void openTransaction() {
-        nativeOpenTransaction();
+        synchronized (SurfaceControl.class) {
+            if (sGlobalTransaction == null) {
+                sGlobalTransaction = new Transaction();
+            }
+            synchronized(SurfaceControl.class) {
+                sTransactionNestCount++;
+            }
+        }
+    }
+
+    private static void closeTransaction(boolean sync) {
+        synchronized(SurfaceControl.class) {
+            if (sTransactionNestCount == 0) {
+                Log.e(TAG, "Call to SurfaceControl.closeTransaction without matching openTransaction");
+            } else if (--sTransactionNestCount > 0) {
+                return;
+            }
+            sGlobalTransaction.apply(sync);
+        }
     }
 
     /** end a transaction */
     public static void closeTransaction() {
-        nativeCloseTransaction(false);
+        closeTransaction(false);
     }
 
     public static void closeTransactionSync() {
-        nativeCloseTransaction(true);
+        closeTransaction(true);
     }
 
     public void deferTransactionUntil(IBinder handle, long frame) {
         if (frame > 0) {
-            nativeDeferTransactionUntil(mNativeObject, handle, frame);
+            synchronized(SurfaceControl.class) {
+                sGlobalTransaction.deferTransactionUntil(this, handle, frame);
+            }
         }
     }
 
     public void deferTransactionUntil(Surface barrier, long frame) {
         if (frame > 0) {
-            nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
+            synchronized(SurfaceControl.class) {
+                sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
+            }
         }
     }
 
     public void reparentChildren(IBinder newParentHandle) {
-        nativeReparentChildren(mNativeObject, newParentHandle);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.reparentChildren(this, newParentHandle);
+        }
     }
 
-    /** Re-parents this layer to a new parent. */
     public void reparent(IBinder newParentHandle) {
-        nativeReparent(mNativeObject, newParentHandle);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.reparent(this, newParentHandle);
+        }
     }
 
     public void detachChildren() {
-        nativeSeverChildren(mNativeObject);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.detachChildren(this);
+        }
     }
 
     public void setOverrideScalingMode(int scalingMode) {
         checkNotReleased();
-        nativeSetOverrideScalingMode(mNativeObject, scalingMode);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setOverrideScalingMode(this, scalingMode);
+        }
     }
 
     public IBinder getHandle() {
         return nativeGetHandle(mNativeObject);
     }
 
-    /** flag the transaction as an animation */
     public static void setAnimationTransaction() {
-        nativeSetAnimationTransaction();
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setAnimationTransaction();
+        }
     }
 
     public void setLayer(int zorder) {
         checkNotReleased();
-        nativeSetLayer(mNativeObject, zorder);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setLayer(this, zorder);
+        }
     }
 
-    public void setRelativeLayer(IBinder relativeTo, int zorder) {
+    public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
         checkNotReleased();
-        nativeSetRelativeLayer(mNativeObject, relativeTo, zorder);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
+        }
     }
 
     public void setPosition(float x, float y) {
         checkNotReleased();
-        nativeSetPosition(mNativeObject, x, y);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setPosition(this, x, y);
+        }
     }
 
-    /**
-     * If the buffer size changes in this transaction, position and crop updates specified
-     * in this transaction will not complete until a buffer of the new size
-     * arrives. As transform matrix and size are already frozen in this fashion,
-     * this enables totally freezing the surface until the resize has completed
-     * (at which point the geometry influencing aspects of this transaction will then occur)
-     */
     public void setGeometryAppliesWithResize() {
         checkNotReleased();
-        nativeSetGeometryAppliesWithResize(mNativeObject);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setGeometryAppliesWithResize(this);
+        }
     }
 
     public void setSize(int w, int h) {
         checkNotReleased();
-        nativeSetSize(mNativeObject, w, h);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setSize(this, w, h);
+        }
     }
 
     public void hide() {
         checkNotReleased();
-        nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.hide(this);
+        }
     }
 
     public void show() {
         checkNotReleased();
-        nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.show(this);
+        }
     }
 
     public void setTransparentRegionHint(Region region) {
         checkNotReleased();
-        nativeSetTransparentRegionHint(mNativeObject, region);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setTransparentRegionHint(this, region);
+        }
     }
 
     public boolean clearContentFrameStats() {
@@ -543,71 +782,70 @@
         return nativeGetAnimationFrameStats(outStats);
     }
 
-    /**
-     * Sets an alpha value for the entire Surface.  This value is combined with the
-     * per-pixel alpha.  It may be used with opaque Surfaces.
-     */
     public void setAlpha(float alpha) {
         checkNotReleased();
-        nativeSetAlpha(mNativeObject, alpha);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setAlpha(this, alpha);
+        }
+    }
+
+    public void setColor(@Size(3) float[] color) {
+        checkNotReleased();
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setColor(this, color);
+        }
     }
 
     public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
         checkNotReleased();
-        nativeSetMatrix(mNativeObject, dsdx, dtdx, dtdy, dsdy);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setMatrix(this, dsdx, dtdx, dtdy, dsdy);
+        }
     }
 
     public void setWindowCrop(Rect crop) {
         checkNotReleased();
-        if (crop != null) {
-            nativeSetWindowCrop(mNativeObject,
-                crop.left, crop.top, crop.right, crop.bottom);
-        } else {
-            nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setWindowCrop(this, crop);
         }
     }
 
     public void setFinalCrop(Rect crop) {
         checkNotReleased();
-        if (crop != null) {
-            nativeSetFinalCrop(mNativeObject,
-                crop.left, crop.top, crop.right, crop.bottom);
-        } else {
-            nativeSetFinalCrop(mNativeObject, 0, 0, 0, 0);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setFinalCrop(this, crop);
         }
     }
 
     public void setLayerStack(int layerStack) {
         checkNotReleased();
-        nativeSetLayerStack(mNativeObject, layerStack);
+        synchronized(SurfaceControl.class) {
+            sGlobalTransaction.setLayerStack(this, layerStack);
+        }
     }
 
-    /**
-     * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
-     * Surface with the {@link #OPAQUE} flag.
-     */
     public void setOpaque(boolean isOpaque) {
         checkNotReleased();
-        if (isOpaque) {
-            nativeSetFlags(mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
-        } else {
-            nativeSetFlags(mNativeObject, 0, SURFACE_OPAQUE);
+
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setOpaque(this, isOpaque);
         }
     }
 
-    /**
-     * Sets the security of the surface.  Setting the flag is equivalent to creating the
-     * Surface with the {@link #SECURE} flag.
-     */
     public void setSecure(boolean isSecure) {
         checkNotReleased();
-        if (isSecure) {
-            nativeSetFlags(mNativeObject, SECURE, SECURE);
-        } else {
-            nativeSetFlags(mNativeObject, 0, SECURE);
+
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setSecure(this, isSecure);
         }
     }
 
+    @Override
+    public String toString() {
+        return "Surface(name=" + mName + ")/@0x" +
+                Integer.toHexString(System.identityHashCode(this));
+    }
+
     /*
      * set display parameters.
      * needs to be inside open/closeTransaction block
@@ -730,50 +968,28 @@
 
     public static void setDisplayProjection(IBinder displayToken,
             int orientation, Rect layerStackRect, Rect displayRect) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplayProjection(displayToken, orientation,
+                    layerStackRect, displayRect);
         }
-        if (layerStackRect == null) {
-            throw new IllegalArgumentException("layerStackRect must not be null");
-        }
-        if (displayRect == null) {
-            throw new IllegalArgumentException("displayRect must not be null");
-        }
-        nativeSetDisplayProjection(displayToken, orientation,
-                layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
-                displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
     }
 
     public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
         }
-        nativeSetDisplayLayerStack(displayToken, layerStack);
     }
 
     public static void setDisplaySurface(IBinder displayToken, Surface surface) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        if (surface != null) {
-            synchronized (surface.mLock) {
-                nativeSetDisplaySurface(displayToken, surface.mNativeObject);
-            }
-        } else {
-            nativeSetDisplaySurface(displayToken, 0);
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplaySurface(displayToken, surface);
         }
     }
 
     public static void setDisplaySize(IBinder displayToken, int width, int height) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
+        synchronized (SurfaceControl.class) {
+            sGlobalTransaction.setDisplaySize(displayToken, width, height);
         }
-        if (width <= 0 || height <= 0) {
-            throw new IllegalArgumentException("width and height must be positive");
-        }
-
-        nativeSetDisplaySize(displayToken, width, height);
     }
 
     public static Display.HdrCapabilities getHdrCapabilities(IBinder displayToken) {
@@ -935,4 +1151,275 @@
         nativeScreenshot(display, consumer, sourceCrop, width, height,
                 minLayer, maxLayer, allLayers, useIdentityTransform);
     }
+
+    /**
+     * Captures a layer and its children into the provided {@link Surface}.
+     *
+     * @param layerHandleToken The root layer to capture.
+     * @param consumer The {@link Surface} to capture the layer into.
+     * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
+     *                 Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
+     *                 native portrait orientation by default, so this is useful for returning
+     *                 captures that are independent of device orientation.
+     */
+    public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
+        nativeCaptureLayers(layerHandleToken, consumer, rotation);
+    }
+
+    public static class Transaction implements Closeable {
+        public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+                Transaction.class.getClassLoader(),
+                nativeGetNativeTransactionFinalizer(), 512);
+        private long mNativeObject;
+
+        Runnable mFreeNativeResources;
+
+        public Transaction() {
+            mNativeObject = nativeCreateTransaction();
+            mFreeNativeResources
+                = sRegistry.registerNativeAllocation(this, mNativeObject);
+        }
+
+        /**
+         * Apply the transaction, clearing it's state, and making it usable
+         * as a new transaction.
+         */
+        public void apply() {
+            apply(false);
+        }
+
+        /**
+         * Close the transaction, if the transaction was not already applied this will cancel the
+         * transaction.
+         */
+        @Override
+        public void close() {
+            mFreeNativeResources.run();
+            mNativeObject = 0;
+        }
+
+        /**
+         * Jankier version of apply. Avoid use (b/28068298).
+         */
+        public void apply(boolean sync) {
+            nativeApplyTransaction(mNativeObject, sync);
+        }
+
+        public Transaction show(SurfaceControl sc) {
+            nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
+            return this;
+        }
+
+        public Transaction hide(SurfaceControl sc) {
+            nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+            return this;
+        }
+
+        public Transaction setPosition(SurfaceControl sc, float x, float y) {
+            nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
+            return this;
+        }
+
+        public Transaction setSize(SurfaceControl sc, int w, int h) {
+            nativeSetSize(mNativeObject, sc.mNativeObject,
+                    w, h);
+            return this;
+        }
+
+        public Transaction setLayer(SurfaceControl sc, int z) {
+            nativeSetLayer(mNativeObject, sc.mNativeObject, z);
+            return this;
+        }
+
+        public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
+            nativeSetRelativeLayer(mNativeObject, sc.mNativeObject,
+                    relativeTo.getHandle(), z);
+            return this;
+        }
+
+        public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
+            nativeSetTransparentRegionHint(mNativeObject,
+                    sc.mNativeObject, transparentRegion);
+            return this;
+        }
+
+        public Transaction setAlpha(SurfaceControl sc, float alpha) {
+            nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
+            return this;
+        }
+
+        public Transaction setMatrix(SurfaceControl sc,
+                float dsdx, float dtdx, float dtdy, float dsdy) {
+            nativeSetMatrix(mNativeObject, sc.mNativeObject,
+                    dsdx, dtdx, dtdy, dsdy);
+            return this;
+        }
+
+        public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+            if (crop != null) {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        public Transaction setFinalCrop(SurfaceControl sc, Rect crop) {
+            if (crop != null) {
+                nativeSetFinalCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetFinalCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+            nativeSetLayerStack(mNativeObject, sc.mNativeObject, layerStack);
+            return this;
+        }
+
+        public Transaction deferTransactionUntil(SurfaceControl sc, IBinder handle, long frameNumber) {
+            nativeDeferTransactionUntil(mNativeObject, sc.mNativeObject, handle, frameNumber);
+            return this;
+        }
+
+        public Transaction deferTransactionUntilSurface(SurfaceControl sc, Surface barrierSurface,
+                long frameNumber) {
+            nativeDeferTransactionUntilSurface(mNativeObject, sc.mNativeObject,
+                    barrierSurface.mNativeObject, frameNumber);
+            return this;
+        }
+
+        public Transaction reparentChildren(SurfaceControl sc, IBinder newParentHandle) {
+            nativeReparentChildren(mNativeObject, sc.mNativeObject, newParentHandle);
+            return this;
+        }
+
+        /** Re-parents a specific child layer to a new parent */
+        public Transaction reparent(SurfaceControl sc, IBinder newParentHandle) {
+            nativeReparent(mNativeObject, sc.mNativeObject,
+                    newParentHandle);
+            return this;
+        }
+
+        public Transaction detachChildren(SurfaceControl sc) {
+            nativeSeverChildren(mNativeObject, sc.mNativeObject);
+            return this;
+        }
+
+        public Transaction setOverrideScalingMode(SurfaceControl sc, int overrideScalingMode) {
+            nativeSetOverrideScalingMode(mNativeObject, sc.mNativeObject,
+                    overrideScalingMode);
+            return this;
+        }
+
+        /**
+         * Sets a color for the Surface.
+         * @param color A float array with three values to represent r, g, b in range [0..1]
+         */
+        public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
+            nativeSetColor(mNativeObject, sc.mNativeObject, color);
+            return this;
+        }
+
+        /**
+         * If the buffer size changes in this transaction, position and crop updates specified
+         * in this transaction will not complete until a buffer of the new size
+         * arrives. As transform matrix and size are already frozen in this fashion,
+         * this enables totally freezing the surface until the resize has completed
+         * (at which point the geometry influencing aspects of this transaction will then occur)
+         */
+        public Transaction setGeometryAppliesWithResize(SurfaceControl sc) {
+            nativeSetGeometryAppliesWithResize(mNativeObject, sc.mNativeObject);
+            return this;
+        }
+
+        /**
+         * Sets the security of the surface.  Setting the flag is equivalent to creating the
+         * Surface with the {@link #SECURE} flag.
+         */
+        Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+            if (isSecure) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SECURE);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
+         * Surface with the {@link #OPAQUE} flag.
+         */
+        public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+            if (isOpaque) {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
+            } else {
+                nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_OPAQUE);
+            }
+            return this;
+        }
+
+        public Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+
+            if (surface != null) {
+                synchronized (surface.mLock) {
+                    nativeSetDisplaySurface(mNativeObject, displayToken, surface.mNativeObject);
+                }
+            } else {
+                nativeSetDisplaySurface(mNativeObject, displayToken, 0);
+            }
+            return this;
+        }
+
+        public Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            nativeSetDisplayLayerStack(mNativeObject, displayToken, layerStack);
+            return this;
+        }
+
+        public Transaction setDisplayProjection(IBinder displayToken,
+                int orientation, Rect layerStackRect, Rect displayRect) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            if (layerStackRect == null) {
+                throw new IllegalArgumentException("layerStackRect must not be null");
+            }
+            if (displayRect == null) {
+                throw new IllegalArgumentException("displayRect must not be null");
+            }
+            nativeSetDisplayProjection(mNativeObject, displayToken, orientation,
+                    layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+                    displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+            return this;
+        }
+
+        public Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+            if (displayToken == null) {
+                throw new IllegalArgumentException("displayToken must not be null");
+            }
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("width and height must be positive");
+            }
+
+            nativeSetDisplaySize(mNativeObject, displayToken, width, height);
+            return this;
+        }
+
+        /** flag the transaction as an animation */
+        public Transaction setAnimationTransaction() {
+            nativeSetAnimationTransaction(mNativeObject);
+            return this;
+        }
+    }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 462dad3..4eab496e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -532,10 +532,15 @@
                     mDeferredDestroySurfaceControl = mSurfaceControl;
 
                     updateOpaqueFlag();
-                    mSurfaceControl = new SurfaceControlWithBackground(mSurfaceSession,
-                            "SurfaceView - " + viewRoot.getTitle().toString(),
-                            mSurfaceWidth, mSurfaceHeight, mFormat,
-                            mSurfaceFlags);
+                    final String name = "SurfaceView - " + viewRoot.getTitle().toString();
+
+                    mSurfaceControl = new SurfaceControlWithBackground(
+                            name,
+                            (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
+                            new SurfaceControl.Builder(mSurfaceSession)
+                                    .setSize(mSurfaceWidth, mSurfaceHeight)
+                                    .setFormat(mFormat)
+                                    .setFlags(mSurfaceFlags));
                 } else if (mSurfaceControl == null) {
                     return;
                 }
@@ -1098,13 +1103,15 @@
         private boolean mOpaque = true;
         public boolean mVisible = false;
 
-        public SurfaceControlWithBackground(SurfaceSession s,
-                        String name, int w, int h, int format, int flags)
+        public SurfaceControlWithBackground(String name, boolean opaque, SurfaceControl.Builder b)
                        throws Exception {
-            super(s, name, w, h, format, flags);
-            mBackgroundControl = new SurfaceControl(s, "Background for - " + name, w, h,
-                    PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM);
-            mOpaque = (flags & SurfaceControl.OPAQUE) != 0;
+            super(b.setName(name).build());
+
+            mBackgroundControl = b.setName("Background for -" + name)
+                    .setFormat(OPAQUE)
+                    .setColorLayer(true)
+                    .build();
+            mOpaque = opaque;
         }
 
         @Override
diff --git a/core/java/android/view/TouchDelegate.java b/core/java/android/view/TouchDelegate.java
index cf36f43..dc50fa1 100644
--- a/core/java/android/view/TouchDelegate.java
+++ b/core/java/android/view/TouchDelegate.java
@@ -44,7 +44,7 @@
 
     /**
      * mBounds inflated to include some slop. This rect is to track whether the motion events
-     * should be considered to be be within the delegate view.
+     * should be considered to be within the delegate view.
      */
     private Rect mSlopBounds;
 
@@ -64,14 +64,12 @@
     public static final int BELOW = 2;
 
     /**
-     * The touchable region of the View extends to the left of its
-     * actual extent.
+     * The touchable region of the View extends to the left of its actual extent.
      */
     public static final int TO_LEFT = 4;
 
     /**
-     * The touchable region of the View extends to the right of its
-     * actual extent.
+     * The touchable region of the View extends to the right of its actual extent.
      */
     public static final int TO_RIGHT = 8;
 
@@ -108,28 +106,24 @@
         boolean handled = false;
 
         switch (event.getAction()) {
-        case MotionEvent.ACTION_DOWN:
-            Rect bounds = mBounds;
-
-            if (bounds.contains(x, y)) {
-                mDelegateTargeted = true;
-                sendToDelegate = true;
-            }
-            break;
-        case MotionEvent.ACTION_UP:
-        case MotionEvent.ACTION_MOVE:
-            sendToDelegate = mDelegateTargeted;
-            if (sendToDelegate) {
-                Rect slopBounds = mSlopBounds;
-                if (!slopBounds.contains(x, y)) {
-                    hit = false;
+            case MotionEvent.ACTION_DOWN:
+                mDelegateTargeted = mBounds.contains(x, y);
+                sendToDelegate = mDelegateTargeted;
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_MOVE:
+                sendToDelegate = mDelegateTargeted;
+                if (sendToDelegate) {
+                    Rect slopBounds = mSlopBounds;
+                    if (!slopBounds.contains(x, y)) {
+                        hit = false;
+                    }
                 }
-            }
-            break;
-        case MotionEvent.ACTION_CANCEL:
-            sendToDelegate = mDelegateTargeted;
-            mDelegateTargeted = false;
-            break;
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                sendToDelegate = mDelegateTargeted;
+                mDelegateTargeted = false;
+                break;
         }
         if (sendToDelegate) {
             final View delegateView = mDelegateView;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1556297..c043dca 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1448,17 +1448,59 @@
 
     /**
      * <p>Enables low quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int DRAWING_CACHE_QUALITY_LOW = 0x00080000;
 
     /**
      * <p>Enables high quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int DRAWING_CACHE_QUALITY_HIGH = 0x00100000;
 
     /**
      * <p>Enables automatic quality mode for the drawing cache.</p>
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int DRAWING_CACHE_QUALITY_AUTO = 0x00000000;
 
     private static final int[] DRAWING_CACHE_QUALITY_FLAGS = {
@@ -2300,9 +2342,9 @@
     private static final int PFLAG_HOVERED             = 0x10000000;
 
     /**
-     * no longer needed, should be reused
+     * Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
      */
-    private static final int PFLAG_DOES_NOTHING_REUSE_PLEASE = 0x20000000;
+    private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
 
     /** {@hide} */
     static final int PFLAG_ACTIVATED                   = 0x40000000;
@@ -3742,15 +3784,90 @@
      * @hide
      */
     @ViewDebug.ExportedProperty(flagMapping = {
-        @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
-                                equals = SYSTEM_UI_FLAG_LOW_PROFILE,
-                                name = "SYSTEM_UI_FLAG_LOW_PROFILE", outputIf = true),
-        @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
-                                equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
-                                name = "SYSTEM_UI_FLAG_HIDE_NAVIGATION", outputIf = true),
-        @ViewDebug.FlagToString(mask = PUBLIC_STATUS_BAR_VISIBILITY_MASK,
-                                equals = SYSTEM_UI_FLAG_VISIBLE,
-                                name = "SYSTEM_UI_FLAG_VISIBLE", outputIf = true)
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LOW_PROFILE,
+                    equals = SYSTEM_UI_FLAG_LOW_PROFILE,
+                    name = "LOW_PROFILE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+                    equals = SYSTEM_UI_FLAG_HIDE_NAVIGATION,
+                    name = "HIDE_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_FULLSCREEN,
+                    equals = SYSTEM_UI_FLAG_FULLSCREEN,
+                    name = "FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_STABLE,
+                    name = "LAYOUT_STABLE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,
+                    name = "LAYOUT_HIDE_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                    equals = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,
+                    name = "LAYOUT_FULLSCREEN"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE,
+                    equals = SYSTEM_UI_FLAG_IMMERSIVE,
+                    name = "IMMERSIVE"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+                    equals = SYSTEM_UI_FLAG_IMMERSIVE_STICKY,
+                    name = "IMMERSIVE_STICKY"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    equals = SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    name = "LIGHT_STATUS_BAR"),
+            @ViewDebug.FlagToString(mask = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    equals = SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    name = "LIGHT_NAVIGATION_BAR"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_EXPAND,
+                    equals = STATUS_BAR_DISABLE_EXPAND,
+                    name = "STATUS_BAR_DISABLE_EXPAND"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_ICONS,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_ICONS"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_ALERTS,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_ALERTS"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+                    equals = STATUS_BAR_DISABLE_NOTIFICATION_TICKER,
+                    name = "STATUS_BAR_DISABLE_NOTIFICATION_TICKER"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SYSTEM_INFO,
+                    equals = STATUS_BAR_DISABLE_SYSTEM_INFO,
+                    name = "STATUS_BAR_DISABLE_SYSTEM_INFO"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_HOME,
+                    equals = STATUS_BAR_DISABLE_HOME,
+                    name = "STATUS_BAR_DISABLE_HOME"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_BACK,
+                    equals = STATUS_BAR_DISABLE_BACK,
+                    name = "STATUS_BAR_DISABLE_BACK"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_CLOCK,
+                    equals = STATUS_BAR_DISABLE_CLOCK,
+                    name = "STATUS_BAR_DISABLE_CLOCK"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_RECENT,
+                    equals = STATUS_BAR_DISABLE_RECENT,
+                    name = "STATUS_BAR_DISABLE_RECENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_DISABLE_SEARCH,
+                    equals = STATUS_BAR_DISABLE_SEARCH,
+                    name = "STATUS_BAR_DISABLE_SEARCH"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSIENT,
+                    equals = STATUS_BAR_TRANSIENT,
+                    name = "STATUS_BAR_TRANSIENT"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSIENT,
+                    equals = NAVIGATION_BAR_TRANSIENT,
+                    name = "NAVIGATION_BAR_TRANSIENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_UNHIDE,
+                    equals = STATUS_BAR_UNHIDE,
+                    name = "STATUS_BAR_UNHIDE"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_UNHIDE,
+                    equals = NAVIGATION_BAR_UNHIDE,
+                    name = "NAVIGATION_BAR_UNHIDE"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSLUCENT,
+                    equals = STATUS_BAR_TRANSLUCENT,
+                    name = "STATUS_BAR_TRANSLUCENT"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSLUCENT,
+                    equals = NAVIGATION_BAR_TRANSLUCENT,
+                    name = "NAVIGATION_BAR_TRANSLUCENT"),
+            @ViewDebug.FlagToString(mask = NAVIGATION_BAR_TRANSPARENT,
+                    equals = NAVIGATION_BAR_TRANSPARENT,
+                    name = "NAVIGATION_BAR_TRANSPARENT"),
+            @ViewDebug.FlagToString(mask = STATUS_BAR_TRANSPARENT,
+                    equals = STATUS_BAR_TRANSPARENT,
+                    name = "STATUS_BAR_TRANSPARENT")
     }, formatToHexString = true)
     int mSystemUiVisibility;
 
@@ -6280,6 +6397,42 @@
         return null;
     }
 
+    /** @hide */
+    public void setNotifyAutofillManagerOnClick(boolean notify) {
+        if (notify) {
+            mPrivateFlags |= PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+        } else {
+            mPrivateFlags &= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+        }
+    }
+
+    private void notifyAutofillManagerOnClick() {
+        if ((mPrivateFlags & PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK) != 0) {
+            try {
+                getAutofillManager().notifyViewClicked(this);
+            } finally {
+                // Set it to already called so it's not called twice when called by
+                // performClickInternal()
+                mPrivateFlags |= ~PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK;
+            }
+        }
+    }
+
+    /**
+     * Entry point for {@link #performClick()} - other methods on View should call it instead of
+     * {@code performClick()} directly to make sure the autofill manager is notified when
+     * necessary (as subclasses could extend {@code performClick()} without calling the parent's
+     * method).
+     */
+    private boolean performClickInternal() {
+        // Must notify autofill manager before performing the click actions to avoid scenarios where
+        // the app has a click listener that changes the state of views the autofill service might
+        // be interested on.
+        notifyAutofillManagerOnClick();
+
+        return performClick();
+    }
+
     /**
      * Call this view's OnClickListener, if it is defined.  Performs all normal
      * actions associated with clicking: reporting accessibility event, playing
@@ -6288,7 +6441,14 @@
      * @return True there was an assigned OnClickListener that was called, false
      *         otherwise is returned.
      */
+    // NOTE: other methods on View should not call this method directly, but performClickInternal()
+    // instead, to guarantee that the autofill manager is notified when necessary (as subclasses
+    // could extend this method without calling super.performClick()).
     public boolean performClick() {
+        // We still need to call this method to handle the cases where performClick() was called
+        // externally, instead of through performClickInternal()
+        notifyAutofillManagerOnClick();
+
         final boolean result;
         final ListenerInfo li = mListenerInfo;
         if (li != null && li.mOnClickListener != null) {
@@ -8832,7 +8992,21 @@
      * @see #isDrawingCacheEnabled()
      *
      * @attr ref android.R.styleable#View_drawingCacheQuality
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @DrawingCacheQuality
     public int getDrawingCacheQuality() {
         return mViewFlags & DRAWING_CACHE_QUALITY_MASK;
@@ -8850,7 +9024,21 @@
      * @see #isDrawingCacheEnabled()
      *
      * @attr ref android.R.styleable#View_drawingCacheQuality
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setDrawingCacheQuality(@DrawingCacheQuality int quality) {
         setFlags(quality, DRAWING_CACHE_QUALITY_MASK);
     }
@@ -11358,7 +11546,7 @@
         switch (action) {
             case AccessibilityNodeInfo.ACTION_CLICK: {
                 if (isClickable()) {
-                    performClick();
+                    performClickInternal();
                     return true;
                 }
             } break;
@@ -12470,7 +12658,7 @@
                     // This is a tap, so remove the longpress check
                     removeLongPressCallback();
                     if (!event.isCanceled()) {
-                        return performClick();
+                        return performClickInternal();
                     }
                 }
             }
@@ -13042,7 +13230,7 @@
                                     mPerformClick = new PerformClick();
                                 }
                                 if (!post(mPerformClick)) {
-                                    performClick();
+                                    performClickInternal();
                                 }
                             }
                         }
@@ -15454,7 +15642,13 @@
      * {@code dirty}.
      *
      * @param dirty the rectangle representing the bounds of the dirty region
+     *
+     * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+     * the importance of the dirty rectangle. In API 21 the given rectangle is
+     * ignored entirely in favor of an internally-calculated area instead.
+     * Because of this, clients are encouraged to just call {@link #invalidate()}.
      */
+    @Deprecated
     public void invalidate(Rect dirty) {
         final int scrollX = mScrollX;
         final int scrollY = mScrollY;
@@ -15475,7 +15669,13 @@
      * @param t the top position of the dirty region
      * @param r the right position of the dirty region
      * @param b the bottom position of the dirty region
+     *
+     * @deprecated The switch to hardware accelerated rendering in API 14 reduced
+     * the importance of the dirty rectangle. In API 21 the given rectangle is
+     * ignored entirely in favor of an internally-calculated area instead.
+     * Because of this, clients are encouraged to just call {@link #invalidate()}.
      */
+    @Deprecated
     public void invalidate(int l, int t, int r, int b) {
         final int scrollX = mScrollX;
         final int scrollY = mScrollY;
@@ -18016,7 +18216,21 @@
      * @see #getDrawingCache()
      * @see #buildDrawingCache()
      * @see #setLayerType(int, android.graphics.Paint)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setDrawingCacheEnabled(boolean enabled) {
         mCachingFailed = false;
         setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
@@ -18029,7 +18243,21 @@
      *
      * @see #setDrawingCacheEnabled(boolean)
      * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isDrawingCacheEnabled() {
         return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
@@ -18043,10 +18271,11 @@
      */
     @SuppressWarnings({"UnusedDeclaration"})
     public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
-        Log.d("View", indent + this + "             DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
-                ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
-                (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
-                ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
+        Log.d(VIEW_LOG_TAG, indent + this + "             DIRTY("
+                + (mPrivateFlags & View.PFLAG_DIRTY_MASK)
+                + ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID("
+                + (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID)
+                + ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
         if (clear) {
             mPrivateFlags &= clearMask;
         }
@@ -18170,7 +18399,21 @@
      * @return A non-scaled bitmap representing this view or null if cache is disabled.
      *
      * @see #getDrawingCache(boolean)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public Bitmap getDrawingCache() {
         return getDrawingCache(false);
     }
@@ -18201,7 +18444,21 @@
      * @see #isDrawingCacheEnabled()
      * @see #buildDrawingCache(boolean)
      * @see #destroyDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public Bitmap getDrawingCache(boolean autoScale) {
         if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
             return null;
@@ -18221,7 +18478,21 @@
      * @see #setDrawingCacheEnabled(boolean)
      * @see #buildDrawingCache()
      * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void destroyDrawingCache() {
         if (mDrawingCache != null) {
             mDrawingCache.recycle();
@@ -18243,7 +18514,21 @@
      * @see #setDrawingCacheEnabled(boolean)
      * @see #buildDrawingCache()
      * @see #getDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setDrawingCacheBackgroundColor(@ColorInt int color) {
         if (color != mDrawingCacheBackgroundColor) {
             mDrawingCacheBackgroundColor = color;
@@ -18255,7 +18540,21 @@
      * @see #setDrawingCacheBackgroundColor(int)
      *
      * @return The background color to used for the drawing cache's bitmap
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @ColorInt
     public int getDrawingCacheBackgroundColor() {
         return mDrawingCacheBackgroundColor;
@@ -18265,7 +18564,21 @@
      * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
      *
      * @see #buildDrawingCache(boolean)
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void buildDrawingCache() {
         buildDrawingCache(false);
     }
@@ -18292,7 +18605,21 @@
      *
      * @see #getDrawingCache()
      * @see #destroyDrawingCache()
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void buildDrawingCache(boolean autoScale) {
         if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
                 mDrawingCache == null : mUnscaledDrawingCache == null)) {
@@ -19725,7 +20052,7 @@
         boolean changed = false;
 
         if (DBG) {
-            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                     + right + "," + bottom + ")");
         }
 
@@ -24771,7 +25098,7 @@
     private final class PerformClick implements Runnable {
         @Override
         public void run() {
-            performClick();
+            performClickInternal();
         }
     }
 
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 574137b..4500862 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -84,12 +84,17 @@
 
     /**
      * Defines the duration in milliseconds a user needs to hold down the
-     * appropriate button to bring up the accessibility shortcut (first time) or enable it
-     * (once shortcut is configured).
+     * appropriate button to bring up the accessibility shortcut for the first time
      */
     private static final int A11Y_SHORTCUT_KEY_TIMEOUT = 3000;
 
     /**
+     * Defines the duration in milliseconds a user needs to hold down the
+     * appropriate button to enable the accessibility shortcut once it's configured.
+     */
+    private static final int A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION = 1500;
+
+    /**
      * Defines the duration in milliseconds we will wait to see if a touch event
      * is a tap or a scroll. If the user does not move within this interval, it is
      * considered to be a tap.
@@ -851,6 +856,15 @@
     }
 
     /**
+     * @return The amount of time a user needs to press the relevant keys to activate the
+     *   accessibility shortcut after it's confirmed that accessibility shortcut is used.
+     * @hide
+     */
+    public long getAccessibilityShortcutKeyTimeoutAfterConfirmation() {
+        return A11Y_SHORTCUT_KEY_TIMEOUT_AFTER_CONFIRMATION;
+    }
+
+    /**
      * The amount of friction applied to scrolls and flings.
      *
      * @return A scalar dimensionless value representing the coefficient of
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 66c0578..afa9413 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -528,84 +528,23 @@
     /** @hide */
     public static void profileViewAndChildren(final View view, BufferedWriter out)
             throws IOException {
-        profileViewAndChildren(view, out, true);
+        RenderNode node = RenderNode.create("ViewDebug", null);
+        profileViewAndChildren(view, node, out, true);
+        node.destroy();
     }
 
-    private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
-            throws IOException {
-
+    private static void profileViewAndChildren(View view, RenderNode node, BufferedWriter out,
+            boolean root) throws IOException {
         long durationMeasure =
                 (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
-                        ? profileViewOperation(view, new ViewOperation<Void>() {
-                    public Void[] pre() {
-                        forceLayout(view);
-                        return null;
-                    }
-
-                    private void forceLayout(View view) {
-                        view.forceLayout();
-                        if (view instanceof ViewGroup) {
-                            ViewGroup group = (ViewGroup) view;
-                            final int count = group.getChildCount();
-                            for (int i = 0; i < count; i++) {
-                                forceLayout(group.getChildAt(i));
-                            }
-                        }
-                    }
-
-                    public void run(Void... data) {
-                        view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
-                    }
-
-                    public void post(Void... data) {
-                    }
-                })
-                        : 0;
+                        ? profileViewMeasure(view) : 0;
         long durationLayout =
                 (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
-                        ? profileViewOperation(view, new ViewOperation<Void>() {
-                    public Void[] pre() {
-                        return null;
-                    }
-
-                    public void run(Void... data) {
-                        view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
-                    }
-
-                    public void post(Void... data) {
-                    }
-                }) : 0;
+                        ? profileViewLayout(view) : 0;
         long durationDraw =
                 (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
-                        ? profileViewOperation(view, new ViewOperation<Object>() {
-                    public Object[] pre() {
-                        final DisplayMetrics metrics =
-                                (view != null && view.getResources() != null) ?
-                                        view.getResources().getDisplayMetrics() : null;
-                        final Bitmap bitmap = metrics != null ?
-                                Bitmap.createBitmap(metrics, metrics.widthPixels,
-                                        metrics.heightPixels, Bitmap.Config.RGB_565) : null;
-                        final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
-                        return new Object[] {
-                                bitmap, canvas
-                        };
-                    }
+                        ? profileViewDraw(view, node) : 0;
 
-                    public void run(Object... data) {
-                        if (data[1] != null) {
-                            view.draw((Canvas) data[1]);
-                        }
-                    }
-
-                    public void post(Object... data) {
-                        if (data[1] != null) {
-                            ((Canvas) data[1]).setBitmap(null);
-                        }
-                        if (data[0] != null) {
-                            ((Bitmap) data[0]).recycle();
-                        }
-                    }
-                }) : 0;
         out.write(String.valueOf(durationMeasure));
         out.write(' ');
         out.write(String.valueOf(durationLayout));
@@ -616,34 +555,86 @@
             ViewGroup group = (ViewGroup) view;
             final int count = group.getChildCount();
             for (int i = 0; i < count; i++) {
-                profileViewAndChildren(group.getChildAt(i), out, false);
+                profileViewAndChildren(group.getChildAt(i), node, out, false);
             }
         }
     }
 
-    interface ViewOperation<T> {
-        T[] pre();
-        void run(T... data);
-        void post(T... data);
+    private static long profileViewMeasure(final View view) {
+        return profileViewOperation(view, new ViewOperation() {
+            @Override
+            public void pre() {
+                forceLayout(view);
+            }
+
+            private void forceLayout(View view) {
+                view.forceLayout();
+                if (view instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) view;
+                    final int count = group.getChildCount();
+                    for (int i = 0; i < count; i++) {
+                        forceLayout(group.getChildAt(i));
+                    }
+                }
+            }
+
+            @Override
+            public void run() {
+                view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+            }
+        });
     }
 
-    private static <T> long profileViewOperation(View view, final ViewOperation<T> operation) {
+    private static long profileViewLayout(View view) {
+        return profileViewOperation(view,
+                () -> view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom));
+    }
+
+    private static long profileViewDraw(View view, RenderNode node) {
+        DisplayMetrics dm = view.getResources().getDisplayMetrics();
+        if (dm == null) {
+            return 0;
+        }
+
+        if (view.isHardwareAccelerated()) {
+            DisplayListCanvas canvas = node.start(dm.widthPixels, dm.heightPixels);
+            try {
+                return profileViewOperation(view, () -> view.draw(canvas));
+            } finally {
+                node.end(canvas);
+            }
+        } else {
+            Bitmap bitmap = Bitmap.createBitmap(
+                    dm, dm.widthPixels, dm.heightPixels, Bitmap.Config.RGB_565);
+            Canvas canvas = new Canvas(bitmap);
+            try {
+                return profileViewOperation(view, () -> view.draw(canvas));
+            } finally {
+                canvas.setBitmap(null);
+                bitmap.recycle();
+            }
+        }
+    }
+
+    interface ViewOperation {
+        default void pre() {}
+
+        void run();
+    }
+
+    private static long profileViewOperation(View view, final ViewOperation operation) {
         final CountDownLatch latch = new CountDownLatch(1);
         final long[] duration = new long[1];
 
-        view.post(new Runnable() {
-            public void run() {
-                try {
-                    T[] data = operation.pre();
-                    long start = Debug.threadCpuTimeNanos();
-                    //noinspection unchecked
-                    operation.run(data);
-                    duration[0] = Debug.threadCpuTimeNanos() - start;
-                    //noinspection unchecked
-                    operation.post(data);
-                } finally {
-                    latch.countDown();
-                }
+        view.post(() -> {
+            try {
+                operation.pre();
+                long start = Debug.threadCpuTimeNanos();
+                //noinspection unchecked
+                operation.run();
+                duration[0] = Debug.threadCpuTimeNanos() - start;
+            } finally {
+                latch.countDown();
             }
         });
 
@@ -1375,6 +1366,81 @@
         }
     }
 
+    /**
+     * Converts an integer from a field that is mapped with {@link IntToString} to its string
+     * representation.
+     *
+     * @param clazz The class the field is defined on.
+     * @param field The field on which the {@link ExportedProperty} is defined on.
+     * @param integer The value to convert.
+     * @return The value converted into its string representation.
+     * @hide
+     */
+    public static String intToString(Class<?> clazz, String field, int integer) {
+        final IntToString[] mapping = getMapping(clazz, field);
+        if (mapping == null) {
+            return Integer.toString(integer);
+        }
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final IntToString map = mapping[j];
+            if (map.from() == integer) {
+                return map.to();
+            }
+        }
+        return Integer.toString(integer);
+    }
+
+    /**
+     * Converts a set of flags from a field that is mapped with {@link FlagToString} to its string
+     * representation.
+     *
+     * @param clazz The class the field is defined on.
+     * @param field The field on which the {@link ExportedProperty} is defined on.
+     * @param flags The flags to convert.
+     * @return The flags converted into their string representations.
+     * @hide
+     */
+    public static String flagsToString(Class<?> clazz, String field, int flags) {
+        final FlagToString[] mapping = getFlagMapping(clazz, field);
+        if (mapping == null) {
+            return Integer.toHexString(flags);
+        }
+        final StringBuilder result = new StringBuilder();
+        final int count = mapping.length;
+        for (int j = 0; j < count; j++) {
+            final FlagToString flagMapping = mapping[j];
+            final boolean ifTrue = flagMapping.outputIf();
+            final int maskResult = flags & flagMapping.mask();
+            final boolean test = maskResult == flagMapping.equals();
+            if (test && ifTrue) {
+                final String name = flagMapping.name();
+                result.append(name).append(' ');
+            }
+        }
+        if (result.length() > 0) {
+            result.deleteCharAt(result.length() - 1);
+        }
+        return result.toString();
+    }
+
+    private static FlagToString[] getFlagMapping(Class<?> clazz, String field) {
+        try {
+            return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class)
+                    .flagMapping();
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
+    private static IntToString[] getMapping(Class<?> clazz, String field) {
+        try {
+            return clazz.getDeclaredField(field).getAnnotation(ExportedProperty.class).mapping();
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
     private static void exportUnrolledArray(Context context, BufferedWriter out,
             ExportedProperty property, int[] array, String prefix, String suffix)
             throws IOException {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b2e5a16..929beae 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -421,22 +421,78 @@
 
     /**
      * Used to indicate that no drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_NO_CACHE = 0x0;
 
     /**
      * Used to indicate that the animation drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_ANIMATION_CACHE = 0x1;
 
     /**
      * Used to indicate that the scrolling drawing cache should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_SCROLLING_CACHE = 0x2;
 
     /**
      * Used to indicate that all drawing caches should be kept in memory.
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public static final int PERSISTENT_ALL_CACHES = 0x3;
 
     // Layout Modes
@@ -3769,7 +3825,21 @@
      * Enables or disables the drawing cache for each child of this view group.
      *
      * @param enabled true to enable the cache, false to dispose of it
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
         if (enabled || (mPersistentDrawingCache & PERSISTENT_ALL_CACHES) != PERSISTENT_ALL_CACHES) {
             final View[] children = mChildren;
@@ -6331,7 +6401,21 @@
      * @return one or a combination of {@link #PERSISTENT_NO_CACHE},
      *         {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
      *         and {@link #PERSISTENT_ALL_CACHES}
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     @ViewDebug.ExportedProperty(category = "drawing", mapping = {
         @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE,        to = "NONE"),
         @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
@@ -6352,7 +6436,21 @@
      * @param drawingCacheToKeep one or a combination of {@link #PERSISTENT_NO_CACHE},
      *        {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
      *        and {@link #PERSISTENT_ALL_CACHES}
+     *
+     * @deprecated The view drawing cache was largely made obsolete with the introduction of
+     * hardware-accelerated rendering in API 11. With hardware-acceleration, intermediate cache
+     * layers are largely unnecessary and can easily result in a net loss in performance due to the
+     * cost of creating and updating the layer. In the rare cases where caching layers are useful,
+     * such as for alpha animations, {@link #setLayerType(int, Paint)} handles this with hardware
+     * rendering. For software-rendered snapshots of a small part of the View hierarchy or
+     * individual Views it is recommended to create a {@link Canvas} from either a {@link Bitmap} or
+     * {@link android.graphics.Picture} and call {@link #draw(Canvas)} on the View. However these
+     * software-rendered usages are discouraged and have compatibility issues with hardware-only
+     * rendering features such as {@link android.graphics.Bitmap.Config#HARDWARE Config.HARDWARE}
+     * bitmaps, real-time shadows, and outline clipping. For screenshots of the UI for feedback
+     * reports or unit testing the {@link PixelCopy} API is recommended.
      */
+    @Deprecated
     public void setPersistentDrawingCache(int drawingCacheToKeep) {
         mPersistentDrawingCache = drawingCacheToKeep & PERSISTENT_ALL_CACHES;
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 415aad5..37829f0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -72,6 +72,7 @@
 import android.util.Log;
 import android.util.MergedConfiguration;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.view.Surface.OutOfResourcesException;
@@ -366,7 +367,7 @@
 
     // These can be accessed by any thread, must be protected with a lock.
     // Surface can never be reassigned or cleared (use Surface.clear()).
-    final Surface mSurface = new Surface();
+    public final Surface mSurface = new Surface();
 
     boolean mAdded;
     boolean mAddedTouchMode;
@@ -512,7 +513,7 @@
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
 
         if (!sCompatibilityDone) {
-            sAlwaysAssignFocus = true;
+            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
 
             sCompatibilityDone = true;
         }
@@ -1668,8 +1669,6 @@
             host.dispatchAttachedToWindow(mAttachInfo, 0);
             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
             dispatchApplyInsets(host);
-            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
-
         } else {
             desiredWindowWidth = frame.width();
             desiredWindowHeight = frame.height();
@@ -2285,18 +2284,36 @@
             }
         }
 
-        if (mFirst && sAlwaysAssignFocus) {
-            // handle first focus request
-            if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
-                    + mView.hasFocus());
-            if (mView != null) {
-                if (!mView.hasFocus()) {
-                    mView.restoreDefaultFocus();
-                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
-                            + mView.findFocus());
-                } else {
-                    if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
-                            + mView.findFocus());
+        if (mFirst) {
+            if (sAlwaysAssignFocus) {
+                // handle first focus request
+                if (DEBUG_INPUT_RESIZE) {
+                    Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
+                }
+                if (mView != null) {
+                    if (!mView.hasFocus()) {
+                        mView.restoreDefaultFocus();
+                        if (DEBUG_INPUT_RESIZE) {
+                            Log.v(mTag, "First: requested focused view=" + mView.findFocus());
+                        }
+                    } else {
+                        if (DEBUG_INPUT_RESIZE) {
+                            Log.v(mTag, "First: existing focused view=" + mView.findFocus());
+                        }
+                    }
+                }
+            } else {
+                // Some views (like ScrollView) won't hand focus to descendants that aren't within
+                // their viewport. Before layout, there's a good change these views are size 0
+                // which means no children can get focus. After layout, this view now has size, but
+                // is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
+                // case where the child has a size prior to layout and thus won't trigger
+                // focusableViewAvailable).
+                View focused = mView.findFocus();
+                if (focused instanceof ViewGroup
+                        && ((ViewGroup) focused).getDescendantFocusability()
+                                == ViewGroup.FOCUS_AFTER_DESCENDANTS) {
+                    focused.restoreDefaultFocus();
                 }
             }
         }
@@ -2827,7 +2844,7 @@
                 try {
                     mWindowDrawCountDown.await();
                 } catch (InterruptedException e) {
-                    Log.e(mTag, "Window redraw count down interruped!");
+                    Log.e(mTag, "Window redraw count down interrupted!");
                 }
                 mWindowDrawCountDown = null;
             }
@@ -2897,8 +2914,6 @@
         final float appScale = mAttachInfo.mApplicationScale;
         final boolean scalingRequired = mAttachInfo.mScalingRequired;
 
-        int resizeAlpha = 0;
-
         final Rect dirty = mDirty;
         if (mSurfaceHolder != null) {
             // The app owns the surface, we won't draw.
@@ -3469,6 +3484,7 @@
     }
 
     void dispatchDetachedFromWindow() {
+        mFirstInputStage.onDetachedFromWindow();
         if (mView != null && mView.mAttachInfo != null) {
             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
             mView.dispatchDetachedFromWindow();
@@ -3731,266 +3747,273 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-            case MSG_INVALIDATE:
-                ((View) msg.obj).invalidate();
-                break;
-            case MSG_INVALIDATE_RECT:
-                final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
-                info.target.invalidate(info.left, info.top, info.right, info.bottom);
-                info.recycle();
-                break;
-            case MSG_PROCESS_INPUT_EVENTS:
-                mProcessInputEventsScheduled = false;
-                doProcessInputEvents();
-                break;
-            case MSG_DISPATCH_APP_VISIBILITY:
-                handleAppVisibility(msg.arg1 != 0);
-                break;
-            case MSG_DISPATCH_GET_NEW_SURFACE:
-                handleGetNewSurface();
-                break;
-            case MSG_RESIZED: {
-                // Recycled in the fall through...
-                SomeArgs args = (SomeArgs) msg.obj;
-                if (mWinFrame.equals(args.arg1)
-                        && mPendingOverscanInsets.equals(args.arg5)
-                        && mPendingContentInsets.equals(args.arg2)
-                        && mPendingStableInsets.equals(args.arg6)
-                        && mPendingVisibleInsets.equals(args.arg3)
-                        && mPendingOutsets.equals(args.arg7)
-                        && mPendingBackDropFrame.equals(args.arg8)
-                        && args.arg4 == null
-                        && args.argi1 == 0
-                        && mDisplay.getDisplayId() == args.argi3) {
+                case MSG_INVALIDATE:
+                    ((View) msg.obj).invalidate();
                     break;
-                }
-                } // fall through...
-            case MSG_RESIZED_REPORT:
-                if (mAdded) {
+                case MSG_INVALIDATE_RECT:
+                    final View.AttachInfo.InvalidateInfo info =
+                            (View.AttachInfo.InvalidateInfo) msg.obj;
+                    info.target.invalidate(info.left, info.top, info.right, info.bottom);
+                    info.recycle();
+                    break;
+                case MSG_PROCESS_INPUT_EVENTS:
+                    mProcessInputEventsScheduled = false;
+                    doProcessInputEvents();
+                    break;
+                case MSG_DISPATCH_APP_VISIBILITY:
+                    handleAppVisibility(msg.arg1 != 0);
+                    break;
+                case MSG_DISPATCH_GET_NEW_SURFACE:
+                    handleGetNewSurface();
+                    break;
+                case MSG_RESIZED: {
+                    // Recycled in the fall through...
                     SomeArgs args = (SomeArgs) msg.obj;
-
-                    final int displayId = args.argi3;
-                    MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
-                    final boolean displayChanged = mDisplay.getDisplayId() != displayId;
-
-                    if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
-                        // If configuration changed - notify about that and, maybe, about move to
-                        // display.
-                        performConfigurationChange(mergedConfiguration, false /* force */,
-                                displayChanged ? displayId : INVALID_DISPLAY /* same display */);
-                    } else if (displayChanged) {
-                        // Moved to display without config change - report last applied one.
-                        onMovedToDisplay(displayId, mLastConfigurationFromResources);
+                    if (mWinFrame.equals(args.arg1)
+                            && mPendingOverscanInsets.equals(args.arg5)
+                            && mPendingContentInsets.equals(args.arg2)
+                            && mPendingStableInsets.equals(args.arg6)
+                            && mPendingVisibleInsets.equals(args.arg3)
+                            && mPendingOutsets.equals(args.arg7)
+                            && mPendingBackDropFrame.equals(args.arg8)
+                            && args.arg4 == null
+                            && args.argi1 == 0
+                            && mDisplay.getDisplayId() == args.argi3) {
+                        break;
                     }
+                } // fall through...
+                case MSG_RESIZED_REPORT:
+                    if (mAdded) {
+                        SomeArgs args = (SomeArgs) msg.obj;
 
-                    final boolean framesChanged = !mWinFrame.equals(args.arg1)
-                            || !mPendingOverscanInsets.equals(args.arg5)
-                            || !mPendingContentInsets.equals(args.arg2)
-                            || !mPendingStableInsets.equals(args.arg6)
-                            || !mPendingVisibleInsets.equals(args.arg3)
-                            || !mPendingOutsets.equals(args.arg7);
+                        final int displayId = args.argi3;
+                        MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
+                        final boolean displayChanged = mDisplay.getDisplayId() != displayId;
 
-                    mWinFrame.set((Rect) args.arg1);
-                    mPendingOverscanInsets.set((Rect) args.arg5);
-                    mPendingContentInsets.set((Rect) args.arg2);
-                    mPendingStableInsets.set((Rect) args.arg6);
-                    mPendingVisibleInsets.set((Rect) args.arg3);
-                    mPendingOutsets.set((Rect) args.arg7);
-                    mPendingBackDropFrame.set((Rect) args.arg8);
-                    mForceNextWindowRelayout = args.argi1 != 0;
-                    mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+                        if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
+                            // If configuration changed - notify about that and, maybe,
+                            // about move to display.
+                            performConfigurationChange(mergedConfiguration, false /* force */,
+                                    displayChanged
+                                            ? displayId : INVALID_DISPLAY /* same display */);
+                        } else if (displayChanged) {
+                            // Moved to display without config change - report last applied one.
+                            onMovedToDisplay(displayId, mLastConfigurationFromResources);
+                        }
 
-                    args.recycle();
+                        final boolean framesChanged = !mWinFrame.equals(args.arg1)
+                                || !mPendingOverscanInsets.equals(args.arg5)
+                                || !mPendingContentInsets.equals(args.arg2)
+                                || !mPendingStableInsets.equals(args.arg6)
+                                || !mPendingVisibleInsets.equals(args.arg3)
+                                || !mPendingOutsets.equals(args.arg7);
 
-                    if (msg.what == MSG_RESIZED_REPORT) {
-                        reportNextDraw();
+                        mWinFrame.set((Rect) args.arg1);
+                        mPendingOverscanInsets.set((Rect) args.arg5);
+                        mPendingContentInsets.set((Rect) args.arg2);
+                        mPendingStableInsets.set((Rect) args.arg6);
+                        mPendingVisibleInsets.set((Rect) args.arg3);
+                        mPendingOutsets.set((Rect) args.arg7);
+                        mPendingBackDropFrame.set((Rect) args.arg8);
+                        mForceNextWindowRelayout = args.argi1 != 0;
+                        mPendingAlwaysConsumeNavBar = args.argi2 != 0;
+
+                        args.recycle();
+
+                        if (msg.what == MSG_RESIZED_REPORT) {
+                            reportNextDraw();
+                        }
+
+                        if (mView != null && framesChanged) {
+                            forceLayout(mView);
+                        }
+                        requestLayout();
                     }
+                    break;
+                case MSG_WINDOW_MOVED:
+                    if (mAdded) {
+                        final int w = mWinFrame.width();
+                        final int h = mWinFrame.height();
+                        final int l = msg.arg1;
+                        final int t = msg.arg2;
+                        mWinFrame.left = l;
+                        mWinFrame.right = l + w;
+                        mWinFrame.top = t;
+                        mWinFrame.bottom = t + h;
 
-                    if (mView != null && framesChanged) {
-                        forceLayout(mView);
+                        mPendingBackDropFrame.set(mWinFrame);
+                        maybeHandleWindowMove(mWinFrame);
                     }
-                    requestLayout();
-                }
-                break;
-            case MSG_WINDOW_MOVED:
-                if (mAdded) {
-                    final int w = mWinFrame.width();
-                    final int h = mWinFrame.height();
-                    final int l = msg.arg1;
-                    final int t = msg.arg2;
-                    mWinFrame.left = l;
-                    mWinFrame.right = l + w;
-                    mWinFrame.top = t;
-                    mWinFrame.bottom = t + h;
+                    break;
+                case MSG_WINDOW_FOCUS_CHANGED: {
+                    final boolean hasWindowFocus = msg.arg1 != 0;
+                    if (mAdded) {
+                        mAttachInfo.mHasWindowFocus = hasWindowFocus;
 
-                    mPendingBackDropFrame.set(mWinFrame);
-                    maybeHandleWindowMove(mWinFrame);
-                }
-                break;
-            case MSG_WINDOW_FOCUS_CHANGED: {
-                if (mAdded) {
-                    boolean hasWindowFocus = msg.arg1 != 0;
-                    mAttachInfo.mHasWindowFocus = hasWindowFocus;
+                        profileRendering(hasWindowFocus);
 
-                    profileRendering(hasWindowFocus);
-
-                    if (hasWindowFocus) {
-                        boolean inTouchMode = msg.arg2 != 0;
-                        ensureTouchModeLocally(inTouchMode);
-
-                        if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()){
-                            mFullRedrawNeeded = true;
-                            try {
-                                final WindowManager.LayoutParams lp = mWindowAttributes;
-                                final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
-                                mAttachInfo.mThreadedRenderer.initializeIfNeeded(
-                                        mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
-                            } catch (OutOfResourcesException e) {
-                                Log.e(mTag, "OutOfResourcesException locking surface", e);
+                        if (hasWindowFocus) {
+                            boolean inTouchMode = msg.arg2 != 0;
+                            ensureTouchModeLocally(inTouchMode);
+                            if (mAttachInfo.mThreadedRenderer != null && mSurface.isValid()) {
+                                mFullRedrawNeeded = true;
                                 try {
-                                    if (!mWindowSession.outOfMemory(mWindow)) {
-                                        Slog.w(mTag, "No processes killed for memory; killing self");
-                                        Process.killProcess(Process.myPid());
+                                    final WindowManager.LayoutParams lp = mWindowAttributes;
+                                    final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null;
+                                    mAttachInfo.mThreadedRenderer.initializeIfNeeded(
+                                            mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
+                                } catch (OutOfResourcesException e) {
+                                    Log.e(mTag, "OutOfResourcesException locking surface", e);
+                                    try {
+                                        if (!mWindowSession.outOfMemory(mWindow)) {
+                                            Slog.w(mTag, "No processes killed for memory;"
+                                                    + " killing self");
+                                            Process.killProcess(Process.myPid());
+                                        }
+                                    } catch (RemoteException ex) {
                                     }
-                                } catch (RemoteException ex) {
+                                    // Retry in a bit.
+                                    sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2),
+                                            500);
+                                    return;
                                 }
-                                // Retry in a bit.
-                                sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500);
-                                return;
+                            }
+                        }
+
+                        mLastWasImTarget = WindowManager.LayoutParams
+                                .mayUseInputMethod(mWindowAttributes.flags);
+
+                        InputMethodManager imm = InputMethodManager.peekInstance();
+                        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+                            imm.onPreWindowFocus(mView, hasWindowFocus);
+                        }
+                        if (mView != null) {
+                            mAttachInfo.mKeyDispatchState.reset();
+                            mView.dispatchWindowFocusChanged(hasWindowFocus);
+                            mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+
+                            if (mAttachInfo.mTooltipHost != null) {
+                                mAttachInfo.mTooltipHost.hideTooltip();
+                            }
+                        }
+
+                        // Note: must be done after the focus change callbacks,
+                        // so all of the view state is set up correctly.
+                        if (hasWindowFocus) {
+                            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
+                                imm.onPostWindowFocus(mView, mView.findFocus(),
+                                        mWindowAttributes.softInputMode,
+                                        !mHasHadWindowFocus, mWindowAttributes.flags);
+                            }
+                            // Clear the forward bit.  We can just do this directly, since
+                            // the window manager doesn't care about it.
+                            mWindowAttributes.softInputMode &=
+                                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                            ((WindowManager.LayoutParams) mView.getLayoutParams())
+                                    .softInputMode &=
+                                        ~WindowManager.LayoutParams
+                                                .SOFT_INPUT_IS_FORWARD_NAVIGATION;
+                            mHasHadWindowFocus = true;
+                        } else {
+                            if (mPointerCapture) {
+                                handlePointerCaptureChanged(false);
                             }
                         }
                     }
-
-                    mLastWasImTarget = WindowManager.LayoutParams
-                            .mayUseInputMethod(mWindowAttributes.flags);
-
+                    mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+                } break;
+                case MSG_DIE:
+                    doDie();
+                    break;
+                case MSG_DISPATCH_INPUT_EVENT: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    InputEvent event = (InputEvent) args.arg1;
+                    InputEventReceiver receiver = (InputEventReceiver) args.arg2;
+                    enqueueInputEvent(event, receiver, 0, true);
+                    args.recycle();
+                } break;
+                case MSG_SYNTHESIZE_INPUT_EVENT: {
+                    InputEvent event = (InputEvent) msg.obj;
+                    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+                } break;
+                case MSG_DISPATCH_KEY_FROM_IME: {
+                    if (LOCAL_LOGV) {
+                        Log.v(TAG, "Dispatching key " + msg.obj + " from IME to " + mView);
+                    }
+                    KeyEvent event = (KeyEvent) msg.obj;
+                    if ((event.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) != 0) {
+                        // The IME is trying to say this event is from the
+                        // system!  Bad bad bad!
+                        //noinspection UnusedAssignment
+                        event = KeyEvent.changeFlags(event,
+                                event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+                    }
+                    enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
+                } break;
+                case MSG_CHECK_FOCUS: {
                     InputMethodManager imm = InputMethodManager.peekInstance();
-                    if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-                        imm.onPreWindowFocus(mView, hasWindowFocus);
+                    if (imm != null) {
+                        imm.checkFocus();
                     }
+                } break;
+                case MSG_CLOSE_SYSTEM_DIALOGS: {
                     if (mView != null) {
-                        mAttachInfo.mKeyDispatchState.reset();
-                        mView.dispatchWindowFocusChanged(hasWindowFocus);
-                        mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
-
-                        if (mAttachInfo.mTooltipHost != null) {
-                            mAttachInfo.mTooltipHost.hideTooltip();
-                        }
+                        mView.onCloseSystemDialogs((String) msg.obj);
+                    }
+                } break;
+                case MSG_DISPATCH_DRAG_EVENT: {
+                } // fall through
+                case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
+                    DragEvent event = (DragEvent) msg.obj;
+                    // only present when this app called startDrag()
+                    event.mLocalState = mLocalDragState;
+                    handleDragEvent(event);
+                } break;
+                case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
+                    handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+                } break;
+                case MSG_UPDATE_CONFIGURATION: {
+                    Configuration config = (Configuration) msg.obj;
+                    if (config.isOtherSeqNewer(
+                            mLastReportedMergedConfiguration.getMergedConfiguration())) {
+                        // If we already have a newer merged config applied - use its global part.
+                        config = mLastReportedMergedConfiguration.getGlobalConfiguration();
                     }
 
-                    // Note: must be done after the focus change callbacks,
-                    // so all of the view state is set up correctly.
-                    if (hasWindowFocus) {
-                        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-                            imm.onPostWindowFocus(mView, mView.findFocus(),
-                                    mWindowAttributes.softInputMode,
-                                    !mHasHadWindowFocus, mWindowAttributes.flags);
-                        }
-                        // Clear the forward bit.  We can just do this directly, since
-                        // the window manager doesn't care about it.
-                        mWindowAttributes.softInputMode &=
-                                ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
-                        ((WindowManager.LayoutParams)mView.getLayoutParams())
-                                .softInputMode &=
-                                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
-                        mHasHadWindowFocus = true;
-                    } else {
-                        if (mPointerCapture) {
-                            handlePointerCaptureChanged(false);
-                        }
+                    // Use the newer global config and last reported override config.
+                    mPendingMergedConfiguration.setConfiguration(config,
+                            mLastReportedMergedConfiguration.getOverrideConfiguration());
+
+                    performConfigurationChange(mPendingMergedConfiguration, false /* force */,
+                            INVALID_DISPLAY /* same display */);
+                } break;
+                case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
+                    setAccessibilityFocus(null, null);
+                } break;
+                case MSG_INVALIDATE_WORLD: {
+                    if (mView != null) {
+                        invalidateWorld(mView);
                     }
-                }
-            } break;
-            case MSG_DIE:
-                doDie();
-                break;
-            case MSG_DISPATCH_INPUT_EVENT: {
-                SomeArgs args = (SomeArgs)msg.obj;
-                InputEvent event = (InputEvent)args.arg1;
-                InputEventReceiver receiver = (InputEventReceiver)args.arg2;
-                enqueueInputEvent(event, receiver, 0, true);
-                args.recycle();
-            } break;
-            case MSG_SYNTHESIZE_INPUT_EVENT: {
-                InputEvent event = (InputEvent)msg.obj;
-                enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
-            } break;
-            case MSG_DISPATCH_KEY_FROM_IME: {
-                if (LOCAL_LOGV) Log.v(
-                    TAG, "Dispatching key "
-                    + msg.obj + " from IME to " + mView);
-                KeyEvent event = (KeyEvent)msg.obj;
-                if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
-                    // The IME is trying to say this event is from the
-                    // system!  Bad bad bad!
-                    //noinspection UnusedAssignment
-                    event = KeyEvent.changeFlags(event, event.getFlags() &
-                            ~KeyEvent.FLAG_FROM_SYSTEM);
-                }
-                enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
-            } break;
-            case MSG_CHECK_FOCUS: {
-                InputMethodManager imm = InputMethodManager.peekInstance();
-                if (imm != null) {
-                    imm.checkFocus();
-                }
-            } break;
-            case MSG_CLOSE_SYSTEM_DIALOGS: {
-                if (mView != null) {
-                    mView.onCloseSystemDialogs((String)msg.obj);
-                }
-            } break;
-            case MSG_DISPATCH_DRAG_EVENT:
-            case MSG_DISPATCH_DRAG_LOCATION_EVENT: {
-                DragEvent event = (DragEvent)msg.obj;
-                event.mLocalState = mLocalDragState;    // only present when this app called startDrag()
-                handleDragEvent(event);
-            } break;
-            case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
-                handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
-            } break;
-            case MSG_UPDATE_CONFIGURATION: {
-                Configuration config = (Configuration) msg.obj;
-                if (config.isOtherSeqNewer(
-                        mLastReportedMergedConfiguration.getMergedConfiguration())) {
-                    // If we already have a newer merged config applied - use its global part.
-                    config = mLastReportedMergedConfiguration.getGlobalConfiguration();
-                }
-
-                // Use the newer global config and last reported override config.
-                mPendingMergedConfiguration.setConfiguration(config,
-                        mLastReportedMergedConfiguration.getOverrideConfiguration());
-
-                performConfigurationChange(mPendingMergedConfiguration, false /* force */,
-                        INVALID_DISPLAY /* same display */);
-            } break;
-            case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
-                setAccessibilityFocus(null, null);
-            } break;
-            case MSG_INVALIDATE_WORLD: {
-                if (mView != null) {
-                    invalidateWorld(mView);
-                }
-            } break;
-            case MSG_DISPATCH_WINDOW_SHOWN: {
-                handleDispatchWindowShown();
-            } break;
-            case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
-                final IResultReceiver receiver = (IResultReceiver) msg.obj;
-                final int deviceId = msg.arg1;
-                handleRequestKeyboardShortcuts(receiver, deviceId);
-            } break;
-            case MSG_UPDATE_POINTER_ICON: {
-                MotionEvent event = (MotionEvent) msg.obj;
-                resetPointerIcon(event);
-            } break;
-            case MSG_POINTER_CAPTURE_CHANGED: {
-                final boolean hasCapture = msg.arg1 != 0;
-                handlePointerCaptureChanged(hasCapture);
-            } break;
-            case MSG_DRAW_FINISHED: {
-                pendingDrawFinished();
-            } break;
+                } break;
+                case MSG_DISPATCH_WINDOW_SHOWN: {
+                    handleDispatchWindowShown();
+                } break;
+                case MSG_REQUEST_KEYBOARD_SHORTCUTS: {
+                    final IResultReceiver receiver = (IResultReceiver) msg.obj;
+                    final int deviceId = msg.arg1;
+                    handleRequestKeyboardShortcuts(receiver, deviceId);
+                } break;
+                case MSG_UPDATE_POINTER_ICON: {
+                    MotionEvent event = (MotionEvent) msg.obj;
+                    resetPointerIcon(event);
+                } break;
+                case MSG_POINTER_CAPTURE_CHANGED: {
+                    final boolean hasCapture = msg.arg1 != 0;
+                    handlePointerCaptureChanged(hasCapture);
+                } break;
+                case MSG_DRAW_FINISHED: {
+                    pendingDrawFinished();
+                } break;
             }
         }
     }
@@ -4203,6 +4226,18 @@
             }
         }
 
+        protected void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (mNext != null) {
+                mNext.onWindowFocusChanged(hasWindowFocus);
+            }
+        }
+
+        protected void onDetachedFromWindow() {
+            if (mNext != null) {
+                mNext.onDetachedFromWindow();
+            }
+        }
+
         protected boolean shouldDropInputEvent(QueuedInputEvent q) {
             if (mView == null || !mAdded) {
                 Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
@@ -4956,9 +4991,9 @@
                     final MotionEvent event = (MotionEvent)q.mEvent;
                     final int source = event.getSource();
                     if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                        mTrackball.cancel(event);
+                        mTrackball.cancel();
                     } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                        mJoystick.cancel(event);
+                        mJoystick.cancel();
                     } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                             == InputDevice.SOURCE_TOUCH_NAVIGATION) {
                         mTouchNavigation.cancel(event);
@@ -4967,6 +5002,18 @@
             }
             super.onDeliverToNext(q);
         }
+
+        @Override
+        protected void onWindowFocusChanged(boolean hasWindowFocus) {
+            if (!hasWindowFocus) {
+                mJoystick.cancel();
+            }
+        }
+
+        @Override
+        protected void onDetachedFromWindow() {
+            mJoystick.cancel();
+        }
     }
 
     /**
@@ -5079,7 +5126,7 @@
             }
         }
 
-        public void cancel(MotionEvent event) {
+        public void cancel() {
             mLastTime = Integer.MIN_VALUE;
 
             // If we reach this, we consumed a trackball event.
@@ -5263,14 +5310,11 @@
      * Creates dpad events from unhandled joystick movements.
      */
     final class SyntheticJoystickHandler extends Handler {
-        private final static String TAG = "SyntheticJoystickHandler";
         private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
         private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
 
-        private int mLastXDirection;
-        private int mLastYDirection;
-        private int mLastXKeyCode;
-        private int mLastYKeyCode;
+        private final JoystickAxesState mJoystickAxesState = new JoystickAxesState();
+        private final SparseArray<KeyEvent> mDeviceKeyEvents = new SparseArray<>();
 
         public SyntheticJoystickHandler() {
             super(true);
@@ -5281,11 +5325,10 @@
             switch (msg.what) {
                 case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
                 case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
-                    KeyEvent oldEvent = (KeyEvent)msg.obj;
-                    KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
-                            SystemClock.uptimeMillis(),
-                            oldEvent.getRepeatCount() + 1);
                     if (mAttachInfo.mHasWindowFocus) {
+                        KeyEvent oldEvent = (KeyEvent) msg.obj;
+                        KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+                                SystemClock.uptimeMillis(), oldEvent.getRepeatCount() + 1);
                         enqueueInputEvent(e);
                         Message m = obtainMessage(msg.what, e);
                         m.setAsynchronous(true);
@@ -5297,97 +5340,176 @@
 
         public void process(MotionEvent event) {
             switch(event.getActionMasked()) {
-            case MotionEvent.ACTION_CANCEL:
-                cancel(event);
-                break;
-            case MotionEvent.ACTION_MOVE:
-                update(event, true);
-                break;
-            default:
-                Log.w(mTag, "Unexpected action: " + event.getActionMasked());
+                case MotionEvent.ACTION_CANCEL:
+                    cancel();
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    update(event);
+                    break;
+                default:
+                    Log.w(mTag, "Unexpected action: " + event.getActionMasked());
             }
         }
 
-        private void cancel(MotionEvent event) {
+        private void cancel() {
             removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
             removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
-            update(event, false);
+            for (int i = 0; i < mDeviceKeyEvents.size(); i++) {
+                final KeyEvent keyEvent = mDeviceKeyEvents.valueAt(i);
+                if (keyEvent != null) {
+                    enqueueInputEvent(KeyEvent.changeTimeRepeat(keyEvent,
+                            SystemClock.uptimeMillis(), 0));
+                }
+            }
+            mDeviceKeyEvents.clear();
+            mJoystickAxesState.resetState();
         }
 
-        private void update(MotionEvent event, boolean synthesizeNewKeys) {
+        private void update(MotionEvent event) {
+            final int historySize = event.getHistorySize();
+            for (int h = 0; h < historySize; h++) {
+                final long time = event.getHistoricalEventTime(h);
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_X, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_Y, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, 0, h));
+                mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
+                        event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, 0, h));
+            }
             final long time = event.getEventTime();
-            final int metaState = event.getMetaState();
-            final int deviceId = event.getDeviceId();
-            final int source = event.getSource();
-
-            int xDirection = joystickAxisValueToDirection(
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_X,
+                    event.getAxisValue(MotionEvent.AXIS_X));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_Y,
+                    event.getAxisValue(MotionEvent.AXIS_Y));
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_X,
                     event.getAxisValue(MotionEvent.AXIS_HAT_X));
-            if (xDirection == 0) {
-                xDirection = joystickAxisValueToDirection(event.getX());
-            }
-
-            int yDirection = joystickAxisValueToDirection(
+            mJoystickAxesState.updateStateForAxis(event, time, MotionEvent.AXIS_HAT_Y,
                     event.getAxisValue(MotionEvent.AXIS_HAT_Y));
-            if (yDirection == 0) {
-                yDirection = joystickAxisValueToDirection(event.getY());
-            }
-
-            if (xDirection != mLastXDirection) {
-                if (mLastXKeyCode != 0) {
-                    removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
-                    enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastXKeyCode = 0;
-                }
-
-                mLastXDirection = xDirection;
-
-                if (xDirection != 0 && synthesizeNewKeys) {
-                    mLastXKeyCode = xDirection > 0
-                            ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
-                    final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
-                    enqueueInputEvent(e);
-                    Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
-                    m.setAsynchronous(true);
-                    sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
-                }
-            }
-
-            if (yDirection != mLastYDirection) {
-                if (mLastYKeyCode != 0) {
-                    removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
-                    enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastYKeyCode = 0;
-                }
-
-                mLastYDirection = yDirection;
-
-                if (yDirection != 0 && synthesizeNewKeys) {
-                    mLastYKeyCode = yDirection > 0
-                            ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
-                    final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
-                            deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
-                    enqueueInputEvent(e);
-                    Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
-                    m.setAsynchronous(true);
-                    sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
-                }
-            }
         }
 
-        private int joystickAxisValueToDirection(float value) {
-            if (value >= 0.5f) {
-                return 1;
-            } else if (value <= -0.5f) {
-                return -1;
-            } else {
-                return 0;
+        final class JoystickAxesState {
+            // State machine: from neutral state (no button press) can go into
+            // button STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state, emitting an ACTION_DOWN event.
+            // From STATE_UP_OR_LEFT or STATE_DOWN_OR_RIGHT state can go into neutral state,
+            // emitting an ACTION_UP event.
+            private static final int STATE_UP_OR_LEFT = -1;
+            private static final int STATE_NEUTRAL = 0;
+            private static final int STATE_DOWN_OR_RIGHT = 1;
+
+            final int[] mAxisStatesHat = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_HAT_X, AXIS_HAT_Y}
+            final int[] mAxisStatesStick = {STATE_NEUTRAL, STATE_NEUTRAL}; // {AXIS_X, AXIS_Y}
+
+            void resetState() {
+                mAxisStatesHat[0] = STATE_NEUTRAL;
+                mAxisStatesHat[1] = STATE_NEUTRAL;
+                mAxisStatesStick[0] = STATE_NEUTRAL;
+                mAxisStatesStick[1] = STATE_NEUTRAL;
+            }
+
+            void updateStateForAxis(MotionEvent event, long time, int axis, float value) {
+                // Emit KeyEvent if necessary
+                // axis can be AXIS_X, AXIS_Y, AXIS_HAT_X, AXIS_HAT_Y
+                final int axisStateIndex;
+                final int repeatMessage;
+                if (isXAxis(axis)) {
+                    axisStateIndex = 0;
+                    repeatMessage = MSG_ENQUEUE_X_AXIS_KEY_REPEAT;
+                } else if (isYAxis(axis)) {
+                    axisStateIndex = 1;
+                    repeatMessage = MSG_ENQUEUE_Y_AXIS_KEY_REPEAT;
+                } else {
+                    Log.e(mTag, "Unexpected axis " + axis + " in updateStateForAxis!");
+                    return;
+                }
+                final int newState = joystickAxisValueToState(value);
+
+                final int currentState;
+                if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+                    currentState = mAxisStatesStick[axisStateIndex];
+                } else {
+                    currentState = mAxisStatesHat[axisStateIndex];
+                }
+
+                if (currentState == newState) {
+                    return;
+                }
+
+                final int metaState = event.getMetaState();
+                final int deviceId = event.getDeviceId();
+                final int source = event.getSource();
+
+                if (currentState == STATE_DOWN_OR_RIGHT || currentState == STATE_UP_OR_LEFT) {
+                    // send a button release event
+                    final int keyCode = joystickAxisAndStateToKeycode(axis, currentState);
+                    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                        enqueueInputEvent(new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+                                0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+                        // remove the corresponding pending UP event if focus lost/view detached
+                        mDeviceKeyEvents.put(deviceId, null);
+                    }
+                    removeMessages(repeatMessage);
+                }
+
+                if (newState == STATE_DOWN_OR_RIGHT || newState == STATE_UP_OR_LEFT) {
+                    // send a button down event
+                    final int keyCode = joystickAxisAndStateToKeycode(axis, newState);
+                    if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
+                        KeyEvent keyEvent = new KeyEvent(time, time, KeyEvent.ACTION_DOWN, keyCode,
+                                0, metaState, deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+                        enqueueInputEvent(keyEvent);
+                        Message m = obtainMessage(repeatMessage, keyEvent);
+                        m.setAsynchronous(true);
+                        sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+                        // store the corresponding ACTION_UP event so that it can be sent
+                        // if focus is lost or root view is removed
+                        mDeviceKeyEvents.put(deviceId,
+                                new KeyEvent(time, time, KeyEvent.ACTION_UP, keyCode,
+                                        0, metaState, deviceId, 0,
+                                        KeyEvent.FLAG_FALLBACK | KeyEvent.FLAG_CANCELED,
+                                        source));
+                    }
+                }
+                if (axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_Y) {
+                    mAxisStatesStick[axisStateIndex] = newState;
+                } else {
+                    mAxisStatesHat[axisStateIndex] = newState;
+                }
+            }
+
+            private boolean isXAxis(int axis) {
+                return axis == MotionEvent.AXIS_X || axis == MotionEvent.AXIS_HAT_X;
+            }
+            private boolean isYAxis(int axis) {
+                return axis == MotionEvent.AXIS_Y || axis == MotionEvent.AXIS_HAT_Y;
+            }
+
+            private int joystickAxisAndStateToKeycode(int axis, int state) {
+                if (isXAxis(axis) && state == STATE_UP_OR_LEFT) {
+                    return KeyEvent.KEYCODE_DPAD_LEFT;
+                }
+                if (isXAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+                    return KeyEvent.KEYCODE_DPAD_RIGHT;
+                }
+                if (isYAxis(axis) && state == STATE_UP_OR_LEFT) {
+                    return KeyEvent.KEYCODE_DPAD_UP;
+                }
+                if (isYAxis(axis) && state == STATE_DOWN_OR_RIGHT) {
+                    return KeyEvent.KEYCODE_DPAD_DOWN;
+                }
+                Log.e(mTag, "Unknown axis " + axis + " or direction " + state);
+                return KeyEvent.KEYCODE_UNKNOWN; // should never happen
+            }
+
+            private int joystickAxisValueToState(float value) {
+                if (value >= 0.5f) {
+                    return STATE_DOWN_OR_RIGHT;
+                } else if (value <= -0.5f) {
+                    return STATE_UP_OR_LEFT;
+                } else {
+                    return STATE_NEUTRAL;
+                }
             }
         }
     }
@@ -6108,7 +6230,6 @@
             if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
         }
 
-        //Log.d(mTag, ">>>>>> CALLING relayout");
         if (params != null && mOrigWindowType != params.type) {
             // For compatibility with old apps, don't crash here.
             if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
@@ -6129,7 +6250,6 @@
         mPendingAlwaysConsumeNavBar =
                 (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0;
 
-        //Log.d(mTag, "<<<<<< BACK FROM relayout");
         if (restore) {
             params.restore();
         }
@@ -7714,7 +7834,7 @@
         public void onAccessibilityStateChanged(boolean enabled) {
             if (enabled) {
                 ensureConnection();
-                if (mAttachInfo.mHasWindowFocus) {
+                if (mAttachInfo.mHasWindowFocus && (mView != null)) {
                     mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
                     View focusedView = mView.findFocus();
                     if (focusedView != null && focusedView != mView) {
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 0ecd20d..d665dde 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -365,6 +365,30 @@
     public abstract void setDataIsSensitive(boolean sensitive);
 
     /**
+     * Sets the minimum width in ems of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMinTextEms(@SuppressWarnings("unused") int minEms) {}
+
+    /**
+     * Sets the maximum width in ems of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMaxTextEms(@SuppressWarnings("unused") int maxEms) {}
+
+    /**
+     * Sets the maximum length of the text associated with this view, when supported.
+     *
+     * <p>Should only be set when the node is used for autofill purposes - it will be ignored
+     * when used for Assist.
+     */
+    public void setMaxTextLength(@SuppressWarnings("unused") int maxLength) {}
+
+    /**
      * Call when done populating a {@link ViewStructure} returned by
      * {@link #asyncNewChild}.
      */
@@ -378,7 +402,7 @@
      *
      * <p>Typically used when the view is a container for an HTML document.
      *
-     * @param domain URL representing the domain; only the host part will be used.
+     * @param domain RFC 2396-compliant URI representing the domain.
      */
     public abstract void setWebDomain(@Nullable String domain);
 
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e56a82f..c29a1da 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT;
+
 import android.Manifest.permission;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -268,93 +270,93 @@
          */
         @ViewDebug.ExportedProperty(mapping = {
                 @ViewDebug.IntToString(from = TYPE_BASE_APPLICATION,
-                        to = "TYPE_BASE_APPLICATION"),
+                        to = "BASE_APPLICATION"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION,
-                        to = "TYPE_APPLICATION"),
+                        to = "APPLICATION"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_STARTING,
-                        to = "TYPE_APPLICATION_STARTING"),
+                        to = "APPLICATION_STARTING"),
                 @ViewDebug.IntToString(from = TYPE_DRAWN_APPLICATION,
-                        to = "TYPE_DRAWN_APPLICATION"),
+                        to = "DRAWN_APPLICATION"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_PANEL,
-                        to = "TYPE_APPLICATION_PANEL"),
+                        to = "APPLICATION_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA,
-                        to = "TYPE_APPLICATION_MEDIA"),
+                        to = "APPLICATION_MEDIA"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL,
-                        to = "TYPE_APPLICATION_SUB_PANEL"),
+                        to = "APPLICATION_SUB_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_ABOVE_SUB_PANEL,
-                        to = "TYPE_APPLICATION_ABOVE_SUB_PANEL"),
+                        to = "APPLICATION_ABOVE_SUB_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG,
-                        to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+                        to = "APPLICATION_ATTACHED_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY,
-                        to = "TYPE_APPLICATION_MEDIA_OVERLAY"),
+                        to = "APPLICATION_MEDIA_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_STATUS_BAR,
-                        to = "TYPE_STATUS_BAR"),
+                        to = "STATUS_BAR"),
                 @ViewDebug.IntToString(from = TYPE_SEARCH_BAR,
-                        to = "TYPE_SEARCH_BAR"),
+                        to = "SEARCH_BAR"),
                 @ViewDebug.IntToString(from = TYPE_PHONE,
-                        to = "TYPE_PHONE"),
+                        to = "PHONE"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_ALERT,
-                        to = "TYPE_SYSTEM_ALERT"),
+                        to = "SYSTEM_ALERT"),
                 @ViewDebug.IntToString(from = TYPE_TOAST,
-                        to = "TYPE_TOAST"),
+                        to = "TOAST"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY,
-                        to = "TYPE_SYSTEM_OVERLAY"),
+                        to = "SYSTEM_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE,
-                        to = "TYPE_PRIORITY_PHONE"),
+                        to = "PRIORITY_PHONE"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG,
-                        to = "TYPE_SYSTEM_DIALOG"),
+                        to = "SYSTEM_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG,
-                        to = "TYPE_KEYGUARD_DIALOG"),
+                        to = "KEYGUARD_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR,
-                        to = "TYPE_SYSTEM_ERROR"),
+                        to = "SYSTEM_ERROR"),
                 @ViewDebug.IntToString(from = TYPE_INPUT_METHOD,
-                        to = "TYPE_INPUT_METHOD"),
+                        to = "INPUT_METHOD"),
                 @ViewDebug.IntToString(from = TYPE_INPUT_METHOD_DIALOG,
-                        to = "TYPE_INPUT_METHOD_DIALOG"),
+                        to = "INPUT_METHOD_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_WALLPAPER,
-                        to = "TYPE_WALLPAPER"),
+                        to = "WALLPAPER"),
                 @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL,
-                        to = "TYPE_STATUS_BAR_PANEL"),
+                        to = "STATUS_BAR_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_SECURE_SYSTEM_OVERLAY,
-                        to = "TYPE_SECURE_SYSTEM_OVERLAY"),
+                        to = "SECURE_SYSTEM_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_DRAG,
-                        to = "TYPE_DRAG"),
+                        to = "DRAG"),
                 @ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL,
-                        to = "TYPE_STATUS_BAR_SUB_PANEL"),
+                        to = "STATUS_BAR_SUB_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_POINTER,
-                        to = "TYPE_POINTER"),
+                        to = "POINTER"),
                 @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR,
-                        to = "TYPE_NAVIGATION_BAR"),
+                        to = "NAVIGATION_BAR"),
                 @ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY,
-                        to = "TYPE_VOLUME_OVERLAY"),
+                        to = "VOLUME_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS,
-                        to = "TYPE_BOOT_PROGRESS"),
+                        to = "BOOT_PROGRESS"),
                 @ViewDebug.IntToString(from = TYPE_INPUT_CONSUMER,
-                        to = "TYPE_INPUT_CONSUMER"),
+                        to = "INPUT_CONSUMER"),
                 @ViewDebug.IntToString(from = TYPE_DREAM,
-                        to = "TYPE_DREAM"),
+                        to = "DREAM"),
                 @ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL,
-                        to = "TYPE_NAVIGATION_BAR_PANEL"),
+                        to = "NAVIGATION_BAR_PANEL"),
                 @ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY,
-                        to = "TYPE_DISPLAY_OVERLAY"),
+                        to = "DISPLAY_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY,
-                        to = "TYPE_MAGNIFICATION_OVERLAY"),
+                        to = "MAGNIFICATION_OVERLAY"),
                 @ViewDebug.IntToString(from = TYPE_PRESENTATION,
-                        to = "TYPE_PRESENTATION"),
+                        to = "PRESENTATION"),
                 @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION,
-                        to = "TYPE_PRIVATE_PRESENTATION"),
+                        to = "PRIVATE_PRESENTATION"),
                 @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION,
-                        to = "TYPE_VOICE_INTERACTION"),
+                        to = "VOICE_INTERACTION"),
                 @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING,
-                        to = "TYPE_VOICE_INTERACTION_STARTING"),
+                        to = "VOICE_INTERACTION_STARTING"),
                 @ViewDebug.IntToString(from = TYPE_DOCK_DIVIDER,
-                        to = "TYPE_DOCK_DIVIDER"),
+                        to = "DOCK_DIVIDER"),
                 @ViewDebug.IntToString(from = TYPE_QS_DIALOG,
-                        to = "TYPE_QS_DIALOG"),
+                        to = "QS_DIALOG"),
                 @ViewDebug.IntToString(from = TYPE_SCREENSHOT,
-                        to = "TYPE_SCREENSHOT"),
+                        to = "SCREENSHOT"),
                 @ViewDebug.IntToString(from = TYPE_APPLICATION_OVERLAY,
-                        to = "TYPE_APPLICATION_OVERLAY")
+                        to = "APPLICATION_OVERLAY")
         })
         public int type;
 
@@ -1198,63 +1200,69 @@
          */
         @ViewDebug.ExportedProperty(flagMapping = {
             @ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
-                    name = "FLAG_ALLOW_LOCK_WHILE_SCREEN_ON"),
+                    name = "ALLOW_LOCK_WHILE_SCREEN_ON"),
             @ViewDebug.FlagToString(mask = FLAG_DIM_BEHIND, equals = FLAG_DIM_BEHIND,
-                    name = "FLAG_DIM_BEHIND"),
+                    name = "DIM_BEHIND"),
             @ViewDebug.FlagToString(mask = FLAG_BLUR_BEHIND, equals = FLAG_BLUR_BEHIND,
-                    name = "FLAG_BLUR_BEHIND"),
+                    name = "BLUR_BEHIND"),
             @ViewDebug.FlagToString(mask = FLAG_NOT_FOCUSABLE, equals = FLAG_NOT_FOCUSABLE,
-                    name = "FLAG_NOT_FOCUSABLE"),
+                    name = "NOT_FOCUSABLE"),
             @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCHABLE, equals = FLAG_NOT_TOUCHABLE,
-                    name = "FLAG_NOT_TOUCHABLE"),
+                    name = "NOT_TOUCHABLE"),
             @ViewDebug.FlagToString(mask = FLAG_NOT_TOUCH_MODAL, equals = FLAG_NOT_TOUCH_MODAL,
-                    name = "FLAG_NOT_TOUCH_MODAL"),
+                    name = "NOT_TOUCH_MODAL"),
             @ViewDebug.FlagToString(mask = FLAG_TOUCHABLE_WHEN_WAKING, equals = FLAG_TOUCHABLE_WHEN_WAKING,
-                    name = "FLAG_TOUCHABLE_WHEN_WAKING"),
+                    name = "TOUCHABLE_WHEN_WAKING"),
             @ViewDebug.FlagToString(mask = FLAG_KEEP_SCREEN_ON, equals = FLAG_KEEP_SCREEN_ON,
-                    name = "FLAG_KEEP_SCREEN_ON"),
+                    name = "KEEP_SCREEN_ON"),
             @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_SCREEN, equals = FLAG_LAYOUT_IN_SCREEN,
-                    name = "FLAG_LAYOUT_IN_SCREEN"),
+                    name = "LAYOUT_IN_SCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_LAYOUT_NO_LIMITS, equals = FLAG_LAYOUT_NO_LIMITS,
-                    name = "FLAG_LAYOUT_NO_LIMITS"),
+                    name = "LAYOUT_NO_LIMITS"),
             @ViewDebug.FlagToString(mask = FLAG_FULLSCREEN, equals = FLAG_FULLSCREEN,
-                    name = "FLAG_FULLSCREEN"),
+                    name = "FULLSCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_FORCE_NOT_FULLSCREEN, equals = FLAG_FORCE_NOT_FULLSCREEN,
-                    name = "FLAG_FORCE_NOT_FULLSCREEN"),
+                    name = "FORCE_NOT_FULLSCREEN"),
             @ViewDebug.FlagToString(mask = FLAG_DITHER, equals = FLAG_DITHER,
-                    name = "FLAG_DITHER"),
+                    name = "DITHER"),
             @ViewDebug.FlagToString(mask = FLAG_SECURE, equals = FLAG_SECURE,
-                    name = "FLAG_SECURE"),
+                    name = "SECURE"),
             @ViewDebug.FlagToString(mask = FLAG_SCALED, equals = FLAG_SCALED,
-                    name = "FLAG_SCALED"),
+                    name = "SCALED"),
             @ViewDebug.FlagToString(mask = FLAG_IGNORE_CHEEK_PRESSES, equals = FLAG_IGNORE_CHEEK_PRESSES,
-                    name = "FLAG_IGNORE_CHEEK_PRESSES"),
+                    name = "IGNORE_CHEEK_PRESSES"),
             @ViewDebug.FlagToString(mask = FLAG_LAYOUT_INSET_DECOR, equals = FLAG_LAYOUT_INSET_DECOR,
-                    name = "FLAG_LAYOUT_INSET_DECOR"),
+                    name = "LAYOUT_INSET_DECOR"),
             @ViewDebug.FlagToString(mask = FLAG_ALT_FOCUSABLE_IM, equals = FLAG_ALT_FOCUSABLE_IM,
-                    name = "FLAG_ALT_FOCUSABLE_IM"),
+                    name = "ALT_FOCUSABLE_IM"),
             @ViewDebug.FlagToString(mask = FLAG_WATCH_OUTSIDE_TOUCH, equals = FLAG_WATCH_OUTSIDE_TOUCH,
-                    name = "FLAG_WATCH_OUTSIDE_TOUCH"),
+                    name = "WATCH_OUTSIDE_TOUCH"),
             @ViewDebug.FlagToString(mask = FLAG_SHOW_WHEN_LOCKED, equals = FLAG_SHOW_WHEN_LOCKED,
-                    name = "FLAG_SHOW_WHEN_LOCKED"),
+                    name = "SHOW_WHEN_LOCKED"),
             @ViewDebug.FlagToString(mask = FLAG_SHOW_WALLPAPER, equals = FLAG_SHOW_WALLPAPER,
-                    name = "FLAG_SHOW_WALLPAPER"),
+                    name = "SHOW_WALLPAPER"),
             @ViewDebug.FlagToString(mask = FLAG_TURN_SCREEN_ON, equals = FLAG_TURN_SCREEN_ON,
-                    name = "FLAG_TURN_SCREEN_ON"),
+                    name = "TURN_SCREEN_ON"),
             @ViewDebug.FlagToString(mask = FLAG_DISMISS_KEYGUARD, equals = FLAG_DISMISS_KEYGUARD,
-                    name = "FLAG_DISMISS_KEYGUARD"),
+                    name = "DISMISS_KEYGUARD"),
             @ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
-                    name = "FLAG_SPLIT_TOUCH"),
+                    name = "SPLIT_TOUCH"),
             @ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
-                    name = "FLAG_HARDWARE_ACCELERATED"),
-            @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
-                    name = "FLAG_LOCAL_FOCUS_MODE"),
+                    name = "HARDWARE_ACCELERATED"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_IN_OVERSCAN, equals = FLAG_LAYOUT_IN_OVERSCAN,
+                    name = "LOCAL_FOCUS_MODE"),
             @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_STATUS, equals = FLAG_TRANSLUCENT_STATUS,
-                    name = "FLAG_TRANSLUCENT_STATUS"),
+                    name = "TRANSLUCENT_STATUS"),
             @ViewDebug.FlagToString(mask = FLAG_TRANSLUCENT_NAVIGATION, equals = FLAG_TRANSLUCENT_NAVIGATION,
-                    name = "FLAG_TRANSLUCENT_NAVIGATION"),
+                    name = "TRANSLUCENT_NAVIGATION"),
+            @ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
+                    name = "LOCAL_FOCUS_MODE"),
+            @ViewDebug.FlagToString(mask = FLAG_SLIPPERY, equals = FLAG_SLIPPERY,
+                    name = "FLAG_SLIPPERY"),
+            @ViewDebug.FlagToString(mask = FLAG_LAYOUT_ATTACHED_IN_DECOR, equals = FLAG_LAYOUT_ATTACHED_IN_DECOR,
+                    name = "FLAG_LAYOUT_ATTACHED_IN_DECOR"),
             @ViewDebug.FlagToString(mask = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, equals = FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
-                    name = "FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS")
+                    name = "DRAWS_SYSTEM_BAR_BACKGROUNDS")
         }, formatToHexString = true)
         public int flags;
 
@@ -1438,6 +1446,88 @@
          * Control flags that are private to the platform.
          * @hide
          */
+        @ViewDebug.ExportedProperty(flagMapping = {
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+                        equals = PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED,
+                        name = "FAKE_HARDWARE_ACCELERATED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                        equals = PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
+                        name = "FORCE_HARDWARE_ACCELERATED"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                        equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
+                        name = "WANTS_OFFSET_NOTIFICATIONS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+                        equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+                        name = "SHOW_FOR_ALL_USERS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                        equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
+                        name = "NO_MOVE_ANIMATION"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                        equals = PRIVATE_FLAG_COMPATIBLE_WINDOW,
+                        name = "COMPATIBLE_WINDOW"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SYSTEM_ERROR,
+                        equals = PRIVATE_FLAG_SYSTEM_ERROR,
+                        name = "SYSTEM_ERROR"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+                        equals = PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR,
+                        name = "INHERIT_TRANSLUCENT_DECOR"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_KEYGUARD,
+                        equals = PRIVATE_FLAG_KEYGUARD,
+                        name = "KEYGUARD"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                        equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                        name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+                        equals = PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT,
+                        name = "FORCE_STATUS_BAR_VISIBLE_TRANSPARENT"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+                        equals = PRIVATE_FLAG_PRESERVE_GEOMETRY,
+                        name = "PRESERVE_GEOMETRY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                        equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
+                        name = "FORCE_DECOR_VIEW_VISIBILITY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                        equals = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
+                        name = "WILL_NOT_REPLACE_ON_RELAUNCH"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                        equals = PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
+                        name = "LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+                        equals = PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND,
+                        name = "FORCE_DRAW_STATUS_BAR_BACKGROUND"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                        equals = PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE,
+                        name = "SUSTAINED_PERFORMANCE_MODE"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        equals = PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+                        name = "HIDE_NON_SYSTEM_OVERLAY_WINDOWS"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                        equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
+                        name = "IS_ROUNDED_CORNERS_OVERLAY"),
+                @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+                        equals = PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN,
+                        name = "ACQUIRES_SLEEP_TOKEN")
+        })
         @TestApi
         public int privateFlags;
 
@@ -1977,7 +2067,7 @@
          * @hide
          */
         @ActivityInfo.ColorMode
-        private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+        private int mColorMode = COLOR_MODE_DEFAULT;
 
         public LayoutParams() {
             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
@@ -2442,9 +2532,15 @@
 
         @Override
         public String toString() {
+            return toString("");
+        }
+
+        /**
+         * @hide
+         */
+        public String toString(String prefix) {
             StringBuilder sb = new StringBuilder(256);
-            sb.append("WM.LayoutParams{");
-            sb.append("(");
+            sb.append("{(");
             sb.append(x);
             sb.append(',');
             sb.append(y);
@@ -2464,26 +2560,19 @@
                 sb.append(verticalMargin);
             }
             if (gravity != 0) {
-                sb.append(" gr=#");
-                sb.append(Integer.toHexString(gravity));
+                sb.append(" gr=");
+                sb.append(Gravity.toString(gravity));
             }
             if (softInputMode != 0) {
-                sb.append(" sim=#");
-                sb.append(Integer.toHexString(softInputMode));
+                sb.append(" sim={");
+                sb.append(softInputModeToString(softInputMode));
+                sb.append('}');
             }
             sb.append(" ty=");
-            sb.append(type);
-            sb.append(" fl=#");
-            sb.append(Integer.toHexString(flags));
-            if (privateFlags != 0) {
-                if ((privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) {
-                    sb.append(" compatible=true");
-                }
-                sb.append(" pfl=0x").append(Integer.toHexString(privateFlags));
-            }
+            sb.append(ViewDebug.intToString(LayoutParams.class, "type", type));
             if (format != PixelFormat.OPAQUE) {
                 sb.append(" fmt=");
-                sb.append(format);
+                sb.append(PixelFormat.formatToString(format));
             }
             if (windowAnimations != 0) {
                 sb.append(" wanim=0x");
@@ -2491,7 +2580,7 @@
             }
             if (screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
                 sb.append(" or=");
-                sb.append(screenOrientation);
+                sb.append(ActivityInfo.screenOrientationToString(screenOrientation));
             }
             if (alpha != 1.0f) {
                 sb.append(" alpha=");
@@ -2507,7 +2596,7 @@
             }
             if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
                 sb.append(" rotAnim=");
-                sb.append(rotationAnimation);
+                sb.append(rotationAnimationToString(rotationAnimation));
             }
             if (preferredRefreshRate != 0) {
                 sb.append(" preferredRefreshRate=");
@@ -2517,20 +2606,12 @@
                 sb.append(" preferredDisplayMode=");
                 sb.append(preferredDisplayModeId);
             }
-            if (systemUiVisibility != 0) {
-                sb.append(" sysui=0x");
-                sb.append(Integer.toHexString(systemUiVisibility));
-            }
-            if (subtreeSystemUiVisibility != 0) {
-                sb.append(" vsysui=0x");
-                sb.append(Integer.toHexString(subtreeSystemUiVisibility));
-            }
             if (hasSystemUiListeners) {
                 sb.append(" sysuil=");
                 sb.append(hasSystemUiListeners);
             }
             if (inputFeatures != 0) {
-                sb.append(" if=0x").append(Integer.toHexString(inputFeatures));
+                sb.append(" if=").append(inputFeatureToString(inputFeatures));
             }
             if (userActivityTimeout >= 0) {
                 sb.append(" userActivityTimeout=").append(userActivityTimeout);
@@ -2546,11 +2627,30 @@
                     sb.append(" (!preservePreviousSurfaceInsets)");
                 }
             }
-            if (needsMenuKey != NEEDS_MENU_UNSET) {
-                sb.append(" needsMenuKey=");
-                sb.append(needsMenuKey);
+            if (needsMenuKey == NEEDS_MENU_SET_TRUE) {
+                sb.append(" needsMenuKey");
             }
-            sb.append(" colorMode=").append(mColorMode);
+            if (mColorMode != COLOR_MODE_DEFAULT) {
+                sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode));
+            }
+            sb.append(System.lineSeparator());
+            sb.append(prefix).append("  fl=").append(
+                    ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+            if (privateFlags != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
+                        LayoutParams.class, "privateFlags", privateFlags));
+            }
+            if (systemUiVisibility != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  sysui=").append(ViewDebug.flagsToString(
+                        View.class, "mSystemUiVisibility", systemUiVisibility));
+            }
+            if (subtreeSystemUiVisibility != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  vsysui=").append(ViewDebug.flagsToString(
+                        View.class, "mSystemUiVisibility", subtreeSystemUiVisibility));
+            }
             sb.append('}');
             return sb.toString();
         }
@@ -2634,5 +2734,88 @@
                     && width == WindowManager.LayoutParams.MATCH_PARENT
                     && height == WindowManager.LayoutParams.MATCH_PARENT;
         }
+
+        private static String softInputModeToString(@SoftInputModeFlags int softInputMode) {
+            final StringBuilder result = new StringBuilder();
+            final int state = softInputMode & SOFT_INPUT_MASK_STATE;
+            if (state != 0) {
+                result.append("state=");
+                switch (state) {
+                    case SOFT_INPUT_STATE_UNCHANGED:
+                        result.append("unchanged");
+                        break;
+                    case SOFT_INPUT_STATE_HIDDEN:
+                        result.append("hidden");
+                        break;
+                    case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                        result.append("always_hidden");
+                        break;
+                    case SOFT_INPUT_STATE_VISIBLE:
+                        result.append("visible");
+                        break;
+                    case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                        result.append("always_visible");
+                        break;
+                    default:
+                        result.append(state);
+                        break;
+                }
+                result.append(' ');
+            }
+            final int adjust = softInputMode & SOFT_INPUT_MASK_ADJUST;
+            if (adjust != 0) {
+                result.append("adjust=");
+                switch (adjust) {
+                    case SOFT_INPUT_ADJUST_RESIZE:
+                        result.append("resize");
+                        break;
+                    case SOFT_INPUT_ADJUST_PAN:
+                        result.append("pan");
+                        break;
+                    case SOFT_INPUT_ADJUST_NOTHING:
+                        result.append("nothing");
+                        break;
+                    default:
+                        result.append(adjust);
+                        break;
+                }
+                result.append(' ');
+            }
+            if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                result.append("forwardNavigation").append(' ');
+            }
+            result.deleteCharAt(result.length() - 1);
+            return result.toString();
+        }
+
+        private static String rotationAnimationToString(int rotationAnimation) {
+            switch (rotationAnimation) {
+                case ROTATION_ANIMATION_UNSPECIFIED:
+                    return "UNSPECIFIED";
+                case ROTATION_ANIMATION_ROTATE:
+                    return "ROTATE";
+                case ROTATION_ANIMATION_CROSSFADE:
+                    return "CROSSFADE";
+                case ROTATION_ANIMATION_JUMPCUT:
+                    return "JUMPCUT";
+                case ROTATION_ANIMATION_SEAMLESS:
+                    return "SEAMLESS";
+                default:
+                    return Integer.toString(rotationAnimation);
+            }
+        }
+
+        private static String inputFeatureToString(int inputFeature) {
+            switch (inputFeature) {
+                case INPUT_FEATURE_DISABLE_POINTER_GESTURES:
+                    return "DISABLE_POINTER_GESTURES";
+                case INPUT_FEATURE_NO_INPUT_CHANNEL:
+                    return "NO_INPUT_CHANNEL";
+                case INPUT_FEATURE_DISABLE_USER_ACTIVITY:
+                    return "DISABLE_USER_ACTIVITY";
+                default:
+                    return Integer.toString(inputFeature);
+            }
+        }
     }
 }
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 98f8dc8..69cc100 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -335,8 +335,8 @@
     public abstract void setOnHardKeyboardStatusChangeListener(
         OnHardKeyboardStatusChangeListener listener);
 
-    /** Returns true if the stack with the input Id is currently visible. */
-    public abstract boolean isStackVisible(int stackId);
+    /** Returns true if a stack in the windowing mode is currently visible. */
+    public abstract boolean isStackVisible(int windowingMode);
 
     /**
      * @return True if and only if the docked divider is currently in resize mode.
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 4131fd1..137e551 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -467,11 +467,8 @@
          */
         public boolean isDimming();
 
-        /**
-         * @return the stack id this windows belongs to, or {@link StackId#INVALID_STACK_ID} if
-         *         not attached to any stack.
-         */
-        int getStackId();
+        /** @return the current windowing mode of this window. */
+        int getWindowingMode();
 
         /**
          * Returns true if the window is current in multi-windowing mode. i.e. it shares the
@@ -592,9 +589,10 @@
         int getDockedDividerInsetsLw();
 
         /**
-         * Retrieves the {@param outBounds} from the stack with id {@param stackId}.
+         * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
+         * {@param activityType}.
          */
-        void getStackBounds(int stackId, Rect outBounds);
+        void getStackBounds(int windowingMode, int activityType, Rect outBounds);
 
         /**
          * Notifies window manager that {@link #isShowingDreamLw} has changed.
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index 0f21c5c..d785117 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -329,8 +329,6 @@
                 final long oldParentId = oldInfo.getParentNodeId();
                 if (info.getParentNodeId() != oldParentId) {
                     clearSubTreeLocked(windowId, oldParentId);
-                } else {
-                    oldInfo.recycle();
                 }
            }
 
diff --git a/core/java/android/view/autofill/AutoFillType.aidl b/core/java/android/view/autofill/AutoFillType.aidl
deleted file mode 100644
index 4606b48..0000000
--- a/core/java/android/view/autofill/AutoFillType.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2016, 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.view.autofill;
-
-/*
- * TODO(b/35956626): remove once clients use getAutoFilltype()
- */
-parcelable AutoFillType;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutoFillValue.aidl b/core/java/android/view/autofill/AutoFillValue.aidl
deleted file mode 100644
index 05b7562..0000000
--- a/core/java/android/view/autofill/AutoFillValue.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) 2016, 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.view.autofill;
-
-//  @deprecated TODO(b/35956626): remove once clients use AutofillValue
-parcelable AutoFillValue;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index e906a1f..6a0669b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -91,10 +92,10 @@
  * </ul>
  *
  * <p>When the service returns datasets, the Android System displays an autofill dataset picker
- * UI affordance associated with the view, when the view is focused on and is part of a dataset.
- * The application can be notified when the affordance is shown by registering an
+ * UI associated with the view, when the view is focused on and is part of a dataset.
+ * The application can be notified when the UI is shown by registering an
  * {@link AutofillCallback} through {@link #registerCallback(AutofillCallback)}. When the user
- * selects a dataset from the affordance, all views present in the dataset are autofilled, through
+ * selects a dataset from the UI, all views present in the dataset are autofilled, through
  * calls to {@link View#autofill(AutofillValue)} or {@link View#autofill(SparseArray)}.
  *
  * <p>When the service returns ids of savable views, the Android System keeps track of changes
@@ -108,7 +109,7 @@
  * </ul>
  *
  * <p>Finally, after the autofill context is commited (i.e., not cancelled), the Android System
- * shows a save UI affordance if the value of savable views have changed. If the user selects the
+ * shows an autofill save UI if the value of savable views have changed. If the user selects the
  * option to Save, the current value of the views is then sent to the autofill service.
  *
  * <p>It is safe to call into its methods from any thread.
@@ -150,6 +151,12 @@
      * service authentication will contain the Bundle set by
      * {@link android.service.autofill.FillResponse.Builder#setClientState(Bundle)} on this extra.
      *
+     * <p>On Android {@link android.os.Build.VERSION_CODES#P} and higher, the autofill service
+     * can also add this bundle to the {@link Intent} set as the
+     * {@link android.app.Activity#setResult(int, Intent) result} for an authentication request,
+     * so the bundle can be recovered later on
+     * {@link android.service.autofill.SaveRequest#getClientState()}.
+     *
      * <p>
      * Type: {@link android.os.Bundle}
      */
@@ -218,7 +225,7 @@
 
     /**
      * State where the autofill context was finished by the server because the autofill
-     * service could not autofill the page.
+     * service could not autofill the activity.
      *
      * <p>In this state, most apps callback (such as {@link #notifyViewEntered(View)}) are ignored,
      * exception {@link #requestAutofill(View)} (and {@link #requestAutofill(View, int, Rect)}).
@@ -236,6 +243,16 @@
     public static final int STATE_SHOWING_SAVE_UI = 3;
 
     /**
+     * State where the autofill is disabled because the service cannot autofill the activity at all.
+     *
+     * <p>In this state, every call is ignored, even {@link #requestAutofill(View)}
+     * (and {@link #requestAutofill(View, int, Rect)}).
+     *
+     * @hide
+     */
+    public static final int STATE_DISABLED_BY_SERVICE = 4;
+
+    /**
      * Makes an authentication id from a request id and a dataset id.
      *
      * @param requestId The request id.
@@ -311,6 +328,14 @@
     @GuardedBy("mLock")
     @Nullable private ArraySet<AutofillId> mFillableIds;
 
+    /** If set, session is commited when the field is clicked. */
+    @GuardedBy("mLock")
+    @Nullable private AutofillId mSaveTriggerId;
+
+    /** If set, session is commited when the activity is finished; otherwise session is canceled. */
+    @GuardedBy("mLock")
+    private boolean mSaveOnFinish;
+
     /** @hide */
     public interface AutofillClient {
         /**
@@ -384,6 +409,11 @@
          * Runs the specified action on the UI thread.
          */
         void runOnUiThread(Runnable action);
+
+        /**
+         * Gets the complete component name of this client.
+         */
+        ComponentName getComponentName();
     }
 
     /**
@@ -492,7 +522,7 @@
      * @return whether autofill is enabled for the current user.
      */
     public boolean isEnabled() {
-        if (!hasAutofillFeature()) {
+        if (!hasAutofillFeature() || isDisabledByService()) {
             return false;
         }
         synchronized (mLock) {
@@ -566,19 +596,31 @@
         notifyViewEntered(view, 0);
     }
 
+    private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
+        if (isDisabledByService()) {
+            if (sVerbose) {
+                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+                        + ") on state " + getStateAsStringLocked());
+            }
+            return true;
+        }
+        if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
+            if (sVerbose) {
+                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+                        + ") on state " + getStateAsStringLocked());
+            }
+            return true;
+        }
+        return false;
+    }
+
     private void notifyViewEntered(@NonNull View view, int flags) {
         if (!hasAutofillFeature()) {
             return;
         }
         AutofillCallback callback = null;
         synchronized (mLock) {
-            if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
-                if (sVerbose) {
-                    Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
-                            + "): ignored on state " + getStateAsStringLocked());
-                }
-                return;
-            }
+            if (shouldIgnoreViewEnteredLocked(view, flags)) return;
 
             ensureServiceClientAddedIfNeededLocked();
 
@@ -703,14 +745,8 @@
         }
         AutofillCallback callback = null;
         synchronized (mLock) {
-            if (isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
-                if (sVerbose) {
-                    Log.v(TAG, "notifyViewEntered(flags=" + flags + ", view=" + view
-                            + ", virtualId=" + virtualId
-                            + "): ignored on state " + getStateAsStringLocked());
-                }
-                return;
-            }
+            if (shouldIgnoreViewEnteredLocked(view, flags)) return;
+
             ensureServiceClientAddedIfNeededLocked();
 
             if (!mEnabled) {
@@ -834,6 +870,46 @@
         }
     }
 
+
+    /**
+     * Called when a {@link View} is clicked. Currently only used by views that should trigger save.
+     *
+     * @hide
+     */
+    public void notifyViewClicked(View view) {
+        final AutofillId id = view.getAutofillId();
+
+        if (sVerbose) Log.v(TAG, "notifyViewClicked(): id=" + id + ", trigger=" + mSaveTriggerId);
+
+        synchronized (mLock) {
+            if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
+                if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
+                commitLocked();
+                mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
+                        mContext.getPackageName());
+            }
+        }
+    }
+
+    /**
+     * Called by {@link android.app.Activity} to commit or cancel the session on finish.
+     *
+     * @hide
+     */
+    public void onActivityFinished() {
+        if (!hasAutofillFeature()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mSaveOnFinish) {
+                commitLocked();
+            } else {
+                if (sDebug) Log.d(TAG, "Cancelling session on finish() as requested by service");
+                cancelLocked();
+            }
+        }
+    }
+
     /**
      * Called to indicate the current autofill context should be commited.
      *
@@ -850,14 +926,17 @@
             return;
         }
         synchronized (mLock) {
-            if (!mEnabled && !isActiveLocked()) {
-                return;
-            }
-
-            finishSessionLocked();
+            commitLocked();
         }
     }
 
+    private void commitLocked() {
+        if (!mEnabled && !isActiveLocked()) {
+            return;
+        }
+        finishSessionLocked();
+    }
+
     /**
      * Called to indicate the current autofill context should be cancelled.
      *
@@ -874,14 +953,17 @@
             return;
         }
         synchronized (mLock) {
-            if (!mEnabled && !isActiveLocked()) {
-                return;
-            }
-
-            cancelSessionLocked();
+            cancelLocked();
         }
     }
 
+    private void cancelLocked() {
+        if (!mEnabled && !isActiveLocked()) {
+            return;
+        }
+        cancelSessionLocked();
+    }
+
     /** @hide */
     public void disableOwnedAutofillServices() {
         disableAutofillServices();
@@ -937,10 +1019,12 @@
     }
 
     private AutofillClient getClientLocked() {
-        if (mContext instanceof AutofillClient) {
-            return (AutofillClient) mContext;
+        final AutofillClient client = mContext.getAutofillClient();
+        if (client == null && sDebug) {
+            Log.d(TAG, "No AutofillClient for " + mContext.getPackageName() + " on context "
+                    + mContext);
         }
-        return null;
+        return client;
     }
 
     /** @hide */
@@ -962,6 +1046,10 @@
             final Parcelable result = data.getParcelableExtra(EXTRA_AUTHENTICATION_RESULT);
             final Bundle responseData = new Bundle();
             responseData.putParcelable(EXTRA_AUTHENTICATION_RESULT, result);
+            final Bundle newClientState = data.getBundleExtra(EXTRA_CLIENT_STATE);
+            if (newClientState != null) {
+                responseData.putBundle(EXTRA_CLIENT_STATE, newClientState);
+            }
             try {
                 mService.setAuthenticationResult(responseData, mSessionId, authenticationId,
                         mContext.getUserId());
@@ -993,13 +1081,13 @@
             return;
         }
         try {
+            final AutofillClient client = getClientLocked();
             mSessionId = mService.startSession(mContext.getActivityToken(),
                     mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
-                    mCallback != null, flags, mContext.getOpPackageName());
+                    mCallback != null, flags, client.getComponentName());
             if (mSessionId != NO_SESSION) {
                 mState = STATE_ACTIVE;
             }
-            final AutofillClient client = getClientLocked();
             if (client != null) {
                 client.autofillCallbackResetableStateAvailable();
             }
@@ -1041,6 +1129,7 @@
         mState = STATE_UNKNOWN;
         mTrackedViews = null;
         mFillableIds = null;
+        mSaveTriggerId = null;
     }
 
     private void updateSessionLocked(AutofillId id, Rect bounds, AutofillValue value, int action,
@@ -1053,14 +1142,14 @@
 
         try {
             if (restartIfNecessary) {
+                final AutofillClient client = getClientLocked();
                 final int newId = mService.updateOrRestartSession(mContext.getActivityToken(),
                         mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
-                        mCallback != null, flags, mContext.getOpPackageName(), mSessionId, action);
+                        mCallback != null, flags, client.getComponentName(), mSessionId, action);
                 if (newId != mSessionId) {
                     if (sDebug) Log.d(TAG, "Session restarted: " + mSessionId + "=>" + newId);
                     mSessionId = newId;
                     mState = (mSessionId == NO_SESSION) ? STATE_UNKNOWN : STATE_ACTIVE;
-                    final AutofillClient client = getClientLocked();
                     if (client != null) {
                         client.autofillCallbackResetableStateAvailable();
                     }
@@ -1183,18 +1272,32 @@
         }
     }
 
-    private void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+    /** @hide */
+    public static final int SET_STATE_FLAG_ENABLED = 0x01;
+    /** @hide */
+    public static final int SET_STATE_FLAG_RESET_SESSION = 0x02;
+    /** @hide */
+    public static final int SET_STATE_FLAG_RESET_CLIENT = 0x04;
+    /** @hide */
+    public static final int SET_STATE_FLAG_DEBUG = 0x08;
+    /** @hide */
+    public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+
+    private void setState(int flags) {
+        if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
         synchronized (mLock) {
-            mEnabled = enabled;
-            if (!mEnabled || resetSession) {
+            mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
+            if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
                 // Reset the session state
                 resetSessionLocked();
             }
-            if (resetClient) {
+            if ((flags & SET_STATE_FLAG_RESET_CLIENT) != 0) {
                 // Reset connection to system
                 mServiceClient = null;
             }
         }
+        sDebug = (flags & SET_STATE_FLAG_DEBUG) != 0;
+        sVerbose = (flags & SET_STATE_FLAG_VERBOSE) != 0;
     }
 
     /**
@@ -1292,12 +1395,15 @@
     /**
      *  Set the tracked views.
      *
-     * @param trackedIds The views to be tracked
+     * @param trackedIds The views to be tracked.
      * @param saveOnAllViewsInvisible Finish the session once all tracked views are invisible.
+     * @param saveOnFinish Finish the session once the activity is finished.
      * @param fillableIds Views that might anchor FillUI.
+     * @param saveTriggerId View that when clicked triggers commit().
      */
     private void setTrackedViews(int sessionId, @Nullable AutofillId[] trackedIds,
-            boolean saveOnAllViewsInvisible, @Nullable AutofillId[] fillableIds) {
+            boolean saveOnAllViewsInvisible, boolean saveOnFinish,
+            @Nullable AutofillId[] fillableIds, @Nullable AutofillId saveTriggerId) {
         synchronized (mLock) {
             if (mEnabled && mSessionId == sessionId) {
                 if (saveOnAllViewsInvisible) {
@@ -1305,6 +1411,7 @@
                 } else {
                     mTrackedViews = null;
                 }
+                mSaveOnFinish = saveOnFinish;
                 if (fillableIds != null) {
                     if (mFillableIds == null) {
                         mFillableIds = new ArraySet<>(fillableIds.length);
@@ -1317,10 +1424,30 @@
                                 + ", mFillableIds" + mFillableIds);
                     }
                 }
+
+                if (mSaveTriggerId != null && !mSaveTriggerId.equals(saveTriggerId)) {
+                    // Turn off trigger on previous view id.
+                    setNotifyOnClickLocked(mSaveTriggerId, false);
+                }
+
+                if (saveTriggerId != null && !saveTriggerId.equals(mSaveTriggerId)) {
+                    // Turn on trigger on new view id.
+                    mSaveTriggerId = saveTriggerId;
+                    setNotifyOnClickLocked(mSaveTriggerId, true);
+                }
             }
         }
     }
 
+    private void setNotifyOnClickLocked(@NonNull AutofillId id, boolean notify) {
+        final View view = findView(id);
+        if (view == null) {
+            Log.w(TAG, "setNotifyOnClick(): invalid id: " + id);
+            return;
+        }
+        view.setNotifyAutofillManagerOnClick(notify);
+    }
+
     private void setSaveUiState(int sessionId, boolean shown) {
         if (sDebug) Log.d(TAG, "setSaveUiState(" + sessionId + "): " + shown);
         synchronized (mLock) {
@@ -1346,7 +1473,9 @@
      * Marks the state of the session as finished.
      *
      * @param newState {@link #STATE_FINISHED} (because the autofill service returned a {@code null}
-     *  FillResponse) or {@link #STATE_UNKNOWN} (because the session was removed).
+     *  FillResponse), {@link #STATE_UNKNOWN} (because the session was removed), or
+     *  {@link #STATE_DISABLED_BY_SERVICE} (because the autofill service disabled further autofill
+     *  requests for the activity).
      */
     private void setSessionFinished(int newState) {
         synchronized (mLock) {
@@ -1391,10 +1520,10 @@
         }
     }
 
-    private void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+    private void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
         if (sVerbose) {
             Log.v(TAG, "notifyNoFillUi(): sessionId=" + sessionId + ", autofillId=" + id
-                    + ", finished=" + sessionFinished);
+                    + ", sessionFinishedState=" + sessionFinishedState);
         }
         final View anchor = findView(id);
         if (anchor == null) {
@@ -1417,9 +1546,9 @@
             }
         }
 
-        if (sessionFinished) {
+        if (sessionFinishedState != 0) {
             // Callback call was "hijacked" to also update the session state.
-            setSessionFinished(STATE_FINISHED);
+            setSessionFinished(sessionFinishedState);
         }
     }
 
@@ -1493,6 +1622,8 @@
         final String pfx = outerPrefix + "  ";
         pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId);
         pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked());
+        pw.print(pfx); pw.print("context: "); pw.println(mContext);
+        pw.print(pfx); pw.print("client: "); pw.println(getClientLocked());
         pw.print(pfx); pw.print("enabled: "); pw.println(mEnabled);
         pw.print(pfx); pw.print("hasService: "); pw.println(mService != null);
         pw.print(pfx); pw.print("hasCallback: "); pw.println(mCallback != null);
@@ -1507,6 +1638,10 @@
             pw.print(pfx2); pw.print("invisible:"); pw.println(mTrackedViews.mInvisibleTrackedIds);
         }
         pw.print(pfx); pw.print("fillable ids: "); pw.println(mFillableIds);
+        pw.print(pfx); pw.print("save trigger id: "); pw.println(mSaveTriggerId);
+        pw.print(pfx); pw.print("save on finish(): "); pw.println(mSaveOnFinish);
+        pw.print(pfx); pw.print("debug: "); pw.print(sDebug);
+        pw.print(" verbose: "); pw.println(sVerbose);
     }
 
     private String getStateAsStringLocked() {
@@ -1519,6 +1654,8 @@
                 return "STATE_FINISHED";
             case STATE_SHOWING_SAVE_UI:
                 return "STATE_SHOWING_SAVE_UI";
+            case STATE_DISABLED_BY_SERVICE:
+                return "STATE_DISABLED_BY_SERVICE";
             default:
                 return "INVALID:" + mState;
         }
@@ -1528,8 +1665,8 @@
         return mState == STATE_ACTIVE;
     }
 
-    private boolean isFinishedLocked() {
-        return mState == STATE_FINISHED;
+    private boolean isDisabledByService() {
+        return mState == STATE_DISABLED_BY_SERVICE;
     }
 
     private void post(Runnable runnable) {
@@ -1755,7 +1892,7 @@
      * Callback for autofill related events.
      *
      * <p>Typically used for applications that display their own "auto-complete" views, so they can
-     * enable / disable such views when the autofill UI affordance is shown / hidden.
+     * enable / disable such views when the autofill UI is shown / hidden.
      */
     public abstract static class AutofillCallback {
 
@@ -1765,26 +1902,26 @@
         public @interface AutofillEventType {}
 
         /**
-         * The autofill input UI affordance associated with the view was shown.
+         * The autofill input UI associated with the view was shown.
          *
-         * <p>If the view provides its own auto-complete UI affordance and its currently shown, it
+         * <p>If the view provides its own auto-complete UI and its currently shown, it
          * should be hidden upon receiving this event.
          */
         public static final int EVENT_INPUT_SHOWN = 1;
 
         /**
-         * The autofill input UI affordance associated with the view was hidden.
+         * The autofill input UI associated with the view was hidden.
          *
-         * <p>If the view provides its own auto-complete UI affordance that was hidden upon a
+         * <p>If the view provides its own auto-complete UI that was hidden upon a
          * {@link #EVENT_INPUT_SHOWN} event, it could be shown again now.
          */
         public static final int EVENT_INPUT_HIDDEN = 2;
 
         /**
-         * The autofill input UI affordance associated with the view isn't shown because
+         * The autofill input UI associated with the view isn't shown because
          * autofill is not available.
          *
-         * <p>If the view provides its own auto-complete UI affordance but was not displaying it
+         * <p>If the view provides its own auto-complete UI but was not displaying it
          * to avoid flickering, it could shown it upon receiving this event.
          */
         public static final int EVENT_INPUT_UNAVAILABLE = 3;
@@ -1820,10 +1957,10 @@
         }
 
         @Override
-        public void setState(boolean enabled, boolean resetSession, boolean resetClient) {
+        public void setState(int flags) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() -> afm.setState(enabled, resetSession, resetClient));
+                afm.post(() -> afm.setState(flags));
             }
         }
 
@@ -1863,10 +2000,10 @@
         }
 
         @Override
-        public void notifyNoFillUi(int sessionId, AutofillId id, boolean sessionFinished) {
+        public void notifyNoFillUi(int sessionId, AutofillId id, int sessionFinishedState) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinished));
+                afm.post(() -> afm.notifyNoFillUi(sessionId, id, sessionFinishedState));
             }
         }
 
@@ -1886,12 +2023,12 @@
 
         @Override
         public void setTrackedViews(int sessionId, AutofillId[] ids,
-                boolean saveOnAllViewsInvisible, AutofillId[] fillableIds) {
+                boolean saveOnAllViewsInvisible, boolean saveOnFinish, AutofillId[] fillableIds,
+                AutofillId saveTriggerId) {
             final AutofillManager afm = mAfm.get();
             if (afm != null) {
-                afm.post(() ->
-                        afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible, fillableIds)
-                );
+                afm.post(() -> afm.setTrackedViews(sessionId, ids, saveOnAllViewsInvisible,
+                        saveOnFinish, fillableIds, saveTriggerId));
             }
         }
 
diff --git a/core/java/android/view/autofill/AutofillPopupWindow.java b/core/java/android/view/autofill/AutofillPopupWindow.java
index 5f47638..b4688bb 100644
--- a/core/java/android/view/autofill/AutofillPopupWindow.java
+++ b/core/java/android/view/autofill/AutofillPopupWindow.java
@@ -47,6 +47,19 @@
     private final WindowPresenter mWindowPresenter;
     private WindowManager.LayoutParams mWindowLayoutParams;
 
+    private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
+            new View.OnAttachStateChangeListener() {
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            /* ignore - handled by the super class */
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            dismiss();
+        }
+    };
+
     /**
      * Creates a popup window with a presenter owning the window and responsible for
      * showing/hiding/updating the backing window. This can be useful of the window is
@@ -208,7 +221,21 @@
         p.packageName = anchor.getContext().getPackageName();
         mWindowPresenter.show(p, getTransitionEpicenter(), isLayoutInsetDecor(),
                 anchor.getLayoutDirection());
-        return;
+    }
+
+    @Override
+    protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+        super.attachToAnchor(anchor, xoff, yoff, gravity);
+        anchor.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+    }
+
+    @Override
+    protected void detachFromAnchor() {
+        final View anchor = getAnchor();
+        if (anchor != null) {
+            anchor.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
+        }
+        super.detachFromAnchor();
     }
 
     @Override
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 6bd9bec..9329c4d 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -16,6 +16,7 @@
 
 package android.view.autofill;
 
+import android.content.ComponentName;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -34,14 +35,15 @@
     int addClient(in IAutoFillManagerClient client, int userId);
     int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
             in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
-            String packageName);
+            in ComponentName componentName);
     FillEventHistory getFillEventHistory();
     boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
     void updateSession(int sessionId, in AutofillId id, in Rect bounds,
             in AutofillValue value, int action, int flags, int userId);
     int updateOrRestartSession(IBinder activityToken, in IBinder appCallback,
             in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId,
-            boolean hasCallback, int flags, String packageName, int sessionId, int action);
+            boolean hasCallback, int flags, in ComponentName componentName, int sessionId,
+            int action);
     void finishSession(int sessionId, int userId);
     void cancelSession(int sessionId, int userId);
     void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 3dabcec..254c8a5 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -35,7 +35,7 @@
     /**
      * Notifies the client when the autofill enabled state changed.
      */
-    void setState(boolean enabled, boolean resetSession, boolean resetClient);
+    void setState(int flags);
 
     /**
       * Autofills the activity with the contents of a dataset.
@@ -53,7 +53,8 @@
       * the session is finished automatically.
       */
     void setTrackedViews(int sessionId, in @nullable AutofillId[] savableIds,
-            boolean saveOnAllViewsInvisible, in @nullable AutofillId[] fillableIds);
+            boolean saveOnAllViewsInvisible, boolean saveOnFinish,
+            in @nullable AutofillId[] fillableIds, in AutofillId saveTriggerId);
 
     /**
      * Requests showing the fill UI.
@@ -67,9 +68,10 @@
     void requestHideFillUi(int sessionId, in AutofillId id);
 
     /**
-     * Notifies no fill UI will be shown, and also mark the state as finished if necessary.
+     * Notifies no fill UI will be shown, and also mark the state as finished if necessary (if
+     * sessionFinishedState != 0).
      */
-    void notifyNoFillUi(int sessionId, in AutofillId id, boolean sessionFinished);
+    void notifyNoFillUi(int sessionId, in AutofillId id, int sessionFinishedState);
 
     /**
      * Starts the provided intent sender.
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 0922422..ab8886b 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -90,8 +91,9 @@
      * accept the first token given to you.  Any after that may come from the
      * client.
      */
+    @MainThread
     public void attachToken(IBinder token);
-    
+
     /**
      * Bind a new application environment in to the input method, so that it
      * can later start and stop input processing.
@@ -104,6 +106,7 @@
      * @see InputBinding
      * @see #unbindInput()
      */
+    @MainThread
     public void bindInput(InputBinding binding);
 
     /**
@@ -114,6 +117,7 @@
      * Typically this method is called when the application changes to be
      * non-foreground.
      */
+    @MainThread
     public void unbindInput();
 
     /**
@@ -129,6 +133,7 @@
      * 
      * @see EditorInfo
      */
+    @MainThread
     public void startInput(InputConnection inputConnection, EditorInfo info);
 
     /**
@@ -147,6 +152,7 @@
      * 
      * @see EditorInfo
      */
+    @MainThread
     public void restartInput(InputConnection inputConnection, EditorInfo attribute);
 
     /**
@@ -177,6 +183,7 @@
      * @see EditorInfo
      * @hide
      */
+    @MainThread
     default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
             @NonNull EditorInfo editorInfo, boolean restarting,
             @NonNull IBinder startInputToken) {
@@ -195,6 +202,7 @@
      * 
      * @param callback Interface that is called with the newly created session.
      */
+    @MainThread
     public void createSession(SessionCallback callback);
     
     /**
@@ -203,6 +211,7 @@
      * @param session The {@link InputMethodSession} previously provided through
      * SessionCallback.sessionCreated() that is to be changed.
      */
+    @MainThread
     public void setSessionEnabled(InputMethodSession session, boolean enabled);
     
     /**
@@ -214,6 +223,7 @@
      * @param session The {@link InputMethodSession} previously provided through
      * SessionCallback.sessionCreated() that is to be revoked.
      */
+    @MainThread
     public void revokeSession(InputMethodSession session);
     
     /**
@@ -244,6 +254,7 @@
      * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
+    @MainThread
     public void showSoftInput(int flags, ResultReceiver resultReceiver);
     
     /**
@@ -258,11 +269,13 @@
      * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or
      * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}.
      */
+    @MainThread
     public void hideSoftInput(int flags, ResultReceiver resultReceiver);
 
     /**
      * Notify that the input method subtype is being changed in the same input method.
      * @param subtype New subtype of the notified input method
      */
+    @MainThread
     public void changeInputMethodSubtype(InputMethodSubtype subtype);
 }
diff --git a/core/java/android/view/textclassifier/Log.java b/core/java/android/view/textclassifier/Log.java
new file mode 100644
index 0000000..83ca15d
--- /dev/null
+++ b/core/java/android/view/textclassifier/Log.java
@@ -0,0 +1,46 @@
+/*
+ * 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.view.textclassifier;
+
+import android.util.Slog;
+
+/**
+ * Logging for android.view.textclassifier package.
+ */
+final class Log {
+
+    /**
+     * true: Enables full logging.
+     * false: Limits logging to debug level.
+     */
+    private static final boolean ENABLE_FULL_LOGGING = false;
+
+    private Log() {}
+
+    public static void d(String tag, String msg) {
+        Slog.d(tag, msg);
+    }
+
+    public static void e(String tag, String msg, Throwable tr) {
+        if (ENABLE_FULL_LOGGING) {
+            Slog.e(tag, msg, tr);
+        } else {
+            final String trString = (tr != null) ? tr.getClass().getSimpleName() : "??";
+            Slog.d(tag, String.format("%s (%s)", msg, trString));
+        }
+    }
+}
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index f0e83d1..2c93a19 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -16,6 +16,8 @@
 
 package android.view.textclassifier;
 
+import android.content.res.AssetFileDescriptor;
+
 /**
  *  Java wrapper for SmartSelection native library interface.
  *  This library is used for detecting entities in text.
@@ -42,6 +44,26 @@
     }
 
     /**
+     * Creates a new instance of SmartSelect predictor, using the provided model image, given as a
+     * file path.
+     */
+    SmartSelection(String path) {
+        mCtx = nativeNewFromPath(path);
+    }
+
+    /**
+     * Creates a new instance of SmartSelect predictor, using the provided model image, given as an
+     * AssetFileDescriptor.
+     */
+    SmartSelection(AssetFileDescriptor afd) {
+        mCtx = nativeNewFromAssetFileDescriptor(afd, afd.getStartOffset(), afd.getLength());
+        if (mCtx == 0L) {
+            throw new IllegalArgumentException(
+                "Couldn't initialize TC from given AssetFileDescriptor");
+        }
+    }
+
+    /**
      * Given a string context and current selection, computes the SmartSelection suggestion.
      *
      * The begin and end are character indices into the context UTF8 string. selectionBegin is the
@@ -69,6 +91,15 @@
     }
 
     /**
+     * Annotates given input text. Every word of the input is a part of some annotation.
+     * The annotations are sorted by their position in the context string.
+     * The annotations do not overlap.
+     */
+    public AnnotatedSpan[] annotate(String text) {
+        return nativeAnnotate(mCtx, text);
+    }
+
+    /**
      * Frees up the allocated memory.
      */
     public void close() {
@@ -91,12 +122,19 @@
 
     private static native long nativeNew(int fd);
 
+    private static native long nativeNewFromPath(String path);
+
+    private static native long nativeNewFromAssetFileDescriptor(AssetFileDescriptor afd,
+                                                                long offset, long size);
+
     private static native int[] nativeSuggest(
             long context, String text, int selectionBegin, int selectionEnd);
 
     private static native ClassificationResult[] nativeClassifyText(
             long context, String text, int selectionBegin, int selectionEnd, int hintFlags);
 
+    private static native AnnotatedSpan[] nativeAnnotate(long context, String text);
+
     private static native void nativeClose(long context);
 
     private static native String nativeGetLanguage(int fd);
@@ -114,4 +152,29 @@
             mScore = score;
         }
     }
+
+    /** Represents a result of Annotate call. */
+    public static final class AnnotatedSpan {
+        final int mStartIndex;
+        final int mEndIndex;
+        final ClassificationResult[] mClassification;
+
+        AnnotatedSpan(int startIndex, int endIndex, ClassificationResult[] classification) {
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+            mClassification = classification;
+        }
+
+        public int getStartIndex() {
+            return mStartIndex;
+        }
+
+        public int getEndIndex() {
+            return mEndIndex;
+        }
+
+        public ClassificationResult[] getClassification() {
+            return mClassification;
+        }
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 1849368..26d2141 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -28,10 +28,57 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Information for generating a widget to handle classified text.
+ *
+ * <p>A TextClassification object contains icons, labels, onClickListeners and intents that may
+ * be used to build a widget that can be used to act on classified text.
+ *
+ * <p>e.g. building a view that, when clicked, shares the classified text with the preferred app:
+ *
+ * <pre>{@code
+ *   // Called preferably outside the UiThread.
+ *   TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ *   // Called on the UiThread.
+ *   Button button = new Button(context);
+ *   button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
+ *   button.setText(classification.getLabel());
+ *   button.setOnClickListener(classification.getOnClickListener());
+ * }</pre>
+ *
+ * <p>e.g. starting an action mode with menu items that can handle the classified text:
+ *
+ * <pre>{@code
+ *   // Called preferably outside the UiThread.
+ *   final TextClassification classification = textClassifier.classifyText(allText, 10, 25, null);
+ *
+ *   // Called on the UiThread.
+ *   view.startActionMode(new ActionMode.Callback() {
+ *
+ *       public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ *           for (int i = 0; i < classification.getActionCount(); i++) {
+ *               if (thisAppHasPermissionToInvokeIntent(classification.getIntent(i))) {
+ *                   menu.add(Menu.NONE, i, 20, classification.getLabel(i))
+ *                      .setIcon(classification.getIcon(i))
+ *                      .setIntent(classification.getIntent(i));
+ *               }
+ *           }
+ *           return true;
+ *       }
+ *
+ *       public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ *           context.startActivity(item.getIntent());
+ *           return true;
+ *       }
+ *
+ *       ...
+ *   });
+ * }</pre>
+ *
  */
 public final class TextClassification {
 
@@ -41,10 +88,10 @@
     static final TextClassification EMPTY = new TextClassification.Builder().build();
 
     @NonNull private final String mText;
-    @Nullable private final Drawable mIcon;
-    @Nullable private final String mLabel;
-    @Nullable private final Intent mIntent;
-    @Nullable private final OnClickListener mOnClickListener;
+    @NonNull private final List<Drawable> mIcons;
+    @NonNull private final List<String> mLabels;
+    @NonNull private final List<Intent> mIntents;
+    @NonNull private final List<OnClickListener> mOnClickListeners;
     @NonNull private final EntityConfidence<String> mEntityConfidence;
     @NonNull private final List<String> mEntities;
     private int mLogType;
@@ -52,18 +99,21 @@
 
     private TextClassification(
             @Nullable String text,
-            @Nullable Drawable icon,
-            @Nullable String label,
-            @Nullable Intent intent,
-            @Nullable OnClickListener onClickListener,
+            @NonNull List<Drawable> icons,
+            @NonNull List<String> labels,
+            @NonNull List<Intent> intents,
+            @NonNull List<OnClickListener> onClickListeners,
             @NonNull EntityConfidence<String> entityConfidence,
             int logType,
             @NonNull String versionInfo) {
+        Preconditions.checkArgument(labels.size() == intents.size());
+        Preconditions.checkArgument(icons.size() == intents.size());
+        Preconditions.checkArgument(onClickListeners.size() == intents.size());
         mText = text;
-        mIcon = icon;
-        mLabel = label;
-        mIntent = intent;
-        mOnClickListener = onClickListener;
+        mIcons = icons;
+        mLabels = labels;
+        mIntents = intents;
+        mOnClickListeners = onClickListeners;
         mEntityConfidence = new EntityConfidence<>(entityConfidence);
         mEntities = mEntityConfidence.getEntities();
         mLogType = logType;
@@ -109,35 +159,106 @@
     }
 
     /**
-     * Returns an icon that may be rendered on a widget used to act on the classified text.
+     * Returns the number of actions that are available to act on the classified text.
+     * @see #getIntent(int)
+     * @see #getLabel(int)
+     * @see #getIcon(int)
+     * @see #getOnClickListener(int)
+     */
+    @IntRange(from = 0)
+    public int getActionCount() {
+        return mIntents.size();
+    }
+
+    /**
+     * Returns one of the icons that maybe rendered on a widget used to act on the classified text.
+     * @param index Index of the action to get the icon for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount() for the number of entities available.
+     * @see #getIntent(int)
+     * @see #getLabel(int)
+     * @see #getOnClickListener(int)
+     */
+    @Nullable
+    public Drawable getIcon(int index) {
+        return mIcons.get(index);
+    }
+
+    /**
+     * Returns an icon for the default intent that may be rendered on a widget used to act on the
+     * classified text.
      */
     @Nullable
     public Drawable getIcon() {
-        return mIcon;
+        return mIcons.isEmpty() ? null : mIcons.get(0);
     }
 
     /**
-     * Returns a label that may be rendered on a widget used to act on the classified text.
+     * Returns one of the labels that may be rendered on a widget used to act on the classified
+     * text.
+     * @param index Index of the action to get the label for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount()
+     * @see #getIntent(int)
+     * @see #getIcon(int)
+     * @see #getOnClickListener(int)
+     */
+    @Nullable
+    public CharSequence getLabel(int index) {
+        return mLabels.get(index);
+    }
+
+    /**
+     * Returns a label for the default intent that may be rendered on a widget used to act on the
+     * classified text.
      */
     @Nullable
     public CharSequence getLabel() {
-        return mLabel;
+        return mLabels.isEmpty() ? null : mLabels.get(0);
     }
 
     /**
-     * Returns an intent that may be fired to act on the classified text.
+     * Returns one of the intents that may be fired to act on the classified text.
+     * @param index Index of the action to get the intent for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount()
+     * @see #getLabel(int)
+     * @see #getIcon(int)
+     * @see #getOnClickListener(int)
+     */
+    @Nullable
+    public Intent getIntent(int index) {
+        return mIntents.get(index);
+    }
+
+    /**
+     * Returns the default intent that may be fired to act on the classified text.
      */
     @Nullable
     public Intent getIntent() {
-        return mIntent;
+        return mIntents.isEmpty() ? null : mIntents.get(0);
     }
 
     /**
-     * Returns an OnClickListener that may be triggered to act on the classified text.
+     * Returns one of the OnClickListeners that may be triggered to act on the classified text.
+     * @param index Index of the action to get the click listener for.
+     * @throws IndexOutOfBoundsException if the specified index is out of range.
+     * @see #getActionCount()
+     * @see #getIntent(int)
+     * @see #getLabel(int)
+     * @see #getIcon(int)
+     */
+    @Nullable
+    public OnClickListener getOnClickListener(int index) {
+        return mOnClickListeners.get(index);
+    }
+
+    /**
+     * Returns the default OnClickListener that may be triggered to act on the classified text.
      */
     @Nullable
     public OnClickListener getOnClickListener() {
-        return mOnClickListener;
+        return mOnClickListeners.isEmpty() ? null : mOnClickListeners.get(0);
     }
 
     /**
@@ -160,8 +281,8 @@
     @Override
     public String toString() {
         return String.format("TextClassification {"
-                        + "text=%s, entities=%s, label=%s, intent=%s}",
-                mText, mEntityConfidence, mLabel, mIntent);
+                        + "text=%s, entities=%s, labels=%s, intents=%s}",
+                mText, mEntityConfidence, mLabels, mIntents);
     }
 
     /**
@@ -184,10 +305,10 @@
     public static final class Builder {
 
         @NonNull private String mText;
-        @Nullable private Drawable mIcon;
-        @Nullable private String mLabel;
-        @Nullable private Intent mIntent;
-        @Nullable private OnClickListener mOnClickListener;
+        @NonNull private final List<Drawable> mIcons = new ArrayList<>();
+        @NonNull private final List<String> mLabels = new ArrayList<>();
+        @NonNull private final List<Intent> mIntents = new ArrayList<>();
+        @NonNull private final List<OnClickListener> mOnClickListeners = new ArrayList<>();
         @NonNull private final EntityConfidence<String> mEntityConfidence =
                 new EntityConfidence<>();
         private int mLogType;
@@ -216,26 +337,57 @@
         }
 
         /**
-         * Sets an icon that may be rendered on a widget used to act on the classified text.
+         * Adds an action that may be performed on the classified text. The label and icon are used
+         * for rendering of widgets that offer the intent. Actions should be added in order of
+         * priority and the first one will be treated as the default.
+         */
+        public Builder addAction(
+                Intent intent, @Nullable String label, @Nullable Drawable icon,
+                @Nullable OnClickListener onClickListener) {
+            mIntents.add(intent);
+            mLabels.add(label);
+            mIcons.add(icon);
+            mOnClickListeners.add(onClickListener);
+            return this;
+        }
+
+        /**
+         * Removes all actions.
+         */
+        public Builder clearActions() {
+            mIntents.clear();
+            mOnClickListeners.clear();
+            mLabels.clear();
+            mIcons.clear();
+            return this;
+        }
+
+        /**
+         * Sets the icon for the default action that may be rendered on a widget used to act on the
+         * classified text.
          */
         public Builder setIcon(@Nullable Drawable icon) {
-            mIcon = icon;
+            ensureDefaultActionAvailable();
+            mIcons.set(0, icon);
             return this;
         }
 
         /**
-         * Sets a label that may be rendered on a widget used to act on the classified text.
+         * Sets the label for the default action that may be rendered on a widget used to act on the
+         * classified text.
          */
         public Builder setLabel(@Nullable String label) {
-            mLabel = label;
+            ensureDefaultActionAvailable();
+            mLabels.set(0, label);
             return this;
         }
 
         /**
-         * Sets an intent that may be fired to act on the classified text.
+         * Sets the intent for the default action that may be fired to act on the classified text.
          */
         public Builder setIntent(@Nullable Intent intent) {
-            mIntent = intent;
+            ensureDefaultActionAvailable();
+            mIntents.set(0, intent);
             return this;
         }
 
@@ -249,10 +401,12 @@
         }
 
         /**
-         * Sets an OnClickListener that may be triggered to act on the classified text.
+         * Sets the OnClickListener for the default action that may be triggered to act on the
+         * classified text.
          */
         public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
-            mOnClickListener = onClickListener;
+            ensureDefaultActionAvailable();
+            mOnClickListeners.set(0, onClickListener);
             return this;
         }
 
@@ -266,11 +420,21 @@
         }
 
         /**
+         * Ensures that we have at we have storage for the default action.
+         */
+        private void ensureDefaultActionAvailable() {
+            if (mIntents.isEmpty()) mIntents.add(null);
+            if (mLabels.isEmpty()) mLabels.add(null);
+            if (mIcons.isEmpty()) mIcons.add(null);
+            if (mOnClickListeners.isEmpty()) mOnClickListeners.add(null);
+        }
+
+        /**
          * Builds and returns a {@link TextClassification} object.
          */
         public TextClassification build() {
             return new TextClassification(
-                    mText, mIcon, mLabel, mIntent, mOnClickListener, mEntityConfidence,
+                    mText, mIcons, mLabels, mIntents, mOnClickListeners, mEntityConfidence,
                     mLogType, mVersionInfo);
         }
     }
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index bb1e693..46dbd0e 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -29,8 +29,8 @@
 /**
  * Interface for providing text classification related features.
  *
- * <p>Unless otherwise stated, methods of this interface are blocking operations and you should
- * avoid calling them on the UI thread.
+ * <p>Unless otherwise stated, methods of this interface are blocking operations.
+ * Avoid calling them on the UI thread.
  */
 public interface TextClassifier {
 
@@ -75,8 +75,8 @@
     };
 
     /**
-     * Returns suggested text selection indices, recognized types and their associated confidence
-     * scores. The selections are ordered from highest to lowest scoring.
+     * Returns suggested text selection start and end indices, recognized entity types, and their
+     * associated confidence scores. The entity types are ordered from highest to lowest scoring.
      *
      * @param text text providing context for the selected text (which is specified
      *      by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex)
@@ -152,4 +152,12 @@
      */
     @WorkerThread
     default void logEvent(String source, String event) {}
+
+    /**
+     * Returns this TextClassifier's settings.
+     * @hide
+     */
+    default TextClassifierConstants getSettings() {
+        return TextClassifierConstants.DEFAULT;
+    }
 }
diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java
new file mode 100644
index 0000000..51e6168
--- /dev/null
+++ b/core/java/android/view/textclassifier/TextClassifierConstants.java
@@ -0,0 +1,90 @@
+/*
+ * 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.view.textclassifier;
+
+import android.annotation.Nullable;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+/**
+ * TextClassifier specific settings.
+ * This is encoded as a key=value list, separated by commas. Ex:
+ *
+ * <pre>
+ * smart_selection_dark_launch              (boolean)
+ * smart_selection_enabled_for_edit_text    (boolean)
+ * </pre>
+ *
+ * <p>
+ * Type: string
+ * see also android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
+ *
+ * Example of setting the values for testing.
+ * adb shell settings put global text_classifier_constants smart_selection_dark_launch=true,smart_selection_enabled_for_edit_text=true
+ * @hide
+ */
+public final class TextClassifierConstants {
+
+    private static final String LOG_TAG = "TextClassifierConstants";
+
+    private static final String SMART_SELECTION_DARK_LAUNCH =
+            "smart_selection_dark_launch";
+    private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT =
+            "smart_selection_enabled_for_edit_text";
+
+    private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
+    private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
+
+    /** Default settings. */
+    static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
+
+    private final boolean mDarkLaunch;
+    private final boolean mSuggestSelectionEnabledForEditableText;
+
+    private TextClassifierConstants() {
+        mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
+        mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
+    }
+
+    private TextClassifierConstants(@Nullable String settings) {
+        final KeyValueListParser parser = new KeyValueListParser(',');
+        try {
+            parser.setString(settings);
+        } catch (IllegalArgumentException e) {
+            // Failed to parse the settings string, log this and move on with defaults.
+            Slog.e(LOG_TAG, "Bad TextClassifier settings: " + settings);
+        }
+        mDarkLaunch = parser.getBoolean(
+                SMART_SELECTION_DARK_LAUNCH,
+                SMART_SELECTION_DARK_LAUNCH_DEFAULT);
+        mSuggestSelectionEnabledForEditableText = parser.getBoolean(
+                SMART_SELECTION_ENABLED_FOR_EDIT_TEXT,
+                SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT);
+    }
+
+    static TextClassifierConstants loadFromString(String settings) {
+        return new TextClassifierConstants(settings);
+    }
+
+    public boolean isDarkLaunch() {
+        return mDarkLaunch;
+    }
+
+    public boolean isSuggestSelectionEnabledForEditableText() {
+        return mSuggestSelectionEnabledForEditableText;
+    }
+}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 7e93b78..1c07be4 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -24,17 +24,17 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
-import android.icu.text.BreakIterator;
 import android.net.Uri;
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.provider.Browser;
+import android.provider.ContactsContract;
+import android.provider.Settings;
 import android.text.Spannable;
 import android.text.TextUtils;
 import android.text.method.WordIterator;
 import android.text.style.ClickableSpan;
 import android.text.util.Linkify;
-import android.util.Log;
 import android.util.Patterns;
 import android.view.View;
 import android.widget.TextViewMetrics;
@@ -46,6 +46,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.text.BreakIterator;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -90,6 +91,8 @@
     @GuardedBy("mSmartSelectionLock") // Do not access outside this lock.
     private SmartSelection mSmartSelection;
 
+    private TextClassifierConstants mSettings;
+
     TextClassifierImpl(Context context) {
         mContext = Preconditions.checkNotNull(context);
     }
@@ -159,7 +162,7 @@
             }
         } catch (Throwable t) {
             // Avoid throwing from this method. Log the error.
-            Log.e(LOG_TAG, "Error getting assist info.", t);
+            Log.e(LOG_TAG, "Error getting text classification info.", t);
         }
         // Getting here means something went wrong, return a NO_OP result.
         return TextClassifier.NO_OP.classifyText(
@@ -188,6 +191,15 @@
         }
     }
 
+    @Override
+    public TextClassifierConstants getSettings() {
+        if (mSettings == null) {
+            mSettings = TextClassifierConstants.loadFromString(Settings.Global.getString(
+                    mContext.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS));
+        }
+        return mSettings;
+    }
+
     private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException {
         synchronized (mSmartSelectionLock) {
             localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList;
@@ -356,7 +368,16 @@
         final String type = getHighestScoringType(classifications);
         builder.setLogType(IntentFactory.getLogType(type));
 
-        final Intent intent = IntentFactory.create(mContext, type, text.toString());
+        final List<Intent> intents = IntentFactory.create(mContext, type, text.toString());
+        for (Intent intent : intents) {
+            extendClassificationWithIntent(intent, builder);
+        }
+
+        return builder.setVersionInfo(getVersionInfo()).build();
+    }
+
+    /** Extends the classification with the intent if it can be resolved. */
+    private void extendClassificationWithIntent(Intent intent, TextClassification.Builder builder) {
         final PackageManager pm;
         final ResolveInfo resolveInfo;
         if (intent != null) {
@@ -367,30 +388,29 @@
             resolveInfo = null;
         }
         if (resolveInfo != null && resolveInfo.activityInfo != null) {
-            builder.setIntent(intent)
-                    .setOnClickListener(TextClassification.createStartActivityOnClickListener(
-                            mContext, intent));
-
             final String packageName = resolveInfo.activityInfo.packageName;
+            CharSequence label;
+            Drawable icon;
             if ("android".equals(packageName)) {
                 // Requires the chooser to find an activity to handle the intent.
-                builder.setLabel(IntentFactory.getLabel(mContext, type));
+                label = IntentFactory.getLabel(mContext, intent);
+                icon = null;
             } else {
                 // A default activity will handle the intent.
                 intent.setComponent(new ComponentName(packageName, resolveInfo.activityInfo.name));
-                Drawable icon = resolveInfo.activityInfo.loadIcon(pm);
+                icon = resolveInfo.activityInfo.loadIcon(pm);
                 if (icon == null) {
                     icon = resolveInfo.loadIcon(pm);
                 }
-                builder.setIcon(icon);
-                CharSequence label = resolveInfo.activityInfo.loadLabel(pm);
+                label = resolveInfo.activityInfo.loadLabel(pm);
                 if (label == null) {
                     label = resolveInfo.loadLabel(pm);
                 }
-                builder.setLabel(label != null ? label.toString() : null);
             }
+            builder.addAction(
+                    intent, label != null ? label.toString() : null, icon,
+                    TextClassification.createStartActivityOnClickListener(mContext, intent));
         }
-        return builder.setVersionInfo(getVersionInfo()).build();
     }
 
     private static int getHintFlags(CharSequence text, int start, int end) {
@@ -477,10 +497,11 @@
                     if (results.length > 0) {
                         final String type = getHighestScoringType(results);
                         if (matches(type, linkMask)) {
-                            final Intent intent = IntentFactory.create(
+                            // For links without disambiguation, we simply use the default intent.
+                            final List<Intent> intents = IntentFactory.create(
                                     context, type, text.substring(selectionStart, selectionEnd));
-                            if (hasActivityHandler(context, intent)) {
-                                final ClickableSpan span = createSpan(context, intent);
+                            if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
+                                final ClickableSpan span = createSpan(context, intents.get(0));
                                 spans.add(new SpanSpec(selectionStart, selectionEnd, span));
                             }
                         }
@@ -564,7 +585,7 @@
             };
         }
 
-        private static boolean hasActivityHandler(Context context, @Nullable Intent intent) {
+        private static boolean hasActivityHandler(Context context, Intent intent) {
             if (intent == null) {
                 return false;
             }
@@ -625,20 +646,32 @@
 
         private IntentFactory() {}
 
-        @Nullable
-        public static Intent create(Context context, String type, String text) {
+        @NonNull
+        public static List<Intent> create(Context context, String type, String text) {
+            final List<Intent> intents = new ArrayList<>();
             type = type.trim().toLowerCase(Locale.ENGLISH);
             text = text.trim();
             switch (type) {
                 case TextClassifier.TYPE_EMAIL:
-                    return new Intent(Intent.ACTION_SENDTO)
-                            .setData(Uri.parse(String.format("mailto:%s", text)));
+                    intents.add(new Intent(Intent.ACTION_SENDTO)
+                            .setData(Uri.parse(String.format("mailto:%s", text))));
+                    intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+                                    .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                                    .putExtra(ContactsContract.Intents.Insert.EMAIL, text));
+                    break;
                 case TextClassifier.TYPE_PHONE:
-                    return new Intent(Intent.ACTION_DIAL)
-                            .setData(Uri.parse(String.format("tel:%s", text)));
+                    intents.add(new Intent(Intent.ACTION_DIAL)
+                            .setData(Uri.parse(String.format("tel:%s", text))));
+                    intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+                            .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
+                            .putExtra(ContactsContract.Intents.Insert.PHONE, text));
+                    intents.add(new Intent(Intent.ACTION_SENDTO)
+                            .setData(Uri.parse(String.format("smsto:%s", text))));
+                    break;
                 case TextClassifier.TYPE_ADDRESS:
-                    return new Intent(Intent.ACTION_VIEW)
-                            .setData(Uri.parse(String.format("geo:0,0?q=%s", text)));
+                    intents.add(new Intent(Intent.ACTION_VIEW)
+                            .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
+                    break;
                 case TextClassifier.TYPE_URL:
                     final String httpPrefix = "http://";
                     final String httpsPrefix = "https://";
@@ -649,25 +682,47 @@
                     } else {
                         text = httpPrefix + text;
                     }
-                    return new Intent(Intent.ACTION_VIEW, Uri.parse(text))
-                            .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
-                default:
-                    return null;
+                    intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+                            .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
+                    break;
             }
+            return intents;
         }
 
         @Nullable
-        public static String getLabel(Context context, String type) {
-            type = type.trim().toLowerCase(Locale.ENGLISH);
-            switch (type) {
-                case TextClassifier.TYPE_EMAIL:
-                    return context.getString(com.android.internal.R.string.email);
-                case TextClassifier.TYPE_PHONE:
+        public static String getLabel(Context context, @Nullable Intent intent) {
+            if (intent == null || intent.getAction() == null) {
+                return null;
+            }
+            switch (intent.getAction()) {
+                case Intent.ACTION_DIAL:
                     return context.getString(com.android.internal.R.string.dial);
-                case TextClassifier.TYPE_ADDRESS:
-                    return context.getString(com.android.internal.R.string.map);
-                case TextClassifier.TYPE_URL:
-                    return context.getString(com.android.internal.R.string.browse);
+                case Intent.ACTION_SENDTO:
+                    switch (intent.getScheme()) {
+                        case "mailto":
+                            return context.getString(com.android.internal.R.string.email);
+                        case "smsto":
+                            return context.getString(com.android.internal.R.string.sms);
+                        default:
+                            return null;
+                    }
+                case Intent.ACTION_INSERT_OR_EDIT:
+                    switch (intent.getDataString()) {
+                        case ContactsContract.Contacts.CONTENT_ITEM_TYPE:
+                            return context.getString(com.android.internal.R.string.add_contact);
+                        default:
+                            return null;
+                    }
+                case Intent.ACTION_VIEW:
+                    switch (intent.getScheme()) {
+                        case "geo":
+                            return context.getString(com.android.internal.R.string.map);
+                        case "http": // fall through
+                        case "https":
+                            return context.getString(com.android.internal.R.string.browse);
+                        default:
+                            return null;
+                    }
                 default:
                     return null;
             }
diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 8d88ba6..83af19b 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -581,6 +581,7 @@
                 case ActionType.SMART_SHARE:  // fall through
                 case ActionType.DRAG:  // fall through
                 case ActionType.ABANDON:  // fall through
+                case ActionType.OTHER:  // fall through
                     return true;
                 default:
                     return false;
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index b839420..fc76029 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -16,13 +16,14 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Map;
 
-
 /**
  * Manages the HTTP cache used by an application's {@link WebView} instances.
  * @deprecated Access to the HTTP cache will be removed in a future release.
@@ -233,6 +234,7 @@
      * @deprecated This method no longer has any effect and always returns {@code null}.
      */
     @Deprecated
+    @Nullable
     public static File getCacheFileBaseDir() {
         return null;
     }
@@ -287,6 +289,7 @@
      * @deprecated This method no longer has any effect and always returns {@code null}.
      */
     @Deprecated
+    @Nullable
     public static CacheResult getCacheFile(String url,
             Map<String, String> headers) {
         return null;
diff --git a/core/java/android/webkit/ClientCertRequest.java b/core/java/android/webkit/ClientCertRequest.java
index de17534..0fc47f1 100644
--- a/core/java/android/webkit/ClientCertRequest.java
+++ b/core/java/android/webkit/ClientCertRequest.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.cert.X509Certificate;
@@ -42,14 +44,16 @@
     public ClientCertRequest() { }
 
     /**
-     * Returns the acceptable types of asymmetric keys (can be {@code null}).
+     * Returns the acceptable types of asymmetric keys.
      */
+    @Nullable
     public abstract String[] getKeyTypes();
 
     /**
      * Returns the acceptable certificate issuers for the certificate
-     *            matching the private key (can be {@code null}).
+     *            matching the private key.
      */
+    @Nullable
     public abstract Principal[] getPrincipals();
 
     /**
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 8989293..ae6a2fd 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.WebAddress;
 
@@ -116,7 +117,8 @@
      *              HTTP response header
      * @param callback a callback to be executed when the cookie has been set
      */
-    public abstract void setCookie(String url, String value, ValueCallback<Boolean> callback);
+    public abstract void setCookie(String url, String value, @Nullable ValueCallback<Boolean>
+            callback);
 
     /**
      * Gets the cookies for the given URL.
@@ -175,7 +177,7 @@
      * method from a thread without a Looper.
      * @param callback a callback which is executed when the session cookies have been removed
      */
-    public abstract void removeSessionCookies(ValueCallback<Boolean> callback);
+    public abstract void removeSessionCookies(@Nullable ValueCallback<Boolean> callback);
 
     /**
      * Removes all cookies.
@@ -197,7 +199,7 @@
      * method from a thread without a Looper.
      * @param callback a callback which is executed when the cookies have been removed
      */
-    public abstract void removeAllCookies(ValueCallback<Boolean> callback);
+    public abstract void removeAllCookies(@Nullable ValueCallback<Boolean> callback);
 
     /**
      * Gets whether there are stored cookies.
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index 71f85d7..e011d51 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.res.Resources;
@@ -69,7 +70,7 @@
         mActionMode.finish();
     }
 
-    /*
+    /**
      * Place text in the text field so it can be searched for.  Need to press
      * the find next or find previous button to find all of the matches.
      */
@@ -87,10 +88,12 @@
         mMatchesFound = false;
     }
 
-    /*
-     * Set the WebView to search.  Must be non null.
+    /**
+     * Set the WebView to search.
+     *
+     * @param webView an implementation of WebView
      */
-    public void setWebView(WebView webView) {
+    public void setWebView(@NonNull WebView webView) {
         if (null == webView) {
             throw new AssertionError("WebView supplied to "
                     + "FindActionModeCallback cannot be null");
@@ -107,7 +110,7 @@
         }
     }
 
-    /*
+    /**
      * Move the highlight to the next match.
      * @param next If {@code true}, find the next match further down in the document.
      *             If {@code false}, find the previous match, up in the document.
@@ -130,7 +133,7 @@
         updateMatchesString();
     }
 
-    /*
+    /**
      * Highlight all the instances of the string from mEditText in mWebView.
      */
     public void findAll() {
@@ -169,7 +172,7 @@
         }
     }
 
-    /*
+    /**
      * Update the string which tells the user how many matches were found, and
      * which match is currently highlighted.
      */
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index e172c02..3861695 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.text.TextUtils;
 
 import libcore.net.MimeUtils;
@@ -36,7 +37,7 @@
     }
 
     /**
-     * Returns the file extension or an empty string iff there is no
+     * Returns the file extension or an empty string if there is no
      * extension. This method is a convenience method for obtaining the
      * extension of a url and has undefined results for other Strings.
      * @param url
@@ -75,7 +76,7 @@
     /**
      * Return {@code true} if the given MIME type has an entry in the map.
      * @param mimeType A MIME type (i.e. text/plain)
-     * @return {@code true} iff there is a mimeType entry in the map.
+     * @return {@code true} if there is a mimeType entry in the map.
      */
     public boolean hasMimeType(String mimeType) {
         return MimeUtils.hasMimeType(mimeType);
@@ -84,8 +85,9 @@
     /**
      * Return the MIME type for the given extension.
      * @param extension A file extension without the leading '.'
-     * @return The MIME type for the given extension or {@code null} iff there is none.
+     * @return The MIME type for the given extension or {@code null} if there is none.
      */
+    @Nullable
     public String getMimeTypeFromExtension(String extension) {
         return MimeUtils.guessMimeTypeFromExtension(extension);
     }
@@ -98,7 +100,7 @@
     /**
      * Return {@code true} if the given extension has a registered MIME type.
      * @param extension A file extension without the leading '.'
-     * @return {@code true} iff there is an extension entry in the map.
+     * @return {@code true} if there is an extension entry in the map.
      */
     public boolean hasExtension(String extension) {
         return MimeUtils.hasExtension(extension);
@@ -109,8 +111,9 @@
      * MIME types map to multiple extensions. This call will return the most
      * common extension for the given MIME type.
      * @param mimeType A MIME type (i.e. text/plain)
-     * @return The extension for the given MIME type or {@code null} iff there is none.
+     * @return The extension for the given MIME type or {@code null} if there is none.
      */
+    @Nullable
     public String getExtensionFromMimeType(String mimeType) {
         return MimeUtils.guessExtensionFromMimeType(mimeType);
     }
@@ -125,7 +128,7 @@
      * @param contentDisposition Content-disposition header given by the server.
      * @return The MIME type that should be used for this data.
      */
-    /* package */ String remapGenericMimeType(String mimeType, String url,
+    /* package */ String remapGenericMimeType(@Nullable String mimeType, String url,
             String contentDisposition) {
         // If we have one of "generic" MIME types, try to deduce
         // the right MIME type from the file extension (if any):
diff --git a/core/java/android/webkit/Plugin.java b/core/java/android/webkit/Plugin.java
index 072e02a..29a5edc 100644
--- a/core/java/android/webkit/Plugin.java
+++ b/core/java/android/webkit/Plugin.java
@@ -16,12 +16,12 @@
 
 package android.webkit;
 
-import com.android.internal.R;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 
+import com.android.internal.R;
+
 /**
  * Represents a plugin (Java equivalent of the PluginPackageAndroid
  * C++ class in libs/WebKitLib/WebKit/WebCore/plugins/android/)
@@ -32,13 +32,13 @@
  */
 @Deprecated
 public class Plugin {
-    /*
+    /**
      * @hide
      * @deprecated This interface was intended to be used by Gears. Since Gears was
      * deprecated, so is this class.
      */
     public interface PreferencesClickHandler {
-        /*
+        /**
          * @hide
          * @deprecated This interface was intended to be used by Gears. Since Gears was
          * deprecated, so is this class.
diff --git a/core/java/android/webkit/ServiceWorkerClient.java b/core/java/android/webkit/ServiceWorkerClient.java
index 51d25c7..9124c85 100644
--- a/core/java/android/webkit/ServiceWorkerClient.java
+++ b/core/java/android/webkit/ServiceWorkerClient.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 /**
  * Base class for clients to capture Service Worker related callbacks,
  * see {@link ServiceWorkerController} for usage example.
@@ -37,6 +39,7 @@
      *         resource itself.
      * @see WebViewClient#shouldInterceptRequest(WebView, WebResourceRequest)
      */
+    @Nullable
     public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
         return null;
     }
diff --git a/core/java/android/webkit/TokenBindingService.java b/core/java/android/webkit/TokenBindingService.java
index 43565c2..b37e1b8 100644
--- a/core/java/android/webkit/TokenBindingService.java
+++ b/core/java/android/webkit/TokenBindingService.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.Uri;
 
@@ -84,31 +86,30 @@
      * The user can pass {@code null} if any algorithm is acceptable.
      *
      * @param origin The origin for the server.
-     * @param algorithm The list of algorithms. Can be {@code null}. An
-     *        IllegalArgumentException is thrown if array is empty.
+     * @param algorithm The list of algorithms. An IllegalArgumentException is thrown if array is
+     *                  empty.
      * @param callback The callback that will be called when key is available.
-     *        Cannot be {@code null}.
      */
     public abstract void getKey(Uri origin,
-                                String[] algorithm,
-                                ValueCallback<TokenBindingKey> callback);
+                                @Nullable String[] algorithm,
+                                @NonNull ValueCallback<TokenBindingKey> callback);
     /**
      * Deletes specified key (for use when associated cookie is cleared).
      *
      * @param origin The origin of the server.
      * @param callback The callback that will be called when key is deleted. The
      *        callback parameter (Boolean) will indicate if operation is
-     *        successful or if failed. The callback can be {@code null}.
+     *        successful or if failed.
      */
     public abstract void deleteKey(Uri origin,
-                                   ValueCallback<Boolean> callback);
+                                   @Nullable ValueCallback<Boolean> callback);
 
      /**
       * Deletes all the keys (for use when cookies are cleared).
       *
       * @param callback The callback that will be called when keys are deleted.
       *        The callback parameter (Boolean) will indicate if operation is
-      *        successful or if failed. The callback can be {@code null}.
+      *        successful or if failed.
       */
-    public abstract void deleteAllKeys(ValueCallback<Boolean> callback);
+    public abstract void deleteAllKeys(@Nullable ValueCallback<Boolean> callback);
 }
diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java
index c8bfb77..84c000a 100644
--- a/core/java/android/webkit/URLUtil.java
+++ b/core/java/android/webkit/URLUtil.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.net.ParseException;
 import android.net.Uri;
 import android.net.WebAddress;
@@ -136,7 +137,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is correctly URL encoded
+     * @return {@code true} if the url is correctly URL encoded
      */
     static boolean verifyURLEncoding(String url) {
         int count = url.length();
@@ -170,14 +171,14 @@
     }
 
     /**
-     * @return {@code true} iff the url is an asset file.
+     * @return {@code true} if the url is an asset file.
      */
     public static boolean isAssetUrl(String url) {
         return (null != url) && url.startsWith(ASSET_BASE);
     }
 
     /**
-     * @return {@code true} iff the url is a resource file.
+     * @return {@code true} if the url is a resource file.
      * @hide
      */
     public static boolean isResourceUrl(String url) {
@@ -185,7 +186,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is a proxy url to allow cookieless network
+     * @return {@code true} if the url is a proxy url to allow cookieless network
      * requests from a file url.
      * @deprecated Cookieless proxy is no longer supported.
      */
@@ -195,7 +196,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is a local file.
+     * @return {@code true} if the url is a local file.
      */
     public static boolean isFileUrl(String url) {
         return (null != url) && (url.startsWith(FILE_BASE) &&
@@ -204,28 +205,28 @@
     }
 
     /**
-     * @return {@code true} iff the url is an about: url.
+     * @return {@code true} if the url is an about: url.
      */
     public static boolean isAboutUrl(String url) {
         return (null != url) && url.startsWith("about:");
     }
 
     /**
-     * @return {@code true} iff the url is a data: url.
+     * @return {@code true} if the url is a data: url.
      */
     public static boolean isDataUrl(String url) {
         return (null != url) && url.startsWith("data:");
     }
 
     /**
-     * @return {@code true} iff the url is a javascript: url.
+     * @return {@code true} if the url is a javascript: url.
      */
     public static boolean isJavaScriptUrl(String url) {
         return (null != url) && url.startsWith("javascript:");
     }
 
     /**
-     * @return {@code true} iff the url is an http: url.
+     * @return {@code true} if the url is an http: url.
      */
     public static boolean isHttpUrl(String url) {
         return (null != url) &&
@@ -234,7 +235,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is an https: url.
+     * @return {@code true} if the url is an https: url.
      */
     public static boolean isHttpsUrl(String url) {
         return (null != url) &&
@@ -243,7 +244,7 @@
     }
 
     /**
-     * @return {@code true} iff the url is a network url.
+     * @return {@code true} if the url is a network url.
      */
     public static boolean isNetworkUrl(String url) {
         if (url == null || url.length() == 0) {
@@ -253,14 +254,14 @@
     }
 
     /**
-     * @return {@code true} iff the url is a content: url.
+     * @return {@code true} if the url is a content: url.
      */
     public static boolean isContentUrl(String url) {
         return (null != url) && url.startsWith(CONTENT_BASE);
     }
 
     /**
-     * @return {@code true} iff the url is valid.
+     * @return {@code true} if the url is valid.
      */
     public static boolean isValidUrl(String url) {
         if (url == null || url.length() == 0) {
@@ -300,8 +301,8 @@
      */
     public static final String guessFileName(
             String url,
-            String contentDisposition,
-            String mimeType) {
+            @Nullable String contentDisposition,
+            @Nullable String mimeType) {
         String filename = null;
         String extension = null;
 
@@ -388,7 +389,7 @@
             Pattern.compile("attachment;\\s*filename\\s*=\\s*(\"?)([^\"]*)\\1\\s*$",
             Pattern.CASE_INSENSITIVE);
 
-    /*
+    /**
      * Parse the Content-Disposition HTTP Header. The format of the header
      * is defined here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html
      * This header provides a filename for content that is going to be
diff --git a/core/java/android/webkit/UrlInterceptHandler.java b/core/java/android/webkit/UrlInterceptHandler.java
index aa5c6dc..0a6e51f 100644
--- a/core/java/android/webkit/UrlInterceptHandler.java
+++ b/core/java/android/webkit/UrlInterceptHandler.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.webkit.CacheManager.CacheResult;
 import android.webkit.PluginData;
 
@@ -35,14 +36,15 @@
      * not interested.
      *
      * @param url URL string.
-     * @param headers The headers associated with the request. May be {@code null}.
+     * @param headers The headers associated with the request.
      * @return The CacheResult containing the surrogate response.
      *
      * @hide
      * @deprecated Do not use, this interface is deprecated.
      */
     @Deprecated
-    public CacheResult service(String url, Map<String, String> headers);
+    @Nullable
+    CacheResult service(String url, @Nullable Map<String, String> headers);
 
     /**
      * Given an URL, returns the PluginData which contains the
@@ -50,12 +52,13 @@
      * not interested.
      *
      * @param url URL string.
-     * @param headers The headers associated with the request. May be {@code null}.
+     * @param headers The headers associated with the request.
      * @return The PluginData containing the surrogate response.
      *
      * @hide
      * @deprecated Do not use, this interface is deprecated.
      */
     @Deprecated
-    public PluginData getPluginData(String url, Map<String, String> headers);
+    @Nullable
+    PluginData getPluginData(String url, @Nullable Map<String, String> headers);
 }
diff --git a/core/java/android/webkit/UrlInterceptRegistry.java b/core/java/android/webkit/UrlInterceptRegistry.java
index 67af2ad..700d6d9 100644
--- a/core/java/android/webkit/UrlInterceptRegistry.java
+++ b/core/java/android/webkit/UrlInterceptRegistry.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.webkit.CacheManager.CacheResult;
 import android.webkit.PluginData;
 import android.webkit.UrlInterceptHandler;
@@ -121,6 +122,7 @@
      * deprecated, so is this class.
      */
     @Deprecated
+    @Nullable
     public static synchronized CacheResult getSurrogate(
             String url, Map<String, String> headers) {
         if (urlInterceptDisabled()) {
@@ -149,6 +151,7 @@
      * deprecated, so is this class.
      */
     @Deprecated
+    @Nullable
     public static synchronized PluginData getPluginData(
             String url, Map<String, String> headers) {
         if (urlInterceptDisabled()) {
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index 3349b06..0c34e3c 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 import java.io.Serializable;
 
 /**
@@ -29,6 +31,7 @@
      * empty.
      * @return The current history item.
      */
+    @Nullable
     public abstract WebHistoryItem getCurrentItem();
 
     /**
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 742daa9..4aa1c4a 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -383,6 +384,7 @@
      * @return Bitmap The image to use as a default poster, or {@code null} if no such image is
      * available.
      */
+    @Nullable
     public Bitmap getDefaultVideoPoster() {
         return null;
     }
@@ -394,6 +396,7 @@
      *
      * @return View The View to be displayed whilst the video is loading.
      */
+    @Nullable
     public View getVideoLoadingProgressView() {
         return null;
     }
@@ -452,6 +455,7 @@
          * @return the Uris of selected file(s) or {@code null} if the resultCode indicates
          *         activity canceled or any other error.
          */
+        @Nullable
         public static Uri[] parseResult(int resultCode, Intent data) {
             return WebViewFactory.getProvider().getStatics().parseFileChooserResult(resultCode, data);
         }
@@ -477,14 +481,16 @@
         public abstract boolean isCaptureEnabled();
 
         /**
-         * Returns the title to use for this file selector, or null. If {@code null} a default
-         * title should be used.
+         * Returns the title to use for this file selector. If {@code null} a default title should
+         * be used.
          */
+        @Nullable
         public abstract CharSequence getTitle();
 
         /**
          * The file name of a default selection if specified, or {@code null}.
          */
+        @Nullable
         public abstract String getFilenameHint();
 
         /**
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index 1591833..74db039 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.graphics.Bitmap;
 
@@ -70,6 +71,7 @@
      * Note: The VM ensures 32-bit atomic read/write operations so we don't have
      * to synchronize this method.
      */
+    @Nullable
     public abstract Bitmap getFavicon();
 
     /**
diff --git a/core/java/android/webkit/WebMessage.java b/core/java/android/webkit/WebMessage.java
index 7fe66dc..bfc00e7 100644
--- a/core/java/android/webkit/WebMessage.java
+++ b/core/java/android/webkit/WebMessage.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
+
 /**
  * The Java representation of the HTML5 PostMessage event. See
  * https://html.spec.whatwg.org/multipage/comms.html#the-messageevent-interfaces
@@ -56,6 +58,7 @@
      * Returns the ports that are sent with the message, or {@code null} if no port
      * is sent.
      */
+    @Nullable
     public WebMessagePort[] getPorts() {
         return mPorts;
     }
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index 80c43c1..7bc7b07 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -16,12 +16,13 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
 import java.io.InputStream;
 import java.io.StringBufferInputStream;
 import java.util.Map;
 
-import android.annotation.SystemApi;
-
 /**
  * Encapsulates a resource response. Applications can return an instance of this
  * class from {@link WebViewClient#shouldInterceptRequest} to provide a custom
@@ -63,15 +64,15 @@
      * @param encoding the resource response's encoding
      * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
      *                   Causing a redirect by specifying a 3xx code is not supported.
-     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
-     *                     and not empty.
+     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
+     *                     non-empty.
      * @param responseHeaders the resource response's headers represented as a mapping of header
      *                        name -> header value.
      * @param data the input stream that provides the resource response's data. Must not be a
      *             StringBufferInputStream.
      */
     public WebResourceResponse(String mimeType, String encoding, int statusCode,
-            String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
+            @NonNull String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
         this(mimeType, encoding, data);
         setStatusCodeAndReasonPhrase(statusCode, reasonPhrase);
         setResponseHeaders(responseHeaders);
@@ -121,10 +122,10 @@
      *
      * @param statusCode the status code needs to be in the ranges [100, 299], [400, 599].
      *                   Causing a redirect by specifying a 3xx code is not supported.
-     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be non-null
-     *                     and not empty.
+     * @param reasonPhrase the phrase describing the status code, for example "OK". Must be
+     *                     non-empty.
      */
-    public void setStatusCodeAndReasonPhrase(int statusCode, String reasonPhrase) {
+    public void setStatusCodeAndReasonPhrase(int statusCode, @NonNull String reasonPhrase) {
         checkImmutable();
         if (statusCode < 100)
             throw new IllegalArgumentException("statusCode can't be less than 100.");
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 22d8561..203de9c 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 
@@ -1238,7 +1239,7 @@
      *
      * @param ua new user-agent string
      */
-    public abstract void setUserAgentString(String ua);
+    public abstract void setUserAgentString(@Nullable String ua);
 
     /**
      * Gets the WebView's user-agent string.
diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java
index 801be12..03b94e7 100644
--- a/core/java/android/webkit/WebSyncManager.java
+++ b/core/java/android/webkit/WebSyncManager.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 
-/*
+/**
  * @deprecated The WebSyncManager no longer does anything.
  */
 @Deprecated
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 71a0f2a..9295f5c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -438,7 +438,7 @@
          * @deprecated Deprecated due to internal changes.
          */
         @Deprecated
-        public void onNewPicture(WebView view, Picture picture);
+        void onNewPicture(WebView view, @Nullable Picture picture);
     }
 
     public static class HitTestResult {
@@ -529,15 +529,20 @@
          *
          * @return additional type-dependant information about the result
          */
+        @Nullable
         public String getExtra() {
             return mExtra;
         }
     }
 
     /**
-     * Constructs a new WebView with a Context object.
+     * Constructs a new WebView with an Activity Context object.
      *
-     * @param context a Context object used to access application assets
+     * <p class="note"><b>Note:</b> WebView should always be instantiated with an Activity Context.
+     * If instantiated with an Application Context, WebView will be unable to provide several
+     * features, such as JavaScript dialogs and autofill.
+     *
+     * @param context an Activity Context to access application assets
      */
     public WebView(Context context) {
         this(context, null);
@@ -546,7 +551,7 @@
     /**
      * Constructs a new WebView with layout parameters.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context to access application assets
      * @param attrs an AttributeSet passed to our parent
      */
     public WebView(Context context, AttributeSet attrs) {
@@ -556,7 +561,7 @@
     /**
      * Constructs a new WebView with layout parameters and a default style.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context to access application assets
      * @param attrs an AttributeSet passed to our parent
      * @param defStyleAttr an attribute in the current theme that contains a
      *        reference to a style resource that supplies default values for
@@ -569,7 +574,7 @@
     /**
      * Constructs a new WebView with layout parameters and a default style.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context to access application assets
      * @param attrs an AttributeSet passed to our parent
      * @param defStyleAttr an attribute in the current theme that contains a
      *        reference to a style resource that supplies default values for
@@ -586,7 +591,7 @@
     /**
      * Constructs a new WebView with layout parameters and a default style.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context to access application assets
      * @param attrs an AttributeSet passed to our parent
      * @param defStyleAttr an attribute in the current theme that contains a
      *        reference to a style resource that supplies default values for
@@ -611,7 +616,7 @@
      * time. This guarantees that these interfaces will be available when the JS
      * context is initialized.
      *
-     * @param context a Context object used to access application assets
+     * @param context an Activity Context to access application assets
      * @param attrs an AttributeSet passed to our parent
      * @param defStyleAttr an attribute in the current theme that contains a
      *        reference to a style resource that supplies default values for
@@ -717,6 +722,7 @@
      *
      * @return the SSL certificate for the main top-level page
      */
+    @Nullable
     public SslCertificate getCertificate() {
         checkThread();
         return mProvider.getCertificate();
@@ -785,6 +791,7 @@
      * @deprecated Use {@link WebViewDatabase#getHttpAuthUsernamePassword} instead
      */
     @Deprecated
+    @Nullable
     public String[] getHttpAuthUsernamePassword(String host, String realm) {
         checkThread();
         return mProvider.getHttpAuthUsernamePassword(host, realm);
@@ -853,9 +860,10 @@
      * called.
      *
      * @param outState the Bundle to store this WebView's state
-     * @return the same copy of the back/forward list used to save the state. If
-     *         saveState fails, the returned list will be {@code null}.
+     * @return the same copy of the back/forward list used to save the state, {@code null} if the
+     *         method fails.
      */
+    @Nullable
     public WebBackForwardList saveState(Bundle outState) {
         checkThread();
         return mProvider.saveState(outState);
@@ -906,6 +914,7 @@
      * @param inState the incoming Bundle of state
      * @return the restored back/forward list or {@code null} if restoreState failed
      */
+    @Nullable
     public WebBackForwardList restoreState(Bundle inState) {
         checkThread();
         return mProvider.restoreState(inState);
@@ -985,10 +994,11 @@
      * always overrides that specified in the HTML or XML document itself.
      *
      * @param data a String of data in the given encoding
-     * @param mimeType the MIME type of the data, e.g. 'text/html'
+     * @param mimeType the MIMEType of the data, e.g. 'text/html'. If {@code null},
+     *                 defaults to 'text/html'.
      * @param encoding the encoding of the data
      */
-    public void loadData(String data, String mimeType, String encoding) {
+    public void loadData(String data, @Nullable String mimeType, @Nullable String encoding) {
         checkThread();
         mProvider.loadData(data, mimeType, encoding);
     }
@@ -1022,8 +1032,8 @@
      * @param historyUrl the URL to use as the history entry. If {@code null} defaults
      *                   to 'about:blank'. If non-null, this must be a valid URL.
      */
-    public void loadDataWithBaseURL(String baseUrl, String data,
-            String mimeType, String encoding, String historyUrl) {
+    public void loadDataWithBaseURL(@Nullable String baseUrl, String data,
+            @Nullable String mimeType, @Nullable String encoding, @Nullable String historyUrl) {
         checkThread();
         mProvider.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
     }
@@ -1045,7 +1055,7 @@
      *                       completes with the result of the execution (if any).
      *                       May be {@code null} if no notification of the result is required.
      */
-    public void evaluateJavascript(String script, ValueCallback<String> resultCallback) {
+    public void evaluateJavascript(String script, @Nullable ValueCallback<String> resultCallback) {
         checkThread();
         mProvider.evaluateJavaScript(script, resultCallback);
     }
@@ -1072,7 +1082,8 @@
      *                 under which the file was saved, or {@code null} if saving the
      *                 file failed.
      */
-    public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) {
+    public void saveWebArchive(String basename, boolean autoname, @Nullable ValueCallback<String>
+            callback) {
         checkThread();
         mProvider.saveWebArchive(basename, autoname, callback);
     }
@@ -1096,7 +1107,7 @@
     /**
      * Gets whether this WebView has a back history item.
      *
-     * @return {@code true} iff this WebView has a back history item
+     * @return {@code true} if this WebView has a back history item
      */
     public boolean canGoBack() {
         checkThread();
@@ -1114,7 +1125,7 @@
     /**
      * Gets whether this WebView has a forward history item.
      *
-     * @return {@code true} iff this WebView has a forward history item
+     * @return {@code true} if this WebView has a forward history item
      */
     public boolean canGoForward() {
         checkThread();
@@ -1395,7 +1406,7 @@
      *                returns the anchor's href attribute. "title" returns the
      *                anchor's text. "src" returns the image's src attribute.
      */
-    public void requestFocusNodeHref(Message hrefMsg) {
+    public void requestFocusNodeHref(@Nullable Message hrefMsg) {
         checkThread();
         mProvider.requestFocusNodeHref(hrefMsg);
     }
@@ -1615,10 +1626,9 @@
      * shared by all the WebViews that are created by the embedder application.
      *
      * @param onCleared  A runnable to be invoked when client certs are cleared.
-     *                   The embedder can pass {@code null} if not interested in the
-     *                   callback. The runnable will be called in UI thread.
+     *                   The runnable will be called in UI thread.
      */
-    public static void clearClientCertPreferences(Runnable onCleared) {
+    public static void clearClientCertPreferences(@Nullable Runnable onCleared) {
         getFactory().getStatics().clearClientCertPreferences(onCleared);
     }
 
@@ -1640,7 +1650,8 @@
      * @param callback will be called on the UI thread with {@code true} if initialization is
      * successful, {@code false} otherwise.
      */
-    public static void startSafeBrowsing(Context context, ValueCallback<Boolean> callback) {
+    public static void startSafeBrowsing(Context context,
+            @Nullable ValueCallback<Boolean> callback) {
         getFactory().getStatics().initSafeBrowsing(context, callback);
     }
 
@@ -1764,7 +1775,7 @@
      *             provides a more robust solution.
      */
     @Deprecated
-    public boolean showFindDialog(String text, boolean showIme) {
+    public boolean showFindDialog(@Nullable String text, boolean showIme) {
         checkThread();
         return mProvider.showFindDialog(text, showIme);
     }
@@ -1791,6 +1802,7 @@
      * @param addr the string to search for addresses
      * @return the address, or if no address is found, {@code null}
      */
+    @Nullable
     public static String findAddress(String addr) {
         // TODO: Rewrite this in Java so it is not needed to start up chromium
         // Could also be deprecated
@@ -1891,6 +1903,7 @@
      * @return the WebChromeClient, or {@code null} if not yet set
      * @see #setWebChromeClient
      */
+    @Nullable
     public WebChromeClient getWebChromeClient() {
         checkThread();
         return mProvider.getWebChromeClient();
@@ -1973,7 +1986,7 @@
      *
      * @param name the name used to expose the object in JavaScript
      */
-    public void removeJavascriptInterface(String name) {
+    public void removeJavascriptInterface(@NonNull String name) {
         checkThread();
         mProvider.removeJavascriptInterface(name);
     }
@@ -2956,14 +2969,19 @@
      * next time the app starts and loads WebView it will use the new WebView package instead.
      * @return the current WebView package, or {@code null} if there is none.
      */
+    @Nullable
     public static PackageInfo getCurrentWebViewPackage() {
         PackageInfo webviewPackage = WebViewFactory.getLoadedPackageInfo();
         if (webviewPackage != null) {
             return webviewPackage;
         }
 
+        IWebViewUpdateService service = WebViewFactory.getUpdateService();
+        if (service == null) {
+            return null;
+        }
         try {
-            return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
+            return service.getCurrentWebViewPackage();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 52b8649..e55d0e4 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -17,6 +17,7 @@
 package android.webkit;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.net.http.SslError;
 import android.os.Message;
@@ -169,6 +170,7 @@
      *             shouldInterceptRequest(WebView, WebResourceRequest)} instead.
      */
     @Deprecated
+    @Nullable
     public WebResourceResponse shouldInterceptRequest(WebView view,
             String url) {
         return null;
@@ -195,6 +197,7 @@
      *         response information or {@code null} if the WebView should load the
      *         resource itself.
      */
+    @Nullable
     public WebResourceResponse shouldInterceptRequest(WebView view,
             WebResourceRequest request) {
         return shouldInterceptRequest(view, request.getUrl().toString());
@@ -500,7 +503,7 @@
      * @param args Authenticator specific arguments used to log in the user.
      */
     public void onReceivedLoginRequest(WebView view, String realm,
-            String account, String args) {
+            @Nullable String account, String args) {
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index de75d5d0..f6166c5 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.annotation.Nullable;
 import android.content.Context;
 
 /**
@@ -135,6 +136,7 @@
      * @see #hasHttpAuthUsernamePassword
      * @see #clearHttpAuthUsernamePassword
      */
+    @Nullable
     public abstract String[] getHttpAuthUsernamePassword(String host, String realm);
 
     /**
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 994512f..797bdfb 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -25,7 +25,6 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
-import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.StrictMode;
@@ -51,9 +50,6 @@
 
     private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create";
 
-    private static final String NULL_WEBVIEW_FACTORY =
-            "com.android.webview.nullwebview.NullWebViewFactoryProvider";
-
     public static final String CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY =
             "persist.sys.webview.vmsize";
 
@@ -66,6 +62,7 @@
     private static WebViewFactoryProvider sProviderInstance;
     private static final Object sProviderLock = new Object();
     private static PackageInfo sPackageInfo;
+    private static Boolean sWebViewSupported;
 
     // Error codes for loadWebViewNativeLibraryFromPackage
     public static final int LIBLOAD_SUCCESS = 0;
@@ -105,6 +102,16 @@
         public MissingWebViewPackageException(Exception e) { super(e); }
     }
 
+    private static boolean isWebViewSupported() {
+        // No lock; this is a benign race as Boolean's state is final and the PackageManager call
+        // will always return the same value.
+        if (sWebViewSupported == null) {
+            sWebViewSupported = AppGlobals.getInitialApplication().getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
+        }
+        return sWebViewSupported;
+    }
+
     /**
      * @hide
      */
@@ -130,11 +137,15 @@
     }
 
     /**
-     * Load the native library for the given package name iff that package
+     * Load the native library for the given package name if that package
      * name is the same as the one providing the webview.
      */
     public static int loadWebViewNativeLibraryFromPackage(String packageName,
                                                           ClassLoader clazzLoader) {
+        if (!isWebViewSupported()) {
+            return LIBLOAD_WRONG_PACKAGE_NAME;
+        }
+
         WebViewProviderResponse response = null;
         try {
             response = getUpdateService().waitForAndGetProvider();
@@ -188,6 +199,11 @@
                         "For security reasons, WebView is not allowed in privileged processes");
             }
 
+            if (!isWebViewSupported()) {
+                // Device doesn't support WebView; don't try to load it, just throw.
+                throw new UnsupportedOperationException();
+            }
+
             StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
             try {
@@ -410,15 +426,6 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
             }
         } catch (MissingWebViewPackageException e) {
-            // If the package doesn't exist, then try loading the null WebView instead.
-            // If that succeeds, then this is a device without WebView support; if it fails then
-            // swallow the failure, complain that the real WebView is missing and rethrow the
-            // original exception.
-            try {
-                return (Class<WebViewFactoryProvider>) Class.forName(NULL_WEBVIEW_FACTORY);
-            } catch (ClassNotFoundException e2) {
-                // Ignore.
-            }
             Log.e(LOGTAG, "Chromium WebView package does not exist", e);
             throw new AndroidRuntimeException(e);
         }
@@ -437,38 +444,17 @@
         }
     }
 
-    private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
-        if (DEBUG) Log.v(LOGTAG, "creating relro files");
-        int numRelros = 0;
-
-        // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
-        // unexpected values will be handled there to ensure that we trigger notifying any process
-        // waiting on relro creation.
-        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
-            if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
-            WebViewLibraryLoader.createRelroFile(false /* is64Bit */, nativeLibraryPaths);
-            numRelros++;
-        }
-
-        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
-            if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
-            WebViewLibraryLoader.createRelroFile(true /* is64Bit */, nativeLibraryPaths);
-            numRelros++;
-        }
-        return numRelros;
-    }
-
     /**
      * @hide
      */
     public static int onWebViewProviderChanged(PackageInfo packageInfo) {
-        String[] nativeLibs = null;
+        int startedRelroProcesses = 0;
         ApplicationInfo originalAppInfo = new ApplicationInfo(packageInfo.applicationInfo);
         try {
             fixupStubApplicationInfo(packageInfo.applicationInfo,
                                      AppGlobals.getInitialApplication().getPackageManager());
 
-            nativeLibs = WebViewLibraryLoader.updateWebViewZygoteVmSize(packageInfo);
+            startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
         } catch (Throwable t) {
             // Log and discard errors at this stage as we must not crash the system server.
             Log.e(LOGTAG, "error preparing webview native library", t);
@@ -476,13 +462,22 @@
 
         WebViewZygote.onWebViewProviderChanged(packageInfo, originalAppInfo);
 
-        return prepareWebViewInSystemServer(nativeLibs);
+        return startedRelroProcesses;
     }
 
     private static String WEBVIEW_UPDATE_SERVICE_NAME = "webviewupdate";
 
     /** @hide */
     public static IWebViewUpdateService getUpdateService() {
+        if (isWebViewSupported()) {
+            return getUpdateServiceUnchecked();
+        } else {
+            return null;
+        }
+    }
+
+    /** @hide */
+    static IWebViewUpdateService getUpdateServiceUnchecked() {
         return IWebViewUpdateService.Stub.asInterface(
                 ServiceManager.getService(WEBVIEW_UPDATE_SERVICE_NAME));
     }
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index b3cd9cc..de0b97d 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -16,6 +16,8 @@
 
 package android.webkit;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -26,6 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
 import dalvik.system.VMRuntime;
@@ -36,7 +39,11 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-class WebViewLibraryLoader {
+/**
+ * @hide
+ */
+@VisibleForTesting
+public class WebViewLibraryLoader {
     private static final String LOGTAG = WebViewLibraryLoader.class.getSimpleName();
 
     private static final String CHROMIUM_WEBVIEW_NATIVE_RELRO_32 =
@@ -62,25 +69,23 @@
             boolean result = false;
             boolean is64Bit = VMRuntime.getRuntime().is64Bit();
             try {
-                if (args.length != 2 || args[0] == null || args[1] == null) {
+                if (args.length != 1 || args[0] == null) {
                     Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
                     return;
                 }
-                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), "
-                        + " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
+                Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), lib: " + args[0]);
                 if (!sAddressSpaceReserved) {
                     Log.e(LOGTAG, "can't create relro file; address space not reserved");
                     return;
                 }
-                result = nativeCreateRelroFile(args[0] /* path32 */,
-                                               args[1] /* path64 */,
-                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
-                                               CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
+                result = nativeCreateRelroFile(args[0] /* path */,
+                                               is64Bit ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
+                                                         CHROMIUM_WEBVIEW_NATIVE_RELRO_32);
                 if (result && DEBUG) Log.v(LOGTAG, "created relro file");
             } finally {
                 // We must do our best to always notify the update service, even if something fails.
                 try {
-                    WebViewFactory.getUpdateService().notifyRelroCreationCompleted();
+                    WebViewFactory.getUpdateServiceUnchecked().notifyRelroCreationCompleted();
                 } catch (RemoteException e) {
                     Log.e(LOGTAG, "error notifying update service", e);
                 }
@@ -96,7 +101,7 @@
     /**
      * Create a single relro file by invoking an isolated process that to do the actual work.
      */
-    static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
+    static void createRelroFile(final boolean is64Bit, @NonNull WebViewNativeLibrary nativeLib) {
         final String abi =
                 is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
 
@@ -114,13 +119,12 @@
         };
 
         try {
-            if (nativeLibraryPaths == null
-                    || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
+            if (nativeLib == null || nativeLib.path == null) {
                 throw new IllegalArgumentException(
                         "Native library paths to the WebView RelRo process must not be null!");
             }
             int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
-                    RelroFileCreator.class.getName(), nativeLibraryPaths,
+                    RelroFileCreator.class.getName(), new String[] { nativeLib.path },
                     "WebViewLoader-" + abi, abi, Process.SHARED_RELRO_UID, crashHandler);
             if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
         } catch (Throwable t) {
@@ -131,56 +135,77 @@
     }
 
     /**
+     * Perform preparations needed to allow loading WebView from an application. This method should
+     * be called whenever we change WebView provider.
+     * @return the number of relro processes started.
+     */
+    static int prepareNativeLibraries(PackageInfo webviewPackageInfo)
+            throws WebViewFactory.MissingWebViewPackageException {
+        WebViewNativeLibrary nativeLib32bit =
+                getWebViewNativeLibrary(webviewPackageInfo, false /* is64bit */);
+        WebViewNativeLibrary nativeLib64bit =
+                getWebViewNativeLibrary(webviewPackageInfo, true /* is64bit */);
+        updateWebViewZygoteVmSize(nativeLib32bit, nativeLib64bit);
+
+        return createRelros(nativeLib32bit, nativeLib64bit);
+    }
+
+    /**
+     * @return the number of relro processes started.
+     */
+    private static int createRelros(@Nullable WebViewNativeLibrary nativeLib32bit,
+            @Nullable WebViewNativeLibrary nativeLib64bit) {
+        if (DEBUG) Log.v(LOGTAG, "creating relro files");
+        int numRelros = 0;
+
+        if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
+            if (nativeLib32bit == null) {
+                Log.e(LOGTAG, "No 32-bit WebView library path, skipping relro creation.");
+            } else {
+                if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
+                createRelroFile(false /* is64Bit */, nativeLib32bit);
+                numRelros++;
+            }
+        }
+
+        if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
+            if (nativeLib64bit == null) {
+                Log.e(LOGTAG, "No 64-bit WebView library path, skipping relro creation.");
+            } else {
+                if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
+                createRelroFile(true /* is64Bit */, nativeLib64bit);
+                numRelros++;
+            }
+        }
+        return numRelros;
+    }
+
+    /**
      *
      * @return the native WebView libraries in the new WebView APK.
      */
-    static String[] updateWebViewZygoteVmSize(PackageInfo packageInfo)
+    private static void updateWebViewZygoteVmSize(
+            @Nullable WebViewNativeLibrary nativeLib32bit,
+            @Nullable WebViewNativeLibrary nativeLib64bit)
             throws WebViewFactory.MissingWebViewPackageException {
         // Find the native libraries of the new WebView package, to change the size of the
         // memory region in the Zygote reserved for the library.
-        String[] nativeLibs = getWebViewNativeLibraryPaths(packageInfo);
-        if (nativeLibs != null) {
-            long newVmSize = 0L;
+        long newVmSize = 0L;
 
-            for (String path : nativeLibs) {
-                if (path == null || TextUtils.isEmpty(path)) continue;
-                if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
-                File f = new File(path);
-                if (f.exists()) {
-                    newVmSize = Math.max(newVmSize, f.length());
-                    continue;
-                }
-                if (path.contains("!/")) {
-                    String[] split = TextUtils.split(path, "!/");
-                    if (split.length == 2) {
-                        try (ZipFile z = new ZipFile(split[0])) {
-                            ZipEntry e = z.getEntry(split[1]);
-                            if (e != null && e.getMethod() == ZipEntry.STORED) {
-                                newVmSize = Math.max(newVmSize, e.getSize());
-                                continue;
-                            }
-                        }
-                        catch (IOException e) {
-                            Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
-                        }
-                    }
-                }
-                Log.e(LOGTAG, "error sizing load for " + path);
-            }
+        if (nativeLib32bit != null) newVmSize = Math.max(newVmSize, nativeLib32bit.size);
+        if (nativeLib64bit != null) newVmSize = Math.max(newVmSize, nativeLib64bit.size);
 
-            if (DEBUG) {
-                Log.v(LOGTAG, "Based on library size, need " + newVmSize
-                        + " bytes of address space.");
-            }
-            // The required memory can be larger than the file on disk (due to .bss), and an
-            // upgraded version of the library will likely be larger, so always attempt to
-            // reserve twice as much as we think to allow for the library to grow during this
-            // boot cycle.
-            newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
-            Log.d(LOGTAG, "Setting new address space to " + newVmSize);
-            setWebViewZygoteVmSize(newVmSize);
+        if (DEBUG) {
+            Log.v(LOGTAG, "Based on library size, need " + newVmSize
+                    + " bytes of address space.");
         }
-        return nativeLibs;
+        // The required memory can be larger than the file on disk (due to .bss), and an
+        // upgraded version of the library will likely be larger, so always attempt to
+        // reserve twice as much as we think to allow for the library to grow during this
+        // boot cycle.
+        newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
+        Log.d(LOGTAG, "Setting new address space to " + newVmSize);
+        setWebViewZygoteVmSize(newVmSize);
     }
 
     /**
@@ -219,8 +244,9 @@
 
         final String libraryFileName =
                 WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo);
-        int result = nativeLoadWithRelroFile(libraryFileName, CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
-                                             CHROMIUM_WEBVIEW_NATIVE_RELRO_64, clazzLoader);
+        String relroPath = VMRuntime.getRuntime().is64Bit() ? CHROMIUM_WEBVIEW_NATIVE_RELRO_64 :
+                                                              CHROMIUM_WEBVIEW_NATIVE_RELRO_32;
+        int result = nativeLoadWithRelroFile(libraryFileName, relroPath, clazzLoader);
         if (result != WebViewFactory.LIBLOAD_SUCCESS) {
             Log.w(LOGTAG, "failed to load with relro file, proceeding without");
         } else if (DEBUG) {
@@ -231,64 +257,78 @@
 
     /**
      * Fetch WebView's native library paths from {@param packageInfo}.
+     * @hide
      */
-    static String[] getWebViewNativeLibraryPaths(PackageInfo packageInfo)
-            throws WebViewFactory.MissingWebViewPackageException {
+    @Nullable
+    @VisibleForTesting
+    public static WebViewNativeLibrary getWebViewNativeLibrary(PackageInfo packageInfo,
+            boolean is64bit) throws WebViewFactory.MissingWebViewPackageException {
         ApplicationInfo ai = packageInfo.applicationInfo;
         final String nativeLibFileName = WebViewFactory.getWebViewLibrary(ai);
 
-        String path32;
-        String path64;
-        boolean primaryArchIs64bit = VMRuntime.is64BitAbi(ai.primaryCpuAbi);
-        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
-            // Multi-arch case.
-            if (primaryArchIs64bit) {
-                // Primary arch: 64-bit, secondary: 32-bit.
-                path64 = ai.nativeLibraryDir;
-                path32 = ai.secondaryNativeLibraryDir;
-            } else {
-                // Primary arch: 32-bit, secondary: 64-bit.
-                path64 = ai.secondaryNativeLibraryDir;
-                path32 = ai.nativeLibraryDir;
-            }
-        } else if (primaryArchIs64bit) {
-            // Single-arch 64-bit.
-            path64 = ai.nativeLibraryDir;
-            path32 = "";
-        } else {
-            // Single-arch 32-bit.
-            path32 = ai.nativeLibraryDir;
-            path64 = "";
-        }
+        String dir = getWebViewNativeLibraryDirectory(ai, is64bit /* 64bit */);
 
-        // Form the full paths to the extracted native libraries.
-        // If libraries were not extracted, try load from APK paths instead.
-        if (!TextUtils.isEmpty(path32)) {
-            path32 += "/" + nativeLibFileName;
-            File f = new File(path32);
-            if (!f.exists()) {
-                path32 = getLoadFromApkPath(ai.sourceDir,
-                                            Build.SUPPORTED_32_BIT_ABIS,
-                                            nativeLibFileName);
-            }
-        }
-        if (!TextUtils.isEmpty(path64)) {
-            path64 += "/" + nativeLibFileName;
-            File f = new File(path64);
-            if (!f.exists()) {
-                path64 = getLoadFromApkPath(ai.sourceDir,
-                                            Build.SUPPORTED_64_BIT_ABIS,
-                                            nativeLibFileName);
-            }
-        }
+        WebViewNativeLibrary lib = findNativeLibrary(ai, nativeLibFileName,
+                is64bit ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS, dir);
 
-        if (DEBUG) Log.v(LOGTAG, "Native 32-bit lib: " + path32 + ", 64-bit lib: " + path64);
-        return new String[] { path32, path64 };
+        if (DEBUG) {
+            Log.v(LOGTAG, String.format("Native %d-bit lib: %s", is64bit ? 64 : 32, lib.path));
+        }
+        return lib;
     }
 
-    private static String getLoadFromApkPath(String apkPath,
-                                             String[] abiList,
-                                             String nativeLibFileName)
+    /**
+     * @return the directory of the native WebView library with bitness {@param is64bit}.
+     * @hide
+     */
+    @VisibleForTesting
+    public static String getWebViewNativeLibraryDirectory(ApplicationInfo ai, boolean is64bit) {
+        // Primary arch has the same bitness as the library we are looking for.
+        if (is64bit == VMRuntime.is64BitAbi(ai.primaryCpuAbi)) return ai.nativeLibraryDir;
+
+        // Secondary arch has the same bitness as the library we are looking for.
+        if (!TextUtils.isEmpty(ai.secondaryCpuAbi)) {
+            return ai.secondaryNativeLibraryDir;
+        }
+
+        return "";
+    }
+
+    /**
+     * @return an object describing a native WebView library given the directory path of that
+     * library, or null if the library couldn't be found.
+     */
+    @Nullable
+    private static WebViewNativeLibrary findNativeLibrary(ApplicationInfo ai,
+            String nativeLibFileName, String[] abiList, String libDirectory)
+            throws WebViewFactory.MissingWebViewPackageException {
+        if (TextUtils.isEmpty(libDirectory)) return null;
+        String libPath = libDirectory + "/" + nativeLibFileName;
+        File f = new File(libPath);
+        if (f.exists()) {
+            return new WebViewNativeLibrary(libPath, f.length());
+        } else {
+            return getLoadFromApkPath(ai.sourceDir, abiList, nativeLibFileName);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public static class WebViewNativeLibrary {
+        public final String path;
+        public final long size;
+
+        WebViewNativeLibrary(String path, long size) {
+            this.path = path;
+            this.size = size;
+        }
+    }
+
+    private static WebViewNativeLibrary getLoadFromApkPath(String apkPath,
+                                                           String[] abiList,
+                                                           String nativeLibFileName)
             throws WebViewFactory.MissingWebViewPackageException {
         // Search the APK for a native library conforming to a listed ABI.
         try (ZipFile z = new ZipFile(apkPath)) {
@@ -297,13 +337,13 @@
                 ZipEntry e = z.getEntry(entry);
                 if (e != null && e.getMethod() == ZipEntry.STORED) {
                     // Return a path formatted for dlopen() load from APK.
-                    return apkPath + "!/" + entry;
+                    return new WebViewNativeLibrary(apkPath + "!/" + entry, e.getSize());
                 }
             }
         } catch (IOException e) {
             throw new WebViewFactory.MissingWebViewPackageException(e);
         }
-        return "";
+        return null;
     }
 
     /**
@@ -315,8 +355,6 @@
     }
 
     static native boolean nativeReserveAddressSpace(long addressSpaceToReserve);
-    static native boolean nativeCreateRelroFile(String lib32, String lib64,
-                                                        String relro32, String relro64);
-    static native int nativeLoadWithRelroFile(String lib, String relro32, String relro64,
-                                                      ClassLoader clazzLoader);
+    static native boolean nativeCreateRelroFile(String lib, String relro);
+    static native int nativeLoadWithRelroFile(String lib, String relro, ClassLoader clazzLoader);
 }
diff --git a/core/java/android/webkit/WebViewUpdateService.java b/core/java/android/webkit/WebViewUpdateService.java
index 2f7d685..629891c 100644
--- a/core/java/android/webkit/WebViewUpdateService.java
+++ b/core/java/android/webkit/WebViewUpdateService.java
@@ -31,8 +31,12 @@
      * Fetch all packages that could potentially implement WebView.
      */
     public static WebViewProviderInfo[] getAllWebViewPackages() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return new WebViewProviderInfo[0];
+        }
         try {
-            return getUpdateService().getAllWebViewPackages();
+            return service.getAllWebViewPackages();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -42,8 +46,12 @@
      * Fetch all packages that could potentially implement WebView and are currently valid.
      */
     public static WebViewProviderInfo[] getValidWebViewPackages() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return new WebViewProviderInfo[0];
+        }
         try {
-            return getUpdateService().getValidWebViewPackages();
+            return service.getValidWebViewPackages();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -53,8 +61,12 @@
      * Used by DevelopmentSetting to get the name of the WebView provider currently in use.
      */
     public static String getCurrentWebViewPackageName() {
+        IWebViewUpdateService service = getUpdateService();
+        if (service == null) {
+            return null;
+        }
         try {
-            return getUpdateService().getCurrentWebViewPackageName();
+            return service.getCurrentWebViewPackageName();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index dd01251..6c19256 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -202,7 +202,7 @@
      * The last selected position we used when notifying
      */
     int mOldSelectedPosition = INVALID_POSITION;
-    
+
     /**
      * The id of the last selected position we used when notifying
      */
@@ -382,7 +382,7 @@
          * position is different from the previously selected position or if
          * there was no selected item.</p>
          *
-         * Impelmenters can call getItemAtPosition(position) if they need to access the
+         * Implementers can call getItemAtPosition(position) if they need to access the
          * data associated with the selected item.
          *
          * @param parent The AdapterView where the selection happened
@@ -778,8 +778,8 @@
             // We are now GONE, so pending layouts will not be dispatched.
             // Force one here to make sure that the state of the list matches
             // the state of the adapter.
-            if (mDataChanged) {           
-                this.onLayout(false, mLeft, mTop, mRight, mBottom); 
+            if (mDataChanged) {
+                this.onLayout(false, mLeft, mTop, mRight, mBottom);
             }
         } else {
             if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
@@ -1304,4 +1304,4 @@
             structure.setAutofillOptions(options);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0f61724..e6da69d 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -119,6 +119,7 @@
 import com.android.internal.util.GrowingArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.EditableInputConnection;
+import com.android.internal.widget.Magnifier;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -128,7 +129,7 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
-
+import java.util.Map;
 
 /**
  * Helper class used by TextView to handle editable text views.
@@ -138,6 +139,9 @@
 public class Editor {
     private static final String TAG = "Editor";
     private static final boolean DEBUG_UNDO = false;
+    // Specifies whether to use or not the magnifier when pressing the insertion or selection
+    // handles.
+    private static final boolean FLAG_USE_MAGNIFIER = true;
 
     static final int BLINK = 500;
     private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
@@ -159,8 +163,19 @@
     private static final int MENU_ITEM_ORDER_REPLACE = 9;
     private static final int MENU_ITEM_ORDER_AUTOFILL = 10;
     private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11;
+    private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50;
     private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100;
 
+    @IntDef({MagnifierHandleTrigger.SELECTION_START,
+            MagnifierHandleTrigger.SELECTION_END,
+            MagnifierHandleTrigger.INSERTION})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface MagnifierHandleTrigger {
+        int INSERTION = 0;
+        int SELECTION_START = 1;
+        int SELECTION_END = 2;
+    }
+
     // Each Editor manages its own undo stack.
     private final UndoManager mUndoManager = new UndoManager();
     private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this);
@@ -179,6 +194,8 @@
 
     private final boolean mHapticTextHandleEnabled;
 
+    private final Magnifier mMagnifier;
+
     // Used to highlight a word when it is corrected by the IME
     private CorrectionHighlighter mCorrectionHighlighter;
 
@@ -250,7 +267,7 @@
     SuggestionRangeSpan mSuggestionRangeSpan;
     private Runnable mShowSuggestionRunnable;
 
-    Drawable mCursorDrawable = null;
+    Drawable mDrawableForCursor = null;
 
     private Drawable mSelectHandleLeft;
     private Drawable mSelectHandleRight;
@@ -325,6 +342,8 @@
         mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this);
         mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
+
+        mMagnifier = FLAG_USE_MAGNIFIER ? new Magnifier(mTextView) : null;
     }
 
     ParcelableParcel saveInstanceState() {
@@ -1678,7 +1697,7 @@
             mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
         }
 
-        if (highlight != null && selectionStart == selectionEnd && mCursorDrawable != null) {
+        if (highlight != null && selectionStart == selectionEnd && mDrawableForCursor != null) {
             drawCursor(canvas, cursorOffsetVertical);
             // Rely on the drawable entirely, do not draw the cursor line.
             // Has to be done after the IMM related code above which relies on the highlight.
@@ -1873,8 +1892,8 @@
     private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
         final boolean translate = cursorOffsetVertical != 0;
         if (translate) canvas.translate(0, cursorOffsetVertical);
-        if (mCursorDrawable != null) {
-            mCursorDrawable.draw(canvas);
+        if (mDrawableForCursor != null) {
+            mDrawableForCursor.draw(canvas);
         }
         if (translate) canvas.translate(0, -cursorOffsetVertical);
     }
@@ -1933,7 +1952,7 @@
 
     void updateCursorPosition() {
         if (mTextView.mCursorDrawableRes == 0) {
-            mCursorDrawable = null;
+            mDrawableForCursor = null;
             return;
         }
 
@@ -2314,17 +2333,17 @@
     @VisibleForTesting
     @Nullable
     public Drawable getCursorDrawable() {
-        return mCursorDrawable;
+        return mDrawableForCursor;
     }
 
     private void updateCursorPosition(int top, int bottom, float horizontal) {
-        if (mCursorDrawable == null) {
-            mCursorDrawable = mTextView.getContext().getDrawable(
+        if (mDrawableForCursor == null) {
+            mDrawableForCursor = mTextView.getContext().getDrawable(
                     mTextView.mCursorDrawableRes);
         }
-        final int left = clampHorizontalPosition(mCursorDrawable, horizontal);
-        final int width = mCursorDrawable.getIntrinsicWidth();
-        mCursorDrawable.setBounds(left, top - mTempRect.top, left + width,
+        final int left = clampHorizontalPosition(mDrawableForCursor, horizontal);
+        final int width = mDrawableForCursor.getIntrinsicWidth();
+        mDrawableForCursor.setBounds(left, top - mTempRect.top, left + width,
                 bottom + mTempRect.bottom);
     }
 
@@ -3774,6 +3793,7 @@
         private final RectF mSelectionBounds = new RectF();
         private final boolean mHasSelection;
         private final int mHandleHeight;
+        private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>();
 
         public TextActionModeCallback(boolean hasSelection) {
             mHasSelection = hasSelection;
@@ -3801,6 +3821,8 @@
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            mAssistClickHandlers.clear();
+
             mode.setTitle(null);
             mode.setSubtitle(null);
             mode.setTitleOptionalHint(true);
@@ -3869,7 +3891,7 @@
                 if (selected == null || selected.isEmpty()) {
                     menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL,
                             com.android.internal.R.string.autofill)
-                            .setShowAsAction(MenuItem.SHOW_AS_OVERFLOW_ALWAYS);
+                            .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                 }
             }
 
@@ -3884,14 +3906,14 @@
 
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
-            updateAssistMenuItem(menu);
+            updateAssistMenuItems(menu);
         }
 
         @Override
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
-            updateAssistMenuItem(menu);
+            updateAssistMenuItems(menu);
 
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
@@ -3924,32 +3946,118 @@
             }
         }
 
-        private void updateAssistMenuItem(Menu menu) {
-            menu.removeItem(TextView.ID_ASSIST);
+        private void updateAssistMenuItems(Menu menu) {
+            clearAssistMenuItems(menu);
+            if (!mTextView.isDeviceProvisioned()) {
+                return;
+            }
             final TextClassification textClassification =
                     getSelectionActionModeHelper().getTextClassification();
-            if (canAssist()) {
-                menu.add(TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
-                        textClassification.getLabel())
-                        .setIcon(textClassification.getIcon())
-                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
-                mMetricsLogger.write(
-                        new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
-                                .setType(MetricsEvent.TYPE_OPEN)
-                                .setSubtype(textClassification.getLogType()));
+            final int count = textClassification != null ? textClassification.getActionCount() : 0;
+            for (int i = 0; i < count; i++) {
+                if (!isValidAssistMenuItem(i)) {
+                    continue;
+                }
+                final int groupId = TextView.ID_ASSIST;
+                final int order = (i == 0)
+                        ? MENU_ITEM_ORDER_ASSIST
+                        : MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START + i;
+                final int id = (i == 0) ? TextView.ID_ASSIST : Menu.NONE;
+                final int showAsFlag = (i == 0)
+                        ? MenuItem.SHOW_AS_ACTION_ALWAYS
+                        : MenuItem.SHOW_AS_ACTION_NEVER;
+                final MenuItem item = menu.add(
+                        groupId, id, order, textClassification.getLabel(i))
+                        .setIcon(textClassification.getIcon(i))
+                        .setIntent(textClassification.getIntent(i));
+                item.setShowAsAction(showAsFlag);
+                mAssistClickHandlers.put(item, textClassification.getOnClickListener(i));
+                if (id == TextView.ID_ASSIST) {
+                    mMetricsLogger.write(
+                            new LogMaker(MetricsEvent.TEXT_SELECTION_MENU_ITEM_ASSIST)
+                                    .setType(MetricsEvent.TYPE_OPEN)
+                                    .setSubtype(textClassification.getLogType()));
+                }
             }
         }
 
-        private boolean canAssist() {
+        private void clearAssistMenuItems(Menu menu) {
+            int i = 0;
+            while (i < menu.size()) {
+                final MenuItem menuItem = menu.getItem(i);
+                if (menuItem.getGroupId() == TextView.ID_ASSIST) {
+                    menu.removeItem(menuItem.getItemId());
+                    continue;
+                }
+                i++;
+            }
+        }
+
+        private boolean isValidAssistMenuItem(int index) {
             final TextClassification textClassification =
                     getSelectionActionModeHelper().getTextClassification();
-            return mTextView.isDeviceProvisioned()
-                    && textClassification != null
-                    && (textClassification.getIcon() != null
-                            || !TextUtils.isEmpty(textClassification.getLabel()))
-                    && (textClassification.getOnClickListener() != null
-                            || (textClassification.getIntent() != null
-                                    && mTextView.getContext().canStartActivityForResult()));
+            if (!mTextView.isDeviceProvisioned() || textClassification == null
+                    || index < 0 || index >= textClassification.getActionCount()) {
+                return false;
+            }
+            final Drawable icon = textClassification.getIcon(index);
+            final CharSequence label = textClassification.getLabel(index);
+            final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
+            final OnClickListener onClick = textClassification.getOnClickListener(index);
+            final Intent intent = textClassification.getIntent(index);
+            final boolean hasAction = onClick != null || isSupportedIntent(intent);
+            return hasUi && hasAction;
+        }
+
+        private boolean isSupportedIntent(Intent intent) {
+            if (intent == null) {
+                return false;
+            }
+            final Context context = mTextView.getContext();
+            final ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0);
+            final boolean samePackage = context.getPackageName().equals(
+                    info.activityInfo.packageName);
+            if (samePackage) {
+                return true;
+            }
+
+            final boolean exported =  info.activityInfo.exported;
+            final boolean requiresPermission = info.activityInfo.permission != null;
+            final boolean hasPermission = !requiresPermission
+                    || context.checkSelfPermission(info.activityInfo.permission)
+                            == PackageManager.PERMISSION_GRANTED;
+            return exported && hasPermission;
+        }
+
+        private boolean onAssistMenuItemClicked(MenuItem assistMenuItem) {
+            Preconditions.checkArgument(assistMenuItem.getGroupId() == TextView.ID_ASSIST);
+
+            final TextClassification textClassification =
+                    getSelectionActionModeHelper().getTextClassification();
+            if (!mTextView.isDeviceProvisioned() || textClassification == null) {
+                // No textClassification result to handle the click. Eat the click.
+                return true;
+            }
+
+            OnClickListener onClickListener = mAssistClickHandlers.get(assistMenuItem);
+            if (onClickListener == null) {
+                final Intent intent = assistMenuItem.getIntent();
+                if (intent != null) {
+                    onClickListener = TextClassification.createStartActivityOnClickListener(
+                            mTextView.getContext(), intent);
+                }
+            }
+            if (onClickListener != null) {
+                onClickListener.onClick(mTextView);
+                stopTextActionMode();
+                if (assistMenuItem.getItemId() == TextView.ID_ASSIST) {
+                    mMetricsLogger.action(
+                            MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
+                            textClassification.getLogType());
+                }
+            }
+            // We tried our best.
+            return true;
         }
 
         @Override
@@ -3963,25 +4071,7 @@
             if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
-            final TextClassification textClassification =
-                    getSelectionActionModeHelper().getTextClassification();
-            if (TextView.ID_ASSIST == item.getItemId() && textClassification != null) {
-                final OnClickListener onClickListener =
-                        textClassification.getOnClickListener();
-                if (onClickListener != null) {
-                    onClickListener.onClick(mTextView);
-                } else {
-                    final Intent intent = textClassification.getIntent();
-                    if (intent != null) {
-                        TextClassification.createStartActivityOnClickListener(
-                                mTextView.getContext(), intent)
-                                .onClick(mTextView);
-                    }
-                }
-                mMetricsLogger.action(
-                        MetricsEvent.ACTION_TEXT_SELECTION_MENU_ITEM_ASSIST,
-                        textClassification.getLogType());
-                stopTextActionMode();
+            if (item.getGroupId() == TextView.ID_ASSIST && onAssistMenuItemClicked(item)) {
                 return true;
             }
             return mTextView.onTextContextMenuItem(item.getItemId());
@@ -4010,6 +4100,8 @@
             if (mSelectionModifierCursorController != null) {
                 mSelectionModifierCursorController.hide();
             }
+
+            mAssistClickHandlers.clear();
         }
 
         @Override
@@ -4353,6 +4445,9 @@
 
         protected abstract void updatePosition(float x, float y, boolean fromTouchScreen);
 
+        @MagnifierHandleTrigger
+        protected abstract int getMagnifierHandleTrigger();
+
         protected boolean isAtRtlRun(@NonNull Layout layout, int offset) {
             return layout.isRtlCharAt(offset);
         }
@@ -4490,6 +4585,51 @@
             return 0;
         }
 
+        protected final void showMagnifier() {
+            if (mMagnifier == null) {
+                return;
+            }
+
+            final int trigger = getMagnifierHandleTrigger();
+            final int offset;
+            switch (trigger) {
+                case MagnifierHandleTrigger.INSERTION: // Fall through.
+                case MagnifierHandleTrigger.SELECTION_START:
+                    offset = mTextView.getSelectionStart();
+                    break;
+                case MagnifierHandleTrigger.SELECTION_END:
+                    offset = mTextView.getSelectionEnd();
+                    break;
+                default:
+                    offset = -1;
+                    break;
+            }
+
+            if (offset == -1) {
+                dismissMagnifier();
+            }
+
+            final Layout layout = mTextView.getLayout();
+            final int lineNumber = layout.getLineForOffset(offset);
+            // Horizontally snap to character offset.
+            final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
+                    + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+            // Vertically snap to middle of current line.
+            final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
+                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
+                    + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
+
+            suspendBlink();
+            mMagnifier.show(xPosInView, yPosInView);
+        }
+
+        protected final void dismissMagnifier() {
+            if (mMagnifier != null) {
+                mMagnifier.dismiss();
+                resumeBlink();
+            }
+        }
+
         @Override
         public boolean onTouchEvent(MotionEvent ev) {
             updateFloatingToolbarVisibility(ev);
@@ -4542,10 +4682,7 @@
 
                 case MotionEvent.ACTION_UP:
                     filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN));
-                    mIsDragging = false;
-                    updateDrawable();
-                    break;
-
+                    // Fall through.
                 case MotionEvent.ACTION_CANCEL:
                     mIsDragging = false;
                     updateDrawable();
@@ -4646,9 +4783,9 @@
         @Override
         protected int getCursorOffset() {
             int offset = super.getCursorOffset();
-            if (mCursorDrawable != null) {
-                mCursorDrawable.getPadding(mTempRect);
-                offset += (mCursorDrawable.getIntrinsicWidth()
+            if (mDrawableForCursor != null) {
+                mDrawableForCursor.getPadding(mTempRect);
+                offset += (mDrawableForCursor.getIntrinsicWidth()
                            - mTempRect.left - mTempRect.right) / 2;
             }
             return offset;
@@ -4656,9 +4793,9 @@
 
         @Override
         int getCursorHorizontalPosition(Layout layout, int offset) {
-            if (mCursorDrawable != null) {
+            if (mDrawableForCursor != null) {
                 final float horizontal = getHorizontal(layout, offset);
-                return clampHorizontalPosition(mCursorDrawable, horizontal) + mTempRect.left;
+                return clampHorizontalPosition(mDrawableForCursor, horizontal) + mTempRect.left;
             }
             return super.getCursorHorizontalPosition(layout, offset);
         }
@@ -4671,6 +4808,11 @@
                 case MotionEvent.ACTION_DOWN:
                     mDownPositionX = ev.getRawX();
                     mDownPositionY = ev.getRawY();
+                    showMagnifier();
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    showMagnifier();
                     break;
 
                 case MotionEvent.ACTION_UP:
@@ -4696,11 +4838,10 @@
                             mTextActionMode.invalidateContentRect();
                         }
                     }
-                    hideAfterDelay();
-                    break;
-
+                    // Fall through.
                 case MotionEvent.ACTION_CANCEL:
                     hideAfterDelay();
+                    dismissMagnifier();
                     break;
 
                 default:
@@ -4751,6 +4892,12 @@
             super.onDetached();
             removeHiderCallback();
         }
+
+        @Override
+        @MagnifierHandleTrigger
+        protected int getMagnifierHandleTrigger() {
+            return MagnifierHandleTrigger.INSERTION;
+        }
     }
 
     @Retention(RetentionPolicy.SOURCE)
@@ -5009,12 +5156,26 @@
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             boolean superResult = super.onTouchEvent(event);
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                // Reset the touch word offset and x value when the user
-                // re-engages the handle.
-                mTouchWordDelta = 0.0f;
-                mPrevX = UNSET_X_VALUE;
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_DOWN:
+                    // Reset the touch word offset and x value when the user
+                    // re-engages the handle.
+                    mTouchWordDelta = 0.0f;
+                    mPrevX = UNSET_X_VALUE;
+                    showMagnifier();
+                    break;
+
+                case MotionEvent.ACTION_MOVE:
+                    showMagnifier();
+                    break;
+
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    dismissMagnifier();
+                    break;
             }
+
             return superResult;
         }
 
@@ -5110,6 +5271,13 @@
                 return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset;
             }
         }
+
+        @MagnifierHandleTrigger
+        protected int getMagnifierHandleTrigger() {
+            return isStartHandle()
+                    ? MagnifierHandleTrigger.SELECTION_START
+                    : MagnifierHandleTrigger.SELECTION_END;
+        }
     }
 
     private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) {
@@ -6465,6 +6633,9 @@
 
         private void loadSupportedActivities() {
             mSupportedActivities.clear();
+            if (!mContext.canStartActivityForResult()) {
+                return;
+            }
             PackageManager packageManager = mTextView.getContext().getPackageManager();
             List<ResolveInfo> unfiltered =
                     packageManager.queryIntentActivities(createProcessTextIntent(), 0);
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 0d67615..adf366a 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -657,6 +657,7 @@
             mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
                             mDropDownVerticalOffset, (widthSpec < 0)? -1 : widthSpec,
                             (heightSpec < 0)? -1 : heightSpec);
+            mPopup.getContentView().restoreDefaultFocus();
         } else {
             final int widthSpec;
             if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
@@ -695,6 +696,7 @@
             mPopup.showAsDropDown(getAnchorView(), mDropDownHorizontalOffset,
                     mDropDownVerticalOffset, mDropDownGravity);
             mDropDownList.setSelection(ListView.INVALID_POSITION);
+            mPopup.getContentView().restoreDefaultFocus();
 
             if (!mModal || mDropDownList.isInTouchMode()) {
                 clearListSelection();
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index bf25915..8dc8cab 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -2296,8 +2296,8 @@
     }
 
     /** @hide */
-    protected final void detachFromAnchor() {
-        final View anchor = mAnchor != null ? mAnchor.get() : null;
+    protected void detachFromAnchor() {
+        final View anchor = getAnchor();
         if (anchor != null) {
             final ViewTreeObserver vto = anchor.getViewTreeObserver();
             vto.removeOnScrollChangedListener(mOnScrollChangedListener);
@@ -2316,7 +2316,7 @@
     }
 
     /** @hide */
-    protected final void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
+    protected void attachToAnchor(View anchor, int xoff, int yoff, int gravity) {
         detachFromAnchor();
 
         final ViewTreeObserver vto = anchor.getViewTreeObserver();
@@ -2339,6 +2339,11 @@
         mAnchoredGravity = gravity;
     }
 
+    /** @hide */
+    protected @Nullable View getAnchor() {
+        return mAnchor != null ? mAnchor.get() : null;
+    }
+
     private void alignToAnchor() {
         final View anchor = mAnchor != null ? mAnchor.get() : null;
         if (anchor != null && anchor.isAttachedToWindow() && hasDecorView()) {
@@ -2456,14 +2461,14 @@
             for (int i = 0; i < count; i++) {
                 final View child = getChildAt(i);
                 enterTransition.addTarget(child);
-                child.setVisibility(View.INVISIBLE);
+                child.setTransitionVisibility(View.INVISIBLE);
             }
 
             TransitionManager.beginDelayedTransition(this, enterTransition);
 
             for (int i = 0; i < count; i++) {
                 final View child = getChildAt(i);
-                child.setVisibility(View.VISIBLE);
+                child.setTransitionVisibility(View.VISIBLE);
             }
         }
 
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 1653afd..e330916 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,6 +20,7 @@
 
 import android.annotation.ColorInt;
 import android.annotation.DimenRes;
+import android.annotation.NonNull;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.Application;
@@ -80,6 +81,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Stack;
 import java.util.concurrent.Executor;
 
@@ -108,9 +110,9 @@
     // The unique identifiers for each custom {@link Action}.
     private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1;
     private static final int REFLECTION_ACTION_TAG = 2;
-    private static final int SET_DRAWABLE_PARAMETERS_TAG = 3;
+    private static final int SET_DRAWABLE_TINT_TAG = 3;
     private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
-    private static final int SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG = 5;
+    private static final int VIEW_CONTENT_NAVIGATION_TAG = 5;
     private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
     private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
     private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
@@ -121,7 +123,6 @@
     private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
     private static final int VIEW_PADDING_ACTION_TAG = 14;
     private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
-    private static final int TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG = 17;
     private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
     private static final int LAYOUT_PARAM_ACTION_TAG = 19;
     private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
@@ -131,7 +132,7 @@
      *
      * @hide
      */
-    private ApplicationInfo mApplication;
+    public ApplicationInfo mApplication;
 
     /**
      * The resource ID of the layout file. (Added to the parcel)
@@ -185,6 +186,9 @@
      */
     private boolean mIsWidgetCollectionChild = false;
 
+    /** Class cookies of the Parcel this instance was read from. */
+    private final Map<Class, Object> mClassCookies;
+
     private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
 
     private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>();
@@ -389,10 +393,10 @@
             return MERGE_REPLACE;
         }
 
-        public abstract String getActionName();
+        public abstract int getActionTag();
 
         public String getUniqueKey() {
-            return (getActionName() + viewId);
+            return (getActionTag() + "_" + viewId);
         }
 
         /**
@@ -424,8 +428,8 @@
      */
     private static abstract class RuntimeAction extends Action {
         @Override
-        public final String getActionName() {
-            return "RuntimeAction";
+        public final int getActionTag() {
+            return 0;
         }
 
         @Override
@@ -514,7 +518,6 @@
     }
 
     private class SetEmptyView extends Action {
-        int viewId;
         int emptyViewId;
 
         SetEmptyView(int viewId, int emptyViewId) {
@@ -528,7 +531,6 @@
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(SET_EMPTY_VIEW_ACTION_TAG);
             out.writeInt(this.viewId);
             out.writeInt(this.emptyViewId);
         }
@@ -546,8 +548,9 @@
             adapterView.setEmptyView(emptyView);
         }
 
-        public String getActionName() {
-            return "SetEmptyView";
+        @Override
+        public int getActionTag() {
+            return SET_EMPTY_VIEW_ACTION_TAG;
         }
     }
 
@@ -559,13 +562,12 @@
 
         public SetOnClickFillInIntent(Parcel parcel) {
             viewId = parcel.readInt();
-            fillInIntent = Intent.CREATOR.createFromParcel(parcel);
+            fillInIntent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_ON_CLICK_FILL_IN_INTENT_TAG);
             dest.writeInt(viewId);
-            fillInIntent.writeToParcel(dest, 0 /* no flags */);
+            dest.writeTypedObject(fillInIntent, 0 /* no flags */);
         }
 
         @Override
@@ -624,8 +626,9 @@
             }
         }
 
-        public String getActionName() {
-            return "SetOnClickFillInIntent";
+        @Override
+        public int getActionTag() {
+            return SET_ON_CLICK_FILL_IN_INTENT_TAG;
         }
 
         Intent fillInIntent;
@@ -643,9 +646,8 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_PENDING_INTENT_TEMPLATE_TAG);
             dest.writeInt(viewId);
-            pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
+            PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest);
         }
 
         @Override
@@ -699,8 +701,9 @@
             }
         }
 
-        public String getActionName() {
-            return "SetPendingIntentTemplate";
+        @Override
+        public int getActionTag() {
+            return SET_PENDING_INTENT_TEMPLATE_TAG;
         }
 
         PendingIntent pendingIntentTemplate;
@@ -716,30 +719,13 @@
         public SetRemoteViewsAdapterList(Parcel parcel) {
             viewId = parcel.readInt();
             viewTypeCount = parcel.readInt();
-            int count = parcel.readInt();
-            list = new ArrayList<RemoteViews>();
-
-            for (int i = 0; i < count; i++) {
-                RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
-                list.add(rv);
-            }
+            list = parcel.createTypedArrayList(RemoteViews.CREATOR);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_REMOTE_VIEW_ADAPTER_LIST_TAG);
             dest.writeInt(viewId);
             dest.writeInt(viewTypeCount);
-
-            if (list == null || list.size() == 0) {
-                dest.writeInt(0);
-            } else {
-                int count = list.size();
-                dest.writeInt(count);
-                for (int i = 0; i < count; i++) {
-                    RemoteViews rv = list.get(i);
-                    rv.writeToParcel(dest, flags);
-                }
-            }
+            dest.writeTypedList(list, flags);
         }
 
         @Override
@@ -779,8 +765,9 @@
             }
         }
 
-        public String getActionName() {
-            return "SetRemoteViewsAdapterList";
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
         }
 
         int viewTypeCount;
@@ -795,13 +782,12 @@
 
         public SetRemoteViewsAdapterIntent(Parcel parcel) {
             viewId = parcel.readInt();
-            intent = Intent.CREATOR.createFromParcel(parcel);
+            intent = parcel.readTypedObject(Intent.CREATOR);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_REMOTE_VIEW_ADAPTER_INTENT_TAG);
             dest.writeInt(viewId);
-            intent.writeToParcel(dest, flags);
+            dest.writeTypedObject(intent, flags);
         }
 
         @Override
@@ -845,8 +831,9 @@
             return copy;
         }
 
-        public String getActionName() {
-            return "SetRemoteViewsAdapterIntent";
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
         }
 
         Intent intent;
@@ -866,22 +853,12 @@
 
         public SetOnClickPendingIntent(Parcel parcel) {
             viewId = parcel.readInt();
-
-            // We check a flag to determine if the parcel contains a PendingIntent.
-            if (parcel.readInt() != 0) {
-                pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
-            }
+            pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_ON_CLICK_PENDING_INTENT_TAG);
             dest.writeInt(viewId);
-
-            // We use a flag to indicate whether the parcel contains a valid object.
-            dest.writeInt(pendingIntent != null ? 1 : 0);
-            if (pendingIntent != null) {
-                pendingIntent.writeToParcel(dest, 0 /* no flags */);
-            }
+            PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest);
         }
 
         @Override
@@ -922,8 +899,9 @@
             target.setOnClickListener(listener);
         }
 
-        public String getActionName() {
-            return "SetOnClickPendingIntent";
+        @Override
+        public int getActionTag() {
+            return SET_ON_CLICK_PENDING_INTENT_TAG;
         }
 
         PendingIntent pendingIntent;
@@ -1012,55 +990,37 @@
     }
 
     /**
-     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+     * Equivalent to calling
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
-     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
+     * on the {@link Drawable} of a given view.
      * <p>
-     * These operations will be performed on the {@link Drawable} returned by the
+     * The operation will be performed on the {@link Drawable} returned by the
      * target {@link View#getBackground()} by default.  If targetBackground is false,
      * we assume the target is an {@link ImageView} and try applying the operations
      * to {@link ImageView#getDrawable()}.
      * <p>
-     * You can omit specific calls by marking their values with null or -1.
      */
-    private class SetDrawableParameters extends Action {
-        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
-                int colorFilter, PorterDuff.Mode mode, int level) {
+    private class SetDrawableTint extends Action {
+        SetDrawableTint(int id, boolean targetBackground,
+                int colorFilter, @NonNull PorterDuff.Mode mode) {
             this.viewId = id;
             this.targetBackground = targetBackground;
-            this.alpha = alpha;
             this.colorFilter = colorFilter;
             this.filterMode = mode;
-            this.level = level;
         }
 
-        public SetDrawableParameters(Parcel parcel) {
+        SetDrawableTint(Parcel parcel) {
             viewId = parcel.readInt();
             targetBackground = parcel.readInt() != 0;
-            alpha = parcel.readInt();
             colorFilter = parcel.readInt();
-            boolean hasMode = parcel.readInt() != 0;
-            if (hasMode) {
-                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
-            } else {
-                filterMode = null;
-            }
-            level = parcel.readInt();
+            filterMode = PorterDuff.intToMode(parcel.readInt());
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_DRAWABLE_PARAMETERS_TAG);
             dest.writeInt(viewId);
             dest.writeInt(targetBackground ? 1 : 0);
-            dest.writeInt(alpha);
             dest.writeInt(colorFilter);
-            if (filterMode != null) {
-                dest.writeInt(1);
-                dest.writeString(filterMode.toString());
-            } else {
-                dest.writeInt(0);
-            }
-            dest.writeInt(level);
+            dest.writeInt(PorterDuff.modeToInt(filterMode));
         }
 
         @Override
@@ -1078,47 +1038,36 @@
             }
 
             if (targetDrawable != null) {
-                // Perform modifications only if values are set correctly
-                if (alpha != -1) {
-                    targetDrawable.mutate().setAlpha(alpha);
-                }
-                if (filterMode != null) {
-                    targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
-                }
-                if (level != -1) {
-                    targetDrawable.mutate().setLevel(level);
-                }
+                targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
             }
         }
 
-        public String getActionName() {
-            return "SetDrawableParameters";
+        @Override
+        public int getActionTag() {
+            return SET_DRAWABLE_TINT_TAG;
         }
 
         boolean targetBackground;
-        int alpha;
         int colorFilter;
         PorterDuff.Mode filterMode;
-        int level;
     }
 
-    private final class ReflectionActionWithoutParams extends Action {
-        final String methodName;
+    private final class ViewContentNavigation extends Action {
+        final boolean mNext;
 
-        ReflectionActionWithoutParams(int viewId, String methodName) {
+        ViewContentNavigation(int viewId, boolean next) {
             this.viewId = viewId;
-            this.methodName = methodName;
+            this.mNext = next;
         }
 
-        ReflectionActionWithoutParams(Parcel in) {
+        ViewContentNavigation(Parcel in) {
             this.viewId = in.readInt();
-            this.methodName = in.readString();
+            this.mNext = in.readBoolean();
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG);
             out.writeInt(this.viewId);
-            out.writeString(this.methodName);
+            out.writeBoolean(this.mNext);
         }
 
         @Override
@@ -1127,23 +1076,20 @@
             if (view == null) return;
 
             try {
-                getMethod(view, this.methodName, null, false /* async */).invoke(view);
+                getMethod(view,
+                        mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view);
             } catch (Throwable ex) {
                 throw new ActionException(ex);
             }
         }
 
         public int mergeBehavior() {
-            // we don't need to build up showNext or showPrevious calls
-            if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
-                return MERGE_IGNORE;
-            } else {
-                return MERGE_REPLACE;
-            }
+            return MERGE_IGNORE;
         }
 
-        public String getActionName() {
-            return "ReflectionActionWithoutParams";
+        @Override
+        public int getActionTag() {
+            return VIEW_CONTENT_NAVIGATION_TAG;
         }
     }
 
@@ -1157,12 +1103,7 @@
         }
 
         public BitmapCache(Parcel source) {
-            int count = source.readInt();
-            mBitmaps = new ArrayList<>(count);
-            for (int i = 0; i < count; i++) {
-                Bitmap b = Bitmap.CREATOR.createFromParcel(source);
-                mBitmaps.add(b);
-            }
+            mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
         }
 
         public int getBitmapId(Bitmap b) {
@@ -1188,11 +1129,7 @@
         }
 
         public void writeBitmapsToParcel(Parcel dest, int flags) {
-            int count = mBitmaps.size();
-            dest.writeInt(count);
-            for (int i = 0; i < count; i++) {
-                mBitmaps.get(i).writeToParcel(dest, flags);
-            }
+            dest.writeTypedList(mBitmaps, flags);
         }
 
         public int getBitmapMemory() {
@@ -1228,7 +1165,6 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(BITMAP_REFLECTION_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeString(methodName);
             dest.writeInt(bitmapId);
@@ -1247,8 +1183,9 @@
             bitmapId = bitmapCache.getBitmapId(bitmap);
         }
 
-        public String getActionName() {
-            return "BitmapReflectionAction";
+        @Override
+        public int getActionTag() {
+            return BITMAP_REFLECTION_ACTION_TAG;
         }
     }
 
@@ -1300,7 +1237,7 @@
             // written to the parcel.
             switch (this.type) {
                 case BOOLEAN:
-                    this.value = in.readInt() != 0;
+                    this.value = in.readBoolean();
                     break;
                 case BYTE:
                     this.value = in.readByte();
@@ -1330,39 +1267,28 @@
                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
                     break;
                 case URI:
-                    if (in.readInt() != 0) {
-                        this.value = Uri.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Uri.CREATOR);
                     break;
                 case BITMAP:
-                    if (in.readInt() != 0) {
-                        this.value = Bitmap.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Bitmap.CREATOR);
                     break;
                 case BUNDLE:
                     this.value = in.readBundle();
                     break;
                 case INTENT:
-                    if (in.readInt() != 0) {
-                        this.value = Intent.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Intent.CREATOR);
                     break;
                 case COLOR_STATE_LIST:
-                    if (in.readInt() != 0) {
-                        this.value = ColorStateList.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(ColorStateList.CREATOR);
                     break;
                 case ICON:
-                    if (in.readInt() != 0) {
-                        this.value = Icon.CREATOR.createFromParcel(in);
-                    }
+                    this.value = in.readTypedObject(Icon.CREATOR);
                 default:
                     break;
             }
         }
 
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(REFLECTION_ACTION_TAG);
             out.writeInt(this.viewId);
             out.writeString(this.methodName);
             out.writeInt(this.type);
@@ -1376,7 +1302,7 @@
             // we have written a valid value to the parcel.
             switch (this.type) {
                 case BOOLEAN:
-                    out.writeInt((Boolean) this.value ? 1 : 0);
+                    out.writeBoolean((Boolean) this.value);
                     break;
                 case BYTE:
                     out.writeByte((Byte) this.value);
@@ -1413,10 +1339,7 @@
                 case INTENT:
                 case COLOR_STATE_LIST:
                 case ICON:
-                    out.writeInt(this.value != null ? 1 : 0);
-                    if (this.value != null) {
-                        ((Parcelable) this.value).writeToParcel(out, flags);
-                    }
+                    out.writeTypedObject((Parcelable) this.value, flags);
                     break;
                 default:
                     break;
@@ -1522,10 +1445,16 @@
             }
         }
 
-        public String getActionName() {
+        @Override
+        public int getActionTag() {
+            return REFLECTION_ACTION_TAG;
+        }
+
+        @Override
+        public String getUniqueKey() {
             // Each type of reflection action corresponds to a setter, so each should be seen as
             // unique from the standpoint of merging.
-            return "ReflectionAction" + this.methodName + this.type;
+            return super.getUniqueKey() + this.methodName + this.type;
         }
 
         @Override
@@ -1580,14 +1509,13 @@
         }
 
         ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
-                int depth) {
+                int depth, Map<Class, Object> classCookies) {
             viewId = parcel.readInt();
             mIndex = parcel.readInt();
-            mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
+            mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(VIEW_GROUP_ACTION_ADD_TAG);
             dest.writeInt(viewId);
             dest.writeInt(mIndex);
             mNestedViews.writeToParcel(dest, flags);
@@ -1595,8 +1523,7 @@
 
         @Override
         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
-            return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
-                    && mNestedViews.mApplication.uid == parentInfo.uid;
+            return mNestedViews.hasSameAppInfo(parentInfo);
         }
 
         @Override
@@ -1662,10 +1589,9 @@
             return mNestedViews.prefersAsyncApply();
         }
 
-
         @Override
-        public String getActionName() {
-            return "ViewGroupActionAdd";
+        public int getActionTag() {
+            return VIEW_GROUP_ACTION_ADD_TAG;
         }
     }
 
@@ -1697,7 +1623,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(VIEW_GROUP_ACTION_REMOVE_TAG);
             dest.writeInt(viewId);
             dest.writeInt(mViewIdToKeep);
         }
@@ -1763,8 +1688,8 @@
         }
 
         @Override
-        public String getActionName() {
-            return "ViewGroupActionRemove";
+        public int getActionTag() {
+            return VIEW_GROUP_ACTION_REMOVE_TAG;
         }
 
         @Override
@@ -1804,18 +1729,10 @@
             isRelative = (parcel.readInt() != 0);
             useIcons = (parcel.readInt() != 0);
             if (useIcons) {
-                if (parcel.readInt() != 0) {
-                    i1 = Icon.CREATOR.createFromParcel(parcel);
-                }
-                if (parcel.readInt() != 0) {
-                    i2 = Icon.CREATOR.createFromParcel(parcel);
-                }
-                if (parcel.readInt() != 0) {
-                    i3 = Icon.CREATOR.createFromParcel(parcel);
-                }
-                if (parcel.readInt() != 0) {
-                    i4 = Icon.CREATOR.createFromParcel(parcel);
-                }
+                i1 = parcel.readTypedObject(Icon.CREATOR);
+                i2 = parcel.readTypedObject(Icon.CREATOR);
+                i3 = parcel.readTypedObject(Icon.CREATOR);
+                i4 = parcel.readTypedObject(Icon.CREATOR);
             } else {
                 d1 = parcel.readInt();
                 d2 = parcel.readInt();
@@ -1825,35 +1742,14 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(TEXT_VIEW_DRAWABLE_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeInt(isRelative ? 1 : 0);
             dest.writeInt(useIcons ? 1 : 0);
             if (useIcons) {
-                if (i1 != null) {
-                    dest.writeInt(1);
-                    i1.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
-                if (i2 != null) {
-                    dest.writeInt(1);
-                    i2.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
-                if (i3 != null) {
-                    dest.writeInt(1);
-                    i3.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
-                if (i4 != null) {
-                    dest.writeInt(1);
-                    i4.writeToParcel(dest, 0);
-                } else {
-                    dest.writeInt(0);
-                }
+                dest.writeTypedObject(i1, 0);
+                dest.writeTypedObject(i2, 0);
+                dest.writeTypedObject(i3, 0);
+                dest.writeTypedObject(i4, 0);
             } else {
                 dest.writeInt(d1);
                 dest.writeInt(d2);
@@ -1924,8 +1820,9 @@
             return useIcons;
         }
 
-        public String getActionName() {
-            return "TextViewDrawableAction";
+        @Override
+        public int getActionTag() {
+            return TEXT_VIEW_DRAWABLE_ACTION_TAG;
         }
 
         boolean isRelative = false;
@@ -1954,7 +1851,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeInt(units);
             dest.writeFloat(size);
@@ -1967,8 +1863,9 @@
             target.setTextSize(units, size);
         }
 
-        public String getActionName() {
-            return "TextViewSizeAction";
+        @Override
+        public int getActionTag() {
+            return TEXT_VIEW_SIZE_ACTION_TAG;
         }
 
         int units;
@@ -1996,7 +1893,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(VIEW_PADDING_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeInt(left);
             dest.writeInt(top);
@@ -2011,8 +1907,9 @@
             target.setPadding(left, top, right, bottom);
         }
 
-        public String getActionName() {
-            return "ViewPaddingAction";
+        @Override
+        public int getActionTag() {
+            return VIEW_PADDING_ACTION_TAG;
         }
 
         int left, top, right, bottom;
@@ -2029,6 +1926,9 @@
         public static final int LAYOUT_WIDTH = 2;
         public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
 
+        final int mProperty;
+        final int mValue;
+
         /**
          * @param viewId ID of the view alter
          * @param property which layout parameter to alter
@@ -2036,21 +1936,20 @@
          */
         public LayoutParamAction(int viewId, int property, int value) {
             this.viewId = viewId;
-            this.property = property;
-            this.value = value;
+            this.mProperty = property;
+            this.mValue = value;
         }
 
         public LayoutParamAction(Parcel parcel) {
             viewId = parcel.readInt();
-            property = parcel.readInt();
-            value = parcel.readInt();
+            mProperty = parcel.readInt();
+            mValue = parcel.readInt();
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(LAYOUT_PARAM_ACTION_TAG);
             dest.writeInt(viewId);
-            dest.writeInt(property);
-            dest.writeInt(value);
+            dest.writeInt(mProperty);
+            dest.writeInt(mValue);
         }
 
         @Override
@@ -2063,27 +1962,27 @@
             if (layoutParams == null) {
                 return;
             }
-            switch (property) {
+            switch (mProperty) {
                 case LAYOUT_MARGIN_END_DIMEN:
                     if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
-                        int resolved = resolveDimenPixelOffset(target, value);
+                        int resolved = resolveDimenPixelOffset(target, mValue);
                         ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved);
                         target.setLayoutParams(layoutParams);
                     }
                     break;
                 case LAYOUT_MARGIN_BOTTOM_DIMEN:
                     if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
-                        int resolved = resolveDimenPixelOffset(target, value);
+                        int resolved = resolveDimenPixelOffset(target, mValue);
                         ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
                         target.setLayoutParams(layoutParams);
                     }
                     break;
                 case LAYOUT_WIDTH:
-                    layoutParams.width = value;
+                    layoutParams.width = mValue;
                     target.setLayoutParams(layoutParams);
                     break;
                 default:
-                    throw new IllegalArgumentException("Unknown property " + property);
+                    throw new IllegalArgumentException("Unknown property " + mProperty);
             }
         }
 
@@ -2094,79 +1993,15 @@
             return target.getContext().getResources().getDimensionPixelOffset(value);
         }
 
-        public String getActionName() {
-            return "LayoutParamAction" + property + ".";
-        }
-
-        int property;
-        int value;
-    }
-
-    /**
-     * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
-     * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
-     */
-    private class TextViewDrawableColorFilterAction extends Action {
-        public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
-                int color, PorterDuff.Mode mode) {
-            this.viewId = viewId;
-            this.isRelative = isRelative;
-            this.index = index;
-            this.color = color;
-            this.mode = mode;
-        }
-
-        public TextViewDrawableColorFilterAction(Parcel parcel) {
-            viewId = parcel.readInt();
-            isRelative = (parcel.readInt() != 0);
-            index = parcel.readInt();
-            color = parcel.readInt();
-            mode = readPorterDuffMode(parcel);
-        }
-
-        private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
-            int mode = parcel.readInt();
-            if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
-                return PorterDuff.Mode.values()[mode];
-            } else {
-                return PorterDuff.Mode.CLEAR;
-            }
-        }
-
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG);
-            dest.writeInt(viewId);
-            dest.writeInt(isRelative ? 1 : 0);
-            dest.writeInt(index);
-            dest.writeInt(color);
-            dest.writeInt(mode.ordinal());
+        @Override
+        public int getActionTag() {
+            return LAYOUT_PARAM_ACTION_TAG;
         }
 
         @Override
-        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
-            final TextView target = root.findViewById(viewId);
-            if (target == null) return;
-            Drawable[] drawables = isRelative
-                    ? target.getCompoundDrawablesRelative()
-                    : target.getCompoundDrawables();
-            if (index < 0 || index >= 4) {
-                throw new IllegalStateException("index must be in range [0, 3].");
-            }
-            Drawable d = drawables[index];
-            if (d != null) {
-                d.mutate();
-                d.setColorFilter(color, mode);
-            }
+        public String getUniqueKey() {
+            return super.getUniqueKey() + mProperty;
         }
-
-        public String getActionName() {
-            return "TextViewDrawableColorFilterAction";
-        }
-
-        final boolean isRelative;
-        final int index;
-        final int color;
-        final PorterDuff.Mode mode;
     }
 
     /**
@@ -2185,7 +2020,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(SET_REMOTE_INPUTS_ACTION_TAG);
             dest.writeInt(viewId);
             dest.writeTypedArray(remoteInputs, flags);
         }
@@ -2198,8 +2032,9 @@
             target.setTagInternal(R.id.remote_input_tag, remoteInputs);
         }
 
-        public String getActionName() {
-            return "SetRemoteInputsAction";
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_INPUTS_ACTION_TAG;
         }
 
         final Parcelable[] remoteInputs;
@@ -2221,7 +2056,6 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(OVERRIDE_TEXT_COLORS_TAG);
             dest.writeInt(textColor);
         }
 
@@ -2246,8 +2080,9 @@
             }
         }
 
-        public String getActionName() {
-            return "OverrideTextColorsAction";
+        @Override
+        public int getActionTag() {
+            return OVERRIDE_TEXT_COLORS_TAG;
         }
     }
 
@@ -2289,6 +2124,7 @@
         mApplication = application;
         mLayoutId = layoutId;
         mBitmapCache = new BitmapCache();
+        mClassCookies = null;
     }
 
     private boolean hasLandscapeAndPortraitLayouts() {
@@ -2306,8 +2142,7 @@
         if (landscape == null || portrait == null) {
             throw new RuntimeException("Both RemoteViews must be non-null");
         }
-        if (landscape.mApplication.uid != portrait.mApplication.uid
-                || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
+        if (!landscape.hasSameAppInfo(portrait.mApplication)) {
             throw new RuntimeException("Both RemoteViews must share the same package and user");
         }
         mApplication = portrait.mApplication;
@@ -2319,6 +2154,9 @@
         mBitmapCache = new BitmapCache();
         configureRemoteViewsAsChild(landscape);
         configureRemoteViewsAsChild(portrait);
+
+        mClassCookies = (portrait.mClassCookies != null)
+                ? portrait.mClassCookies : landscape.mClassCookies;
     }
 
     /**
@@ -2331,16 +2169,17 @@
         mLayoutId = src.mLayoutId;
         mIsWidgetCollectionChild = src.mIsWidgetCollectionChild;
         mReapplyDisallowed = src.mReapplyDisallowed;
+        mClassCookies = src.mClassCookies;
 
         if (src.hasLandscapeAndPortraitLayouts()) {
             mLandscape = new RemoteViews(src.mLandscape);
             mPortrait = new RemoteViews(src.mPortrait);
-
         }
 
         if (src.mActions != null) {
             Parcel p = Parcel.obtain();
-            writeActionsToParcel(p);
+            p.putClassCookies(mClassCookies);
+            src.writeActionsToParcel(p);
             p.setDataPosition(0);
             // Since src is already in memory, we do not care about stack overflow as it has
             // already been read once.
@@ -2359,10 +2198,11 @@
      * @param parcel
      */
     public RemoteViews(Parcel parcel) {
-        this(parcel, null, null, 0);
+        this(parcel, null, null, 0, null);
     }
 
-    private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) {
+    private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth,
+            Map<Class, Object> classCookies) {
         if (depth > MAX_NESTED_VIEWS
                 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
             throw new IllegalArgumentException("Too many nested views.");
@@ -2374,8 +2214,11 @@
         // We only store a bitmap cache in the root of the RemoteViews.
         if (bitmapCache == null) {
             mBitmapCache = new BitmapCache(parcel);
+            // Store the class cookies such that they are available when we clone this RemoteView.
+            mClassCookies = parcel.copyClassCookies();
         } else {
             setBitmapCache(bitmapCache);
+            mClassCookies = classCookies;
             setNotRoot();
         }
 
@@ -2388,8 +2231,9 @@
             readActionsFromParcel(parcel, depth);
         } else {
             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
-            mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
-            mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth);
+            mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
+            mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
+                    mClassCookies);
             mApplication = mPortrait.mApplication;
             mLayoutId = mPortrait.getLayoutId();
         }
@@ -2411,16 +2255,17 @@
         switch (tag) {
             case SET_ON_CLICK_PENDING_INTENT_TAG:
                 return new SetOnClickPendingIntent(parcel);
-            case SET_DRAWABLE_PARAMETERS_TAG:
-                return new SetDrawableParameters(parcel);
+            case SET_DRAWABLE_TINT_TAG:
+                return new SetDrawableTint(parcel);
             case REFLECTION_ACTION_TAG:
                 return new ReflectionAction(parcel);
             case VIEW_GROUP_ACTION_ADD_TAG:
-                return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth);
+                return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth,
+                        mClassCookies);
             case VIEW_GROUP_ACTION_REMOVE_TAG:
                 return new ViewGroupActionRemove(parcel);
-            case SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG:
-                return new ReflectionActionWithoutParams(parcel);
+            case VIEW_CONTENT_NAVIGATION_TAG:
+                return new ViewContentNavigation(parcel);
             case SET_EMPTY_VIEW_ACTION_TAG:
                 return new SetEmptyView(parcel);
             case SET_PENDING_INTENT_TEMPLATE_TAG:
@@ -2439,8 +2284,6 @@
                 return new BitmapReflectionAction(parcel);
             case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
                 return new SetRemoteViewsAdapterList(parcel);
-            case TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG:
-                return new TextViewDrawableColorFilterAction(parcel);
             case SET_REMOTE_INPUTS_ACTION_TAG:
                 return new SetRemoteInputsAction(parcel);
             case LAYOUT_PARAM_ACTION_TAG:
@@ -2597,7 +2440,7 @@
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
      */
     public void showNext(int viewId) {
-        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
+        addAction(new ViewContentNavigation(viewId, true /* next */));
     }
 
     /**
@@ -2606,7 +2449,7 @@
      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
      */
     public void showPrevious(int viewId) {
-        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
+        addAction(new ViewContentNavigation(viewId, false /* next */));
     }
 
     /**
@@ -2680,28 +2523,6 @@
     }
 
     /**
-     * Equivalent to applying a color filter on one of the drawables in
-     * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
-     *
-     * @param viewId The id of the view whose text should change.
-     * @param index  The index of the drawable in the array of
-     *               {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
-     *               filter on. Must be in [0, 3].
-     * @param color  The color of the color filter. See
-     *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
-     * @param mode   The mode of the color filter. See
-     *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
-     * @hide
-     */
-    public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
-            int index, int color, PorterDuff.Mode mode) {
-        if (index < 0 || index >= 4) {
-            throw new IllegalArgumentException("index must be in range [0, 3].");
-        }
-        addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
-    }
-
-    /**
      * Equivalent to calling {@link
      * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
      * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
@@ -2845,7 +2666,11 @@
     /**
      * Equivalent to calling
      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
-     * to launch the provided {@link PendingIntent}.
+     * to launch the provided {@link PendingIntent}. The source bounds
+     * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked
+     * view in screen space.
+     * Note that any activity options associated with the pendingIntent may get overridden
+     * before starting the intent.
      *
      * When setting the on-click action of items within collections (eg. {@link ListView},
      * {@link StackView} etc.), this method will not work. Instead, use {@link
@@ -2898,12 +2723,10 @@
 
     /**
      * @hide
-     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
+     * Equivalent to calling
      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
-     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
-     * view.
+     * on the {@link Drawable} of a given view.
      * <p>
-     * You can omit specific calls by marking their values with null or -1.
      *
      * @param viewId The id of the view that contains the target
      *            {@link Drawable}
@@ -2912,20 +2735,15 @@
      *            {@link android.view.View#getBackground()}. Otherwise, assume
      *            the target view is an {@link ImageView} and apply them to
      *            {@link ImageView#getDrawable()}.
-     * @param alpha Specify an alpha value for the drawable, or -1 to leave
-     *            unchanged.
      * @param colorFilter Specify a color for a
      *            {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
      *            {@code mode} is {@code null}.
      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
      *            unchanged.
-     * @param level Specify the level for the drawable, or -1 to leave
-     *            unchanged.
      */
-    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
-            int colorFilter, PorterDuff.Mode mode, int level) {
-        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
-                colorFilter, mode, level));
+    public void setDrawableTint(int viewId, boolean targetBackground,
+            int colorFilter, @NonNull PorterDuff.Mode mode) {
+        addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
     }
 
     /**
@@ -3717,6 +3535,7 @@
         parcel.writeInt(count);
         for (int i = 0; i < count; i++) {
             Action a = mActions.get(i);
+            parcel.writeInt(a.getActionTag());
             a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
                     ? PARCELABLE_ELIDE_DUPLICATES : 0);
         }
@@ -3749,6 +3568,15 @@
     }
 
     /**
+     * Returns true if the {@link #mApplication} is same as the provided info.
+     *
+     * @hide
+     */
+    public boolean hasSameAppInfo(ApplicationInfo info) {
+        return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+    }
+
+    /**
      * Parcelable.Creator that instantiates RemoteViews objects
      */
     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 0968652..e5ae0ca 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -16,11 +16,15 @@
 
 package android.widget;
 
-import android.Manifest;
+import android.annotation.WorkerThread;
+import android.app.IServiceConnection;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -28,7 +32,6 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
@@ -38,7 +41,6 @@
 import android.view.ViewGroup;
 import android.widget.RemoteViews.OnClickHandler;
 
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
 
 import java.lang.ref.WeakReference;
@@ -48,52 +50,33 @@
 import java.util.concurrent.Executor;
 
 /**
- * An adapter to a RemoteViewsService which fetches and caches RemoteViews
- * to be later inflated as child views.
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
+ * child views.
+ *
+ * The adapter runs in the host process, typically a Launcher app.
+ *
+ * It makes a service connection to the {@link RemoteViewsService} running in the
+ * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
+ * the platform to get the bind permissions) and all interaction with the service is done on the
+ * background thread.
+ *
+ * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
+ * connection is only made when new RemoteViews are required.
+ * @hide
  */
-/** @hide */
 public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
-    private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 
     private static final String TAG = "RemoteViewsAdapter";
 
     // The max number of items in the cache
-    private static final int sDefaultCacheSize = 40;
+    private static final int DEFAULT_CACHE_SIZE = 40;
     // The delay (in millis) to wait until attempting to unbind from a service after a request.
     // This ensures that we don't stay continually bound to the service and that it can be destroyed
     // if we need the memory elsewhere in the system.
-    private static final int sUnbindServiceDelay = 5000;
+    private static final int UNBIND_SERVICE_DELAY = 5000;
 
     // Default height for the default loading view, in case we cannot get inflate the first view
-    private static final int sDefaultLoadingViewHeight = 50;
-
-    // Type defs for controlling different messages across the main and worker message queues
-    private static final int sDefaultMessageType = 0;
-    private static final int sUnbindServiceMessageType = 1;
-
-    private final Context mContext;
-    private final Intent mIntent;
-    private final int mAppWidgetId;
-    private final Executor mAsyncViewLoadExecutor;
-
-    private RemoteViewsAdapterServiceConnection mServiceConnection;
-    private WeakReference<RemoteAdapterConnectionCallback> mCallback;
-    private OnClickHandler mRemoteViewsOnClickHandler;
-    private final FixedSizeRemoteViewsCache mCache;
-    private int mVisibleWindowLowerBound;
-    private int mVisibleWindowUpperBound;
-
-    // A flag to determine whether we should notify data set changed after we connect
-    private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
-
-    // The set of requested views that are to be notified when the associated RemoteViews are
-    // loaded.
-    private RemoteViewsFrameLayoutRefSet mRequestedViews;
-
-    private HandlerThread mWorkerThread;
-    // items may be interrupted within the normally processed queues
-    private Handler mWorkerQueue;
-    private Handler mMainQueue;
+    private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
 
     // We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
     // structures;
@@ -110,11 +93,37 @@
     // duration, the cache is dropped.
     private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
 
+    private final Context mContext;
+    private final Intent mIntent;
+    private final int mAppWidgetId;
+    private final Executor mAsyncViewLoadExecutor;
+
+    private OnClickHandler mRemoteViewsOnClickHandler;
+    private final FixedSizeRemoteViewsCache mCache;
+    private int mVisibleWindowLowerBound;
+    private int mVisibleWindowUpperBound;
+
+    // The set of requested views that are to be notified when the associated RemoteViews are
+    // loaded.
+    private RemoteViewsFrameLayoutRefSet mRequestedViews;
+
+    private final HandlerThread mWorkerThread;
+    // items may be interrupted within the normally processed queues
+    private final Handler mMainHandler;
+    private final RemoteServiceHandler mServiceHandler;
+    private final RemoteAdapterConnectionCallback mCallback;
+
     // Used to indicate to the AdapterView that it can use this Adapter immediately after
     // construction (happens when we have a cached FixedSizeRemoteViewsCache).
     private boolean mDataReady = false;
 
     /**
+     * USed to dedupe {@link RemoteViews#mApplication} so that we do not hold on to
+     * multiple copies of the same ApplicationInfo object.
+     */
+    private ApplicationInfo mLastRemoteViewAppInfo;
+
+    /**
      * An interface for the RemoteAdapter to notify other classes when adapters
      * are actually connected to/disconnected from their actual services.
      */
@@ -151,154 +160,192 @@
         }
     }
 
+    static final int MSG_REQUEST_BIND = 1;
+    static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
+    static final int MSG_LOAD_NEXT_ITEM = 3;
+    static final int MSG_UNBIND_SERVICE = 4;
+
+    private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
+    private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
+    private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
+    private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
+    private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
+
     /**
-     * The service connection that gets populated when the RemoteViewsService is
-     * bound.  This must be a static inner class to ensure that no references to the outer
-     * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
-     * garbage collected, and would cause us to leak activities due to the caching mechanism for
-     * FrameLayouts in the adapter).
+     * Handler for various interactions with the {@link RemoteViewsService}.
      */
-    private static class RemoteViewsAdapterServiceConnection extends
-            IRemoteViewsAdapterConnection.Stub {
-        private boolean mIsConnected;
-        private boolean mIsConnecting;
-        private WeakReference<RemoteViewsAdapter> mAdapter;
+    private static class RemoteServiceHandler extends Handler implements ServiceConnection {
+
+        private final WeakReference<RemoteViewsAdapter> mAdapter;
+        private final Context mContext;
+
         private IRemoteViewsFactory mRemoteViewsFactory;
 
-        public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
-            mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+        // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
+        private boolean mNotifyDataSetChangedPending = false;
+        private boolean mBindRequested = false;
+
+        RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
+            super(workerLooper);
+            mAdapter = new WeakReference<>(adapter);
+            mContext = context;
         }
 
-        public synchronized void bind(Context context, int appWidgetId, Intent intent) {
-            if (!mIsConnecting) {
-                try {
-                    RemoteViewsAdapter adapter;
-                    final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                    if ((adapter = mAdapter.get()) != null) {
-                        mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId,
-                                intent, asBinder());
-                    } else {
-                        Slog.w(TAG, "bind: adapter was null");
-                    }
-                    mIsConnecting = true;
-                } catch (Exception e) {
-                    Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
-                    mIsConnecting = false;
-                    mIsConnected = false;
-                }
-            }
-        }
-
-        public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
-            try {
-                RemoteViewsAdapter adapter;
-                final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
-                if ((adapter = mAdapter.get()) != null) {
-                    mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
-                } else {
-                    Slog.w(TAG, "unbind: adapter was null");
-                }
-                mIsConnecting = false;
-            } catch (Exception e) {
-                Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
-                mIsConnecting = false;
-                mIsConnected = false;
-            }
-        }
-
-        public synchronized void onServiceConnected(IBinder service) {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            // This is called on the same thread.
             mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+            enqueueDeferredUnbindServiceMessage();
 
-            // Remove any deferred unbind messages
-            final RemoteViewsAdapter adapter = mAdapter.get();
-            if (adapter == null) return;
+            RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter == null) {
+                return;
+            }
 
-            // Queue up work that we need to do for the callback to run
-            adapter.mWorkerQueue.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
-                        // Handle queued notifyDataSetChanged() if necessary
-                        adapter.onNotifyDataSetChanged();
-                    } else {
-                        IRemoteViewsFactory factory =
-                            adapter.mServiceConnection.getRemoteViewsFactory();
-                        try {
-                            if (!factory.isCreated()) {
-                                // We only call onDataSetChanged() if this is the factory was just
-                                // create in response to this bind
-                                factory.onDataSetChanged();
-                            }
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "Error notifying factory of data set changed in " +
-                                        "onServiceConnected(): " + e.getMessage());
-
-                            // Return early to prevent anything further from being notified
-                            // (effectively nothing has changed)
-                            return;
-                        } catch (RuntimeException e) {
-                            Log.e(TAG, "Error notifying factory of data set changed in " +
-                                    "onServiceConnected(): " + e.getMessage());
-                        }
-
-                        // Request meta data so that we have up to date data when calling back to
-                        // the remote adapter callback
-                        adapter.updateTemporaryMetaData();
-
-                        // Notify the host that we've connected
-                        adapter.mMainQueue.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                synchronized (adapter.mCache) {
-                                    adapter.mCache.commitTemporaryMetaData();
-                                }
-
-                                final RemoteAdapterConnectionCallback callback =
-                                    adapter.mCallback.get();
-                                if (callback != null) {
-                                    callback.onRemoteAdapterConnected();
-                                }
-                            }
-                        });
-                    }
-
-                    // Enqueue unbind message
-                    adapter.enqueueDeferredUnbindServiceMessage();
-                    mIsConnected = true;
-                    mIsConnecting = false;
+            if (mNotifyDataSetChangedPending) {
+                mNotifyDataSetChangedPending = false;
+                Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
+                handleMessage(msg);
+                msg.recycle();
+            } else {
+                if (!sendNotifyDataSetChange(false)) {
+                    return;
                 }
-            });
+
+                // Request meta data so that we have up to date data when calling back to
+                // the remote adapter callback
+                adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
+            }
         }
 
-        public synchronized void onServiceDisconnected() {
-            mIsConnected = false;
-            mIsConnecting = false;
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
             mRemoteViewsFactory = null;
+            RemoteViewsAdapter adapter = mAdapter.get();
+            if (adapter != null) {
+                adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
+            }
+        }
 
-            // Clear the main/worker queues
-            final RemoteViewsAdapter adapter = mAdapter.get();
-            if (adapter == null) return;
+        @Override
+        public void handleMessage(Message msg) {
+            RemoteViewsAdapter adapter = mAdapter.get();
 
-            adapter.mMainQueue.post(new Runnable() {
-                @Override
-                public void run() {
-                    // Dequeue any unbind messages
-                    adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
-
-                    final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
-                    if (callback != null) {
-                        callback.onRemoteAdapterDisconnected();
+            switch (msg.what) {
+                case MSG_REQUEST_BIND: {
+                    if (adapter == null || mRemoteViewsFactory != null) {
+                        enqueueDeferredUnbindServiceMessage();
                     }
+                    if (mBindRequested) {
+                        return;
+                    }
+                    int flags = Context.BIND_AUTO_CREATE
+                            | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+                    final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
+                    Intent intent = (Intent) msg.obj;
+                    int appWidgetId = msg.arg1;
+                    mBindRequested = AppWidgetManager.getInstance(mContext)
+                            .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
+                    return;
                 }
-            });
+                case MSG_NOTIFY_DATA_SET_CHANGED: {
+                    enqueueDeferredUnbindServiceMessage();
+                    if (adapter == null) {
+                        return;
+                    }
+                    if (mRemoteViewsFactory == null) {
+                        mNotifyDataSetChangedPending = true;
+                        adapter.requestBindService();
+                        return;
+                    }
+                    if (!sendNotifyDataSetChange(true)) {
+                        return;
+                    }
+
+                    // Flush the cache so that we can reload new items from the service
+                    synchronized (adapter.mCache) {
+                        adapter.mCache.reset();
+                    }
+
+                    // Re-request the new metadata (only after the notification to the factory)
+                    adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+                    int newCount;
+                    int[] visibleWindow;
+                    synchronized (adapter.mCache.getTemporaryMetaData()) {
+                        newCount = adapter.mCache.getTemporaryMetaData().count;
+                        visibleWindow = adapter.getVisibleWindow(newCount);
+                    }
+
+                    // Pre-load (our best guess of) the views which are currently visible in the
+                    // AdapterView. This mitigates flashing and flickering of loading views when a
+                    // widget notifies that its data has changed.
+                    for (int position : visibleWindow) {
+                        // Because temporary meta data is only ever modified from this thread
+                        // (ie. mWorkerThread), it is safe to assume that count is a valid
+                        // representation.
+                        if (position < newCount) {
+                            adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
+                        }
+                    }
+
+                    // Propagate the notification back to the base adapter
+                    adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+                    adapter.mMainHandler.sendEmptyMessage(
+                            MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
+                    return;
+                }
+
+                case MSG_LOAD_NEXT_ITEM: {
+                    if (adapter == null || mRemoteViewsFactory == null) {
+                        return;
+                    }
+                    removeMessages(MSG_UNBIND_SERVICE);
+                    // Get the next index to load
+                    final int position = adapter.mCache.getNextIndexToLoad();
+                    if (position > -1) {
+                        // Load the item, and notify any existing RemoteViewsFrameLayouts
+                        adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
+
+                        // Queue up for the next one to load
+                        sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+                    } else {
+                        // No more items to load, so queue unbind
+                        enqueueDeferredUnbindServiceMessage();
+                    }
+                    return;
+                }
+                case MSG_UNBIND_SERVICE: {
+                    unbindNow();
+                    return;
+                }
+            }
         }
 
-        public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
-            return mRemoteViewsFactory;
+        protected void unbindNow() {
+            if (mBindRequested) {
+                mBindRequested = false;
+                mContext.unbindService(this);
+            }
+            mRemoteViewsFactory = null;
         }
 
-        public synchronized boolean isConnected() {
-            return mIsConnected;
+        private boolean sendNotifyDataSetChange(boolean always) {
+            try {
+                if (always || !mRemoteViewsFactory.isCreated()) {
+                    mRemoteViewsFactory.onDataSetChanged();
+                }
+                return true;
+            } catch (RemoteException | RuntimeException e) {
+                Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+                return false;
+            }
+        }
+
+        private void enqueueDeferredUnbindServiceMessage() {
+            removeMessages(MSG_UNBIND_SERVICE);
+            sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
         }
     }
 
@@ -309,6 +356,8 @@
     static class RemoteViewsFrameLayout extends AppWidgetHostView {
         private final FixedSizeRemoteViewsCache mCache;
 
+        public int cacheIndex = -1;
+
         public RemoteViewsFrameLayout(Context context, FixedSizeRemoteViewsCache cache) {
             super(context);
             mCache = cache;
@@ -359,26 +408,23 @@
      * Stores the references of all the RemoteViewsFrameLayouts that have been returned by the
      * adapter that have not yet had their RemoteViews loaded.
      */
-    private class RemoteViewsFrameLayoutRefSet {
-        private final SparseArray<LinkedList<RemoteViewsFrameLayout>> mReferences =
-                new SparseArray<>();
-        private final HashMap<RemoteViewsFrameLayout, LinkedList<RemoteViewsFrameLayout>>
-                mViewToLinkedList = new HashMap<>();
+    private class RemoteViewsFrameLayoutRefSet
+            extends SparseArray<LinkedList<RemoteViewsFrameLayout>> {
 
         /**
          * Adds a new reference to a RemoteViewsFrameLayout returned by the adapter.
          */
         public void add(int position, RemoteViewsFrameLayout layout) {
-            LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+            LinkedList<RemoteViewsFrameLayout> refs = get(position);
 
             // Create the list if necessary
             if (refs == null) {
-                refs = new LinkedList<RemoteViewsFrameLayout>();
-                mReferences.put(position, refs);
+                refs = new LinkedList<>();
+                put(position, refs);
             }
-            mViewToLinkedList.put(layout, refs);
 
             // Add the references to the list
+            layout.cacheIndex = position;
             refs.add(layout);
         }
 
@@ -389,18 +435,13 @@
         public void notifyOnRemoteViewsLoaded(int position, RemoteViews view) {
             if (view == null) return;
 
-            final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(position);
+            // Remove this set from the original mapping
+            final LinkedList<RemoteViewsFrameLayout> refs = removeReturnOld(position);
             if (refs != null) {
                 // Notify all the references for that position of the newly loaded RemoteViews
                 for (final RemoteViewsFrameLayout ref : refs) {
                     ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler, true);
-                    if (mViewToLinkedList.containsKey(ref)) {
-                        mViewToLinkedList.remove(ref);
-                    }
                 }
-                refs.clear();
-                // Remove this set from the original mapping
-                mReferences.remove(position);
             }
         }
 
@@ -408,20 +449,14 @@
          * We need to remove views from this set if they have been recycled by the AdapterView.
          */
         public void removeView(RemoteViewsFrameLayout rvfl) {
-            if (mViewToLinkedList.containsKey(rvfl)) {
-                mViewToLinkedList.get(rvfl).remove(rvfl);
-                mViewToLinkedList.remove(rvfl);
+            if (rvfl.cacheIndex < 0) {
+                return;
             }
-        }
-
-        /**
-         * Removes all references to all RemoteViewsFrameLayouts returned by the adapter.
-         */
-        public void clear() {
-            // We currently just clear the references, and leave all the previous layouts returned
-            // in their default state of the loading view.
-            mReferences.clear();
-            mViewToLinkedList.clear();
+            final LinkedList<RemoteViewsFrameLayout> refs = get(rvfl.cacheIndex);
+            if (refs != null) {
+                refs.remove(rvfl);
+            }
+            rvfl.cacheIndex = -1;
         }
     }
 
@@ -512,7 +547,6 @@
      *
      */
     private static class FixedSizeRemoteViewsCache {
-        private static final String TAG = "FixedSizeRemoteViewsCache";
 
         // The meta data related to all the RemoteViews, ie. count, is stable, etc.
         // The meta data objects are made final so that they can be locked on independently
@@ -534,7 +568,7 @@
         // too much memory.
         private final SparseArray<RemoteViews> mIndexRemoteViews = new SparseArray<>();
 
-        // An array of indices to load, Indices which are explicitely requested are set to true,
+        // An array of indices to load, Indices which are explicitly requested are set to true,
         // and those determined by the preloading algorithm to prefetch are set to false.
         private final SparseBooleanArray mIndicesToLoad = new SparseBooleanArray();
 
@@ -676,7 +710,7 @@
                 }
             }
 
-            int count = 0;
+            int count;
             synchronized (mMetaData) {
                 count = mMetaData.count;
             }
@@ -791,9 +825,11 @@
         // Initialize the worker thread
         mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
         mWorkerThread.start();
-        mWorkerQueue = new Handler(mWorkerThread.getLooper());
-        mMainQueue = new Handler(Looper.myLooper(), this);
+        mMainHandler = new Handler(Looper.myLooper(), this);
+        mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
+                context.getApplicationContext());
         mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
+        mCallback = callback;
 
         if (sCacheRemovalThread == null) {
             sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -801,10 +837,6 @@
             sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
         }
 
-        // Initialize the cache and the service connection on startup
-        mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
-        mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
-
         RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
                 mAppWidgetId);
 
@@ -819,7 +851,7 @@
                     }
                 }
             } else {
-                mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+                mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE);
             }
             if (!mDataReady) {
                 requestBindService();
@@ -830,9 +862,8 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mWorkerThread != null) {
-                mWorkerThread.quit();
-            }
+            mServiceHandler.unbindNow();
+            mWorkerThread.quit();
         } finally {
             super.finalize();
         }
@@ -869,16 +900,13 @@
                 sCachedRemoteViewsCaches.put(key, mCache);
             }
 
-            Runnable r = new Runnable() {
-                @Override
-                public void run() {
-                    synchronized (sCachedRemoteViewsCaches) {
-                        if (sCachedRemoteViewsCaches.containsKey(key)) {
-                            sCachedRemoteViewsCaches.remove(key);
-                        }
-                        if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
-                            sRemoteViewsCacheRemoveRunnables.remove(key);
-                        }
+            Runnable r = () -> {
+                synchronized (sCachedRemoteViewsCaches) {
+                    if (sCachedRemoteViewsCaches.containsKey(key)) {
+                        sCachedRemoteViewsCaches.remove(key);
+                    }
+                    if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+                        sRemoteViewsCacheRemoveRunnables.remove(key);
                     }
                 }
             };
@@ -887,54 +915,8 @@
         }
     }
 
-    private void loadNextIndexInBackground() {
-        mWorkerQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mServiceConnection.isConnected()) {
-                    // Get the next index to load
-                    int position = -1;
-                    synchronized (mCache) {
-                        position = mCache.getNextIndexToLoad();
-                    }
-                    if (position > -1) {
-                        // Load the item, and notify any existing RemoteViewsFrameLayouts
-                        updateRemoteViews(position, true);
-
-                        // Queue up for the next one to load
-                        loadNextIndexInBackground();
-                    } else {
-                        // No more items to load, so queue unbind
-                        enqueueDeferredUnbindServiceMessage();
-                    }
-                }
-            }
-        });
-    }
-
-    private void processException(String method, Exception e) {
-        Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
-
-        // If we encounter a crash when updating, we should reset the metadata & cache and trigger
-        // a notifyDataSetChanged to update the widget accordingly
-        final RemoteViewsMetaData metaData = mCache.getMetaData();
-        synchronized (metaData) {
-            metaData.reset();
-        }
-        synchronized (mCache) {
-            mCache.reset();
-        }
-        mMainQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                superNotifyDataSetChanged();
-            }
-        });
-    }
-
-    private void updateTemporaryMetaData() {
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+    @WorkerThread
+    private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
         try {
             // get the properties/first view (so that we can use it to
             // measure our dummy views)
@@ -958,40 +940,54 @@
                 tmpMetaData.count = count;
                 tmpMetaData.loadingTemplate = loadingTemplate;
             }
-        } catch(RemoteException e) {
-            processException("updateMetaData", e);
-        } catch(RuntimeException e) {
-            processException("updateMetaData", e);
+        } catch (RemoteException | RuntimeException e) {
+            Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
+
+            // If we encounter a crash when updating, we should reset the metadata & cache
+            // and trigger a notifyDataSetChanged to update the widget accordingly
+            synchronized (mCache.getMetaData()) {
+                mCache.getMetaData().reset();
+            }
+            synchronized (mCache) {
+                mCache.reset();
+            }
+            mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
         }
     }
 
-    private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+    @WorkerThread
+    private void updateRemoteViews(IRemoteViewsFactory factory, int position,
+            boolean notifyWhenLoaded) {
         // Load the item information from the remote service
-        RemoteViews remoteViews = null;
-        long itemId = 0;
+        final RemoteViews remoteViews;
+        final long itemId;
         try {
             remoteViews = factory.getViewAt(position);
             itemId = factory.getItemId(position);
-        } catch (RemoteException e) {
+
+            if (remoteViews == null) {
+                throw new RuntimeException("Null remoteViews");
+            }
+        } catch (RemoteException | RuntimeException e) {
             Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
 
             // Return early to prevent additional work in re-centering the view cache, and
             // swapping from the loading view
             return;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
-            return;
         }
 
-        if (remoteViews == null) {
-            // If a null view was returned, we break early to prevent it from getting
-            // into our cache and causing problems later. The effect is that the child  at this
-            // position will remain as a loading view until it is updated.
-            Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
-                    "returned from RemoteViewsFactory.");
-            return;
+        if (remoteViews.mApplication != null) {
+            // We keep track of last application info. This helps when all the remoteViews have
+            // same applicationInfo, which should be the case for a typical adapter. But if every
+            // view has different application info, there will not be any optimization.
+            if (mLastRemoteViewAppInfo != null
+                    && remoteViews.hasSameAppInfo(mLastRemoteViewAppInfo)) {
+                // We should probably also update the remoteViews for nested ViewActions.
+                // Hopefully, RemoteViews in an adapter would be less complicated.
+                remoteViews.mApplication = mLastRemoteViewAppInfo;
+            } else {
+                mLastRemoteViewAppInfo = remoteViews.mApplication;
+            }
         }
 
         int layoutId = remoteViews.getLayoutId();
@@ -1004,21 +1000,15 @@
         }
         synchronized (mCache) {
             if (viewTypeInRange) {
-                int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
-                        mVisibleWindowUpperBound, cacheCount);
+                int[] visibleWindow = getVisibleWindow(cacheCount);
                 // Cache the RemoteViews we loaded
                 mCache.insert(position, remoteViews, itemId, visibleWindow);
 
-                // Notify all the views that we have previously returned for this index that
-                // there is new data for it.
-                final RemoteViews rv = remoteViews;
                 if (notifyWhenLoaded) {
-                    mMainQueue.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
-                        }
-                    });
+                    // Notify all the views that we have previously returned for this index that
+                    // there is new data for it.
+                    Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
+                            remoteViews).sendToTarget();
                 }
             } else {
                 // We need to log an error here, as the the view type count specified by the
@@ -1057,7 +1047,7 @@
     }
 
     public int getItemViewType(int position) {
-        int typeId = 0;
+        final int typeId;
         synchronized (mCache) {
             if (mCache.containsMetaDataAt(position)) {
                 typeId = mCache.getMetaDataAt(position).typeId;
@@ -1088,14 +1078,13 @@
         synchronized (mCache) {
             RemoteViews rv = mCache.getRemoteViewsAt(position);
             boolean isInCache = (rv != null);
-            boolean isConnected = mServiceConnection.isConnected();
             boolean hasNewItems = false;
 
             if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
                 mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
             }
 
-            if (!isInCache && !isConnected) {
+            if (!isInCache) {
                 // Requesting bind service will trigger a super.notifyDataSetChanged(), which will
                 // in turn trigger another request to getView()
                 requestBindService();
@@ -1115,7 +1104,9 @@
             if (isInCache) {
                 // Apply the view synchronously if possible, to avoid flickering
                 layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
-                if (hasNewItems) loadNextIndexInBackground();
+                if (hasNewItems) {
+                    mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+                }
             } else {
                 // If the views is not loaded, apply the loading view. If the loading view doesn't
                 // exist, the layout will create a default view based on the firstView height.
@@ -1125,7 +1116,7 @@
                         false);
                 mRequestedViews.add(position, layout);
                 mCache.queueRequestedPositionToLoad(position);
-                loadNextIndexInBackground();
+                mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
             }
             return layout;
         }
@@ -1149,69 +1140,12 @@
         return getCount() <= 0;
     }
 
-    private void onNotifyDataSetChanged() {
-        // Complete the actual notifyDataSetChanged() call initiated earlier
-        IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-        try {
-            factory.onDataSetChanged();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
-            // Return early to prevent from further being notified (since nothing has
-            // changed)
-            return;
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-            return;
-        }
-
-        // Flush the cache so that we can reload new items from the service
-        synchronized (mCache) {
-            mCache.reset();
-        }
-
-        // Re-request the new metadata (only after the notification to the factory)
-        updateTemporaryMetaData();
-        int newCount;
-        int[] visibleWindow;
-        synchronized(mCache.getTemporaryMetaData()) {
-            newCount = mCache.getTemporaryMetaData().count;
-            visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
-                    mVisibleWindowUpperBound, newCount);
-        }
-
-        // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
-        // This mitigates flashing and flickering of loading views when a widget notifies that
-        // its data has changed.
-        for (int i: visibleWindow) {
-            // Because temporary meta data is only ever modified from this thread (ie.
-            // mWorkerThread), it is safe to assume that count is a valid representation.
-            if (i < newCount) {
-                updateRemoteViews(i, false);
-            }
-        }
-
-        // Propagate the notification back to the base adapter
-        mMainQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                synchronized (mCache) {
-                    mCache.commitTemporaryMetaData();
-                }
-
-                superNotifyDataSetChanged();
-                enqueueDeferredUnbindServiceMessage();
-            }
-        });
-
-        // Reset the notify flagflag
-        mNotifyDataSetChangedAfterOnServiceConnected = false;
-    }
-
     /**
      * Returns a sorted array of all integers between lower and upper.
      */
-    private int[] getVisibleWindow(int lower, int upper, int count) {
+    private int[] getVisibleWindow(int count) {
+        int lower = mVisibleWindowLowerBound;
+        int upper = mVisibleWindowUpperBound;
         // In the case that the window is invalid or uninitialized, return an empty window.
         if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
             return new int[0];
@@ -1241,23 +1175,8 @@
     }
 
     public void notifyDataSetChanged() {
-        // Dequeue any unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-
-        // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
-        // connect
-        if (!mServiceConnection.isConnected()) {
-            mNotifyDataSetChangedAfterOnServiceConnected = true;
-            requestBindService();
-            return;
-        }
-
-        mWorkerQueue.post(new Runnable() {
-            @Override
-            public void run() {
-                onNotifyDataSetChanged();
-            }
-        });
+        mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+        mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
     }
 
     void superNotifyDataSetChanged() {
@@ -1266,35 +1185,38 @@
 
     @Override
     public boolean handleMessage(Message msg) {
-        boolean result = false;
         switch (msg.what) {
-        case sUnbindServiceMessageType:
-            if (mServiceConnection.isConnected()) {
-                mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
+            case MSG_MAIN_HANDLER_COMMIT_METADATA: {
+                mCache.commitTemporaryMetaData();
+                return true;
             }
-            result = true;
-            break;
-        default:
-            break;
+            case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
+                superNotifyDataSetChanged();
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
+                if (mCallback != null) {
+                    mCallback.onRemoteAdapterConnected();
+                }
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
+                if (mCallback != null) {
+                    mCallback.onRemoteAdapterDisconnected();
+                }
+                return true;
+            }
+            case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
+                mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
+                return true;
+            }
         }
-        return result;
+        return false;
     }
 
-    private void enqueueDeferredUnbindServiceMessage() {
-        // Remove any existing deferred-unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-        mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
-    }
-
-    private boolean requestBindService() {
-        // Try binding the service (which will start it if it's not already running)
-        if (!mServiceConnection.isConnected()) {
-            mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
-        }
-
-        // Remove any existing deferred-unbind messages
-        mMainQueue.removeMessages(sUnbindServiceMessageType);
-        return mServiceConnection.isConnected();
+    private void requestBindService() {
+        mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+        Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
     }
 
     private static class HandlerThreadExecutor implements Executor {
@@ -1322,7 +1244,7 @@
             remoteViews = views;
 
             float density = context.getResources().getDisplayMetrics().density;
-            defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+            defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
         }
 
         public void loadFirstViewHeight(
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 36dc330..5e22650 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -43,9 +43,11 @@
 
 import java.text.BreakIterator;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.regex.Pattern;
 
@@ -58,11 +60,7 @@
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public final class SelectionActionModeHelper {
 
-    /**
-     * Maximum time (in milliseconds) to wait for a result before timing out.
-     */
-    // TODO: Consider making this a ViewConfiguration.
-    private static final int TIMEOUT_DURATION = 200;
+    private static final String LOG_TAG = "SelectActionModeHelper";
 
     private static final boolean SMART_SELECT_ANIMATION_ENABLED = true;
 
@@ -83,7 +81,8 @@
         mEditor = Preconditions.checkNotNull(editor);
         mTextView = mEditor.getTextView();
         mTextClassificationHelper = new TextClassificationHelper(
-                mTextView.getTextClassifier(), mTextView.getText(),
+                mTextView.getTextClassifier(),
+                getText(mTextView),
                 0, 1, mTextView.getTextLocales());
         mSelectionTracker = new SelectionTracker(mTextView);
 
@@ -96,11 +95,15 @@
     }
 
     public void startActionModeAsync(boolean adjustSelection) {
+        // Check if the smart selection should run for editable text.
+        adjustSelection &= !mTextView.isTextEditable()
+                || mTextView.getTextClassifier().getSettings()
+                        .isSuggestSelectionEnabledForEditableText();
+
         mSelectionTracker.onOriginalSelection(
-                mTextView.getText(),
+                getText(mTextView),
                 mTextView.getSelectionStart(),
-                mTextView.getSelectionEnd(),
-                mTextView.isTextEditable());
+                mTextView.getSelectionEnd());
         cancelAsyncTask();
         if (skipTextClassification()) {
             startActionMode(null);
@@ -108,7 +111,7 @@
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
                     mTextView,
-                    TIMEOUT_DURATION,
+                    mTextClassificationHelper.getTimeoutDuration(),
                     adjustSelection
                             ? mTextClassificationHelper::suggestSelection
                             : mTextClassificationHelper::classifyText,
@@ -127,7 +130,7 @@
             resetTextClassificationHelper();
             mTextClassificationAsyncTask = new TextClassificationAsyncTask(
                     mTextView,
-                    TIMEOUT_DURATION,
+                    mTextClassificationHelper.getTimeoutDuration(),
                     mTextClassificationHelper::classifyText,
                     this::invalidateActionMode)
                     .execute();
@@ -195,9 +198,12 @@
     }
 
     private void startActionMode(@Nullable SelectionResult result) {
-        final CharSequence text = mTextView.getText();
+        final CharSequence text = getText(mTextView);
         if (result != null && text instanceof Spannable) {
-            Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+            // Do not change the selection if TextClassifier should be dark launched.
+            if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
+                Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+            }
             mTextClassification = result.mClassification;
         } else {
             mTextClassification = null;
@@ -229,7 +235,7 @@
             return;
         }
 
-        final List<RectF> selectionRectangles =
+        final List<SmartSelectSprite.RectangleWithTextSelectionLayout> selectionRectangles =
                 convertSelectionToRectangles(layout, result.mStart, result.mEnd);
 
         final PointF touchPoint = new PointF(
@@ -237,7 +243,8 @@
                 mEditor.getLastUpPositionY());
 
         final PointF animationStartPoint =
-                movePointInsideNearestRectangle(touchPoint, selectionRectangles);
+                movePointInsideNearestRectangle(touchPoint, selectionRectangles,
+                        SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle);
 
         mSmartSelectSprite.startAnimation(
                 animationStartPoint,
@@ -245,38 +252,58 @@
                 onAnimationEndCallback);
     }
 
-    private List<RectF> convertSelectionToRectangles(final Layout layout, final int start,
-            final int end) {
-        final List<RectF> result = new ArrayList<>();
-        layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
-                mergeRectangleIntoList(result, new RectF(left, top, right, bottom)));
+    private List<SmartSelectSprite.RectangleWithTextSelectionLayout> convertSelectionToRectangles(
+            final Layout layout, final int start, final int end) {
+        final List<SmartSelectSprite.RectangleWithTextSelectionLayout> result = new ArrayList<>();
 
-        result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR);
+        final Layout.SelectionRectangleConsumer consumer =
+                (left, top, right, bottom, textSelectionLayout) -> mergeRectangleIntoList(
+                        result,
+                        new RectF(left, top, right, bottom),
+                        SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+                        r -> new SmartSelectSprite.RectangleWithTextSelectionLayout(r,
+                                textSelectionLayout)
+                );
+
+        layout.getSelection(start, end, consumer);
+
+        result.sort(Comparator.comparing(
+                SmartSelectSprite.RectangleWithTextSelectionLayout::getRectangle,
+                SmartSelectSprite.RECTANGLE_COMPARATOR));
+
         return result;
     }
 
+    // TODO: Move public pure functions out of this class and make it package-private.
     /**
-     * Merges a {@link RectF} into an existing list of rectangles. While merging, this method
-     * makes sure that:
+     * Merges a {@link RectF} into an existing list of any objects which contain a rectangle.
+     * While merging, this method makes sure that:
      *
      * <ol>
      * <li>No rectangle is redundant (contained within a bigger rectangle)</li>
      * <li>Rectangles of the same height and vertical position that intersect get merged</li>
      * </ol>
      *
-     * @param list      the list of rectangles to merge the new rectangle in
+     * @param list      the list of rectangles (or other rectangle containers) to merge the new
+     *                  rectangle into
      * @param candidate the {@link RectF} to merge into the list
+     * @param extractor a function that can extract a {@link RectF} from an element of the given
+     *                  list
+     * @param packer    a function that can wrap the resulting {@link RectF} into an element that
+     *                  the list contains
      * @hide
      */
     @VisibleForTesting
-    public static void mergeRectangleIntoList(List<RectF> list, RectF candidate) {
+    public static <T> void mergeRectangleIntoList(final List<T> list,
+            final RectF candidate, final Function<T, RectF> extractor,
+            final Function<RectF, T> packer) {
         if (candidate.isEmpty()) {
             return;
         }
 
         final int elementCount = list.size();
         for (int index = 0; index < elementCount; ++index) {
-            final RectF existingRectangle = list.get(index);
+            final RectF existingRectangle = extractor.apply(list.get(index));
             if (existingRectangle.contains(candidate)) {
                 return;
             }
@@ -299,26 +326,27 @@
         }
 
         for (int index = elementCount - 1; index >= 0; --index) {
-            if (list.get(index).isEmpty()) {
+            final RectF rectangle = extractor.apply(list.get(index));
+            if (rectangle.isEmpty()) {
                 list.remove(index);
             }
         }
 
-        list.add(candidate);
+        list.add(packer.apply(candidate));
     }
 
 
     /** @hide */
     @VisibleForTesting
-    public static PointF movePointInsideNearestRectangle(final PointF point,
-            final List<RectF> rectangles) {
+    public static <T> PointF movePointInsideNearestRectangle(final PointF point,
+            final List<T> list, final Function<T, RectF> extractor) {
         float bestX = -1;
         float bestY = -1;
         double bestDistance = Double.MAX_VALUE;
 
-        final int elementCount = rectangles.size();
+        final int elementCount = list.size();
         for (int index = 0; index < elementCount; ++index) {
-            final RectF rectangle = rectangles.get(index);
+            final RectF rectangle = extractor.apply(list.get(index));
             final float candidateY = rectangle.centerY();
             final float candidateX;
 
@@ -356,7 +384,9 @@
     }
 
     private void resetTextClassificationHelper() {
-        mTextClassificationHelper.reset(mTextView.getTextClassifier(), mTextView.getText(),
+        mTextClassificationHelper.init(
+                mTextView.getTextClassifier(),
+                getText(mTextView),
                 mTextView.getSelectionStart(), mTextView.getSelectionEnd(),
                 mTextView.getTextLocales());
     }
@@ -382,6 +412,7 @@
         private int mSelectionStart;
         private int mSelectionEnd;
         private boolean mAllowReset;
+        private final LogAbandonRunnable mDelayedLogAbandon = new LogAbandonRunnable();
 
         SelectionTracker(TextView textView) {
             mTextView = Preconditions.checkNotNull(textView);
@@ -391,8 +422,11 @@
         /**
          * Called when the original selection happens, before smart selection is triggered.
          */
-        public void onOriginalSelection(
-                CharSequence text, int selectionStart, int selectionEnd, boolean editableText) {
+        public void onOriginalSelection(CharSequence text, int selectionStart, int selectionEnd) {
+            // If we abandoned a selection and created a new one very shortly after, we may still
+            // have a pending request to log ABANDON, which we flush here.
+            mDelayedLogAbandon.flush();
+
             mOriginalStart = mSelectionStart = selectionStart;
             mOriginalEnd = mSelectionEnd = selectionEnd;
             mAllowReset = false;
@@ -433,12 +467,7 @@
         public void onSelectionDestroyed() {
             mAllowReset = false;
             // Wait a few ms to see if the selection was destroyed because of a text change event.
-            mTextView.postDelayed(() -> {
-                mLogger.logSelectionAction(
-                        mSelectionStart, mSelectionEnd,
-                        SelectionEvent.ActionType.ABANDON, null /* classification */);
-                mSelectionStart = mSelectionEnd = -1;
-            }, 100 /* ms */);
+            mDelayedLogAbandon.schedule(100 /* ms */);
         }
 
         /**
@@ -465,7 +494,7 @@
             if (isSelectionStarted()
                     && mAllowReset
                     && textIndex >= mSelectionStart && textIndex <= mSelectionEnd
-                    && textView.getText() instanceof Spannable) {
+                    && getText(textView) instanceof Spannable) {
                 mAllowReset = false;
                 boolean selected = editor.selectCurrentWord();
                 if (selected) {
@@ -495,6 +524,38 @@
         private boolean isSelectionStarted() {
             return mSelectionStart >= 0 && mSelectionEnd >= 0 && mSelectionStart != mSelectionEnd;
         }
+
+        /** A helper for keeping track of pending abandon logging requests. */
+        private final class LogAbandonRunnable implements Runnable {
+            private boolean mIsPending;
+
+            /** Schedules an abandon to be logged with the given delay. Flush if necessary. */
+            void schedule(int delayMillis) {
+                if (mIsPending) {
+                    Log.e(LOG_TAG, "Force flushing abandon due to new scheduling request");
+                    flush();
+                }
+                mIsPending = true;
+                mTextView.postDelayed(this, delayMillis);
+            }
+
+            /** If there is a pending log request, execute it now. */
+            void flush() {
+                mTextView.removeCallbacks(this);
+                run();
+            }
+
+            @Override
+            public void run() {
+                if (mIsPending) {
+                    mLogger.logSelectionAction(
+                            mSelectionStart, mSelectionEnd,
+                            SelectionEvent.ActionType.ABANDON, null /* classification */);
+                    mSelectionStart = mSelectionEnd = -1;
+                    mIsPending = false;
+                }
+            }
+        }
     }
 
     // TODO: Write tests
@@ -689,7 +750,7 @@
             mSelectionResultSupplier = Preconditions.checkNotNull(selectionResultSupplier);
             mSelectionResultCallback = Preconditions.checkNotNull(selectionResultCallback);
             // Make a copy of the original text.
-            mOriginalText = mTextView.getText().toString();
+            mOriginalText = getText(mTextView).toString();
         }
 
         @Override
@@ -705,7 +766,7 @@
         @Override
         @UiThread
         protected void onPostExecute(SelectionResult result) {
-            result = TextUtils.equals(mOriginalText, mTextView.getText()) ? result : null;
+            result = TextUtils.equals(mOriginalText, getText(mTextView)) ? result : null;
             mSelectionResultCallback.accept(result);
         }
 
@@ -752,13 +813,16 @@
         private LocaleList mLastClassificationLocales;
         private SelectionResult mLastClassificationResult;
 
+        /** Whether the TextClassifier has been initialized. */
+        private boolean mHot;
+
         TextClassificationHelper(TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
-            reset(textClassifier, text, selectionStart, selectionEnd, locales);
+            init(textClassifier, text, selectionStart, selectionEnd, locales);
         }
 
         @UiThread
-        public void reset(TextClassifier textClassifier,
+        public void init(TextClassifier textClassifier,
                 CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) {
             mTextClassifier = Preconditions.checkNotNull(textClassifier);
             mText = Preconditions.checkNotNull(text).toString();
@@ -771,19 +835,41 @@
 
         @WorkerThread
         public SelectionResult classifyText() {
+            mHot = true;
             return performClassification(null /* selection */);
         }
 
         @WorkerThread
         public SelectionResult suggestSelection() {
+            mHot = true;
             trimText();
             final TextSelection selection = mTextClassifier.suggestSelection(
                     mTrimmedText, mRelativeStart, mRelativeEnd, mLocales);
-            mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
-            mSelectionEnd = Math.min(mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+            // Do not classify new selection boundaries if TextClassifier should be dark launched.
+            if (!mTextClassifier.getSettings().isDarkLaunch()) {
+                mSelectionStart = Math.max(0, selection.getSelectionStartIndex() + mTrimStart);
+                mSelectionEnd = Math.min(
+                        mText.length(), selection.getSelectionEndIndex() + mTrimStart);
+            }
             return performClassification(selection);
         }
 
+        /**
+         * Maximum time (in milliseconds) to wait for a textclassifier result before timing out.
+         */
+        // TODO: Consider making this a ViewConfiguration.
+        public int getTimeoutDuration() {
+            if (mHot) {
+                return 200;
+            } else {
+                // Return a slightly larger number than usual when the TextClassifier is first
+                // initialized. Initialization would usually take longer than subsequent calls to
+                // the TextClassifier. The impact of this on the UI is that we do not show the
+                // selection handles or toolbar until after this timeout.
+                return 500;
+            }
+        }
+
         private SelectionResult performClassification(@Nullable TextSelection selection) {
             if (!Objects.equals(mText, mLastClassificationText)
                     || mSelectionStart != mLastClassificationSelectionStart
@@ -854,4 +940,14 @@
                 return SelectionEvent.ActionType.OTHER;
         }
     }
+
+    private static CharSequence getText(TextView textView) {
+        // Extracts the textView's text.
+        // TODO: Investigate why/when TextView.getText() is null.
+        final CharSequence text = textView.getText();
+        if (text != null) {
+            return text;
+        }
+        return "";
+    }
 }
diff --git a/core/java/android/widget/SmartSelectSprite.java b/core/java/android/widget/SmartSelectSprite.java
index 27b93bc..a391c6e 100644
--- a/core/java/android/widget/SmartSelectSprite.java
+++ b/core/java/android/widget/SmartSelectSprite.java
@@ -35,6 +35,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ShapeDrawable;
 import android.graphics.drawable.shapes.Shape;
+import android.text.Layout;
 import android.util.TypedValue;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -42,9 +43,9 @@
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.LinkedList;
 import java.util.List;
 
 /**
@@ -76,6 +77,26 @@
     private Drawable mExistingDrawable = null;
     private RectangleList mExistingRectangleList = null;
 
+    static final class RectangleWithTextSelectionLayout {
+        private final RectF mRectangle;
+        @Layout.TextSelectionLayout
+        private final int mTextSelectionLayout;
+
+        RectangleWithTextSelectionLayout(RectF rectangle, int textSelectionLayout) {
+            mRectangle = Preconditions.checkNotNull(rectangle);
+            mTextSelectionLayout = textSelectionLayout;
+        }
+
+        public RectF getRectangle() {
+            return mRectangle;
+        }
+
+        @Layout.TextSelectionLayout
+        public int getTextSelectionLayout() {
+            return mTextSelectionLayout;
+        }
+    }
+
     /**
      * A rounded rectangle with a configurable corner radius and the ability to expand outside of
      * its bounding rectangle and clip against it.
@@ -84,12 +105,23 @@
 
         private static final String PROPERTY_ROUND_RATIO = "roundRatio";
 
+        /**
+         * The direction in which the rectangle will perform its expansion. A rectangle can expand
+         * from its left edge, its right edge or from the center (or, more precisely, the user's
+         * touch point). For example, in left-to-right text, a selection spanning two lines with the
+         * user's action being on the first line will have the top rectangle and expansion direction
+         * of CENTER, while the bottom one will have an expansion direction of RIGHT.
+         */
         @Retention(SOURCE)
         @IntDef({ExpansionDirection.LEFT, ExpansionDirection.CENTER, ExpansionDirection.RIGHT})
         private @interface ExpansionDirection {
-        int LEFT = 0;
-        int CENTER = 1;
-        int RIGHT = 2;
+            int LEFT = -1;
+            int CENTER = 0;
+            int RIGHT = 1;
+        }
+
+        private static @ExpansionDirection int invert(@ExpansionDirection int expansionDirection) {
+            return expansionDirection * -1;
         }
 
         @Retention(SOURCE)
@@ -114,20 +146,33 @@
         private final RectF mClipRect = new RectF();
         private final Path mClipPath = new Path();
 
-        /** How far offset the left edge of the rectangle is from the bounding box. */
+        /** How offset the left edge of the rectangle is from the left side of the bounding box. */
         private float mLeftBoundary = 0;
-        /** How far offset the right edge of the rectangle is from the bounding box. */
+        /** How offset the right edge of the rectangle is from the left side of the bounding box. */
         private float mRightBoundary = 0;
 
+        /** Whether the horizontal bounds are inverted (for RTL scenarios). */
+        private final boolean mInverted;
+
+        private final float mBoundingWidth;
+
         private RoundedRectangleShape(
                 final RectF boundingRectangle,
                 final @ExpansionDirection int expansionDirection,
                 final @RectangleBorderType int rectangleBorderType,
+                final boolean inverted,
                 final float strokeWidth) {
             mBoundingRectangle = new RectF(boundingRectangle);
-            mExpansionDirection = expansionDirection;
+            mBoundingWidth = boundingRectangle.width();
             mRectangleBorderType = rectangleBorderType;
             mStrokeWidth = strokeWidth;
+            mInverted = inverted && expansionDirection != ExpansionDirection.CENTER;
+
+            if (inverted) {
+                mExpansionDirection = invert(expansionDirection);
+            } else {
+                mExpansionDirection = expansionDirection;
+            }
 
             if (boundingRectangle.height() > boundingRectangle.width()) {
                 setRoundRatio(0.0f);
@@ -148,6 +193,10 @@
          */
         @Override
         public void draw(Canvas canvas, Paint paint) {
+            if (mLeftBoundary == mRightBoundary) {
+                return;
+            }
+
             final float cornerRadius = getCornerRadius();
             final float adjustedCornerRadius = getAdjustedCornerRadius();
 
@@ -157,7 +206,7 @@
 
             if (mRectangleBorderType == RectangleBorderType.OVERSHOOT) {
                 mDrawRect.left -= cornerRadius / 2;
-                mDrawRect.right -= cornerRadius / 2;
+                mDrawRect.right += cornerRadius / 2;
             } else {
                 switch (mExpansionDirection) {
                     case ExpansionDirection.CENTER:
@@ -173,7 +222,7 @@
 
             canvas.save();
             mClipRect.set(mBoundingRectangle);
-            mClipRect.inset(-mStrokeWidth, -mStrokeWidth);
+            mClipRect.inset(-mStrokeWidth / 2, -mStrokeWidth / 2);
             canvas.clipRect(mClipRect);
             canvas.drawRoundRect(mDrawRect, adjustedCornerRadius, adjustedCornerRadius, paint);
             canvas.restore();
@@ -190,20 +239,28 @@
             canvas.restore();
         }
 
-        public void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
+        void setRoundRatio(@FloatRange(from = 0.0, to = 1.0) final float roundRatio) {
             mRoundRatio = roundRatio;
         }
 
-        public float getRoundRatio() {
+        float getRoundRatio() {
             return mRoundRatio;
         }
 
-        private void setLeftBoundary(final float leftBoundary) {
-            mLeftBoundary = leftBoundary;
+        private void setStartBoundary(final float startBoundary) {
+            if (mInverted) {
+                mRightBoundary = mBoundingWidth - startBoundary;
+            } else {
+                mLeftBoundary = startBoundary;
+            }
         }
 
-        private void setRightBoundary(final float rightBoundary) {
-            mRightBoundary = rightBoundary;
+        private void setEndBoundary(final float endBoundary) {
+            if (mInverted) {
+                mLeftBoundary = mBoundingWidth - endBoundary;
+            } else {
+                mRightBoundary = endBoundary;
+            }
         }
 
         private float getCornerRadius() {
@@ -247,8 +304,8 @@
         private @DisplayType int mDisplayType = DisplayType.RECTANGLES;
 
         private RectangleList(final List<RoundedRectangleShape> rectangles) {
-            mRectangles = new LinkedList<>(rectangles);
-            mReversedRectangles = new LinkedList<>(rectangles);
+            mRectangles = new ArrayList<>(rectangles);
+            mReversedRectangles = new ArrayList<>(rectangles);
             Collections.reverse(mReversedRectangles);
             mOutlinePolygonPath = generateOutlinePolygonPath(rectangles);
         }
@@ -258,11 +315,11 @@
             for (RoundedRectangleShape rectangle : mReversedRectangles) {
                 final float rectangleLeftBoundary = boundarySoFar - rectangle.getBoundingWidth();
                 if (leftBoundary < rectangleLeftBoundary) {
-                    rectangle.setLeftBoundary(0);
+                    rectangle.setStartBoundary(0);
                 } else if (leftBoundary > boundarySoFar) {
-                    rectangle.setLeftBoundary(rectangle.getBoundingWidth());
+                    rectangle.setStartBoundary(rectangle.getBoundingWidth());
                 } else {
-                    rectangle.setLeftBoundary(
+                    rectangle.setStartBoundary(
                             rectangle.getBoundingWidth() - boundarySoFar + leftBoundary);
                 }
 
@@ -275,11 +332,11 @@
             for (RoundedRectangleShape rectangle : mRectangles) {
                 final float rectangleRightBoundary = rectangle.getBoundingWidth() + boundarySoFar;
                 if (rectangleRightBoundary < rightBoundary) {
-                    rectangle.setRightBoundary(rectangle.getBoundingWidth());
+                    rectangle.setEndBoundary(rectangle.getBoundingWidth());
                 } else if (boundarySoFar > rightBoundary) {
-                    rectangle.setRightBoundary(0);
+                    rectangle.setEndBoundary(0);
                 } else {
-                    rectangle.setRightBoundary(rightBoundary - boundarySoFar);
+                    rectangle.setEndBoundary(rightBoundary - boundarySoFar);
                 }
 
                 boundarySoFar = rectangleRightBoundary;
@@ -331,8 +388,8 @@
     }
 
     /**
-     * @param context     The {@link Context} in which the animation will run
-     * @param invalidator A {@link Runnable} which will be called every time the animation updates,
+     * @param context     the {@link Context} in which the animation will run
+     * @param invalidator a {@link Runnable} which will be called every time the animation updates,
      *                    indicating that the view drawing the animation should invalidate itself
      */
     SmartSelectSprite(final Context context, final Runnable invalidator) {
@@ -356,67 +413,97 @@
      *                              "selection" and finally join them into a single polygon. In
      *                              order to get the correct visual behavior, these rectangles
      *                              should be sorted according to {@link #RECTANGLE_COMPARATOR}.
-     * @param onAnimationEnd        The callback which will be invoked once the whole animation
-     *                              completes.
+     * @param onAnimationEnd        the callback which will be invoked once the whole animation
+     *                              completes
      * @throws IllegalArgumentException if the given start point is not in any of the
-     *                                  destinationRectangles.
+     *                                  destinationRectangles
      * @see #cancelAnimation()
      */
+    // TODO nullability checks on parameters
     public void startAnimation(
             final PointF start,
-            final List<RectF> destinationRectangles,
-            final Runnable onAnimationEnd) throws IllegalArgumentException {
+            final List<RectangleWithTextSelectionLayout> destinationRectangles,
+            final Runnable onAnimationEnd) {
         cancelAnimation();
 
         final ValueAnimator.AnimatorUpdateListener updateListener =
                 valueAnimator -> mInvalidator.run();
 
-        final List<RoundedRectangleShape> shapes = new LinkedList<>();
-        final List<Animator> cornerAnimators = new LinkedList<>();
+        final int rectangleCount = destinationRectangles.size();
 
-        final RectF centerRectangle = destinationRectangles
-                .stream()
-                .filter((r) -> contains(r, start))
-                .findFirst()
-                .orElseThrow(() -> new IllegalArgumentException(
-                        "Center point is not inside any of the rectangles!"));
+        final List<RoundedRectangleShape> shapes = new ArrayList<>(rectangleCount);
+        final List<Animator> cornerAnimators = new ArrayList<>(rectangleCount);
+
+        RectangleWithTextSelectionLayout centerRectangle = null;
 
         int startingOffset = 0;
-        for (RectF rectangle : destinationRectangles) {
-            if (rectangle.equals(centerRectangle)) {
+        int startingRectangleIndex = 0;
+        for (int index = 0; index < rectangleCount; ++index) {
+            final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+                    destinationRectangles.get(index);
+            final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
+            if (contains(rectangle, start)) {
+                centerRectangle = rectangleWithTextSelectionLayout;
                 break;
             }
             startingOffset += rectangle.width();
+            ++startingRectangleIndex;
         }
 
-        startingOffset += start.x - centerRectangle.left;
+        if (centerRectangle == null) {
+            throw new IllegalArgumentException("Center point is not inside any of the rectangles!");
+        }
 
-        final float centerRectangleHalfHeight = centerRectangle.height() / 2;
-        final float startingOffsetLeft = startingOffset - centerRectangleHalfHeight;
-        final float startingOffsetRight = startingOffset + centerRectangleHalfHeight;
+        startingOffset += start.x - centerRectangle.getRectangle().left;
 
         final @RoundedRectangleShape.ExpansionDirection int[] expansionDirections =
                 generateDirections(centerRectangle, destinationRectangles);
 
         final @RoundedRectangleShape.RectangleBorderType int[] rectangleBorderTypes =
-                generateBorderTypes(destinationRectangles);
+                generateBorderTypes(rectangleCount);
 
-        int index = 0;
-
-        for (RectF rectangle : destinationRectangles) {
+        for (int index = 0; index < rectangleCount; ++index) {
+            final RectangleWithTextSelectionLayout rectangleWithTextSelectionLayout =
+                    destinationRectangles.get(index);
+            final RectF rectangle = rectangleWithTextSelectionLayout.getRectangle();
             final RoundedRectangleShape shape = new RoundedRectangleShape(
                     rectangle,
                     expansionDirections[index],
                     rectangleBorderTypes[index],
+                    rectangleWithTextSelectionLayout.getTextSelectionLayout()
+                            == Layout.TEXT_SELECTION_LAYOUT_RIGHT_TO_LEFT,
                     mStrokeWidth);
             cornerAnimators.add(createCornerAnimator(shape, updateListener));
             shapes.add(shape);
-            index++;
         }
 
         final RectangleList rectangleList = new RectangleList(shapes);
         final ShapeDrawable shapeDrawable = new ShapeDrawable(rectangleList);
 
+        final float startingOffsetLeft;
+        final float startingOffsetRight;
+
+        final RoundedRectangleShape startingRectangleShape = shapes.get(startingRectangleIndex);
+        final float cornerRadius = startingRectangleShape.getCornerRadius();
+        if (startingRectangleShape.mRectangleBorderType
+                == RoundedRectangleShape.RectangleBorderType.FIT) {
+            switch (startingRectangleShape.mExpansionDirection) {
+                case RoundedRectangleShape.ExpansionDirection.LEFT:
+                    startingOffsetLeft = startingOffsetRight = startingOffset - cornerRadius / 2;
+                    break;
+                case RoundedRectangleShape.ExpansionDirection.RIGHT:
+                    startingOffsetLeft = startingOffsetRight = startingOffset + cornerRadius / 2;
+                    break;
+                case RoundedRectangleShape.ExpansionDirection.CENTER:  // fall through
+                default:
+                    startingOffsetLeft = startingOffset - cornerRadius / 2;
+                    startingOffsetRight = startingOffset + cornerRadius / 2;
+                    break;
+            }
+        } else {
+            startingOffsetLeft = startingOffsetRight = startingOffset;
+        }
+
         final Paint paint = shapeDrawable.getPaint();
         paint.setColor(mStrokeColor);
         paint.setStyle(Paint.Style.STROKE);
@@ -511,7 +598,8 @@
     }
 
     private static @RoundedRectangleShape.ExpansionDirection int[] generateDirections(
-            final RectF centerRectangle, final List<RectF> rectangles) {
+            final RectangleWithTextSelectionLayout centerRectangle,
+            final List<RectangleWithTextSelectionLayout> rectangles) {
         final @RoundedRectangleShape.ExpansionDirection int[] result = new int[rectangles.size()];
 
         final int centerRectangleIndex = rectangles.indexOf(centerRectangle);
@@ -538,8 +626,8 @@
     }
 
     private static @RoundedRectangleShape.RectangleBorderType int[] generateBorderTypes(
-            final List<RectF> rectangles) {
-        final @RoundedRectangleShape.RectangleBorderType int[] result = new int[rectangles.size()];
+            final int numberOfRectangles) {
+        final @RoundedRectangleShape.RectangleBorderType int[] result = new int[numberOfRectangles];
 
         for (int i = 1; i < result.length - 1; ++i) {
             result[i] = RoundedRectangleShape.RectangleBorderType.OVERSHOOT;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 791a8fa..d9bc51f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6281,7 +6281,7 @@
             final int horizontalPadding = getCompoundPaddingLeft();
             final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
 
-            if (mEditor.mCursorDrawable == null) {
+            if (mEditor.mDrawableForCursor == null) {
                 synchronized (TEMP_RECTF) {
                     /*
                      * The reason for this concern about the thickness of the
@@ -6308,7 +6308,7 @@
                             (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
                 }
             } else {
-                final Rect bounds = mEditor.mCursorDrawable.getBounds();
+                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
                 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
                         bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
             }
@@ -6360,8 +6360,8 @@
             int bottom = mLayout.getLineBottom(lineEnd);
 
             // mEditor can be null in case selection is set programmatically.
-            if (invalidateCursor && mEditor != null && mEditor.mCursorDrawable != null) {
-                final Rect bounds = mEditor.mCursorDrawable.getBounds();
+            if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) {
+                final Rect bounds = mEditor.mDrawableForCursor.getBounds();
                 top = Math.min(top, bounds.top);
                 bottom = Math.max(bottom, bounds.bottom);
             }
@@ -10338,6 +10338,17 @@
                 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
                 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
                         AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
+            } else {
+                structure.setMinTextEms(getMinEms());
+                structure.setMaxTextEms(getMaxEms());
+                int maxLength = -1;
+                for (InputFilter filter: getFilters()) {
+                    if (filter instanceof InputFilter.LengthFilter) {
+                        maxLength = ((InputFilter.LengthFilter) filter).getMax();
+                        break;
+                    }
+                }
+                structure.setMaxTextLength(maxLength);
             }
         }
         structure.setHint(getHint());
diff --git a/core/java/com/android/internal/alsa/AlsaCardsParser.java b/core/java/com/android/internal/alsa/AlsaCardsParser.java
index 5b92a17..bb75bf6 100644
--- a/core/java/com/android/internal/alsa/AlsaCardsParser.java
+++ b/core/java/com/android/internal/alsa/AlsaCardsParser.java
@@ -37,6 +37,12 @@
 
     private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
 
+    public static final int SCANSTATUS_NOTSCANNED = -1;
+    public static final int SCANSTATUS_SUCCESS = 0;
+    public static final int SCANSTATUS_FAIL = 1;
+    public static final int SCANSTATUS_EMPTY = 2;
+    private int mScanStatus = SCANSTATUS_NOTSCANNED;
+
     public class AlsaCardRecord {
         private static final String TAG = "AlsaCardRecord";
         private static final String kUsbCardKeyStr = "at usb-";
@@ -104,10 +110,11 @@
 
     public AlsaCardsParser() {}
 
-    public void scan() {
+    public int scan() {
         if (DEBUG) {
-            Slog.i(TAG, "AlsaCardsParser.scan()");
+            Slog.i(TAG, "AlsaCardsParser.scan()....");
         }
+
         mCardRecords = new ArrayList<AlsaCardRecord>();
 
         File cardsFile = new File(kCardsFilePath);
@@ -134,11 +141,26 @@
                 mCardRecords.add(cardRecord);
             }
             reader.close();
+            if (mCardRecords.size() > 0) {
+                mScanStatus = SCANSTATUS_SUCCESS;
+            } else {
+                mScanStatus = SCANSTATUS_EMPTY;
+            }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         } catch (IOException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         }
+        if (DEBUG) {
+            Slog.i(TAG, "  status:" + mScanStatus);
+        }
+        return mScanStatus;
+    }
+
+    public int getScanStatus() {
+        return mScanStatus;
     }
 
     public ArrayList<AlsaCardRecord> getScanRecords() {
@@ -182,7 +204,11 @@
         }
 
         // get the new list of devices
-        scan();
+        if (scan() != SCANSTATUS_SUCCESS) {
+            Slog.e(TAG, "Error scanning Alsa cards file.");
+            return -1;
+        }
+
         if (DEBUG) {
             LogDevices("Current Devices:", mCardRecords);
         }
diff --git a/core/java/com/android/internal/alsa/AlsaDevicesParser.java b/core/java/com/android/internal/alsa/AlsaDevicesParser.java
index 7cdd897..15261baf 100644
--- a/core/java/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/core/java/com/android/internal/alsa/AlsaDevicesParser.java
@@ -46,6 +46,12 @@
     private boolean mHasPlaybackDevices = false;
     private boolean mHasMIDIDevices = false;
 
+    public static final int SCANSTATUS_NOTSCANNED = -1;
+    public static final int SCANSTATUS_SUCCESS = 0;
+    public static final int SCANSTATUS_FAIL = 1;
+    public static final int SCANSTATUS_EMPTY = 2;
+    private int mScanStatus = SCANSTATUS_NOTSCANNED;
+
     public class AlsaDeviceRecord {
         public static final int kDeviceType_Unknown = -1;
         public static final int kDeviceType_Audio = 0;
@@ -258,7 +264,11 @@
         return line.charAt(kIndex_CardDeviceField) == '[';
     }
 
-    public void scan() {
+    public int scan() {
+        if (DEBUG) {
+            Slog.i(TAG, "AlsaDevicesParser.scan()....");
+        }
+
         mDeviceRecords.clear();
 
         File devicesFile = new File(kDevicesFilePath);
@@ -274,11 +284,27 @@
                 }
             }
             reader.close();
+            // success if we add at least 1 record
+            if (mDeviceRecords.size() > 0) {
+                mScanStatus = SCANSTATUS_SUCCESS;
+            } else {
+                mScanStatus = SCANSTATUS_EMPTY;
+            }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         } catch (IOException e) {
             e.printStackTrace();
+            mScanStatus = SCANSTATUS_FAIL;
         }
+        if (DEBUG) {
+            Slog.i(TAG, "  status:" + mScanStatus);
+        }
+        return mScanStatus;
+    }
+
+    public int getScanStatus() {
+        return mScanStatus;
     }
 
     //
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2cab009..6e0ba341 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -100,7 +100,7 @@
     private static final boolean DEBUG = false;
 
     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
-    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
 
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
@@ -1450,11 +1450,16 @@
                         getFirstRowPosition(rowPosition + 1));
                 int serviceSpacing = holder.row.getContext().getResources()
                         .getDimensionPixelSize(R.dimen.chooser_service_spacing);
-                int top = rowPosition == 0 ? serviceSpacing : 0;
-                if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
-                    setVertPadding(holder, top, serviceSpacing);
+                if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                    // if the row is the only row for target service
+                    setVertPadding(holder, 0, 0);
                 } else {
-                    setVertPadding(holder, top, 0);
+                    int top = rowPosition == 0 ? serviceSpacing : 0;
+                    if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                        setVertPadding(holder, top, serviceSpacing);
+                    } else {
+                        setVertPadding(holder, top, 0);
+                    }
                 }
             } else {
                 holder.row.setBackgroundColor(Color.TRANSPARENT);
@@ -1580,8 +1585,8 @@
                 } catch (RemoteException e) {
                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
                     mChooserActivity.unbindService(this);
-                    destroy();
                     mChooserActivity.mServiceConnections.remove(this);
+                    destroy();
                 }
             }
         }
@@ -1597,7 +1602,6 @@
                 }
 
                 mChooserActivity.unbindService(this);
-                destroy();
                 mChooserActivity.mServiceConnections.remove(this);
                 if (mChooserActivity.mServiceConnections.isEmpty()) {
                     mChooserActivity.mChooserHandler.removeMessages(
@@ -1605,6 +1609,7 @@
                     mChooserActivity.sendVoiceChoicesIfNeeded();
                 }
                 mConnectedComponent = null;
+                destroy();
             }
         }
 
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index 5fdc920..15221b1 100644
--- a/core/java/com/android/internal/app/IAppOpsCallback.aidl
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -17,7 +17,7 @@
 package com.android.internal.app;
 
 // This interface is also used by native code, so must
-// be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
+// be kept in sync with frameworks/native/libs/binder/include/binder/IAppOpsCallback.h
 oneway interface IAppOpsCallback {
     void opChanged(int op, int uid, String packageName);
 }
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 7a119b4..2b975fe 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -22,7 +22,7 @@
 
 interface IAppOpsService {
     // These first methods are also called by native code, so must
-    // be kept in sync with frameworks/native/include/binder/IAppOpsService.h
+    // be kept in sync with frameworks/native/libs/binder/include/binder/IAppOpsService.h
     int checkOperation(int code, int uid, String packageName);
     int noteOperation(int code, int uid, String packageName);
     int startOperation(IBinder token, int code, int uid, String packageName);
diff --git a/core/java/com/android/internal/app/IAssistDataReceiver.aidl b/core/java/com/android/internal/app/IAssistDataReceiver.aidl
new file mode 100644
index 0000000..9c9ffef
--- /dev/null
+++ b/core/java/com/android/internal/app/IAssistDataReceiver.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.internal.app;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+
+/** @hide */
+oneway interface IAssistDataReceiver {
+    void onHandleAssistData(in Bundle resultData);
+    void onHandleAssistScreenshot(in Bitmap screenshot);
+}
diff --git a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl b/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
deleted file mode 100644
index a987a16..0000000
--- a/core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2015 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.internal.app;
-
-import android.graphics.Bitmap;
-
-/** @hide */
-oneway interface IAssistScreenshotReceiver {
-    void send(in Bitmap screenshot);
-}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 4275e0b..f405231 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -29,7 +29,7 @@
 
 interface IBatteryStats {
     // These first methods are also called by native code, so must
-    // be kept in sync with frameworks/native/include/binder/IBatteryStats.h
+    // be kept in sync with frameworks/native/libs/binder/include/binder/IBatteryStats.h
     void noteStartSensor(int uid, int sensor);
     void noteStopSensor(int uid, int sensor);
     void noteStartVideo(int uid);
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 36e4c1c6..ef30cd1 100644
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -21,12 +21,10 @@
 import android.content.res.ObbInfo;
 
 interface IMediaContainerService {
-    String copyPackageToContainer(String packagePath, String containerId, String key,
-            boolean isExternal, boolean isForwardLocked, String abiOverride);
     int copyPackage(String packagePath, in IParcelFileDescriptorFactory target);
 
     PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride);
     ObbInfo getObbInfo(String filename);
     void clearDirectory(String directory);
-    long calculateInstalledSize(String packagePath, boolean isForwardLocked, String abiOverride);
+    long calculateInstalledSize(String packagePath, String abiOverride);
 }
diff --git a/core/java/com/android/internal/app/LocaleHelper.java b/core/java/com/android/internal/app/LocaleHelper.java
index 386aa84..0a230a9 100644
--- a/core/java/com/android/internal/app/LocaleHelper.java
+++ b/core/java/com/android/internal/app/LocaleHelper.java
@@ -136,7 +136,16 @@
      * @return the localized country name.
      */
     public static String getDisplayCountry(Locale locale, Locale displayLocale) {
-        return ULocale.getDisplayCountry(locale.toLanguageTag(), ULocale.forLocale(displayLocale));
+        final String languageTag = locale.toLanguageTag();
+        final ULocale uDisplayLocale = ULocale.forLocale(displayLocale);
+        final String country = ULocale.getDisplayCountry(languageTag, uDisplayLocale);
+        final String numberingSystem = locale.getUnicodeLocaleType("nu");
+        if (numberingSystem != null) {
+            return String.format("%s (%s)", country,
+                    ULocale.getDisplayKeywordValue(languageTag, "numbers", uDisplayLocale));
+        } else {
+            return country;
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 9936ed5..c8c2fcf 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -93,10 +93,6 @@
         return context.getResources().getStringArray(R.array.supported_locales);
     }
 
-    public static String[] getPseudoLocales() {
-        return pseudoLocales;
-    }
-
     public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) {
         final Resources resources = context.getResources();
 
@@ -104,13 +100,6 @@
         List<String> localeList = new ArrayList<String>(locales.length);
         Collections.addAll(localeList, locales);
 
-        // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
-        if (!isInDeveloperMode) {
-            for (String locale : pseudoLocales) {
-                localeList.remove(locale);
-            }
-        }
-
         Collections.sort(localeList);
         final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
         final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
@@ -122,6 +111,10 @@
                     || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
                 continue;
             }
+            // Don't show the pseudolocales unless we're in developer mode. http://b/17190407.
+            if (!isInDeveloperMode && LocaleList.isPseudoLocale(l)) {
+                continue;
+            }
 
             if (localeInfos.isEmpty()) {
                 if (DEBUG) {
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3d5cd0f..d0719ee 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -238,7 +238,7 @@
             mSearchView.setOnQueryTextListener(this);
 
             // Restore previous search status
-            if (mPreviousSearch != null) {
+            if (!TextUtils.isEmpty(mPreviousSearch)) {
                 searchMenuItem.expandActionView();
                 mSearchView.setIconified(false);
                 mSearchView.setActivated(true);
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index e3fce51..2b0b5ee 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -17,6 +17,7 @@
 package com.android.internal.app;
 
 import android.content.Context;
+import android.os.LocaleList;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 
@@ -68,7 +69,9 @@
                 return null;
             }
             return new Locale.Builder()
-                    .setLocale(locale).setRegion("")
+                    .setLocale(locale)
+                    .setRegion("")
+                    .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
                     .build();
         }
 
@@ -253,11 +256,25 @@
 
         Set<String> simCountries = getSimCountries(context);
 
+        final boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
         for (String localeId : LocalePicker.getSupportedLocales(context)) {
             if (localeId.isEmpty()) {
                 throw new IllformedLocaleException("Bad locale entry in locale_config.xml");
             }
             LocaleInfo li = new LocaleInfo(localeId);
+
+            if (LocaleList.isPseudoLocale(li.getLocale())) {
+                if (isInDeveloperMode) {
+                    li.setTranslated(true);
+                    li.mIsPseudo = true;
+                    li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
+                } else {
+                    // Do not display pseudolocales unless in development mode.
+                    continue;
+                }
+            }
+
             if (simCountries.contains(li.getLocale().getCountry())) {
                 li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
             }
@@ -271,19 +288,6 @@
             }
         }
 
-        boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
-        for (String localeId : LocalePicker.getPseudoLocales()) {
-            LocaleInfo li = getLocaleInfo(Locale.forLanguageTag(localeId));
-            if (isInDeveloperMode) {
-                li.setTranslated(true);
-                li.mIsPseudo = true;
-                li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM;
-            } else {
-                sLocaleCache.remove(li.getId());
-            }
-        }
-
         // TODO: See if we can reuse what LocaleList.matchScore does
         final HashSet<String> localizedLocales = new HashSet<>();
         for (String localeId : LocalePicker.getSystemAssetLocales()) {
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java
index 7a1383c..b8bfc64 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/NightDisplayController.java
@@ -25,7 +25,9 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.provider.Settings.Secure;
+import android.provider.Settings.System;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -76,6 +78,35 @@
      */
     public static final int AUTO_MODE_TWILIGHT = 2;
 
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED })
+    public @interface ColorMode {}
+
+    /**
+     * Color mode with natural colors.
+     *
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_NATURAL = 0;
+    /**
+     * Color mode with boosted colors.
+     *
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_BOOSTED = 1;
+    /**
+     * Color mode with saturated colors.
+     *
+     * @see #setColorMode(int)
+     */
+    public static final int COLOR_MODE_SATURATED = 2;
+
+    /**
+     * See com.android.server.display.DisplayTransformManager.
+     */
+    private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
+    private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+
     private final Context mContext;
     private final int mUserId;
 
@@ -306,6 +337,37 @@
     }
 
     /**
+     * Get the current color mode.
+     */
+    public int getColorMode() {
+        final int colorMode = System.getIntForUser(mContext.getContentResolver(),
+            System.DISPLAY_COLOR_MODE, -1, mUserId);
+        if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+            // There still might be a legacy system property controlling color mode that we need to
+            // respect.
+            if ("1".equals(SystemProperties.get(PERSISTENT_PROPERTY_NATIVE_MODE))) {
+                return COLOR_MODE_SATURATED;
+            }
+            return "1.0".equals(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
+                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+        }
+        return colorMode;
+    }
+
+    /**
+     * Set the current color mode.
+     *
+     * @param colorMode the color mode
+     */
+    public void setColorMode(@ColorMode int colorMode) {
+        if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+            throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
+        }
+        System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
+                mUserId);
+    }
+
+    /**
      * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated.
      */
     public int getMinimumColorTemperature() {
@@ -351,6 +413,9 @@
                 case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE:
                     mCallback.onColorTemperatureChanged(getColorTemperature());
                     break;
+                case System.DISPLAY_COLOR_MODE:
+                    mCallback.onDisplayColorModeChanged(getColorMode());
+                    break;
             }
         }
     }
@@ -379,6 +444,8 @@
                         false /* notifyForDescendants */, mContentObserver, mUserId);
                 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE),
                         false /* notifyForDescendants */, mContentObserver, mUserId);
+                cr.registerContentObserver(System.getUriFor(System.DISPLAY_COLOR_MODE),
+                        false /* notifyForDecendants */, mContentObserver, mUserId);
             }
         }
     }
@@ -425,5 +492,12 @@
          * @param colorTemperature the color temperature to tint the screen
          */
         default void onColorTemperatureChanged(int colorTemperature) {}
+
+        /**
+         * Callback invoked when the color mode changes.
+         *
+         * @param displayColorMode the color mode
+         */
+        default void onDisplayColorModeChanged(int displayColorMode) {}
     }
 }
diff --git a/core/java/com/android/internal/app/ShutdownActivity.java b/core/java/com/android/internal/app/ShutdownActivity.java
index 745d28f..f81e838 100644
--- a/core/java/com/android/internal/app/ShutdownActivity.java
+++ b/core/java/com/android/internal/app/ShutdownActivity.java
@@ -41,6 +41,9 @@
         mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
         mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
         mUserRequested = intent.getBooleanExtra(Intent.EXTRA_USER_REQUESTED_SHUTDOWN, false);
+        final String reason = mUserRequested
+                ? PowerManager.SHUTDOWN_USER_REQUESTED
+                : intent.getStringExtra(Intent.EXTRA_REASON);
         Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
 
         Thread thr = new Thread("ShutdownActivity") {
@@ -52,9 +55,7 @@
                     if (mReboot) {
                         pm.reboot(mConfirm, null, false);
                     } else {
-                        pm.shutdown(mConfirm,
-                                    mUserRequested ? PowerManager.SHUTDOWN_USER_REQUESTED : null,
-                                    false);
+                        pm.shutdown(mConfirm, reason, false);
                     }
                 } catch (RemoteException e) {
                 }
diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java
index ebedc89..0bc8c483 100644
--- a/core/java/com/android/internal/app/procstats/DumpUtils.java
+++ b/core/java/com/android/internal/app/procstats/DumpUtils.java
@@ -29,6 +29,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import static com.android.internal.app.procstats.ProcessStats.*;
 
@@ -66,6 +67,8 @@
             "cch-activity", "cch-aclient", "cch-empty"
     };
 
+    // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto
+    // Update states must sync enum definition as well, the ordering must not be changed.
     static final String[] ADJ_SCREEN_TAGS = new String[] {
             "0", "1"
     };
@@ -177,6 +180,13 @@
         printArrayEntry(pw, STATE_TAGS,  state, 1);
     }
 
+    public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId,
+            long stateId, int state) {
+        state = printProto(proto, screenId, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD * STATE_COUNT);
+        state = printProto(proto, memId, ADJ_MEM_TAGS, state, STATE_COUNT);
+        printProto(proto, stateId, STATE_TAGS, state, 1);
+    }
+
     public static void printAdjTag(PrintWriter pw, int state) {
         state = printArrayEntry(pw, ADJ_SCREEN_TAGS,  state, ADJ_SCREEN_MOD);
         printArrayEntry(pw, ADJ_MEM_TAGS, state, 1);
@@ -352,6 +362,15 @@
         return value - index*mod;
     }
 
+    public static int printProto(ProtoOutputStream proto, long fieldId, String[] array, int value, int mod) {
+        int index = value/mod;
+        if (index >= 0 && index < array.length) {
+            // Valid state enum number starts at 1, 0 stands for unknown.
+            proto.write(fieldId, index + 1);
+        } // else enum default is always zero in proto3
+        return value - index*mod;
+    }
+
     public static String collapseString(String pkgName, String itemName) {
         if (itemName.startsWith(pkgName)) {
             final int ITEMLEN = itemName.length();
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index e0a4053..7519fce 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -21,6 +21,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.pm.PackageProto;
+import android.service.procstats.ProcessStatsProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -29,6 +31,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ProcessStats.PackageState;
@@ -69,6 +73,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 public final class ProcessState {
@@ -1157,6 +1164,7 @@
         }
     }
 
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("ProcessState{").append(Integer.toHexString(System.identityHashCode(this)))
@@ -1167,4 +1175,76 @@
         sb.append("}");
         return sb.toString();
     }
+
+    public void toProto(ProtoOutputStream proto, String procName, int uid, long now) {
+        proto.write(ProcessStatsProto.PROCESS, procName);
+        proto.write(ProcessStatsProto.UID, uid);
+        if (mNumExcessiveCpu > 0 || mNumCachedKill > 0 ) {
+            final long killToken = proto.start(ProcessStatsProto.KILL);
+            proto.write(ProcessStatsProto.Kill.CPU, mNumExcessiveCpu);
+            proto.write(ProcessStatsProto.Kill.CACHED, mNumCachedKill);
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.Kill.CACHED_PSS,
+                    mMinCachedKillPss, mAvgCachedKillPss, mMaxCachedKillPss);
+            proto.end(killToken);
+        }
+
+        // Group proc stats by type (screen state + mem state + process state)
+        Map<Integer, Long> durationByState = new HashMap<>();
+        boolean didCurState = false;
+        for (int i=0; i<mDurations.getKeyCount(); i++) {
+            final int key = mDurations.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            long time = mDurations.getValue(key);
+            if (mCurState == type) {
+                didCurState = true;
+                time += now - mStartTime;
+            }
+            durationByState.put(type, time);
+        }
+        if (!didCurState && mCurState != STATE_NOTHING) {
+            durationByState.put(mCurState, now - mStartTime);
+        }
+
+        for (int i=0; i<mPssTable.getKeyCount(); i++) {
+            final int key = mPssTable.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            if (!durationByState.containsKey(type)) {
+                // state without duration should not have stats!
+                continue;
+            }
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    type);
+
+            long duration = durationByState.get(type);
+            durationByState.remove(type); // remove the key since it is already being dumped.
+            proto.write(ProcessStatsProto.State.DURATION_MS, duration);
+
+            proto.write(ProcessStatsProto.State.SAMPLE_SIZE, mPssTable.getValue(key, PSS_SAMPLE_COUNT));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.PSS,
+                    mPssTable.getValue(key, PSS_MINIMUM),
+                    mPssTable.getValue(key, PSS_AVERAGE),
+                    mPssTable.getValue(key, PSS_MAXIMUM));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.USS,
+                    mPssTable.getValue(key, PSS_USS_MINIMUM),
+                    mPssTable.getValue(key, PSS_USS_AVERAGE),
+                    mPssTable.getValue(key, PSS_USS_MAXIMUM));
+
+            proto.end(stateToken);
+        }
+
+        for (Map.Entry<Integer, Long> entry : durationByState.entrySet()) {
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    entry.getKey());
+            proto.write(ProcessStatsProto.State.DURATION_MS, entry.getValue());
+            proto.end(stateToken);
+        }
+    }
 }
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 35b53c2..14f5e5b 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -22,6 +22,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.procstats.ProcessStatsSectionProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -30,6 +31,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.procstats.DurationsTable;
@@ -1706,6 +1708,46 @@
         }
     }
 
+    public void toProto(ProtoOutputStream proto, long now) {
+        final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+
+        proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
+        proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
+                mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
+        proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime);
+        proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime);
+        proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime);
+        proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss);
+        boolean partial = true;
+        if ((mFlags&FLAG_SHUTDOWN) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN);
+            partial = false;
+        }
+        if ((mFlags&FLAG_SYSPROPS) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS);
+            partial = false;
+        }
+        if ((mFlags&FLAG_COMPLETE) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE);
+            partial = false;
+        }
+        if (partial) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL);
+        }
+
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int ip=0; ip<procMap.size(); ip++) {
+            String procName = procMap.keyAt(ip);
+            SparseArray<ProcessState> uids = procMap.valueAt(ip);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final int uid = uids.keyAt(iu);
+                final ProcessState procState = uids.valueAt(iu);
+                final long processStateToken = proto.start(ProcessStatsSectionProto.PROCESS_STATS);
+                procState.toProto(proto, procName, uid, now);
+                proto.end(processStateToken);
+            }
+        }
+    }
 
     final public static class ProcessStateHolder {
         public final int appVersion;
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index caf35b3..a4da6b9c 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -26,6 +26,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.widget.RemoteViews;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
 
 /** {@hide} */
 interface IAppWidgetService {
@@ -62,9 +64,9 @@
     void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission);
     boolean bindAppWidgetId(in String callingPackage, int appWidgetId,
             int providerProfileId, in ComponentName providerComponent, in Bundle options);
-    void bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
-            in IBinder connection);
-    void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent);
+    boolean bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
+            IApplicationThread caller, IBinder token, IServiceConnection connection, int flags);
+
     int[] getAppWidgetIds(in ComponentName providerComponent);
     boolean isBoundWidgetPackage(String packageName, int userId);
     boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index e923223..59a7995 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.content;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
 
 import android.content.Context;
@@ -27,13 +26,11 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageParser.PackageLite;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
-import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.provider.Settings;
@@ -45,15 +42,9 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.UUID;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
 
 /**
  * Constants used internally between the PackageManager
@@ -72,7 +63,6 @@
     public static final int RECOMMEND_FAILED_INVALID_URI = -6;
     public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
 
-    private static final boolean localLOGV = false;
     private static final String TAG = "PackageHelper";
     // App installation location settings values
     public static final int APP_INSTALL_AUTO = 0;
@@ -91,259 +81,6 @@
         }
     }
 
-    public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
-            boolean isExternal) {
-        // Round up to nearest MB, plus another MB for filesystem overhead
-        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
-        try {
-            IStorageManager storageManager = getStorageManager();
-
-            if (localLOGV)
-                Log.i(TAG, "Size of container " + sizeMb + " MB");
-
-            int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
-                    isExternal);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, "Failed to create secure container " + cid);
-                return null;
-            }
-            String cachePath = storageManager.getSecureContainerPath(cid);
-            if (localLOGV) Log.i(TAG, "Created secure container " + cid +
-                    " at " + cachePath);
-                return cachePath;
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        return null;
-    }
-
-    public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
-        // Round up to nearest MB, plus another MB for filesystem overhead
-        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
-        try {
-            IStorageManager storageManager = getStorageManager();
-            int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey);
-            if (rc == StorageResultCode.OperationSucceeded) {
-                return true;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        Log.e(TAG, "Failed to create secure container " + cid);
-        return false;
-    }
-
-    public static String mountSdDir(String cid, String key, int ownerUid) {
-        return mountSdDir(cid, key, ownerUid, true);
-    }
-
-    public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
-        try {
-            int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
-                return null;
-            }
-            return getStorageManager().getSecureContainerPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        return null;
-    }
-
-   public static boolean unMountSdDir(String cid) {
-    try {
-        int rc = getStorageManager().unmountSecureContainer(cid, true);
-        if (rc != StorageResultCode.OperationSucceeded) {
-            Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
-            return false;
-        }
-        return true;
-    } catch (RemoteException e) {
-        Log.e(TAG, "StorageManagerService running?");
-    }
-        return false;
-   }
-
-   public static boolean renameSdDir(String oldId, String newId) {
-       try {
-           int rc = getStorageManager().renameSecureContainer(oldId, newId);
-           if (rc != StorageResultCode.OperationSucceeded) {
-               Log.e(TAG, "Failed to rename " + oldId + " to " +
-                       newId + "with rc " + rc);
-               return false;
-           }
-           return true;
-       } catch (RemoteException e) {
-           Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
-                   " with exception : " + e);
-       }
-       return false;
-   }
-
-   public static String getSdDir(String cid) {
-       try {
-            return getStorageManager().getSecureContainerPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get container path for " + cid +
-                " with exception " + e);
-        }
-        return null;
-   }
-
-   public static String getSdFilesystem(String cid) {
-       try {
-            return getStorageManager().getSecureContainerFilesystemPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get container path for " + cid +
-                " with exception " + e);
-        }
-        return null;
-   }
-
-    public static boolean finalizeSdDir(String cid) {
-        try {
-            int rc = getStorageManager().finalizeSecureContainer(cid);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to finalize container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to finalize container " + cid +
-                    " with exception " + e);
-        }
-        return false;
-    }
-
-    public static boolean destroySdDir(String cid) {
-        try {
-            if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
-            int rc = getStorageManager().destroySecureContainer(cid, true);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to destroy container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to destroy container " + cid +
-                    " with exception " + e);
-        }
-        return false;
-    }
-
-    public static String[] getSecureContainerList() {
-        try {
-            return getStorageManager().getSecureContainerList();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get secure container list with exception" +
-                    e);
-        }
-        return null;
-    }
-
-   public static boolean isContainerMounted(String cid) {
-       try {
-           return getStorageManager().isSecureContainerMounted(cid);
-       } catch (RemoteException e) {
-           Log.e(TAG, "Failed to find out if container " + cid + " mounted");
-       }
-       return false;
-   }
-
-    /**
-     * Extract public files for the single given APK.
-     */
-    public static long extractPublicFiles(File apkFile, File publicZipFile)
-            throws IOException {
-        final FileOutputStream fstr;
-        final ZipOutputStream publicZipOutStream;
-
-        if (publicZipFile == null) {
-            fstr = null;
-            publicZipOutStream = null;
-        } else {
-            fstr = new FileOutputStream(publicZipFile);
-            publicZipOutStream = new ZipOutputStream(fstr);
-            Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
-        }
-
-        long size = 0L;
-
-        try {
-            final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
-            try {
-                // Copy manifest, resources.arsc and res directory to public zip
-                for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
-                    final String zipEntryName = zipEntry.getName();
-                    if ("AndroidManifest.xml".equals(zipEntryName)
-                            || "resources.arsc".equals(zipEntryName)
-                            || zipEntryName.startsWith("res/")) {
-                        size += zipEntry.getSize();
-                        if (publicZipFile != null) {
-                            copyZipEntry(zipEntry, privateZip, publicZipOutStream);
-                        }
-                    }
-                }
-            } finally {
-                try { privateZip.close(); } catch (IOException e) {}
-            }
-
-            if (publicZipFile != null) {
-                publicZipOutStream.finish();
-                publicZipOutStream.flush();
-                FileUtils.sync(fstr);
-                publicZipOutStream.close();
-                FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
-                        | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
-            }
-        } finally {
-            IoUtils.closeQuietly(publicZipOutStream);
-        }
-
-        return size;
-    }
-
-    private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
-            ZipOutputStream outZipStream) throws IOException {
-        byte[] buffer = new byte[4096];
-        int num;
-
-        ZipEntry newEntry;
-        if (zipEntry.getMethod() == ZipEntry.STORED) {
-            // Preserve the STORED method of the input entry.
-            newEntry = new ZipEntry(zipEntry);
-        } else {
-            // Create a new entry so that the compressed len is recomputed.
-            newEntry = new ZipEntry(zipEntry.getName());
-        }
-        outZipStream.putNextEntry(newEntry);
-
-        final InputStream data = inZipFile.getInputStream(zipEntry);
-        try {
-            while ((num = data.read(buffer)) > 0) {
-                outZipStream.write(buffer, 0, num);
-            }
-            outZipStream.flush();
-        } finally {
-            IoUtils.closeQuietly(data);
-        }
-    }
-
-    public static boolean fixSdPermissions(String cid, int gid, String filename) {
-        try {
-            int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to fixperms container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
-        }
-        return false;
-    }
-
     /**
      * A group of external dependencies used in
      * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
@@ -638,29 +375,37 @@
         return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
     }
 
+    @Deprecated
     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
             String abiOverride) throws IOException {
+        return calculateInstalledSize(pkg, abiOverride);
+    }
+
+    public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
+            throws IOException {
         NativeLibraryHelper.Handle handle = null;
         try {
             handle = NativeLibraryHelper.Handle.create(pkg);
-            return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
+            return calculateInstalledSize(pkg, handle, abiOverride);
         } finally {
             IoUtils.closeQuietly(handle);
         }
     }
 
+    @Deprecated
+    public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
+            NativeLibraryHelper.Handle handle, String abiOverride) throws IOException {
+        return calculateInstalledSize(pkg, handle, abiOverride);
+    }
+
     public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
-            boolean isForwardLocked, String abiOverride) throws IOException {
+            String abiOverride) throws IOException {
         long sizeBytes = 0;
 
         // Include raw APKs, and possibly unpacked resources
         for (String codePath : pkg.getAllCodePaths()) {
             final File codeFile = new File(codePath);
             sizeBytes += codeFile.length();
-
-            if (isForwardLocked) {
-                sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
-            }
         }
 
         // Include all relevant native code
diff --git a/core/java/com/android/internal/net/OWNERS b/core/java/com/android/internal/net/OWNERS
index 7cb32ff..e2064a8 100644
--- a/core/java/com/android/internal/net/OWNERS
+++ b/core/java/com/android/internal/net/OWNERS
@@ -2,5 +2,5 @@
 
 ek@google.com
 hugobenichi@google.com
-jsharkey@google.com
+jsharkey@android.com
 lorenzo@google.com
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index d64c9a1..4a181b2 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -20,6 +20,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.media.AudioAttributes;
 import android.os.RemoteException;
 import android.provider.Settings;
 
@@ -47,6 +48,7 @@
     public static String RETAIL_MODE = "RETAIL_MODE";
     public static String USB = "USB";
     public static String FOREGROUND_SERVICE = "FOREGROUND_SERVICE";
+    public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP";
 
     public static void createAll(Context context) {
         final NotificationManager nm = context.getSystemService(NotificationManager.class);
@@ -139,6 +141,17 @@
         foregroundChannel.setBlockableSystem(true);
         channelsList.add(foregroundChannel);
 
+        NotificationChannel heavyWeightChannel = new NotificationChannel(
+                HEAVY_WEIGHT_APP,
+                context.getString(R.string.notification_channel_heavy_weight_app),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        heavyWeightChannel.setShowBadge(false);
+        heavyWeightChannel.setSound(null, new AudioAttributes.Builder()
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
+                .build());
+        channelsList.add(heavyWeightChannel);
+
         nm.createNotificationChannels(channelsList);
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index f085e29..15dc6f5 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -143,6 +143,9 @@
     public static boolean checkWifiOnly(Context context) {
         ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
+        if (cm == null) {
+            return false;
+        }
         return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0bd2981..f0d05da 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -63,6 +63,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
+import android.util.StatsLog;
 import android.util.TimeUtils;
 import android.util.Xml;
 import android.view.Display;
@@ -119,7 +120,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 166 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -341,8 +342,8 @@
     protected final TimeBase mOnBatteryTimeBase = new TimeBase();
 
     // These are the objects that will want to do something when the device
-    // is unplugged from power *and* the screen is off.
-    final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+    // is unplugged from power *and* the screen is off or doze.
+    protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
 
     // Set to true when we want to distribute CPU across wakelocks for the next
     // CPU update, even if we aren't currently running wake locks.
@@ -436,8 +437,12 @@
     public boolean mRecordAllHistory;
     boolean mNoAutoReset;
 
-    int mScreenState = Display.STATE_UNKNOWN;
-    StopwatchTimer mScreenOnTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected int mScreenState = Display.STATE_UNKNOWN;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mScreenOnTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mScreenDozeTimer;
 
     int mScreenBrightnessBin = -1;
     final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -583,12 +588,16 @@
     int mHighDischargeAmountSinceCharge;
     int mDischargeScreenOnUnplugLevel;
     int mDischargeScreenOffUnplugLevel;
+    int mDischargeScreenDozeUnplugLevel;
     int mDischargeAmountScreenOn;
     int mDischargeAmountScreenOnSinceCharge;
     int mDischargeAmountScreenOff;
     int mDischargeAmountScreenOffSinceCharge;
+    int mDischargeAmountScreenDoze;
+    int mDischargeAmountScreenDozeSinceCharge;
 
     private LongSamplingCounter mDischargeScreenOffCounter;
+    private LongSamplingCounter mDischargeScreenDozeCounter;
     private LongSamplingCounter mDischargeCounter;
 
     static final int MAX_LEVEL_STEPS = 200;
@@ -673,13 +682,18 @@
     }
 
     @Override
-    public LongCounter getDischargeScreenOffCoulombCounter() {
-        return mDischargeScreenOffCounter;
+    public long getUahDischarge(int which) {
+        return mDischargeCounter.getCountLocked(which);
     }
 
     @Override
-    public LongCounter getDischargeCoulombCounter() {
-        return mDischargeCounter;
+    public long getUahDischargeScreenOff(int which) {
+        return mDischargeScreenOffCounter.getCountLocked(which);
+    }
+
+    @Override
+    public long getUahDischargeScreenDoze(int which) {
+        return mDischargeScreenDozeCounter.getCountLocked(which);
     }
 
     @Override
@@ -3573,8 +3587,9 @@
         mActiveHistoryStates2 = 0xffffffff;
     }
 
-    public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+    public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
             long realtime) {
+        final boolean screenOff = !isScreenOn(screenState);
         final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
         final boolean updateOnBatteryScreenOffTimeBase =
                 (unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
@@ -3591,20 +3606,22 @@
                 updateRpmStatsLocked(); // if either OnBattery or OnBatteryScreenOff timebase changes.
             }
             if (DEBUG_ENERGY_CPU) {
-                Slog.d(TAG, "Updating cpu time because screen is now " + (screenOff ? "off" : "on")
+                Slog.d(TAG, "Updating cpu time because screen is now "
+                        + Display.stateToString(screenState)
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
-            mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
-            for (int i = mUidStats.size() - 1; i >= 0; --i) {
-                final Uid u = mUidStats.valueAt(i);
-                if (updateOnBatteryTimeBase) {
-                    u.updateOnBatteryBgTimeBase(uptime, realtime);
+            if (updateOnBatteryTimeBase) {
+                for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                    mUidStats.valueAt(i).updateOnBatteryBgTimeBase(uptime, realtime);
                 }
-                if (updateOnBatteryScreenOffTimeBase) {
-                    u.updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
+            }
+            if (updateOnBatteryScreenOffTimeBase) {
+                mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
+                for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                    mUidStats.valueAt(i).updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
                 }
             }
         }
@@ -3864,8 +3881,10 @@
     }
 
     public void setPretendScreenOff(boolean pretendScreenOff) {
-        mPretendScreenOff = pretendScreenOff;
-        noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+        if (mPretendScreenOff != pretendScreenOff) {
+            mPretendScreenOff = pretendScreenOff;
+            noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+        }
     }
 
     private String mInitialAcquireWakeName;
@@ -4012,6 +4031,7 @@
         }
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
                 historyName, uid);
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
     }
 
     public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
@@ -4027,6 +4047,7 @@
         }
         addHistoryEventLocked(elapsedRealtime, uptime, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
                 historyName, uid);
+        StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
     }
 
     void aggregateLastWakeupUptimeLocked(long uptimeMs) {
@@ -4034,6 +4055,7 @@
             long deltaUptime = uptimeMs - mLastWakeupUptimeMs;
             SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
             timer.add(deltaUptime * 1000, 1); // time in in microseconds
+            StatsLog.write(StatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason, deltaUptime * 1000);
             mLastWakeupReason = null;
         }
     }
@@ -4195,54 +4217,58 @@
                 }
             }
 
-            if (state == Display.STATE_ON) {
-                // Screen turning on.
-                final long elapsedRealtime = mClocks.elapsedRealtime();
-                final long uptime = mClocks.uptimeMillis();
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+
+            boolean updateHistory = false;
+            if (isScreenDoze(state)) {
+                mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mScreenDozeTimer.startRunningLocked(elapsedRealtime);
+                updateHistory = true;
+            } else if (isScreenDoze(oldState)) {
+                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mScreenDozeTimer.stopRunningLocked(elapsedRealtime);
+                updateHistory = true;
+            }
+            if (isScreenOn(state)) {
                 mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
                 if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
                         + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtime, uptime);
                 mScreenOnTimer.startRunningLocked(elapsedRealtime);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
                 }
-
-                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
-                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
-                // Fake a wake lock, so we consider the device waked as long
-                // as the screen is on.
-                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
-                        elapsedRealtime, uptime);
-
-                // Update discharge amounts.
-                if (mOnBatteryInternal) {
-                    updateDischargeScreenLevelsLocked(false, true);
-                }
-            } else if (oldState == Display.STATE_ON) {
-                // Screen turning off or dozing.
-                final long elapsedRealtime = mClocks.elapsedRealtime();
-                final long uptime = mClocks.uptimeMillis();
+                updateHistory = true;
+            } else if (isScreenOn(oldState)) {
                 mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
                 if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
                         + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtime, uptime);
                 mScreenOnTimer.stopRunningLocked(elapsedRealtime);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
                 }
-
+                updateHistory = true;
+            }
+            if (updateHistory) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
+                        + Display.stateToString(state));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            }
+            if (isScreenOn(state)) {
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
+                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
+                // Fake a wake lock, so we consider the device waked as long as the screen is on.
+                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+                        elapsedRealtime, uptime);
+            } else if (isScreenOn(oldState)) {
                 noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
                         elapsedRealtime, uptime);
-
-                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
                         mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
-                // Update discharge amounts.
-                if (mOnBatteryInternal) {
-                    updateDischargeScreenLevelsLocked(true, false);
-                }
+            }
+            // Update discharge amounts.
+            if (mOnBatteryInternal) {
+                updateDischargeScreenLevelsLocked(oldState, state);
             }
         }
     }
@@ -4381,6 +4407,7 @@
                 mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtime);
             }
             addHistoryRecordLocked(elapsedRealtime, uptime);
+            StatsLog.write(StatsLog.BATTERY_SAVER_MODE_STATE_CHANGED, enabled ? 1 : 0);
         }
     }
 
@@ -4607,6 +4634,7 @@
                 if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
                         + Integer.toHexString(mHistoryCur.states));
                 newHistory = true;
+                StatsLog.write(StatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
             } else {
                 stopAllPhoneSignalStrengthTimersLocked(-1);
             }
@@ -5162,6 +5190,7 @@
             if (strengthBin >= 0) {
                 if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
                     mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtime);
+                    StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED, strengthBin);
                 }
                 mHistoryCur.states2 =
                         (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
@@ -5391,12 +5420,24 @@
         return mScreenOnTimer.getCountLocked(which);
     }
 
+    @Override public long getScreenDozeTime(long elapsedRealtimeUs, int which) {
+        return mScreenDozeTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getScreenDozeCount(int which) {
+        return mScreenDozeTimer.getCountLocked(which);
+    }
+
     @Override public long getScreenBrightnessTime(int brightnessBin,
             long elapsedRealtimeUs, int which) {
         return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
                 elapsedRealtimeUs, which);
     }
 
+    @Override public Timer getScreenBrightnessTimer(int brightnessBin) {
+        return mScreenBrightnessTimer[brightnessBin];
+    }
+
     @Override public long getInteractiveTime(long elapsedRealtimeUs, int which) {
         return mInteractiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -5490,10 +5531,18 @@
                 elapsedRealtimeUs, which);
     }
 
+    @Override public Timer getPhoneSignalScanningTimer() {
+        return mPhoneSignalScanningTimer;
+    }
+
     @Override public int getPhoneSignalStrengthCount(int strengthBin, int which) {
         return mPhoneSignalStrengthsTimer[strengthBin].getCountLocked(which);
     }
 
+    @Override public Timer getPhoneSignalStrengthTimer(int strengthBin) {
+        return mPhoneSignalStrengthsTimer[strengthBin];
+    }
+
     @Override public long getPhoneDataConnectionTime(int dataType,
             long elapsedRealtimeUs, int which) {
         return mPhoneDataConnectionsTimer[dataType].getTotalTimeLocked(
@@ -5504,6 +5553,10 @@
         return mPhoneDataConnectionsTimer[dataType].getCountLocked(which);
     }
 
+    @Override public Timer getPhoneDataConnectionTimer(int dataType) {
+        return mPhoneDataConnectionsTimer[dataType];
+    }
+
     @Override public long getMobileRadioActiveTime(long elapsedRealtimeUs, int which) {
         return mMobileRadioActiveTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
     }
@@ -5542,6 +5595,10 @@
         return mWifiStateTimer[wifiState].getCountLocked(which);
     }
 
+    @Override public Timer getWifiStateTimer(int wifiState) {
+        return mWifiStateTimer[wifiState];
+    }
+
     @Override public long getWifiSupplStateTime(int state,
             long elapsedRealtimeUs, int which) {
         return mWifiSupplStateTimer[state].getTotalTimeLocked(
@@ -5552,6 +5609,10 @@
         return mWifiSupplStateTimer[state].getCountLocked(which);
     }
 
+    @Override public Timer getWifiSupplStateTimer(int state) {
+        return mWifiSupplStateTimer[state];
+    }
+
     @Override public long getWifiSignalStrengthTime(int strengthBin,
             long elapsedRealtimeUs, int which) {
         return mWifiSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
@@ -5562,6 +5623,10 @@
         return mWifiSignalStrengthsTimer[strengthBin].getCountLocked(which);
     }
 
+    @Override public Timer getWifiSignalStrengthTimer(int strengthBin) {
+        return mWifiSignalStrengthsTimer[strengthBin];
+    }
+
     @Override
     public ControllerActivityCounter getBluetoothControllerActivity() {
         return mBluetoothActivity;
@@ -5991,6 +6056,8 @@
                             mBsi.mFullWifiLockTimers, mBsi.mOnBatteryTimeBase);
                 }
                 mFullWifiLockTimer.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -5999,6 +6066,10 @@
             if (mFullWifiLockOut) {
                 mFullWifiLockOut = false;
                 mFullWifiLockTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mFullWifiLockTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
@@ -6012,6 +6083,8 @@
                             mOnBatteryBackgroundTimeBase);
                 }
                 mWifiScanTimer.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 1);
             }
         }
 
@@ -6020,6 +6093,10 @@
             if (mWifiScanStarted) {
                 mWifiScanStarted = false;
                 mWifiScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mWifiScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
@@ -6122,17 +6199,25 @@
 
         public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
             createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 1);
         }
 
         public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetAudioLocked(long elapsedRealtimeMs) {
             if (mAudioTurnedOnTimer != null) {
                 mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6146,17 +6231,25 @@
 
         public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
             createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 1);
         }
 
         public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetVideoLocked(long elapsedRealtimeMs) {
             if (mVideoTurnedOnTimer != null) {
                 mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
             }
         }
 
@@ -6170,17 +6263,25 @@
 
         public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
             createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 1);
         }
 
         public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mFlashlightTurnedOnTimer.isRunningLocked()) {
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
             if (mFlashlightTurnedOnTimer != null) {
                 mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6194,17 +6295,25 @@
 
         public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
             createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 1);
         }
 
         public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetCameraLocked(long elapsedRealtimeMs) {
             if (mCameraTurnedOnTimer != null) {
                 mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6253,26 +6362,42 @@
 
         public void noteBluetoothScanStartedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
             createBluetoothScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 1);
             if (isUnoptimized) {
                 createBluetoothUnoptimizedScanTimerLocked().startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 1);
             }
         }
 
         public void noteBluetoothScanStoppedLocked(long elapsedRealtimeMs, boolean isUnoptimized) {
             if (mBluetoothScanTimer != null) {
                 mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mBluetoothScanTimer.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
             if (isUnoptimized && mBluetoothUnoptimizedScanTimer != null) {
                 mBluetoothUnoptimizedScanTimer.stopRunningLocked(elapsedRealtimeMs);
+                if (!mBluetoothUnoptimizedScanTimer.isRunningLocked()) {
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0);
+                }
             }
         }
 
         public void noteResetBluetoothScanLocked(long elapsedRealtimeMs) {
             if (mBluetoothScanTimer != null) {
                 mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, getUid(), 0);
             }
             if (mBluetoothUnoptimizedScanTimer != null) {
                 mBluetoothUnoptimizedScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, getUid(), 0);
             }
         }
 
@@ -6294,6 +6419,9 @@
             createBluetoothScanResultCounterLocked().addAtomic(numNewResults);
             // Uses background timebase, so the count will only be incremented if uid in background.
             createBluetoothScanResultBgCounterLocked().addAtomic(numNewResults);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            // TODO(statsd): This could be in AppScanStats instead, if desired.
+            StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, getUid(), numNewResults);
         }
 
         @Override
@@ -6370,6 +6498,11 @@
         }
 
         @Override
+        public Timer getWifiScanTimer() {
+            return mWifiScanTimer;
+        }
+
+        @Override
         public int getWifiScanBackgroundCount(int which) {
             if (mWifiScanTimer == null || mWifiScanTimer.getSubTimer() == null) {
                 return 0;
@@ -6396,6 +6529,14 @@
         }
 
         @Override
+        public Timer getWifiScanBackgroundTimer() {
+            if (mWifiScanTimer == null) {
+                return null;
+            }
+            return mWifiScanTimer.getSubTimer();
+        }
+
+        @Override
         public long getWifiBatchedScanTime(int csphBin, long elapsedRealtimeUs, int which) {
             if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0;
             if (mWifiBatchedScanTimer[csphBin] == null) {
@@ -8654,6 +8795,8 @@
             DualTimer t = mSyncStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 1);
             }
         }
 
@@ -8661,6 +8804,10 @@
             DualTimer t = mSyncStats.stopObject(name);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
+                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 0);
+                }
             }
         }
 
@@ -8668,6 +8815,8 @@
             DualTimer t = mJobStats.startObject(name);
             if (t != null) {
                 t.startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 1);
             }
         }
 
@@ -8675,6 +8824,10 @@
             DualTimer t = mJobStats.stopObject(name);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
+                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 0);
+                }
             }
             if (mBsi.mOnBatteryTimeBase.isRunning()) {
                 SparseIntArray types = mJobCompletions.get(name);
@@ -8738,9 +8891,13 @@
             Wakelock wl = mWakelockStats.startObject(name);
             if (wl != null) {
                 getWakelockTimerLocked(wl, type).startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Hopefully use a worksource instead of a uid (so move elsewhere)
+                StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 1);
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 createAggregatedPartialWakelockTimerLocked().startRunningLocked(elapsedRealtimeMs);
+                // TODO(statsd): Possibly use a worksource instead of a uid.
+                StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type, 1);
                 if (pid >= 0) {
                     Pid p = getPidStatsLocked(pid);
                     if (p.mWakeNesting++ == 0) {
@@ -8753,11 +8910,21 @@
         public void noteStopWakeLocked(int pid, String name, int type, long elapsedRealtimeMs) {
             Wakelock wl = mWakelockStats.stopObject(name);
             if (wl != null) {
-                getWakelockTimerLocked(wl, type).stopRunningLocked(elapsedRealtimeMs);
+                StopwatchTimer wlt = getWakelockTimerLocked(wl, type);
+                wlt.stopRunningLocked(elapsedRealtimeMs);
+                if (!wlt.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, getUid(), type, name, 0);
+                }
             }
             if (type == WAKE_TYPE_PARTIAL) {
                 if (mAggregatedPartialWakelockTimer != null) {
                     mAggregatedPartialWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
+                    if (!mAggregatedPartialWakelockTimer.isRunningLocked()) {
+                        // TODO(statsd): Possibly use a worksource instead of a uid.
+                        StatsLog.write(StatsLog.UID_WAKELOCK_STATE_CHANGED, getUid(), type,
+                                0);
+                    }
                 }
                 if (pid >= 0) {
                     Pid p = mPids.get(pid);
@@ -8781,6 +8948,12 @@
         public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
             DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
             t.startRunningLocked(elapsedRealtimeMs);
+            // TODO(statsd): Possibly use a worksource instead of a uid.
+            if (sensor == Sensor.GPS) {
+                StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 1);
+            } else {
+                StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 1);
+            }
         }
 
         public void noteStopSensor(int sensor, long elapsedRealtimeMs) {
@@ -8788,6 +8961,14 @@
             DualTimer t = getSensorTimerLocked(sensor, false);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
+                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
+                    // TODO(statsd): Possibly use a worksource instead of a uid.
+                    if (sensor == Sensor.GPS) {
+                        StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 0);
+                    } else {
+                        StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 0);
+                    }
+                }
             }
         }
 
@@ -8829,6 +9010,7 @@
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
+        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
                     mOnBatteryTimeBase);
@@ -8887,6 +9069,7 @@
         mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
+        mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mOnBattery = mOnBatteryInternal = false;
         long uptime = mClocks.uptimeMillis() * 1000;
@@ -9430,8 +9613,16 @@
         return mCharging;
     }
 
-    public boolean isScreenOn() {
-        return mScreenState == Display.STATE_ON;
+    public boolean isScreenOn(int state) {
+        return state == Display.STATE_ON || state == Display.STATE_VR;
+    }
+
+    public boolean isScreenOff(int state) {
+        return state == Display.STATE_OFF;
+    }
+
+    public boolean isScreenDoze(int state) {
+        return state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND;
     }
 
     void initTimes(long uptime, long realtime) {
@@ -9451,9 +9642,12 @@
         mDischargeAmountScreenOnSinceCharge = 0;
         mDischargeAmountScreenOff = 0;
         mDischargeAmountScreenOffSinceCharge = 0;
+        mDischargeAmountScreenDoze = 0;
+        mDischargeAmountScreenDozeSinceCharge = 0;
         mDischargeStepTracker.init();
         mChargeStepTracker.init();
         mDischargeScreenOffCounter.reset(false);
+        mDischargeScreenDozeCounter.reset(false);
         mDischargeCounter.reset(false);
     }
 
@@ -9471,15 +9665,22 @@
         mOnBatteryTimeBase.reset(uptime, realtime);
         mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
         if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
-            if (mScreenState == Display.STATE_ON) {
+            if (isScreenOn(mScreenState)) {
                 mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenDozeUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else if (isScreenDoze(mScreenState)) {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
             }
             mDischargeAmountScreenOn = 0;
             mDischargeAmountScreenOff = 0;
+            mDischargeAmountScreenDoze = 0;
         }
         initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
     }
@@ -9490,6 +9691,7 @@
         mStartCount = 0;
         initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
         mScreenOnTimer.reset(false);
+        mScreenDozeTimer.reset(false);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].reset(false);
         }
@@ -9626,33 +9828,52 @@
         }
     }
 
-    void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
-        if (oldScreenOn) {
+    void updateDischargeScreenLevelsLocked(int oldState, int newState) {
+        updateOldDischargeScreenLevelLocked(oldState);
+        updateNewDischargeScreenLevelLocked(newState);
+    }
+
+    private void updateOldDischargeScreenLevelLocked(int state) {
+        if (isScreenOn(state)) {
             int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
             if (diff > 0) {
                 mDischargeAmountScreenOn += diff;
                 mDischargeAmountScreenOnSinceCharge += diff;
             }
-        } else {
+        } else if (isScreenDoze(state)) {
+            int diff = mDischargeScreenDozeUnplugLevel - mDischargeCurrentLevel;
+            if (diff > 0) {
+                mDischargeAmountScreenDoze += diff;
+                mDischargeAmountScreenDozeSinceCharge += diff;
+            }
+        } else if (isScreenOff(state)){
             int diff = mDischargeScreenOffUnplugLevel - mDischargeCurrentLevel;
             if (diff > 0) {
                 mDischargeAmountScreenOff += diff;
                 mDischargeAmountScreenOffSinceCharge += diff;
             }
         }
-        if (newScreenOn) {
+    }
+
+    private void updateNewDischargeScreenLevelLocked(int state) {
+        if (isScreenOn(state)) {
             mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel;
             mDischargeScreenOffUnplugLevel = 0;
-        } else {
+            mDischargeScreenDozeUnplugLevel = 0;
+        } else if (isScreenDoze(state)){
             mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenDozeUnplugLevel = mDischargeCurrentLevel;
+            mDischargeScreenOffUnplugLevel = 0;
+        } else if (isScreenOff(state)) {
+            mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenDozeUnplugLevel = 0;
             mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
         }
     }
 
     public void pullPendingStateUpdatesLocked() {
         if (mOnBatteryInternal) {
-            final boolean screenOn = mScreenState == Display.STATE_ON;
-            updateDischargeScreenLevelsLocked(screenOn, screenOn);
+            updateDischargeScreenLevelsLocked(mScreenState, mScreenState);
         }
     }
 
@@ -10785,8 +11006,8 @@
         return false;
     }
 
-    void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
-            final int oldStatus, final int level, final int chargeUAh) {
+    protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
+            final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
         boolean doWrite = false;
         Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
         m.arg1 = onBattery ? 1 : 0;
@@ -10794,7 +11015,7 @@
 
         final long uptime = mSecUptime * 1000;
         final long realtime = mSecRealtime * 1000;
-        final boolean screenOn = mScreenState == Display.STATE_ON;
+        final int screenState = mScreenState;
         if (onBattery) {
             // We will reset our status if we are unplugging after the
             // battery was last full, or the level is at 100, or
@@ -10870,16 +11091,23 @@
             }
             addHistoryRecordLocked(mSecRealtime, mSecUptime);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
-            if (screenOn) {
+            if (isScreenOn(screenState)) {
                 mDischargeScreenOnUnplugLevel = level;
+                mDischargeScreenDozeUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else if (isScreenDoze(screenState)) {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = level;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = level;
             }
             mDischargeAmountScreenOn = 0;
+            mDischargeAmountScreenDoze = 0;
             mDischargeAmountScreenOff = 0;
-            updateTimeBasesLocked(true, !screenOn, uptime, realtime);
+            updateTimeBasesLocked(true, screenState, uptime, realtime);
         } else {
             mLastChargingStateLevel = level;
             mOnBattery = mOnBatteryInternal = false;
@@ -10894,8 +11122,8 @@
                 mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
                 mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
             }
-            updateDischargeScreenLevelsLocked(screenOn, screenOn);
-            updateTimeBasesLocked(false, !screenOn, uptime, realtime);
+            updateDischargeScreenLevelsLocked(screenState, screenState);
+            updateTimeBasesLocked(false, screenState, uptime, realtime);
             mChargeStepTracker.init();
             mLastChargeStepLevel = level;
             mMaxChargeStepLevel = level;
@@ -10950,11 +11178,15 @@
     // This should probably be exposed in the API, though it's not critical
     public static final int BATTERY_PLUGGED_NONE = 0;
 
-    public void setBatteryStateLocked(int status, int health, int plugType, int level,
-            int temp, int volt, int chargeUAh, int chargeFullUAh) {
+    public void setBatteryStateLocked(final int status, final int health, final int plugType,
+            final int level, /* not final */ int temp, final int volt, final int chargeUAh,
+            final int chargeFullUAh) {
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
+        reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
+                status, plugType, level, temp);
+
         final boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
         final long uptime = mClocks.uptimeMillis();
         final long elapsedRealtime = mClocks.elapsedRealtime();
@@ -11012,6 +11244,9 @@
                 final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                 mDischargeCounter.addCountLocked(chargeDiff);
                 mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                if (isScreenDoze(mScreenState)) {
+                    mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+                }
             }
             mHistoryCur.batteryChargeUAh = chargeUAh;
             setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11054,6 +11289,9 @@
                     final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                     mDischargeCounter.addCountLocked(chargeDiff);
                     mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                    if (isScreenDoze(mScreenState)) {
+                        mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+                    }
                 }
                 mHistoryCur.batteryChargeUAh = chargeUAh;
                 changed = true;
@@ -11125,6 +11363,24 @@
         mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh);
     }
 
+    // Inform StatsLog of setBatteryState changes.
+    // If this is the first reporting, pass in recentPast == null.
+    private void reportChangesToStatsLog(HistoryItem recentPast,
+            final int status, final int plugType, final int level, final int temp) {
+
+        if (recentPast == null || recentPast.batteryStatus != status) {
+            StatsLog.write(StatsLog.CHARGING_STATE_CHANGED, status);
+        }
+        if (recentPast == null || recentPast.batteryPlugType != plugType) {
+            StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, plugType);
+        }
+        if (recentPast == null || recentPast.batteryLevel != level) {
+            StatsLog.write(StatsLog.BATTERY_LEVEL_CHANGED, level);
+        }
+        // Let's just always print the temperature, regardless of whether it changed.
+        StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, temp);
+    }
+
     public long getAwakeTimeBattery() {
         return computeBatteryUptime(getBatteryUptimeLocked(), STATS_CURRENT);
     }
@@ -11362,10 +11618,11 @@
         return dischargeAmount;
     }
 
+    @Override
     public int getDischargeAmountScreenOn() {
         synchronized(this) {
             int val = mDischargeAmountScreenOn;
-            if (mOnBattery && mScreenState == Display.STATE_ON
+            if (mOnBattery && isScreenOn(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
                 val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
             }
@@ -11373,10 +11630,11 @@
         }
     }
 
+    @Override
     public int getDischargeAmountScreenOnSinceCharge() {
         synchronized(this) {
             int val = mDischargeAmountScreenOnSinceCharge;
-            if (mOnBattery && mScreenState == Display.STATE_ON
+            if (mOnBattery && isScreenOn(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
                 val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
             }
@@ -11384,23 +11642,51 @@
         }
     }
 
+    @Override
     public int getDischargeAmountScreenOff() {
         synchronized(this) {
             int val = mDischargeAmountScreenOff;
-            if (mOnBattery && mScreenState != Display.STATE_ON
+            if (mOnBattery && isScreenOff(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
                 val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
             }
+            // For backward compatibility, doze discharge is counted into screen off.
+            return val + getDischargeAmountScreenDoze();
+        }
+    }
+
+    @Override
+    public int getDischargeAmountScreenOffSinceCharge() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOffSinceCharge;
+            if (mOnBattery && isScreenOff(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
+                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            }
+            // For backward compatibility, doze discharge is counted into screen off.
+            return val + getDischargeAmountScreenDozeSinceCharge();
+        }
+    }
+
+    @Override
+    public int getDischargeAmountScreenDoze() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenDoze;
+            if (mOnBattery && isScreenDoze(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+                val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
+            }
             return val;
         }
     }
 
-    public int getDischargeAmountScreenOffSinceCharge() {
+    @Override
+    public int getDischargeAmountScreenDozeSinceCharge() {
         synchronized(this) {
-            int val = mDischargeAmountScreenOffSinceCharge;
-            if (mOnBattery && mScreenState != Display.STATE_ON
-                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
-                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            int val = mDischargeAmountScreenDozeSinceCharge;
+            if (mOnBattery && isScreenDoze(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+                val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
             }
             return val;
         }
@@ -11759,12 +12045,14 @@
         mHighDischargeAmountSinceCharge = in.readInt();
         mDischargeAmountScreenOnSinceCharge = in.readInt();
         mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeAmountScreenDozeSinceCharge = in.readInt();
         mDischargeStepTracker.readFromParcel(in);
         mChargeStepTracker.readFromParcel(in);
         mDailyDischargeStepTracker.readFromParcel(in);
         mDailyChargeStepTracker.readFromParcel(in);
         mDischargeCounter.readSummaryFromParcelLocked(in);
         mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
+        mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
         int NPKG = in.readInt();
         if (NPKG > 0) {
             mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -11787,6 +12075,7 @@
 
         mScreenState = Display.STATE_UNKNOWN;
         mScreenOnTimer.readSummaryFromParcelLocked(in);
+        mScreenDozeTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].readSummaryFromParcelLocked(in);
         }
@@ -12180,12 +12469,14 @@
         out.writeInt(getHighDischargeAmountSinceCharge());
         out.writeInt(getDischargeAmountScreenOnSinceCharge());
         out.writeInt(getDischargeAmountScreenOffSinceCharge());
+        out.writeInt(getDischargeAmountScreenDozeSinceCharge());
         mDischargeStepTracker.writeToParcel(out);
         mChargeStepTracker.writeToParcel(out);
         mDailyDischargeStepTracker.writeToParcel(out);
         mDailyChargeStepTracker.writeToParcel(out);
         mDischargeCounter.writeSummaryFromParcelLocked(out);
         mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
+        mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
         if (mDailyPackageChanges != null) {
             final int NPKG = mDailyPackageChanges.size();
             out.writeInt(NPKG);
@@ -12203,6 +12494,7 @@
         out.writeLong(mNextMaxDailyDeadline);
 
         mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mScreenDozeTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         }
@@ -12635,6 +12927,7 @@
 
         mScreenState = Display.STATE_UNKNOWN;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
+        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
                     mOnBatteryTimeBase, in);
@@ -12671,7 +12964,7 @@
         mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
         mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null,
                 mOnBatteryTimeBase, in);
-        mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null, 
+        mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
                 mOnBatteryTimeBase, in);
         mMobileRadioActiveAdjustedTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mMobileRadioActiveUnknownTime = new LongSamplingCounter(mOnBatteryTimeBase, in);
@@ -12728,10 +13021,13 @@
         mDischargeAmountScreenOnSinceCharge = in.readInt();
         mDischargeAmountScreenOff = in.readInt();
         mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeAmountScreenDoze = in.readInt();
+        mDischargeAmountScreenDozeSinceCharge = in.readInt();
         mDischargeStepTracker.readFromParcel(in);
         mChargeStepTracker.readFromParcel(in);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
-        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
+        mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mLastWriteTime = in.readLong();
 
         mRpmStats.clear();
@@ -12848,6 +13144,7 @@
         mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
 
         mScreenOnTimer.writeToParcel(out, uSecRealtime);
+        mScreenDozeTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
         }
@@ -12910,10 +13207,13 @@
         out.writeInt(mDischargeAmountScreenOnSinceCharge);
         out.writeInt(mDischargeAmountScreenOff);
         out.writeInt(mDischargeAmountScreenOffSinceCharge);
+        out.writeInt(mDischargeAmountScreenDoze);
+        out.writeInt(mDischargeAmountScreenDozeSinceCharge);
         mDischargeStepTracker.writeToParcel(out);
         mChargeStepTracker.writeToParcel(out);
         mDischargeCounter.writeToParcel(out);
         mDischargeScreenOffCounter.writeToParcel(out);
+        mDischargeScreenDozeCounter.writeToParcel(out);
         out.writeLong(mLastWriteTime);
 
         out.writeInt(mRpmStats.size());
@@ -12963,7 +13263,7 @@
                 }
             }
         } else {
-            // TODO: There should be two 0's printed here, not just one.
+            out.writeInt(0);
             out.writeInt(0);
         }
 
@@ -13020,8 +13320,10 @@
             pw.println("mOnBatteryScreenOffTimeBase:");
             mOnBatteryScreenOffTimeBase.dump(pw, "  ");
             Printer pr = new PrintWriterPrinter(pw);
-            pr.println("*** Screen timer:");
+            pr.println("*** Screen on timer:");
             mScreenOnTimer.logState(pr, "  ");
+            pr.println("*** Screen doze timer:");
+            mScreenDozeTimer.logState(pr, "  ");
             for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
                 pr.println("*** Screen brightness #" + i + ":");
                 mScreenBrightnessTimer[i].logState(pr, "  ");
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index ea4575a..5bddd2f 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -16,9 +16,15 @@
 
 package com.android.internal.os;
 
+import android.annotation.NonNull;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.SystemClock;
 import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.Preconditions;
 
 import dalvik.system.VMRuntime;
 
@@ -31,11 +37,14 @@
  * @see IBinder
  */
 public class BinderInternal {
+    private static final String TAG = "BinderInternal";
     static WeakReference<GcWatcher> sGcWatcher
             = new WeakReference<GcWatcher>(new GcWatcher());
     static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
     static Runnable[] sTmpWatchers = new Runnable[1];
     static long sLastGcTime;
+    static final BinderProxyLimitListenerDelegate sBinderProxyLimitListenerDelegate =
+            new BinderProxyLimitListenerDelegate();
 
     static final class GcWatcher {
         @Override
@@ -106,4 +115,96 @@
     static void forceBinderGc() {
         forceGc("Binder");
     }
+
+    /**
+     * Enable/disable Binder Proxy Instance Counting by Uid. While enabled, the set callback will
+     * be called if this process holds too many Binder Proxies on behalf of a Uid.
+     * @param enabled true to enable counting, false to disable
+     */
+    public static final native void nSetBinderProxyCountEnabled(boolean enabled);
+
+    /**
+     * Get the current number of Binder Proxies held for each uid.
+     * @return SparseIntArray mapping uids to the number of Binder Proxies currently held
+     */
+    public static final native SparseIntArray nGetBinderProxyPerUidCounts();
+
+    /**
+     * Get the current number of Binder Proxies held for an individual uid.
+     * @param uid Requested uid for Binder Proxy count
+     * @return int with the number of Binder proxies held for a uid
+     */
+    public static final native int nGetBinderProxyCount(int uid);
+
+    /**
+     * Set the Binder Proxy watermarks. Default high watermark = 2500. Default low watermark = 2000
+     * @param high  The limit at which the BinderProxyListener callback will be called.
+     * @param low   The threshold a binder count must drop below before the callback
+     *              can be called again. (This is to avoid many repeated calls to the
+     *              callback in a brief period of time)
+     */
+    public static final native void nSetBinderProxyCountWatermarks(int high, int low);
+
+    /**
+     * Interface for callback invocation when the Binder Proxy limit is reached. onLimitReached will
+     * be called with the uid of the app causing too many Binder Proxies
+     */
+    public interface BinderProxyLimitListener {
+        public void onLimitReached(int uid);
+    }
+
+    /**
+     * Callback used by native code to trigger a callback in java code. The callback will be
+     * triggered when too many binder proxies from a uid hits the allowed limit.
+     * @param uid The uid of the bad behaving app sending too many binders
+     */
+    public static void binderProxyLimitCallbackFromNative(int uid) {
+       sBinderProxyLimitListenerDelegate.notifyClient(uid);
+    }
+
+    /**
+     * Set a callback to be triggered when a uid's Binder Proxy limit is reached for this process.
+     * @param listener OnLimitReached of listener will be called in the thread provided by handler
+     * @param handler must not be null, callback will be posted through the handler;
+     *
+     */
+    public static void setBinderProxyCountCallback(BinderProxyLimitListener listener,
+            @NonNull Handler handler) {
+        Preconditions.checkNotNull(handler,
+                "Must provide NonNull Handler to setBinderProxyCountCallback when setting "
+                        + "BinderProxyLimitListener");
+        sBinderProxyLimitListenerDelegate.setListener(listener, handler);
+    }
+
+    /**
+     * Clear the Binder Proxy callback
+     */
+    public static void clearBinderProxyCountCallback() {
+        sBinderProxyLimitListenerDelegate.setListener(null, null);
+    }
+
+    static private class BinderProxyLimitListenerDelegate {
+        private BinderProxyLimitListener mBinderProxyLimitListener;
+        private Handler mHandler;
+
+        void setListener(BinderProxyLimitListener listener, Handler handler) {
+            synchronized (this) {
+                mBinderProxyLimitListener = listener;
+                mHandler = handler;
+            }
+        }
+
+        void notifyClient(final int uid) {
+            synchronized (this) {
+                if (mBinderProxyLimitListener != null) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mBinderProxyLimitListener.onLimitReached(uid);
+                        }
+                    });
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/com/android/internal/os/IShellCallback.aidl b/core/java/com/android/internal/os/IShellCallback.aidl
index 57d6789..5704342 100644
--- a/core/java/com/android/internal/os/IShellCallback.aidl
+++ b/core/java/com/android/internal/os/IShellCallback.aidl
@@ -20,5 +20,5 @@
 
 /** @hide */
 interface IShellCallback {
-    ParcelFileDescriptor openOutputFile(String path, String seLinuxContext);
+    ParcelFileDescriptor openFile(String path, String seLinuxContext, String mode);
 }
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index 757a112..4c0370c 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -15,13 +15,12 @@
  */
 package com.android.internal.os;
 
+import android.system.Os;
 import android.text.TextUtils;
 import android.os.StrictMode;
 import android.system.OsConstants;
 import android.util.Slog;
 
-import libcore.io.Libcore;
-
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
@@ -53,7 +52,7 @@
                 cpuNumber);
         mLastSpeedTimesMs = new long[numSpeedSteps];
         mDeltaSpeedTimesMs = new long[numSpeedSteps];
-        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+        long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
         mJiffyMillis = 1000/jiffyHz;
     }
 
diff --git a/core/java/com/android/internal/os/LoggingPrintStream.java b/core/java/com/android/internal/os/LoggingPrintStream.java
index f14394a..d27874c 100644
--- a/core/java/com/android/internal/os/LoggingPrintStream.java
+++ b/core/java/com/android/internal/os/LoggingPrintStream.java
@@ -28,12 +28,15 @@
 import java.util.Formatter;
 import java.util.Locale;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * A print stream which logs output line by line.
  *
  * {@hide}
  */
-abstract class LoggingPrintStream extends PrintStream {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public abstract class LoggingPrintStream extends PrintStream {
 
     private final StringBuilder builder = new StringBuilder();
 
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index e46dfc4..bf31c7d 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -22,6 +22,7 @@
 import android.os.Process;
 import android.os.StrictMode;
 import android.os.SystemClock;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.Slog;
 
@@ -294,7 +295,7 @@
 
     public ProcessCpuTracker(boolean includeThreads) {
         mIncludeThreads = includeThreads;
-        long jiffyHz = Libcore.os.sysconf(OsConstants._SC_CLK_TCK);
+        long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
         mJiffyMillis = 1000/jiffyHz;
     }
 
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 4abab28..2be6212 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -549,7 +549,7 @@
             try {
                 dexoptNeeded = DexFile.getDexOptNeeded(
                     classPathElement, instructionSet, systemServerFilter,
-                    false /* newProfile */, false /* downgrade */);
+                    null /* classLoaderContext */, false /* newProfile */, false /* downgrade */);
             } catch (FileNotFoundException ignored) {
                 // Do not add to the classpath.
                 Log.w(TAG, "Missing classpath element for system server: " + classPathElement);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 82eb1ab..03603e4 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -58,10 +58,12 @@
     void onNotificationError(String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId);
     void onClearAllNotifications(int userId);
-    void onNotificationClear(String pkg, String tag, int id, int userId);
+    void onNotificationClear(String pkg, String tag, int id, int userId, String key, int dismissalSurface);
     void onNotificationVisibilityChanged( in NotificationVisibility[] newlyVisibleKeys,
             in NotificationVisibility[] noLongerVisibleKeys);
     void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded);
+    void onNotificationDirectReplied(String key);
+    void onNotificationSettingsViewed(String key);
     void setSystemUiVisibility(int vis, int mask, String cause);
 
     void onGlobalActionsShown();
diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java
index 28f12eb..ba80aea 100644
--- a/core/java/com/android/internal/util/BitUtils.java
+++ b/core/java/com/android/internal/util/BitUtils.java
@@ -93,6 +93,10 @@
         return s & 0xffff;
     }
 
+    public static int uint16(byte hi, byte lo) {
+        return ((hi & 0xff) << 8) | (lo & 0xff);
+    }
+
     public static long uint32(int i) {
         return i & 0xffffffffL;
     }
diff --git a/core/java/com/android/internal/util/LocalLog.java b/core/java/com/android/internal/util/LocalLog.java
index f0e6171..8edb739 100644
--- a/core/java/com/android/internal/util/LocalLog.java
+++ b/core/java/com/android/internal/util/LocalLog.java
@@ -20,6 +20,7 @@
 import java.util.ArrayList;
 
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 /**
  * Helper class for logging serious issues, which also keeps a small
@@ -63,4 +64,16 @@
             return true;
         }
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        synchronized (mLines) {
+            for (int i = 0; i < mLines.size(); ++i) {
+                proto.write(LocalLogProto.LINES, mLines.get(i));
+            }
+        }
+
+        proto.end(token);
+    }
 }
diff --git a/core/java/com/android/internal/util/MemInfoReader.java b/core/java/com/android/internal/util/MemInfoReader.java
index b71fa06..8d71666 100644
--- a/core/java/com/android/internal/util/MemInfoReader.java
+++ b/core/java/com/android/internal/util/MemInfoReader.java
@@ -82,7 +82,7 @@
      * that are mapped in to processes.
      */
     public long getCachedSizeKb() {
-        return mInfos[Debug.MEMINFO_BUFFERS]
+        return mInfos[Debug.MEMINFO_BUFFERS] + mInfos[Debug.MEMINFO_SLAB_RECLAIMABLE]
                 + mInfos[Debug.MEMINFO_CACHED] - mInfos[Debug.MEMINFO_MAPPED];
     }
 
@@ -90,7 +90,7 @@
      * Amount of RAM that is in use by the kernel for actual allocations.
      */
     public long getKernelUsedSizeKb() {
-        return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB]
+        return mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE]
                 + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES]
                 + mInfos[Debug.MEMINFO_KERNEL_STACK];
     }
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
new file mode 100644
index 0000000..ad84353
--- /dev/null
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.internal.util;
+
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * A simple ring buffer structure with bounded capacity backed by an array.
+ * Events can always be added at the logical end of the buffer. If the buffer is
+ * full, oldest events are dropped when new events are added.
+ * {@hide}
+ */
+public class RingBuffer<T> {
+
+    // Array for storing events.
+    private final T[] mBuffer;
+    // Cursor keeping track of the logical end of the array. This cursor never
+    // wraps and instead keeps track of the total number of append() operations.
+    private long mCursor = 0;
+
+    public RingBuffer(Class<T> c, int capacity) {
+        checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
+        // Java cannot create generic arrays without a runtime hint.
+        mBuffer = (T[]) Array.newInstance(c, capacity);
+    }
+
+    public int size() {
+        return (int) Math.min(mBuffer.length, (long) mCursor);
+    }
+
+    public void append(T t) {
+        mBuffer[indexOf(mCursor++)] = t;
+    }
+
+    public T[] toArray() {
+        // Only generic way to create a T[] from another T[]
+        T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
+        // Reverse iteration from youngest event to oldest event.
+        long inCursor = mCursor - 1;
+        int outIdx = out.length - 1;
+        while (outIdx >= 0) {
+            out[outIdx--] = (T) mBuffer[indexOf(inCursor--)];
+        }
+        return out;
+    }
+
+    private int indexOf(long cursor) {
+        return (int) Math.abs(cursor % mBuffer.length);
+    }
+}
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 8d9630f..e5ad1f4 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -804,7 +804,7 @@
 
                 /** State that processed the message */
                 State msgProcessedState = null;
-                if (mIsConstructionCompleted) {
+                if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
                     /** Normal path */
                     msgProcessedState = processMsg(msg);
                 } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
index 46098c5..70b6f96 100644
--- a/core/java/com/android/internal/util/WakeupMessage.java
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -47,17 +47,19 @@
     protected final int mCmd, mArg1, mArg2;
     @VisibleForTesting
     protected final Object mObj;
+    private final Runnable mRunnable;
     private boolean mScheduled;
 
     public WakeupMessage(Context context, Handler handler,
             String cmdName, int cmd, int arg1, int arg2, Object obj) {
-        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mAlarmManager = getAlarmManager(context);
         mHandler = handler;
         mCmdName = cmdName;
         mCmd = cmd;
         mArg1 = arg1;
         mArg2 = arg2;
         mObj = obj;
+        mRunnable = null;
     }
 
     public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
@@ -73,6 +75,21 @@
         this(context, handler, cmdName, cmd, 0, 0, null);
     }
 
+    public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+        mAlarmManager = getAlarmManager(context);
+        mHandler = handler;
+        mCmdName = cmdName;
+        mCmd = 0;
+        mArg1 = 0;
+        mArg2 = 0;
+        mObj = null;
+        mRunnable = runnable;
+    }
+
+    private static AlarmManager getAlarmManager(Context context) {
+        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+    }
+
     /**
      * Schedule the message to be delivered at the time in milliseconds of the
      * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
@@ -107,7 +124,12 @@
             mScheduled = false;
         }
         if (stillScheduled) {
-            Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+            Message msg;
+            if (mRunnable == null) {
+                msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+            } else {
+                msg = Message.obtain(mHandler, mRunnable);
+            }
             mHandler.dispatchMessage(msg);
             msg.recycle();
         }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index cc0ef75..5b65bbe 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.view;
 
+import android.annotation.AnyThread;
+import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
@@ -34,6 +36,7 @@
 import android.view.inputmethod.InputContentInfo;
 
 import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class InputConnectionWrapper implements InputConnection {
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
@@ -44,6 +47,14 @@
     @MissingMethodFlags
     private final int mMissingMethods;
 
+    /**
+     * {@code true} if the system already decided to take away IME focus from the target app. This
+     * can be signaled even when the corresponding signal is in the task queue and
+     * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+     */
+    @NonNull
+    private final AtomicBoolean mIsUnbindIssued;
+
     static class InputContextCallback extends IInputContextCallback.Stub {
         private static final String TAG = "InputConnectionWrapper.ICC";
         public int mSeq;
@@ -67,6 +78,7 @@
          * sequence number is set to a new integer.  We use a sequence number so that replies that
          * occur after a timeout has expired are not interpreted as replies to a later request.
          */
+        @AnyThread
         private static InputContextCallback getInstance() {
             synchronized (InputContextCallback.class) {
                 // Return sInstance if it's non-null, otherwise construct a new callback
@@ -90,6 +102,7 @@
         /**
          * Makes the given InputContextCallback available for use in the future.
          */
+        @AnyThread
         private void dispose() {
             synchronized (InputContextCallback.class) {
                 // If sInstance is non-null, just let this object be garbage-collected
@@ -102,7 +115,8 @@
                 }
             }
         }
-        
+
+        @BinderThread
         public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -116,6 +130,7 @@
             }
         }
 
+        @BinderThread
         public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -129,6 +144,7 @@
             }
         }
 
+        @BinderThread
         public void setSelectedText(CharSequence selectedText, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -142,6 +158,7 @@
             }
         }
 
+        @BinderThread
         public void setCursorCapsMode(int capsMode, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -155,6 +172,7 @@
             }
         }
 
+        @BinderThread
         public void setExtractedText(ExtractedText extractedText, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -168,6 +186,7 @@
             }
         }
 
+        @BinderThread
         public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -181,6 +200,7 @@
             }
         }
 
+        @BinderThread
         public void setCommitContentResult(boolean result, int seq) {
             synchronized (this) {
                 if (seq == mSeq) {
@@ -199,6 +219,7 @@
          * 
          * <p>The caller must be synchronized on this callback object.
          */
+        @AnyThread
         void waitForResultLocked() {
             long startTime = SystemClock.uptimeMillis();
             long endTime = startTime + MAX_WAIT_TIME_MILLIS;
@@ -219,13 +240,20 @@
 
     public InputConnectionWrapper(
             @NonNull WeakReference<AbstractInputMethodService> inputMethodService,
-            IInputContext inputContext, @MissingMethodFlags final int missingMethods) {
+            IInputContext inputContext, @MissingMethodFlags final int missingMethods,
+            @NonNull AtomicBoolean isUnbindIssued) {
         mInputMethodService = inputMethodService;
         mIInputContext = inputContext;
         mMissingMethods = missingMethods;
+        mIsUnbindIssued = isUnbindIssued;
     }
 
+    @AnyThread
     public CharSequence getTextAfterCursor(int length, int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         CharSequence value = null;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -242,8 +270,13 @@
         }
         return value;
     }
-    
+
+    @AnyThread
     public CharSequence getTextBeforeCursor(int length, int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         CharSequence value = null;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -261,7 +294,12 @@
         return value;
     }
 
+    @AnyThread
     public CharSequence getSelectedText(int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         if (isMethodMissing(MissingMethodFlags.GET_SELECTED_TEXT)) {
             // This method is not implemented.
             return null;
@@ -283,7 +321,12 @@
         return value;
     }
 
+    @AnyThread
     public int getCursorCapsMode(int reqModes) {
+        if (mIsUnbindIssued.get()) {
+            return 0;
+        }
+
         int value = 0;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -301,7 +344,12 @@
         return value;
     }
 
+    @AnyThread
     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        if (mIsUnbindIssued.get()) {
+            return null;
+        }
+
         ExtractedText value = null;
         try {
             InputContextCallback callback = InputContextCallback.getInstance();
@@ -318,7 +366,8 @@
         }
         return value;
     }
-    
+
+    @AnyThread
     public boolean commitText(CharSequence text, int newCursorPosition) {
         try {
             mIInputContext.commitText(text, newCursorPosition);
@@ -328,6 +377,7 @@
         }
     }
 
+    @AnyThread
     public boolean commitCompletion(CompletionInfo text) {
         if (isMethodMissing(MissingMethodFlags.COMMIT_CORRECTION)) {
             // This method is not implemented.
@@ -341,6 +391,7 @@
         }
     }
 
+    @AnyThread
     public boolean commitCorrection(CorrectionInfo correctionInfo) {
         try {
             mIInputContext.commitCorrection(correctionInfo);
@@ -350,6 +401,7 @@
         }
     }
 
+    @AnyThread
     public boolean setSelection(int start, int end) {
         try {
             mIInputContext.setSelection(start, end);
@@ -358,7 +410,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean performEditorAction(int actionCode) {
         try {
             mIInputContext.performEditorAction(actionCode);
@@ -367,7 +420,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean performContextMenuAction(int id) {
         try {
             mIInputContext.performContextMenuAction(id);
@@ -377,6 +431,7 @@
         }
     }
 
+    @AnyThread
     public boolean setComposingRegion(int start, int end) {
         if (isMethodMissing(MissingMethodFlags.SET_COMPOSING_REGION)) {
             // This method is not implemented.
@@ -390,6 +445,7 @@
         }
     }
 
+    @AnyThread
     public boolean setComposingText(CharSequence text, int newCursorPosition) {
         try {
             mIInputContext.setComposingText(text, newCursorPosition);
@@ -399,6 +455,7 @@
         }
     }
 
+    @AnyThread
     public boolean finishComposingText() {
         try {
             mIInputContext.finishComposingText();
@@ -408,6 +465,7 @@
         }
     }
 
+    @AnyThread
     public boolean beginBatchEdit() {
         try {
             mIInputContext.beginBatchEdit();
@@ -416,7 +474,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean endBatchEdit() {
         try {
             mIInputContext.endBatchEdit();
@@ -425,7 +484,8 @@
             return false;
         }
     }
-    
+
+    @AnyThread
     public boolean sendKeyEvent(KeyEvent event) {
         try {
             mIInputContext.sendKeyEvent(event);
@@ -435,6 +495,7 @@
         }
     }
 
+    @AnyThread
     public boolean clearMetaKeyStates(int states) {
         try {
             mIInputContext.clearMetaKeyStates(states);
@@ -444,6 +505,7 @@
         }
     }
 
+    @AnyThread
     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
         try {
             mIInputContext.deleteSurroundingText(beforeLength, afterLength);
@@ -453,6 +515,7 @@
         }
     }
 
+    @AnyThread
     public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
         if (isMethodMissing(MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)) {
             // This method is not implemented.
@@ -466,11 +529,13 @@
         }
     }
 
+    @AnyThread
     public boolean reportFullscreenMode(boolean enabled) {
         // Nothing should happen when called from input method.
         return false;
     }
 
+    @AnyThread
     public boolean performPrivateCommand(String action, Bundle data) {
         try {
             mIInputContext.performPrivateCommand(action, data);
@@ -480,7 +545,12 @@
         }
     }
 
+    @AnyThread
     public boolean requestCursorUpdates(int cursorUpdateMode) {
+        if (mIsUnbindIssued.get()) {
+            return false;
+        }
+
         boolean result = false;
         if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
             // This method is not implemented.
@@ -502,16 +572,23 @@
         return result;
     }
 
+    @AnyThread
     public Handler getHandler() {
         // Nothing should happen when called from input method.
         return null;
     }
 
+    @AnyThread
     public void closeConnection() {
         // Nothing should happen when called from input method.
     }
 
+    @AnyThread
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+        if (mIsUnbindIssued.get()) {
+            return false;
+        }
+
         boolean result = false;
         if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
             // This method is not implemented.
@@ -542,10 +619,12 @@
         return result;
     }
 
+    @AnyThread
     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
         return (mMissingMethods & methodFlag) == methodFlag;
     }
 
+    @AnyThread
     @Override
     public String toString() {
         return "InputConnectionWrapper{idHash=#"
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index f76c724..8f80bfe 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -319,13 +319,15 @@
     public void setGroupDividerEnabled(boolean groupDividerEnabled) {
         // If mHasListDivider is true, disabling the groupDivider.
         // Otherwise, checking enbling it according to groupDividerEnabled flag.
-        mGroupDivider.setVisibility(!mHasListDivider
-                && groupDividerEnabled ? View.VISIBLE : View.GONE);
+        if (mGroupDivider != null) {
+            mGroupDivider.setVisibility(!mHasListDivider
+                    && groupDividerEnabled ? View.VISIBLE : View.GONE);
+        }
     }
 
     @Override
     public void adjustListItemSelectionBounds(Rect rect) {
-        if (mGroupDivider.getVisibility() == View.VISIBLE) {
+        if (mGroupDivider != null && mGroupDivider.getVisibility() == View.VISIBLE) {
             // groupDivider is a part of MenuItemListView.
             // If ListMenuItem with divider enabled is hovered/clicked, divider also gets selected.
             // Clipping the selector bounds from the top divider portion when divider is enabled,
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 15e271e..9d012de 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -658,7 +658,7 @@
 
     @Override
     public boolean requiresOverflow() {
-        return (mShowAsAction & SHOW_AS_OVERFLOW_ALWAYS) == SHOW_AS_OVERFLOW_ALWAYS;
+        return !requiresActionButton() && !requestsActionButton();
     }
 
     public void setIsActionButton(boolean isActionButton) {
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index f63b5a2..e3b1c01 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -64,6 +64,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
@@ -118,6 +119,36 @@
     };
 
     /**
+     * Sorts the list of menu items to conform to certain requirements.
+     */
+    private final Comparator<MenuItem> mMenuItemComparator = (menuItem1, menuItem2) -> {
+        // Ensure the assist menu item is always the first item:
+        if (menuItem1.getItemId() == android.R.id.textAssist) {
+            return menuItem2.getItemId() == android.R.id.textAssist ? 0 : -1;
+        }
+        if (menuItem2.getItemId() == android.R.id.textAssist) {
+            return 1;
+        }
+
+        // Order by SHOW_AS_ACTION type:
+        if (menuItem1.requiresActionButton()) {
+            return menuItem2.requiresActionButton() ? 0 : -1;
+        }
+        if (menuItem2.requiresActionButton()) {
+            return 1;
+        }
+        if (menuItem1.requiresOverflow()) {
+            return menuItem2.requiresOverflow() ? 0 : 1;
+        }
+        if (menuItem2.requiresOverflow()) {
+            return -1;
+        }
+
+        // Order by order value:
+        return menuItem1.getOrder() - menuItem2.getOrder();
+    };
+
+    /**
      * Initializes a floating toolbar.
      */
     public FloatingToolbar(Window window) {
@@ -230,7 +261,7 @@
 
     private void doShow() {
         List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
-        tidy(menuItems);
+        menuItems.sort(mMenuItemComparator);
         if (!isCurrentlyShowing(menuItems) || mWidthChanged) {
             mPopup.dismiss();
             mPopup.layoutMenuItems(menuItems, mMenuItemClickListener, mSuggestedWidth);
@@ -288,36 +319,6 @@
         return menuItems;
     }
 
-    /**
-     * Update the list of menu items to conform to certain requirements.
-     */
-    private void tidy(List<MenuItem> menuItems) {
-        int assistItemIndex = -1;
-        Drawable assistItemDrawable = null;
-
-        final int size = menuItems.size();
-        for (int i = 0; i < size; i++) {
-            final MenuItem menuItem = menuItems.get(i);
-
-            if (menuItem.getItemId() == android.R.id.textAssist) {
-                assistItemIndex = i;
-                assistItemDrawable = menuItem.getIcon();
-            }
-
-            // Remove icons for all menu items with text.
-            if (!TextUtils.isEmpty(menuItem.getTitle())) {
-                menuItem.setIcon(null);
-            }
-        }
-        if (assistItemIndex > -1) {
-            final MenuItem assistMenuItem = menuItems.remove(assistItemIndex);
-            // Ensure the assist menu item preserves its icon.
-            assistMenuItem.setIcon(assistItemDrawable);
-            // Ensure the assist menu item is always the first item.
-            menuItems.add(0, assistMenuItem);
-        }
-    }
-
     private void registerOrientationHandler() {
         unregisterOrientationHandler();
         mWindow.getDecorView().addOnLayoutChangeListener(mOrientationChangeHandler);
@@ -1148,7 +1149,8 @@
             // add the overflow menu items to the end of the remainingMenuItems list.
             final LinkedList<MenuItem> overflowMenuItems = new LinkedList();
             for (MenuItem menuItem : menuItems) {
-                if (menuItem.requiresOverflow()) {
+                if (menuItem.getItemId() != android.R.id.textAssist
+                        && menuItem.requiresOverflow()) {
                     overflowMenuItems.add(menuItem);
                 } else {
                     remainingMenuItems.add(menuItem);
@@ -1171,7 +1173,9 @@
                     break;
                 }
 
-                View menuItemButton = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+                final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
+                final View menuItemButton = createMenuItemButton(
+                        mContext, menuItem, mIconTextSpacing, showIcon);
 
                 // Adding additional start padding for the first button to even out button spacing.
                 if (isFirstItem) {
@@ -1193,16 +1197,17 @@
                 }
 
                 menuItemButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-                final int menuItemButtonWidth = Math.min(menuItemButton.getMeasuredWidth(), toolbarWidth);
+                final int menuItemButtonWidth = Math.min(
+                        menuItemButton.getMeasuredWidth(), toolbarWidth);
 
                 final boolean isNewGroup = !isFirstItem && lastGroupId != menuItem.getGroupId();
                 final int extraPadding = isNewGroup ? menuItemButton.getPaddingEnd() * 2 : 0;
 
                 // Check if we can fit an item while reserving space for the overflowButton.
-                boolean canFitWithOverflow =
+                final boolean canFitWithOverflow =
                         menuItemButtonWidth <=
                                 availableWidth - mOverflowButtonSize.getWidth() - extraPadding;
-                boolean canFitNoOverflow =
+                final boolean canFitNoOverflow =
                         isLastItem && menuItemButtonWidth <= availableWidth - extraPadding;
                 if (canFitWithOverflow || canFitNoOverflow) {
                     if (isNewGroup) {
@@ -1211,7 +1216,8 @@
 
                         // Add extra padding to the end of the previous button.
                         // Half of the extra padding (less borderWidth) goes to the previous button.
-                        View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1);
+                        final View previousButton = mMainPanel.getChildAt(
+                                mMainPanel.getChildCount() - 1);
                         final int prevPaddingEnd = previousButton.getPaddingEnd()
                                 + extraPadding / 2 - dividerWidth;
                         previousButton.setPaddingRelative(
@@ -1612,7 +1618,8 @@
             public View getView(MenuItem menuItem, int minimumWidth, View convertView) {
                 Preconditions.checkNotNull(menuItem);
                 if (convertView != null) {
-                    updateMenuItemButton(convertView, menuItem, mIconTextSpacing);
+                    updateMenuItemButton(
+                            convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 } else {
                     convertView = createMenuButton(menuItem);
                 }
@@ -1621,17 +1628,26 @@
             }
 
             public int calculateWidth(MenuItem menuItem) {
-                updateMenuItemButton(mCalculator, menuItem, mIconTextSpacing);
+                updateMenuItemButton(
+                        mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 mCalculator.measure(
                         View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
                 return mCalculator.getMeasuredWidth();
             }
 
             private View createMenuButton(MenuItem menuItem) {
-                View button = createMenuItemButton(mContext, menuItem, mIconTextSpacing);
+                View button = createMenuItemButton(
+                        mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
                 button.setPadding(mSidePadding, 0, mSidePadding, 0);
                 return button;
             }
+
+            private boolean shouldShowIcon(MenuItem menuItem) {
+                if (menuItem != null) {
+                    return menuItem.getGroupId() == android.R.id.textAssist;
+                }
+                return false;
+            }
         }
     }
 
@@ -1639,11 +1655,11 @@
      * Creates and returns a menu button for the specified menu item.
      */
     private static View createMenuItemButton(
-            Context context, MenuItem menuItem, int iconTextSpacing) {
+            Context context, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
         final View menuItemButton = LayoutInflater.from(context)
                 .inflate(R.layout.floating_popup_menu_button, null);
         if (menuItem != null) {
-            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing);
+            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
         }
         return menuItemButton;
     }
@@ -1652,18 +1668,19 @@
      * Updates the specified menu item button with the specified menu item data.
      */
     private static void updateMenuItemButton(
-            View menuItemButton, MenuItem menuItem, int iconTextSpacing) {
-        final TextView buttonText = (TextView) menuItemButton.findViewById(
+            View menuItemButton, MenuItem menuItem, int iconTextSpacing, boolean showIcon) {
+        final TextView buttonText = menuItemButton.findViewById(
                 R.id.floating_toolbar_menu_item_text);
+        buttonText.setEllipsize(null);
         if (TextUtils.isEmpty(menuItem.getTitle())) {
             buttonText.setVisibility(View.GONE);
         } else {
             buttonText.setVisibility(View.VISIBLE);
             buttonText.setText(menuItem.getTitle());
         }
-        final ImageView buttonIcon = (ImageView) menuItemButton
-                .findViewById(R.id.floating_toolbar_menu_item_image);
-        if (menuItem.getIcon() == null) {
+        final ImageView buttonIcon = menuItemButton.findViewById(
+                R.id.floating_toolbar_menu_item_image);
+        if (menuItem.getIcon() == null || !showIcon) {
             buttonIcon.setVisibility(View.GONE);
             if (buttonText != null) {
                 buttonText.setPaddingRelative(0, 0, 0, 0);
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
deleted file mode 100644
index 7294124..0000000
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 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.internal.widget;
-
-import android.os.IBinder;
-
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
-    void onServiceConnected(IBinder service);
-    void onServiceDisconnected();
-}
diff --git a/core/java/com/android/internal/widget/LinearLayoutManager.java b/core/java/com/android/internal/widget/LinearLayoutManager.java
index d82c746..0000a74 100644
--- a/core/java/com/android/internal/widget/LinearLayoutManager.java
+++ b/core/java/com/android/internal/widget/LinearLayoutManager.java
@@ -168,10 +168,6 @@
     /**
      * Constructor used when layout manager is set in XML by RecyclerView attribute
      * "layoutManager". Defaults to vertical orientation.
-     *
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
      */
     public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index b8ef82b..4be6b28 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -303,7 +303,7 @@
     }
 
     public void reportFailedPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -311,7 +311,7 @@
     }
 
     public void reportSuccessfulPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -319,21 +319,21 @@
     }
 
     public void reportPasswordLockout(int timeoutMs, int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getTrustManager().reportUnlockLockout(timeoutMs, userId);
     }
 
     public int getCurrentFailedPasswordAttempts(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return 0;
         }
         return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
     }
 
     public int getMaximumFailedPasswordsForWipe(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return 0;
         }
         return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -1774,11 +1774,12 @@
         return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
     }
 
-    public static boolean userOwnsFrpCredential(UserInfo info) {
-        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled();
+    public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
+        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
     }
 
-    public static boolean frpCredentialEnabled() {
-        return FRP_CREDENTIAL_ENABLED;
+    public static boolean frpCredentialEnabled(Context context) {
+        return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
     }
 }
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
new file mode 100644
index 0000000..f6741c3
--- /dev/null
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -0,0 +1,218 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.PixelCopy;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Android magnifier widget. Can be used by any view which is attached to window.
+ */
+public final class Magnifier {
+    private static final String LOG_TAG = "magnifier";
+    private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
+    // The view for which this magnifier is attached.
+    private final View mView;
+    // The window containing the magnifier.
+    private final PopupWindow mWindow;
+    // The center coordinates of the window containing the magnifier.
+    private final Point mWindowCoords = new Point();
+    // The width of the window containing the magnifier.
+    private final int mWindowWidth;
+    // The height of the window containing the magnifier.
+    private final int mWindowHeight;
+    // The bitmap used to display the contents of the magnifier.
+    private final Bitmap mBitmap;
+    // The center coordinates of the content that is to be magnified.
+    private final Point mCenterZoomCoords = new Point();
+    // The callback of the pixel copy request will be invoked on this Handler when
+    // the copy is finished.
+    private final Handler mPixelCopyHandler = Handler.getMain();
+    // Current magnification scale.
+    private final float mZoomScale;
+    // Timer used to schedule the copy task.
+    private Timer mTimer;
+
+    /**
+     * Initializes a magnifier.
+     *
+     * @param view the view for which this magnifier is attached
+     */
+    @UiThread
+    public Magnifier(@NonNull View view) {
+        mView = Preconditions.checkNotNull(view);
+        final Context context = mView.getContext();
+        final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
+        final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+        content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
+        mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
+        mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
+        mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
+
+        mWindow = new PopupWindow(context);
+        mWindow.setContentView(content);
+        mWindow.setWidth(mWindowWidth);
+        mWindow.setHeight(mWindowHeight);
+        mWindow.setElevation(elevation);
+        mWindow.setTouchable(false);
+        mWindow.setBackgroundDrawable(null);
+
+        final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
+        final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
+        mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+        getImageView().setImageBitmap(mBitmap);
+    }
+
+    /**
+     * Shows the magnifier on the screen.
+     *
+     * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
+     *        to the view. The lower end is clamped to 0
+     * @param yPosInView vertical coordinate of the center point of the magnifier source
+     *        relative to the view. The lower end is clamped to 0
+     */
+    public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
+        if (xPosInView < 0) {
+            xPosInView = 0;
+        }
+
+        if (yPosInView < 0) {
+            yPosInView = 0;
+        }
+
+        configureCoordinates(xPosInView, yPosInView);
+
+        if (mTimer == null) {
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    performPixelCopy();
+                }
+            }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
+        }
+
+        if (mWindow.isShowing()) {
+            mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
+                    mWindow.getHeight());
+        } else {
+            mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
+                    mWindowCoords.x, mWindowCoords.y);
+        }
+    }
+
+    /**
+     * Dismisses the magnifier from the screen.
+     */
+    public void dismiss() {
+        mWindow.dismiss();
+
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer.purge();
+            mTimer = null;
+        }
+    }
+
+    /**
+     * @return the height of the magnifier window.
+     */
+    public int getHeight() {
+        return mWindowHeight;
+    }
+
+    /**
+     * @return the width of the magnifier window.
+     */
+    public int getWidth() {
+        return mWindowWidth;
+    }
+
+    /**
+     * @return the zoom scale of the magnifier.
+     */
+    public float getZoomScale() {
+        return mZoomScale;
+    }
+
+    private void configureCoordinates(float xPosInView, float yPosInView) {
+        final int[] coordinatesOnScreen = new int[2];
+        mView.getLocationOnScreen(coordinatesOnScreen);
+        final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
+        final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
+
+        mCenterZoomCoords.x = (int) posXOnScreen;
+        mCenterZoomCoords.y = (int) posYOnScreen;
+
+        final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
+                R.dimen.magnifier_offset);
+        mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
+        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
+    }
+
+    private void performPixelCopy() {
+        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+        int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+
+        // Clamp startX value to avoid distorting the rendering of the magnifier content.
+        if (rawStartX < 0) {
+            rawStartX = 0;
+        } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
+            rawStartX = mView.getWidth() - mBitmap.getWidth();
+        }
+
+        final int startX = rawStartX;
+        final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
+
+        if (viewRootImpl != null && viewRootImpl.mSurface != null
+                && viewRootImpl.mSurface.isValid()) {
+            PixelCopy.request(
+                    viewRootImpl.mSurface,
+                    new Rect(startX, startY, startX + mBitmap.getWidth(),
+                            startY + mBitmap.getHeight()),
+                    mBitmap,
+                    result -> getImageView().invalidate(),
+                    mPixelCopyHandler);
+        } else {
+            Log.d(LOG_TAG, "Could not perform PixelCopy request");
+        }
+    }
+
+    private ImageView getImageView() {
+        return mWindow.getContentView().findViewById(R.id.magnifier_image);
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d63e22c..f88db25 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -1,3 +1,13 @@
+
+genrule {
+    name: "android_util_StatsLog.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --jni $(genDir)/android_util_StatsLog.cpp",
+    out: [
+        "android_util_StatsLog.cpp",
+    ],
+}
+
 cc_library_shared {
     name: "libandroid_runtime",
 
@@ -28,6 +38,7 @@
     ],
 
     cppflags: ["-Wno-conversion-null"],
+    cpp_std: "c++17",
 
     srcs: [
         "AndroidRuntime.cpp",
@@ -77,7 +88,7 @@
         "android_view_ThreadedRenderer.cpp",
         "android_view_VelocityTracker.cpp",
         "android_text_AndroidCharacter.cpp",
-        "android_text_AndroidBidi.cpp",
+        "android_text_Hyphenator.cpp",
         "android_text_StaticLayout.cpp",
         "android_os_Debug.cpp",
         "android_os_GraphicsEnvironment.cpp",
@@ -103,7 +114,6 @@
         "android_nio_utils.cpp",
         "android_util_AssetManager.cpp",
         "android_util_Binder.cpp",
-	"android_util_StatsLog.cpp",
         "android_util_EventLog.cpp",
         "android_util_MemoryIntArray.cpp",
         "android_util_Log.cpp",
@@ -239,7 +249,6 @@
         "libinput",
         "libcamera_client",
         "libcamera_metadata",
-        "libskia",
         "libsqlite",
         "libEGL",
         "libGLESv1_CM",
@@ -271,11 +280,13 @@
         "libhwbinder",
         "libvintf",
         "libnativewindow",
-
         "libhwui",
         "libdl",
+        "libstatslog",
     ],
 
+    generated_sources: ["android_util_StatsLog.cpp"],
+
     local_include_dirs: ["android/graphics"],
     export_include_dirs: [
         ".",
@@ -291,13 +302,4 @@
         // GraphicsJNI.h includes hwui headers
         "libhwui",
     ],
-
-    product_variables: {
-        debuggable: {
-            cflags: ["-D__ANDROID_DEBUGGABLE__"]
-        },
-        treble: {
-            cflags: ["-D__ANDROID_TREBLE__"]
-        },
-    },
 }
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 02c9848..8977891 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -19,6 +19,8 @@
 #define LOG_NDEBUG 1
 
 #include <android_runtime/AndroidRuntime.h>
+
+#include <android-base/properties.h>
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
@@ -47,8 +49,8 @@
 #include <string>
 #include <vector>
 
-
 using namespace android;
+using android::base::GetProperty;
 
 extern int register_android_os_Binder(JNIEnv* env);
 extern int register_android_os_Process(JNIEnv* env);
@@ -173,8 +175,8 @@
 extern int register_android_net_NetworkUtils(JNIEnv* env);
 extern int register_android_net_TrafficStats(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
+extern int register_android_text_Hyphenator(JNIEnv *env);
 extern int register_android_text_StaticLayout(JNIEnv *env);
-extern int register_android_text_AndroidBidi(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
 extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env);
@@ -391,17 +393,6 @@
     return false;
 }
 
-// Convenience wrapper over the property API that returns an
-// std::string.
-std::string getProperty(const char* key, const char* defaultValue) {
-    std::vector<char> temp(PROPERTY_VALUE_MAX);
-    const int len = property_get(key, &temp[0], defaultValue);
-    if (len < 0) {
-        return "";
-    }
-    return std::string(&temp[0], len);
-}
-
 /*
  * Read the persistent locale. Inspects the following system properties
  * (in order) and returns the first non-empty property in the list :
@@ -418,15 +409,15 @@
  */
 const std::string readLocale()
 {
-    const std::string locale = getProperty("persist.sys.locale", "");
+    const std::string locale = GetProperty("persist.sys.locale", "");
     if (!locale.empty()) {
         return locale;
     }
 
-    const std::string language = getProperty("persist.sys.language", "");
+    const std::string language = GetProperty("persist.sys.language", "");
     if (!language.empty()) {
-        const std::string country = getProperty("persist.sys.country", "");
-        const std::string variant = getProperty("persist.sys.localevar", "");
+        const std::string country = GetProperty("persist.sys.country", "");
+        const std::string variant = GetProperty("persist.sys.localevar", "");
 
         std::string out = language;
         if (!country.empty()) {
@@ -440,15 +431,15 @@
         return out;
     }
 
-    const std::string productLocale = getProperty("ro.product.locale", "");
+    const std::string productLocale = GetProperty("ro.product.locale", "");
     if (!productLocale.empty()) {
         return productLocale;
     }
 
     // If persist.sys.locale and ro.product.locale are missing,
     // construct a locale value from the individual locale components.
-    const std::string productLanguage = getProperty("ro.product.locale.language", "en");
-    const std::string productRegion = getProperty("ro.product.locale.region", "US");
+    const std::string productLanguage = GetProperty("ro.product.locale.language", "en");
+    const std::string productRegion = GetProperty("ro.product.locale.region", "US");
 
     return productLanguage + "-" + productRegion;
 }
@@ -616,9 +607,12 @@
     char jitprithreadweightOptBuf[sizeof("-Xjitprithreadweight:")-1 + PROPERTY_VALUE_MAX];
     char jittransitionweightOptBuf[sizeof("-Xjittransitionweight:")-1 + PROPERTY_VALUE_MAX];
     char hotstartupsamplesOptsBuf[sizeof("-Xps-hot-startup-method-samples:")-1 + PROPERTY_VALUE_MAX];
+    char madviseRandomOptsBuf[sizeof("-XX:MadviseRandomAccess:")-1 + PROPERTY_VALUE_MAX];
     char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
     char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
     char heaptargetutilizationOptsBuf[sizeof("-XX:HeapTargetUtilization=")-1 + PROPERTY_VALUE_MAX];
+    char foregroundHeapGrowthMultiplierOptsBuf[
+            sizeof("-XX:ForegroundHeapGrowthMultiplier=")-1 + PROPERTY_VALUE_MAX];
     char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
@@ -648,7 +642,7 @@
     char cpuAbiListBuf[sizeof("--cpu-abilist=") + PROPERTY_VALUE_MAX];
     char methodTraceFileBuf[sizeof("-Xmethod-trace-file:") + PROPERTY_VALUE_MAX];
     char methodTraceFileSizeBuf[sizeof("-Xmethod-trace-file-size:") + PROPERTY_VALUE_MAX];
-    char fingerprintBuf[sizeof("-Xfingerprint:") + PROPERTY_VALUE_MAX];
+    std::string fingerprintBuf;
 
     bool checkJni = false;
     property_get("dalvik.vm.checkjni", propBuf, "");
@@ -722,6 +716,11 @@
                        heaptargetutilizationOptsBuf,
                        "-XX:HeapTargetUtilization=");
 
+    /* Foreground heap growth multiplier option */
+    parseRuntimeOption("dalvik.vm.foreground-heap-growth-multiplier",
+                       foregroundHeapGrowthMultiplierOptsBuf,
+                       "-XX:ForegroundHeapGrowthMultiplier=");
+
     /*
      * JIT related options.
      */
@@ -743,6 +742,11 @@
                        "-Xjittransitionweight:");
 
     /*
+     * Madvise related options.
+     */
+    parseRuntimeOption("dalvik.vm.madvise-random", madviseRandomOptsBuf, "-XX:MadviseRandomAccess:");
+
+    /*
      * Profile related options.
      */
     parseRuntimeOption("dalvik.vm.hot-startup-method-samples", hotstartupsamplesOptsBuf,
@@ -963,8 +967,15 @@
     /*
      * Retrieve the build fingerprint and provide it to the runtime. That way, ANR dumps will
      * contain the fingerprint and can be parsed.
+     * Fingerprints are potentially longer than PROPERTY_VALUE_MAX, so parseRuntimeOption() cannot
+     * be used here.
+     * Do not ever re-assign fingerprintBuf as its c_str() value is stored in mOptions.
      */
-    parseRuntimeOption("ro.build.fingerprint", fingerprintBuf, "-Xfingerprint:");
+    std::string fingerprint = GetProperty("ro.build.fingerprint", "");
+    if (!fingerprint.empty()) {
+        fingerprintBuf = "-Xfingerprint:" + fingerprint;
+        addOption(fingerprintBuf.c_str());
+    }
 
     initArgs.version = JNI_VERSION_1_4;
     initArgs.options = mOptions.editArray();
@@ -1312,17 +1323,17 @@
     REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
     REG_JNI(register_android_os_SystemClock),
     REG_JNI(register_android_util_EventLog),
-    REG_JNI(register_android_util_StatsLog),
     REG_JNI(register_android_util_Log),
     REG_JNI(register_android_util_MemoryIntArray),
     REG_JNI(register_android_util_PathParser),
+    REG_JNI(register_android_util_StatsLog),
     REG_JNI(register_android_app_admin_SecurityLog),
     REG_JNI(register_android_content_AssetManager),
     REG_JNI(register_android_content_StringBlock),
     REG_JNI(register_android_content_XmlBlock),
     REG_JNI(register_android_text_AndroidCharacter),
+    REG_JNI(register_android_text_Hyphenator),
     REG_JNI(register_android_text_StaticLayout),
-    REG_JNI(register_android_text_AndroidBidi),
     REG_JNI(register_android_view_InputDevice),
     REG_JNI(register_android_view_KeyCharacterMap),
     REG_JNI(register_android_os_Process),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index ad05a51..5498a93 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -682,6 +682,8 @@
 
     sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);
     if (!nativeBitmap) {
+        ALOGE("OOM allocating Bitmap with dimensions %i x %i", width, height);
+        doThrowOOME(env);
         return NULL;
     }
 
@@ -1051,7 +1053,7 @@
     }
 
     // Read the bitmap blob.
-    size_t size = bitmap->getSize();
+    size_t size = bitmap->computeByteSize();
     android::Parcel::ReadableBlob blob;
     android::status_t status = p->readBlob(size, &blob);
     if (status) {
@@ -1188,7 +1190,7 @@
             p->allowFds() ? "allowed" : "forbidden");
 #endif
 
-    size_t size = bitmap.getSize();
+    size_t size = bitmap.computeByteSize();
     android::Parcel::WritableBlob blob;
     status = p->writeBlob(size, mutableCopy, &blob);
     if (status) {
@@ -1411,7 +1413,7 @@
         android::AutoBufferPointer abp(env, jbuffer, JNI_TRUE);
 
         // the java side has already checked that buffer is large enough
-        memcpy(abp.pointer(), src, bitmap.getSize());
+        memcpy(abp.pointer(), src, bitmap.computeByteSize());
     }
 }
 
@@ -1424,7 +1426,7 @@
     if (NULL != dst) {
         android::AutoBufferPointer abp(env, jbuffer, JNI_FALSE);
         // the java side has already checked that buffer is large enough
-        memcpy(dst, abp.pointer(), bitmap.getSize());
+        memcpy(dst, abp.pointer(), bitmap.computeByteSize());
         bitmap.notifyPixelsChanged();
     }
 }
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 64e12b4..5990d7b 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -174,13 +174,12 @@
             return false;
         }
 
-        const int64_t size64 = info.getSafeSize64(bitmap->rowBytes());
-        if (!sk_64_isS32(size64)) {
+        const size_t size = info.computeByteSize(bitmap->rowBytes());
+        if (size > SK_MaxS32) {
             ALOGW("bitmap is too large");
             return false;
         }
 
-        const size_t size = sk_64_asS32(size64);
         if (size > mSize) {
             ALOGW("bitmap marked for reuse (%u bytes) can't fit new bitmap "
                   "(%zu bytes)", mSize, size);
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 0ba27f6..9e6985c 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -56,9 +56,9 @@
     if (langs != nullptr) {
         ScopedUtfChars str(env, langs);
         builder = new NativeFamilyBuilder(
-                minikin::FontStyle::registerLanguageList(str.c_str()), variant);
+                minikin::FontStyle::registerLocaleList(str.c_str()), variant);
     } else {
-        builder = new NativeFamilyBuilder(minikin::FontStyle::registerLanguageList(""), variant);
+        builder = new NativeFamilyBuilder(minikin::FontStyle::registerLocaleList(""), variant);
     }
     return reinterpret_cast<jlong>(builder);
 }
@@ -97,21 +97,21 @@
 
 static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
         jint givenWeight, jint givenItalic) {
-    uirenderer::FatVector<SkFontMgr::FontParameters::Axis, 2> skiaAxes;
+    uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
     for (const auto& axis : builder->axes) {
-        skiaAxes.emplace_back(SkFontMgr::FontParameters::Axis{axis.axisTag, axis.value});
+        skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
     }
 
     const size_t fontSize = data->size();
     const void* fontPtr = data->data();
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
 
-    SkFontMgr::FontParameters params;
+    SkFontArguments params;
     params.setCollectionIndex(ttcIndex);
     params.setAxes(skiaAxes.data(), skiaAxes.size());
 
     sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params));
+    sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params));
     if (face == NULL) {
         ALOGE("addFont failed to create font, invalid request");
         builder->axes.clear();
diff --git a/core/jni/android/graphics/FontUtils.cpp b/core/jni/android/graphics/FontUtils.cpp
index 3bcf0c7..0cf61b9 100644
--- a/core/jni/android/graphics/FontUtils.cpp
+++ b/core/jni/android/graphics/FontUtils.cpp
@@ -16,7 +16,7 @@
 
 #include "FontUtils.h"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <core_jni_helpers.h>
 
 namespace android {
diff --git a/core/jni/android/graphics/GraphicBuffer.cpp b/core/jni/android/graphics/GraphicBuffer.cpp
index 1017cba..ae6fd38 100644
--- a/core/jni/android/graphics/GraphicBuffer.cpp
+++ b/core/jni/android/graphics/GraphicBuffer.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "GraphicBuffer"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <inttypes.h>
 
 #include "android_os_Parcel.h"
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 5ea501e..90cc7bb 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -645,7 +645,7 @@
     const int maxHeight = SkTMax(bitmap->height(), mRecycledBitmap->info().height());
     const SkImageInfo maxInfo = bitmap->info().makeWH(maxWidth, maxHeight);
     const size_t rowBytes = maxInfo.minRowBytes();
-    const size_t bytesNeeded = maxInfo.getSafeSize(rowBytes);
+    const size_t bytesNeeded = maxInfo.computeByteSize(rowBytes);
     if (bytesNeeded <= mRecycledBytes) {
         // Here we take advantage of reconfigure() to reset the rowBytes
         // of mRecycledBitmap.  It is very important that we pass in
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 93bd16b2..e8e3f57 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -112,8 +112,8 @@
         float measured = 0;
 
         std::unique_ptr<float[]> advancesArray(new float[count]);
-        MinikinUtils::measureText(&paint, bidiFlags, typeface, text, 0, count, count,
-                advancesArray.get());
+        MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text,
+                0, count, count, advancesArray.get());
 
         for (int i = 0; i < count; i++) {
             // traverse in the given direction
@@ -203,8 +203,9 @@
         if (advances) {
             advancesArray.reset(new jfloat[count]);
         }
-        const float advance = MinikinUtils::measureText(paint, bidiFlags, typeface, text,
-                start, count, contextCount, advancesArray.get());
+        const float advance = MinikinUtils::measureText(paint,
+                static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount,
+                advancesArray.get());
         if (advances) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
         }
@@ -239,7 +240,7 @@
     static jint doTextRunCursor(JNIEnv *env, Paint* paint, const Typeface* typeface,
             const jchar *text, jint start, jint count, jint dir, jint offset, jint opt) {
         minikin::GraphemeBreak::MoveOpt moveOpt = minikin::GraphemeBreak::MoveOpt(opt);
-        int bidiFlags = dir == 1 ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+        minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
                 advancesArray.get());
@@ -305,7 +306,7 @@
     static void getTextPath(JNIEnv* env, Paint* paint, const Typeface* typeface, const jchar* text,
             jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
         minikin::Layout layout = MinikinUtils::doLayout(
-                paint, bidiFlags, typeface, text, 0, count, count);
+                paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count);
         size_t nGlyphs = layout.nGlyphs();
         uint16_t* glyphs = new uint16_t[nGlyphs];
         SkPoint* pos = new SkPoint[nGlyphs];
@@ -346,8 +347,8 @@
         SkRect  r;
         SkIRect ir;
 
-        minikin::Layout layout = MinikinUtils::doLayout(
-                &paint, bidiFlags, typeface, text, 0, count, count);
+        minikin::Layout layout = MinikinUtils::doLayout(&paint,
+                static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count);
         minikin::MinikinRect rect;
         layout.getBounds(&rect);
         r.fLeft = rect.mLeft;
@@ -461,8 +462,9 @@
             nChars++;
             prevCp = cp;
         }
-        minikin::Layout layout = MinikinUtils::doLayout(
-                paint, bidiFlags, typeface, str.get(), 0, str.size(), str.size());
+        minikin::Layout layout = MinikinUtils::doLayout(paint,
+                static_cast<minikin::Bidi>(bidiFlags), typeface, str.get(), 0, str.size(),
+                str.size());
         size_t nGlyphs = countNonSpaceGlyphs(layout);
         if (nGlyphs != 1 && nChars > 1) {
             // multiple-character input, and was not a ligature
@@ -481,8 +483,8 @@
             // since ZZ is reserved for unknown or invalid territory.
             // U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16.
             static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF };
-            minikin::Layout zzLayout = MinikinUtils::doLayout(
-                    paint, bidiFlags, typeface, ZZ_FLAG_STR, 0, 4, 4);
+            minikin::Layout zzLayout = MinikinUtils::doLayout(paint,
+                    static_cast<minikin::Bidi>(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4);
             if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) {
                 // The font collection doesn't have a glyph for unknown flag. Just return true.
                 return true;
@@ -494,7 +496,7 @@
 
     static jfloat doRunAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[],
             jint start, jint count, jint bufSize, jboolean isRtl, jint offset) {
-        int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+        minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         if (offset == start + count) {
             return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
                     bufSize, nullptr);
@@ -519,7 +521,7 @@
 
     static jint doOffsetForAdvance(const Paint* paint, const Typeface* typeface, const jchar buf[],
             jint start, jint count, jint bufSize, jboolean isRtl, jfloat advance) {
-        int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+        minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
                 advancesArray.get());
@@ -544,7 +546,7 @@
     static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
         Paint* obj = reinterpret_cast<Paint*>(objHandle);
         ScopedUtfChars localesChars(env, locales);
-        jint minikinLangListId = minikin::FontStyle::registerLanguageList(localesChars.c_str());
+        jint minikinLangListId = minikin::FontStyle::registerLocaleList(localesChars.c_str());
         obj->setMinikinLangListId(minikinLangListId);
         return minikinLangListId;
     }
diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp
index e4540c0..d5f2a5c 100644
--- a/core/jni/android/graphics/Typeface.cpp
+++ b/core/jni/android/graphics/Typeface.cpp
@@ -30,14 +30,14 @@
 
 static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
     Typeface* family = reinterpret_cast<Typeface*>(familyHandle);
-    Typeface* face = Typeface::createRelative(family, (SkTypeface::Style)style);
+    Typeface* face = Typeface::createRelative(family, (Typeface::Style)style);
     // TODO: the following logic shouldn't be necessary, the above should always succeed.
     // Try to find the closest matching font, using the standard heuristic
     if (NULL == face) {
-        face = Typeface::createRelative(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));
+        face = Typeface::createRelative(family, (Typeface::Style)(style ^ Typeface::kItalic));
     }
     for (int i = 0; NULL == face && i < 4; i++) {
-        face = Typeface::createRelative(family, (SkTypeface::Style)i);
+        face = Typeface::createRelative(family, (Typeface::Style)i);
     }
     return reinterpret_cast<jlong>(face);
 }
@@ -78,7 +78,7 @@
 
 static jint Typeface_getStyle(JNIEnv* env, jobject obj, jlong faceHandle) {
     Typeface* face = reinterpret_cast<Typeface*>(faceHandle);
-    return face->fSkiaStyle;
+    return face->fAPIStyle;
 }
 
 static jint Typeface_getWeight(JNIEnv* env, jobject obj, jlong faceHandle) {
diff --git a/core/jni/android/graphics/pdf/PdfUtils.cpp b/core/jni/android/graphics/pdf/PdfUtils.cpp
index dacca78..36355eb 100644
--- a/core/jni/android/graphics/pdf/PdfUtils.cpp
+++ b/core/jni/android/graphics/pdf/PdfUtils.cpp
@@ -17,7 +17,7 @@
 #include "PdfUtils.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include "fpdfview.h"
 
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 18f3177..09e37e1 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -40,6 +40,7 @@
 #include "android_view_InputChannel.h"
 #include "android_view_KeyEvent.h"
 
+#include "android-base/stringprintf.h"
 #include "nativebridge/native_bridge.h"
 #include "nativeloader/native_loader.h"
 
@@ -265,6 +266,8 @@
 
 // ------------------------------------------------------------------------
 
+static thread_local std::string g_error_msg;
+
 static jlong
 loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jstring funcName,
         jobject messageQueue, jstring internalDataDir, jstring obbDir,
@@ -277,7 +280,6 @@
     ScopedUtfChars pathStr(env, path);
     std::unique_ptr<NativeCode> code;
     bool needs_native_bridge = false;
-    std::string error_msg;
 
     void* handle = OpenNativeLibrary(env,
                                      sdkVersion,
@@ -285,12 +287,12 @@
                                      classLoader,
                                      libraryPath,
                                      &needs_native_bridge,
-                                     &error_msg);
+                                     &g_error_msg);
 
     if (handle == nullptr) {
         ALOGW("NativeActivity LoadNativeLibrary(\"%s\") failed: %s",
               pathStr.c_str(),
-              error_msg.c_str());
+              g_error_msg.c_str());
         return 0;
     }
 
@@ -306,19 +308,22 @@
     env->ReleaseStringUTFChars(funcName, funcStr);
 
     if (code->createActivityFunc == NULL) {
-        ALOGW("ANativeActivity_onCreate not found");
+        g_error_msg = needs_native_bridge ? NativeBridgeGetError() : dlerror();
+        ALOGW("ANativeActivity_onCreate not found: %s", g_error_msg.c_str());
         return 0;
     }
 
     code->messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueue);
     if (code->messageQueue == NULL) {
-        ALOGW("Unable to retrieve native MessageQueue");
+        g_error_msg = "Unable to retrieve native MessageQueue";
+        ALOGW("%s", g_error_msg.c_str());
         return 0;
     }
 
     int msgpipe[2];
     if (pipe(msgpipe)) {
-        ALOGW("could not create pipe: %s", strerror(errno));
+        g_error_msg = android::base::StringPrintf("could not create pipe: %s", strerror(errno));
+        ALOGW("%s", g_error_msg.c_str());
         return 0;
     }
     code->mainWorkRead = msgpipe[0];
@@ -334,7 +339,8 @@
 
     code->ANativeActivity::callbacks = &code->callbacks;
     if (env->GetJavaVM(&code->vm) < 0) {
-        ALOGW("NativeActivity GetJavaVM failed");
+        g_error_msg = "NativeActivity GetJavaVM failed";
+        ALOGW("%s", g_error_msg.c_str());
         return 0;
     }
     code->env = env;
@@ -381,7 +387,9 @@
 }
 
 static jstring getDlError_native(JNIEnv* env, jobject clazz) {
-  return env->NewStringUTF(dlerror());
+  jstring result = env->NewStringUTF(g_error_msg.c_str());
+  g_error_msg.clear();
+  return result;
 }
 
 static void
diff --git a/core/jni/android_app_admin_SecurityLog.cpp b/core/jni/android_app_admin_SecurityLog.cpp
index 5c45b4b..b3bcaa0 100644
--- a/core/jni/android_app_admin_SecurityLog.cpp
+++ b/core/jni/android_app_admin_SecurityLog.cpp
@@ -14,183 +14,26 @@
  * limitations under the License.
  */
 
-#include <fcntl.h>
-
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include "jni.h"
+#include <log/log_id.h>
 #include <private/android_logger.h>
 
-// The size of the tag number comes out of the payload size.
-#define MAX_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - sizeof(int32_t))
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+
+#include "core_jni_helpers.h"
+#include "eventlog_helper.h"
 
 namespace android {
 
-static jclass gCollectionClass;
-static jmethodID gCollectionAddID;
-
-static jclass gEventClass;
-static jmethodID gEventInitID;
-
-static jclass gIntegerClass;
-static jfieldID gIntegerValueID;
-
-static jclass gLongClass;
-static jfieldID gLongValueID;
-
-static jclass gFloatClass;
-static jfieldID gFloatValueID;
-
-static jclass gStringClass;
-
+constexpr char kSecurityLogEventClass[] = "android/app/admin/SecurityLog$SecurityEvent";
+template class EventLogHelper<log_id_t::LOG_ID_SECURITY, kSecurityLogEventClass>;
+using SLog = EventLogHelper<log_id_t::LOG_ID_SECURITY, kSecurityLogEventClass>;
 
 static jboolean android_app_admin_SecurityLog_isLoggingEnabled(JNIEnv* env,
                                                     jobject /* clazz */) {
     return (bool)__android_log_security();
 }
 
-static jint android_app_admin_SecurityLog_writeEvent_String(JNIEnv* env,
-                                                    jobject /* clazz */,
-                                                    jint tag, jstring value) {
-    uint8_t buf[MAX_EVENT_PAYLOAD];
-
-    // Don't throw NPE -- I feel like it's sort of mean for a logging function
-    // to be all crashy if you pass in NULL -- but make the NULL value explicit.
-    const char *str = value != NULL ? env->GetStringUTFChars(value, NULL) : "NULL";
-    uint32_t len = strlen(str);
-    size_t max = sizeof(buf) - sizeof(len) - 2;  // Type byte, final newline
-    if (len > max) len = max;
-
-    buf[0] = EVENT_TYPE_STRING;
-    memcpy(&buf[1], &len, sizeof(len));
-    memcpy(&buf[1 + sizeof(len)], str, len);
-    buf[1 + sizeof(len) + len] = '\n';
-
-    if (value != NULL) env->ReleaseStringUTFChars(value, str);
-    return __android_log_security_bwrite(tag, buf, 2 + sizeof(len) + len);
-}
-
-static jint android_app_admin_SecurityLog_writeEvent_Array(JNIEnv* env, jobject clazz,
-                                                   jint tag, jobjectArray value) {
-    if (value == NULL) {
-        return android_app_admin_SecurityLog_writeEvent_String(env, clazz, tag, NULL);
-    }
-
-    uint8_t buf[MAX_EVENT_PAYLOAD];
-    const size_t max = sizeof(buf) - 1;  // leave room for final newline
-    size_t pos = 2;  // Save room for type tag & array count
-
-    jsize copied = 0, num = env->GetArrayLength(value);
-    for (; copied < num && copied < 255; ++copied) {
-        jobject item = env->GetObjectArrayElement(value, copied);
-        if (item == NULL || env->IsInstanceOf(item, gStringClass)) {
-            if (pos + 1 + sizeof(jint) > max) break;
-            const char *str = item != NULL ? env->GetStringUTFChars((jstring) item, NULL) : "NULL";
-            jint len = strlen(str);
-            if (pos + 1 + sizeof(len) + len > max) len = max - pos - 1 - sizeof(len);
-            buf[pos++] = EVENT_TYPE_STRING;
-            memcpy(&buf[pos], &len, sizeof(len));
-            memcpy(&buf[pos + sizeof(len)], str, len);
-            pos += sizeof(len) + len;
-            if (item != NULL) env->ReleaseStringUTFChars((jstring) item, str);
-        } else if (env->IsInstanceOf(item, gIntegerClass)) {
-            jint intVal = env->GetIntField(item, gIntegerValueID);
-            if (pos + 1 + sizeof(intVal) > max) break;
-            buf[pos++] = EVENT_TYPE_INT;
-            memcpy(&buf[pos], &intVal, sizeof(intVal));
-            pos += sizeof(intVal);
-        } else if (env->IsInstanceOf(item, gLongClass)) {
-            jlong longVal = env->GetLongField(item, gLongValueID);
-            if (pos + 1 + sizeof(longVal) > max) break;
-            buf[pos++] = EVENT_TYPE_LONG;
-            memcpy(&buf[pos], &longVal, sizeof(longVal));
-            pos += sizeof(longVal);
-        } else if (env->IsInstanceOf(item, gFloatClass)) {
-            jfloat floatVal = env->GetFloatField(item, gFloatValueID);
-            if (pos + 1 + sizeof(floatVal) > max) break;
-            buf[pos++] = EVENT_TYPE_FLOAT;
-            memcpy(&buf[pos], &floatVal, sizeof(floatVal));
-            pos += sizeof(floatVal);
-        } else {
-            jniThrowException(env,
-                    "java/lang/IllegalArgumentException",
-                    "Invalid payload item type");
-            return -1;
-        }
-        env->DeleteLocalRef(item);
-    }
-
-    buf[0] = EVENT_TYPE_LIST;
-    buf[1] = copied;
-    buf[pos++] = '\n';
-    return __android_log_security_bwrite(tag, buf, pos);
-}
-
-static void readEvents(JNIEnv* env, int loggerMode, jlong startTime, jobject out) {
-    struct logger_list *logger_list;
-    if (startTime) {
-        logger_list = android_logger_list_alloc_time(loggerMode,
-                log_time(startTime / NS_PER_SEC, startTime % NS_PER_SEC), 0);
-    } else {
-        logger_list = android_logger_list_alloc(loggerMode, 0, 0);
-    }
-    if (!logger_list) {
-        jniThrowIOException(env, errno);
-        return;
-    }
-
-    if (!android_logger_open(logger_list, LOG_ID_SECURITY)) {
-        jniThrowIOException(env, errno);
-        android_logger_list_free(logger_list);
-        return;
-    }
-
-    while (1) {
-        log_msg log_msg;
-        int ret = android_logger_list_read(logger_list, &log_msg);
-
-        if (ret == 0) {
-            break;
-        }
-        if (ret < 0) {
-            if (ret == -EINTR) {
-                continue;
-            }
-            if (ret == -EINVAL) {
-                jniThrowException(env, "java/io/IOException", "Event too short");
-            } else if (ret != -EAGAIN) {
-                jniThrowIOException(env, -ret);  // Will throw on return
-            }
-            break;
-        }
-
-        if (log_msg.id() != LOG_ID_SECURITY) {
-            continue;
-        }
-
-        jsize len = ret;
-        jbyteArray array = env->NewByteArray(len);
-        if (array == NULL) {
-            break;
-        }
-
-        jbyte *bytes = env->GetByteArrayElements(array, NULL);
-        memcpy(bytes, log_msg.buf, len);
-        env->ReleaseByteArrayElements(array, bytes, 0);
-
-        jobject event = env->NewObject(gEventClass, gEventInitID, array);
-        if (event == NULL) {
-            break;
-        }
-
-        env->CallBooleanMethod(out, gCollectionAddID, event);
-        env->DeleteLocalRef(event);
-        env->DeleteLocalRef(array);
-    }
-
-    android_logger_list_close(logger_list);
-}
-
 static void android_app_admin_SecurityLog_readEvents(JNIEnv* env, jobject /* clazz */,
                                              jobject out) {
 
@@ -198,7 +41,7 @@
         jniThrowNullPointerException(env, NULL);
         return;
     }
-    readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, out);
+    SLog::readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, out);
 }
 
 static void android_app_admin_SecurityLog_readEventsSince(JNIEnv* env, jobject /* clazz */,
@@ -209,7 +52,7 @@
         jniThrowNullPointerException(env, NULL);
         return;
     }
-    readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, timestamp, out);
+    SLog::readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, timestamp, out);
 }
 
 static void android_app_admin_SecurityLog_readPreviousEvents(JNIEnv* env, jobject /* clazz */,
@@ -219,7 +62,7 @@
         jniThrowNullPointerException(env, NULL);
         return;
     }
-    readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_PSTORE, 0, out);
+    SLog::readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_PSTORE, 0, out);
 }
 
 static void android_app_admin_SecurityLog_readEventsOnWrapping(JNIEnv* env, jobject /* clazz */,
@@ -229,7 +72,8 @@
         jniThrowNullPointerException(env, NULL);
         return;
     }
-    readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_WRAP, timestamp, out);
+    SLog::readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_WRAP, timestamp,
+            out);
 }
 
 /*
@@ -243,11 +87,11 @@
     },
     { "writeEvent",
       "(ILjava/lang/String;)I",
-      (void*) android_app_admin_SecurityLog_writeEvent_String
+      (void*) SLog::writeEventString
     },
     { "writeEvent",
       "(I[Ljava/lang/Object;)I",
-      (void*) android_app_admin_SecurityLog_writeEvent_Array
+      (void*) SLog::writeEventArray
     },
     { "readEvents",
       "(Ljava/util/Collection;)V",
@@ -267,41 +111,8 @@
     },
 };
 
-static struct { const char *name; jclass *clazz; } gClasses[] = {
-    { "android/app/admin/SecurityLog$SecurityEvent", &gEventClass },
-    { "java/lang/Integer", &gIntegerClass },
-    { "java/lang/Long", &gLongClass },
-    { "java/lang/Float", &gFloatClass },
-    { "java/lang/String", &gStringClass },
-    { "java/util/Collection", &gCollectionClass },
-};
-
-static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
-    { &gIntegerClass, "value", "I", &gIntegerValueID },
-    { &gLongClass, "value", "J", &gLongValueID },
-    { &gFloatClass, "value", "F", &gFloatValueID },
-};
-
-static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = {
-    { &gEventClass, "<init>", "([B)V", &gEventInitID },
-    { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID },
-};
-
 int register_android_app_admin_SecurityLog(JNIEnv* env) {
-    for (int i = 0; i < NELEM(gClasses); ++i) {
-        jclass clazz = FindClassOrDie(env, gClasses[i].name);
-        *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz);
-    }
-
-    for (int i = 0; i < NELEM(gFields); ++i) {
-        *gFields[i].id = GetFieldIDOrDie(env,
-                *gFields[i].c, gFields[i].name, gFields[i].ft);
-    }
-
-    for (int i = 0; i < NELEM(gMethods); ++i) {
-        *gMethods[i].id = GetMethodIDOrDie(env,
-                *gMethods[i].c, gMethods[i].name, gMethods[i].mt);
-    }
+    SLog::Init(env);
 
     return RegisterMethodsOrDie(
             env,
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
index c3f9bf7..1efff7f 100644
--- a/core/jni/android_database_SQLiteConnection.cpp
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -765,6 +765,14 @@
     if (startPos > totalRows) {
         ALOGE("startPos %d > actual rows %d", startPos, totalRows);
     }
+    if (totalRows > 0 && addedRows == 0) {
+        String8 msg;
+        msg.appendFormat("Row too big to fit into CursorWindow requiredPos=%d, totalRows=%d",
+                requiredPos, totalRows);
+        throw_sqlite3_exception(env, SQLITE_TOOBIG, NULL, msg.string());
+        return 0;
+    }
+
     jlong result = jlong(startPos) << 32 | jlong(totalRows);
     return result;
 }
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index fba0721..f08b89c 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -480,7 +480,7 @@
     const Typeface* typeface = paint->getAndroidTypeface();
     jchar* jchars = env->GetCharArrayElements(text, NULL);
     get_canvas(canvasHandle)->drawText(jchars + index, 0, count, count, x, y,
-                                       bidiFlags, *paint, typeface);
+            static_cast<minikin::Bidi>(bidiFlags), *paint, typeface);
     env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
 }
 
@@ -492,7 +492,7 @@
     const int count = end - start;
     const jchar* jchars = env->GetStringChars(text, NULL);
     get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y,
-                                       bidiFlags, *paint, typeface);
+            static_cast<minikin::Bidi>(bidiFlags), *paint, typeface);
     env->ReleaseStringChars(text, jchars);
 }
 
@@ -502,7 +502,7 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
 
-    const int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+    const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
     jchar* jchars = env->GetCharArrayElements(text, NULL);
     get_canvas(canvasHandle)->drawText(jchars + contextIndex, index - contextIndex, count,
                                        contextCount, x, y, bidiFlags, *paint, typeface);
@@ -515,7 +515,7 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
 
-    int bidiFlags = isRtl ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR;
+    const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
     jint count = end - start;
     jint contextCount = contextEnd - contextStart;
     const jchar* jchars = env->GetStringChars(text, NULL);
@@ -533,8 +533,8 @@
 
     jchar* jchars = env->GetCharArrayElements(text, NULL);
 
-    get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count, bidiFlags, *path,
-                   hOffset, vOffset, *paint, typeface);
+    get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
+            static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
 
     env->ReleaseCharArrayElements(text, jchars, 0);
 }
@@ -549,8 +549,8 @@
     const jchar* jchars = env->GetStringChars(text, NULL);
     int count = env->GetStringLength(text);
 
-    get_canvas(canvasHandle)->drawTextOnPath(jchars, count, bidiFlags, *path,
-                   hOffset, vOffset, *paint, typeface);
+    get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
+            *path, hOffset, vOffset, *paint, typeface);
 
     env->ReleaseStringChars(text, jchars);
 }
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index ba23450..8174a41 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "HardwareBuffer"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include "android_os_Parcel.h"
 #include "android/graphics/GraphicsJNI.h"
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index c8eef7f..1628220 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -23,13 +23,13 @@
 #include <vector>
 #include <cmath>
 
+#include <android-base/properties.h>
 #include <utils/Log.h>
 #include <utils/Errors.h>
 #include <utils/StrongPointer.h>
 #include <utils/RefBase.h>
 #include <utils/Vector.h>
 #include <utils/String8.h>
-#include <cutils/properties.h>
 #include <system/camera_metadata.h>
 #include <camera/CameraMetadata.h>
 #include <img_utils/DngUtils.h>
@@ -50,6 +50,7 @@
 
 using namespace android;
 using namespace img_utils;
+using android::base::GetProperty;
 
 #define BAIL_IF_INVALID_RET_BOOL(expr, jnienv, tagId, writer) \
     if ((expr) != OK) { \
@@ -1237,26 +1238,24 @@
 
     {
         // make
-        char manufacturer[PROPERTY_VALUE_MAX];
-
         // Use "" to represent unknown make as suggested in TIFF/EP spec.
-        property_get("ro.product.manufacturer", manufacturer, "");
-        uint32_t count = static_cast<uint32_t>(strlen(manufacturer)) + 1;
+        std::string manufacturer = GetProperty("ro.product.manufacturer", "");
+        uint32_t count = static_cast<uint32_t>(manufacturer.size()) + 1;
 
         BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_MAKE, count,
-                reinterpret_cast<uint8_t*>(manufacturer), TIFF_IFD_0), env, TAG_MAKE, writer);
+                reinterpret_cast<const uint8_t*>(manufacturer.c_str()), TIFF_IFD_0), env, TAG_MAKE,
+                writer);
     }
 
     {
         // model
-        char model[PROPERTY_VALUE_MAX];
-
         // Use "" to represent unknown model as suggested in TIFF/EP spec.
-        property_get("ro.product.model", model, "");
-        uint32_t count = static_cast<uint32_t>(strlen(model)) + 1;
+        std::string model = GetProperty("ro.product.model", "");
+        uint32_t count = static_cast<uint32_t>(model.size()) + 1;
 
         BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_MODEL, count,
-                reinterpret_cast<uint8_t*>(model), TIFF_IFD_0), env, TAG_MODEL, writer);
+                reinterpret_cast<const uint8_t*>(model.c_str()), TIFF_IFD_0), env, TAG_MODEL,
+                writer);
     }
 
     {
@@ -1277,11 +1276,11 @@
 
     {
         // software
-        char software[PROPERTY_VALUE_MAX];
-        property_get("ro.build.fingerprint", software, "");
-        uint32_t count = static_cast<uint32_t>(strlen(software)) + 1;
+        std::string software = GetProperty("ro.build.fingerprint", "");
+        uint32_t count = static_cast<uint32_t>(software.size()) + 1;
         BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_SOFTWARE, count,
-                reinterpret_cast<uint8_t*>(software), TIFF_IFD_0), env, TAG_SOFTWARE, writer);
+                reinterpret_cast<const uint8_t*>(software.c_str()), TIFF_IFD_0), env, TAG_SOFTWARE,
+                writer);
     }
 
     if (nativeContext->hasCaptureTime()) {
@@ -1613,20 +1612,15 @@
 
     {
         // Setup unique camera model tag
-        char model[PROPERTY_VALUE_MAX];
-        property_get("ro.product.model", model, "");
+        std::string model = GetProperty("ro.product.model", "");
+        std::string manufacturer = GetProperty("ro.product.manufacturer", "");
+        std::string brand = GetProperty("ro.product.brand", "");
 
-        char manufacturer[PROPERTY_VALUE_MAX];
-        property_get("ro.product.manufacturer", manufacturer, "");
-
-        char brand[PROPERTY_VALUE_MAX];
-        property_get("ro.product.brand", brand, "");
-
-        String8 cameraModel(model);
+        String8 cameraModel(model.c_str());
         cameraModel += "-";
-        cameraModel += manufacturer;
+        cameraModel += manufacturer.c_str();
         cameraModel += "-";
-        cameraModel += brand;
+        cameraModel += brand.c_str();
 
         BAIL_IF_INVALID_RET_NULL_SP(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
                 reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp
index 2d2837c..23c3877 100644
--- a/core/jni/android_hardware_display_DisplayViewport.cpp
+++ b/core/jni/android_hardware_display_DisplayViewport.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "DisplayViewport-JNI"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
 #include <android_hardware_display_DisplayViewport.h>
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index a140b57..d3da21b 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -705,6 +705,8 @@
     MEMINFO_CACHED,
     MEMINFO_SHMEM,
     MEMINFO_SLAB,
+    MEMINFO_SLAB_RECLAIMABLE,
+    MEMINFO_SLAB_UNRECLAIMABLE,
     MEMINFO_SWAP_TOTAL,
     MEMINFO_SWAP_FREE,
     MEMINFO_ZRAM_TOTAL,
@@ -753,7 +755,7 @@
         return;
     }
 
-    int fd = open("/proc/meminfo", O_RDONLY);
+    int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
 
     if (fd < 0) {
         ALOGW("Unable to open /proc/meminfo: %s\n", strerror(errno));
@@ -776,6 +778,8 @@
             "Cached:",
             "Shmem:",
             "Slab:",
+            "SReclaimable:",
+            "SUnreclaim:",
             "SwapTotal:",
             "SwapFree:",
             "ZRam:",
@@ -792,6 +796,8 @@
             7,
             6,
             5,
+            13,
+            11,
             10,
             9,
             5,
@@ -801,7 +807,7 @@
             12,
             0
     };
-    long mem[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+    long mem[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
 
     char* p = buffer;
     while (*p && numFound < (sizeof(tagsLen) / sizeof(tagsLen[0]))) {
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 59ca050..c4f22ee 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -35,6 +35,7 @@
 #include <hidl/HidlTransportSupport.h>
 #include <hwbinder/ProcessState.h>
 #include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include <vintf/parse_string.h>
 #include <utils/misc.h>
 
@@ -261,14 +262,9 @@
         JNIEnv *env,
         jobject thiz,
         jstring serviceNameObj) {
-    if (serviceNameObj == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", NULL);
-        return;
-    }
-
-    const char *serviceName = env->GetStringUTFChars(serviceNameObj, NULL);
-    if (serviceName == NULL) {
-        return;  // XXX exception already pending?
+    ScopedUtfChars str(env, serviceNameObj);
+    if (str.c_str() == nullptr) {
+        return;  // NPE will be pending.
     }
 
     sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz);
@@ -284,15 +280,12 @@
         return;
     }
 
-    Return<bool> ret = manager->add(serviceName, base);
-
-    env->ReleaseStringUTFChars(serviceNameObj, serviceName);
-    serviceName = NULL;
+    Return<bool> ret = manager->add(str.c_str(), base);
 
     bool ok = ret.isOk() && ret;
 
     if (ok) {
-        LOG(INFO) << "Starting thread pool.";
+        LOG(INFO) << "HwBinder: Starting thread pool for " << str.c_str();
         ::android::hardware::ProcessState::self()->startThreadPool();
     }
 
@@ -306,84 +299,28 @@
         jstring serviceNameObj) {
 
     using ::android::hidl::base::V1_0::IBase;
-    using ::android::hidl::manager::V1_0::IServiceManager;
+    using ::android::hardware::details::getRawServiceInternal;
 
-    if (ifaceNameObj == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", NULL);
-        return NULL;
-    }
-    if (serviceNameObj == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", NULL);
-        return NULL;
+    std::string ifaceName;
+    {
+        ScopedUtfChars str(env, ifaceNameObj);
+        if (str.c_str() == nullptr) {
+            return nullptr;  // NPE will be pending.
+        }
+        ifaceName = str.c_str();
     }
 
-    auto manager = hardware::defaultServiceManager();
-
-    if (manager == nullptr) {
-        LOG(ERROR) << "Could not get hwservicemanager.";
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
-        return NULL;
+    std::string serviceName;
+    {
+        ScopedUtfChars str(env, serviceNameObj);
+        if (str.c_str() == nullptr) {
+            return nullptr;  // NPE will be pending.
+        }
+        serviceName = str.c_str();
     }
 
-    const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);
-    if (ifaceNameCStr == NULL) {
-        return NULL; // XXX exception already pending?
-    }
-    std::string ifaceName(ifaceNameCStr);
-    env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);
-    ::android::hardware::hidl_string ifaceNameHStr;
-    ifaceNameHStr.setToExternal(ifaceName.c_str(), ifaceName.size());
-
-    const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);
-    if (serviceNameCStr == NULL) {
-        return NULL; // XXX exception already pending?
-    }
-    std::string serviceName(serviceNameCStr);
-    env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);
-    ::android::hardware::hidl_string serviceNameHStr;
-    serviceNameHStr.setToExternal(serviceName.c_str(), serviceName.size());
-
-    LOG(INFO) << "Looking for service "
-              << ifaceName
-              << "/"
-              << serviceName;
-
-    Return<IServiceManager::Transport> transportRet =
-            manager->getTransport(ifaceNameHStr, serviceNameHStr);
-
-    if (!transportRet.isOk()) {
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
-    IServiceManager::Transport transport = transportRet;
-
-#ifdef __ANDROID_TREBLE__
-#ifdef __ANDROID_DEBUGGABLE__
-    const char* testingOverride = std::getenv("TREBLE_TESTING_OVERRIDE");
-    const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY)
-            && testingOverride && !strcmp(testingOverride, "true");
-#else // __ANDROID_TREBLE__ but not __ANDROID_DEBUGGABLE__
-    const bool vintfLegacy = false;
-#endif // __ANDROID_DEBUGGABLE__
-#else // not __ANDROID_TREBLE__
-    const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY);
-#endif // __ANDROID_TREBLE__";
-
-    if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
-        LOG(ERROR) << "service " << ifaceName << " declares transport method "
-                   << toString(transport) << " but framework expects hwbinder.";
-        signalExceptionForError(env, NAME_NOT_FOUND, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
-    Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceNameHStr, serviceNameHStr);
-
-    if (!ret.isOk()) {
-        signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
-        return NULL;
-    }
-
+    // TODO(b/67981006): true /* retry */
+    sp<IBase> ret = getRawServiceInternal(ifaceName, serviceName, false /* retry */, false /* getStub */);
     sp<hardware::IBinder> service = hardware::toBinder<hidl::base::V1_0::IBase>(ret);
 
     if (service == NULL) {
@@ -391,13 +328,14 @@
         return NULL;
     }
 
-    LOG(INFO) << "Starting thread pool.";
+    LOG(INFO) << "HwBinder: Starting thread pool for " << serviceName << "::" << ifaceName;
     ::android::hardware::ProcessState::self()->startThreadPool();
 
     return JHwRemoteBinder::NewObject(env, service);
 }
 
-void JHwBinder_native_configureRpcThreadpool(jlong maxThreads, jboolean callerWillJoin) {
+void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass,
+        jlong maxThreads, jboolean callerWillJoin) {
     CHECK(maxThreads > 0);
     ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);
 }
@@ -406,7 +344,7 @@
     IPCThreadState::self()->joinThreadPool();
 }
 
-static void JHwBinder_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+static void JHwBinder_report_sysprop_change(JNIEnv * /*env*/, jclass /*clazz*/)
 {
     report_sysprop_change();
 }
diff --git a/core/jni/android_os_SharedMemory.cpp b/core/jni/android_os_SharedMemory.cpp
index 1d29908..f6e5c7a 100644
--- a/core/jni/android_os_SharedMemory.cpp
+++ b/core/jni/android_os_SharedMemory.cpp
@@ -20,7 +20,7 @@
 
 #include <cutils/ashmem.h>
 #include <utils/Log.h>
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include <nativehelper/JniConstants.h>
 #include <nativehelper/ScopedLocalRef.h>
 
diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp
index 315eac1..9379ea6 100644
--- a/core/jni/android_os_VintfRuntimeInfo.cpp
+++ b/core/jni/android_os_VintfRuntimeInfo.cpp
@@ -29,28 +29,33 @@
 using vintf::RuntimeInfo;
 using vintf::VintfObject;
 
-#define MAP_STRING_METHOD(javaMethod, cppString)                                       \
+#define MAP_STRING_METHOD(javaMethod, cppString, flags)                                \
     static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) \
     {                                                                                  \
-        std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo();       \
+        std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(         \
+                false /* skipCache */, flags);                                         \
         if (info == nullptr) return nullptr;                                           \
         return env->NewStringUTF((cppString).c_str());                                 \
     }                                                                                  \
 
-MAP_STRING_METHOD(getCpuInfo, info->cpuInfo());
-MAP_STRING_METHOD(getOsName, info->osName());
-MAP_STRING_METHOD(getNodeName, info->nodeName());
-MAP_STRING_METHOD(getOsRelease, info->osRelease());
-MAP_STRING_METHOD(getOsVersion, info->osVersion());
-MAP_STRING_METHOD(getHardwareId, info->hardwareId());
-MAP_STRING_METHOD(getKernelVersion, vintf::to_string(info->kernelVersion()));
-MAP_STRING_METHOD(getBootAvbVersion, vintf::to_string(info->bootAvbVersion()));
-MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()));
+MAP_STRING_METHOD(getCpuInfo, info->cpuInfo(), RuntimeInfo::FetchFlag::CPU_INFO);
+MAP_STRING_METHOD(getOsName, info->osName(), RuntimeInfo::FetchFlag::CPU_VERSION);
+MAP_STRING_METHOD(getNodeName, info->nodeName(), RuntimeInfo::FetchFlag::CPU_VERSION);
+MAP_STRING_METHOD(getOsRelease, info->osRelease(), RuntimeInfo::FetchFlag::CPU_VERSION);
+MAP_STRING_METHOD(getOsVersion, info->osVersion(), RuntimeInfo::FetchFlag::CPU_VERSION);
+MAP_STRING_METHOD(getHardwareId, info->hardwareId(), RuntimeInfo::FetchFlag::CPU_VERSION);
+MAP_STRING_METHOD(getKernelVersion, vintf::to_string(info->kernelVersion()),
+                  RuntimeInfo::FetchFlag::CPU_VERSION);
+MAP_STRING_METHOD(getBootAvbVersion, vintf::to_string(info->bootAvbVersion()),
+                  RuntimeInfo::FetchFlag::AVB);
+MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbVersion()),
+                  RuntimeInfo::FetchFlag::AVB);
 
 
 static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz)
 {
-    std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo();
+    std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(
+        false /* skipCache */, RuntimeInfo::FetchFlag::POLICYVERS);
     if (info == nullptr) return 0;
     return static_cast<jlong>(info->kernelSepolicyVersion());
 }
diff --git a/core/jni/android_text_AndroidBidi.cpp b/core/jni/android_text_AndroidBidi.cpp
deleted file mode 100644
index f72f0f0..0000000
--- a/core/jni/android_text_AndroidBidi.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/* //device/libs/android_runtime/android_text_AndroidBidi.cpp
-**
-** Copyright 2010, 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 "AndroidUnicode"
-
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include "utils/misc.h"
-#include "utils/Log.h"
-#include "unicode/ubidi.h"
-#include <minikin/Emoji.h>
-
-namespace android {
-
-static jint runBidi(JNIEnv* env, jobject obj, jint dir, jcharArray chsArray,
-                    jbyteArray infoArray, jint n, jboolean haveInfo)
-{
-    // Parameters are checked on java side
-    // Failures from GetXXXArrayElements indicate a serious out-of-memory condition
-    // that we don't bother to report, we're probably dead anyway.
-    jint result = 0;
-    jchar* chs = env->GetCharArrayElements(chsArray, NULL);
-    if (chs != NULL) {
-        jbyte* info = env->GetByteArrayElements(infoArray, NULL);
-        if (info != NULL) {
-            UErrorCode status = U_ZERO_ERROR;
-            UBiDi* bidi = ubidi_openSized(n, 0, &status);
-            // Set callbacks to override bidi classes of new emoji
-            ubidi_setClassCallback(
-                    bidi, minikin::emojiBidiOverride, nullptr, nullptr, nullptr, &status);
-            ubidi_setPara(bidi, reinterpret_cast<const UChar*>(chs), n, dir, NULL, &status);
-            if (U_SUCCESS(status)) {
-                for (int i = 0; i < n; ++i) {
-                  info[i] = ubidi_getLevelAt(bidi, i);
-                }
-                result = ubidi_getParaLevel(bidi);
-            } else {
-                jniThrowException(env, "java/lang/RuntimeException", NULL);
-            }
-            ubidi_close(bidi);
-
-            env->ReleaseByteArrayElements(infoArray, info, 0);
-        }
-        env->ReleaseCharArrayElements(chsArray, chs, JNI_ABORT);
-    }
-    return result;
-}
-
-static const JNINativeMethod gMethods[] = {
-        { "runBidi", "(I[C[BIZ)I", (void*) runBidi }
-};
-
-int register_android_text_AndroidBidi(JNIEnv* env)
-{
-    return RegisterMethodsOrDie(env, "android/text/AndroidBidi", gMethods, NELEM(gMethods));
-}
-
-}
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
new file mode 100644
index 0000000..6f9cc22
--- /dev/null
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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 <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include <core_jni_helpers.h>
+#include <minikin/Hyphenator.h>
+
+namespace android {
+
+static std::string buildFileName(const std::string& locale) {
+    constexpr char SYSTEM_HYPHENATOR_PREFIX[] = "/system/usr/hyphen-data/hyph-";
+    constexpr char SYSTEM_HYPHENATOR_SUFFIX[] = ".hyb";
+    std::string lowerLocale;
+    lowerLocale.reserve(locale.size());
+    std::transform(locale.begin(), locale.end(), std::back_inserter(lowerLocale), ::tolower);
+    return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
+}
+
+static const uint8_t* mmapPatternFile(const std::string& locale) {
+    const std::string hyFilePath = buildFileName(locale);
+    const int fd = open(hyFilePath.c_str(), O_RDONLY);
+    if (fd == -1) {
+        return nullptr;  // Open failed.
+    }
+
+    struct stat st = {};
+    if (fstat(fd, &st) == -1) {  // Unlikely to happen.
+        close(fd);
+        return nullptr;
+    }
+
+    void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
+    close(fd);
+    if (ptr == MAP_FAILED) {
+        return nullptr;
+    }
+    return reinterpret_cast<const uint8_t*>(ptr);
+}
+
+static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
+        int minSuffix) {
+    minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+            nullptr, minPrefix, minSuffix, locale));
+}
+
+static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
+    const uint8_t* ptr = mmapPatternFile(locale);
+    if (ptr == nullptr) {
+        ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
+        return;
+    }
+    minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+            ptr, minPrefix, minSuffix, locale));
+}
+
+static void addHyphenatorAlias(const std::string& from, const std::string& to) {
+    minikin::addHyphenatorAlias(from, to);
+}
+
+static void init() {
+    // TODO: Confirm that these are the best values. Various sources suggest (1, 1), but that
+    // appears too small.
+    constexpr int INDIC_MIN_PREFIX = 2;
+    constexpr int INDIC_MIN_SUFFIX = 2;
+
+    addHyphenator("as", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Assamese
+    addHyphenator("be", 2, 2);  // Belarusian
+    addHyphenator("bg", 2, 2);  // Bulgarian
+    addHyphenator("bn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Bengali
+    addHyphenator("cu", 1, 2);  // Church Slavonic
+    addHyphenator("cy", 2, 3);  // Welsh
+    addHyphenator("da", 2, 2);  // Danish
+    addHyphenator("de-1901", 2, 2);  // German 1901 orthography
+    addHyphenator("de-1996", 2, 2);  // German 1996 orthography
+    addHyphenator("de-CH-1901", 2, 2);  // Swiss High German 1901 orthography
+    addHyphenator("en-GB", 2, 3);  // British English
+    addHyphenator("en-US", 2, 3);  // American English
+    addHyphenator("es", 2, 2);  // Spanish
+    addHyphenator("et", 2, 3);  // Estonian
+    addHyphenator("eu", 2, 2);  // Basque
+    addHyphenator("fr", 2, 3);  // French
+    addHyphenator("ga", 2, 3);  // Irish
+    addHyphenator("gu", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Gujarati
+    addHyphenator("hi", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Hindi
+    addHyphenator("hr", 2, 2);  // Croatian
+    addHyphenator("hu", 2, 2);  // Hungarian
+    // texhyphen sources say Armenian may be (1, 2); but that it needs confirmation.
+    // Going with a more conservative value of (2, 2) for now.
+    addHyphenator("hy", 2, 2);  // Armenian
+    addHyphenator("kn", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Kannada
+    addHyphenator("la", 2, 2);  // Latin
+    addHyphenator("ml", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Malayalam
+    addHyphenator("mn-Cyrl", 2, 2);  // Mongolian in Cyrillic script
+    addHyphenator("mr", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Marathi
+    addHyphenator("nb", 2, 2);  // Norwegian Bokmål
+    addHyphenator("nn", 2, 2);  // Norwegian Nynorsk
+    addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Oriya
+    addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Punjabi
+    addHyphenator("pt", 2, 3);  // Portuguese
+    addHyphenator("sl", 2, 2);  // Slovenian
+    addHyphenator("ta", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Tamil
+    addHyphenator("te", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX);  // Telugu
+    addHyphenator("tk", 2, 2);  // Turkmen
+    addHyphenator("und-Ethi", 1, 1);  // Any language in Ethiopic script
+
+    // Following two hyphenators do not have pattern files but there is some special logic based on
+    // language.
+    addHyphenatorWithoutPatternFile("ca", 2, 2);  // Catalan
+    addHyphenatorWithoutPatternFile("pl", 2, 2);  // Polish
+
+    // English locales that fall back to en-US. The data is from CLDR. It's all English locales,
+    // minus the locales whose parent is en-001 (from supplementalData.xml, under <parentLocales>).
+    // TODO: Figure out how to get this from ICU.
+    addHyphenatorAlias("en-AS", "en-US");  // English (American Samoa)
+    addHyphenatorAlias("en-GU", "en-US");  // English (Guam)
+    addHyphenatorAlias("en-MH", "en-US");  // English (Marshall Islands)
+    addHyphenatorAlias("en-MP", "en-US");  // English (Northern Mariana Islands)
+    addHyphenatorAlias("en-PR", "en-US");  // English (Puerto Rico)
+    addHyphenatorAlias("en-UM", "en-US");  // English (United States Minor Outlying Islands)
+    addHyphenatorAlias("en-VI", "en-US");  // English (Virgin Islands)
+
+    // All English locales other than those falling back to en-US are mapped to en-GB.
+    addHyphenatorAlias("en", "en-GB");
+
+    // For German, we're assuming the 1996 (and later) orthography by default.
+    addHyphenatorAlias("de", "de-1996");
+    // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
+    addHyphenatorAlias("de-LI-1901", "de-CH-1901");
+
+    // Norwegian is very probably Norwegian Bokmål.
+    addHyphenatorAlias("no", "nb");
+
+    // Use mn-Cyrl. According to CLDR's likelySubtags.xml, mn is most likely to be mn-Cyrl.
+    addHyphenatorAlias("mn", "mn-Cyrl");  // Mongolian
+
+    // Fall back to Ethiopic script for languages likely to be written in Ethiopic.
+    // Data is from CLDR's likelySubtags.xml.
+    // TODO: Convert this to a mechanism using ICU4J's ULocale#addLikelySubtags().
+    addHyphenatorAlias("am", "und-Ethi");  // Amharic
+    addHyphenatorAlias("byn", "und-Ethi");  // Blin
+    addHyphenatorAlias("gez", "und-Ethi");  // Geʻez
+    addHyphenatorAlias("ti", "und-Ethi");  // Tigrinya
+    addHyphenatorAlias("wal", "und-Ethi");  // Wolaytta
+
+    // Use Hindi as a fallback hyphenator for all languages written in Devanagari, etc. This makes
+    // sense because our Indic patterns are not really linguistic, but script-based.
+    addHyphenatorAlias("und-Beng", "bn");  // Bengali
+    addHyphenatorAlias("und-Deva", "hi");  // Devanagari -> Hindi
+    addHyphenatorAlias("und-Gujr", "gu");  // Gujarati
+    addHyphenatorAlias("und-Guru", "pa");  // Gurmukhi -> Punjabi
+    addHyphenatorAlias("und-Knda", "kn");  // Kannada
+    addHyphenatorAlias("und-Mlym", "ml");  // Malayalam
+    addHyphenatorAlias("und-Orya", "or");  // Oriya
+    addHyphenatorAlias("und-Taml", "ta");  // Tamil
+    addHyphenatorAlias("und-Telu", "te");  // Telugu
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nInit", "()V", (void*) init},
+};
+
+int register_android_text_Hyphenator(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/text/Hyphenator", gMethods, NELEM(gMethods));
+}
+
+}  // namespace android
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 7442fa2..c1419ba 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -55,61 +55,183 @@
 class JNILineBreakerLineWidth : public minikin::LineBreaker::LineWidthDelegate {
     public:
         JNILineBreakerLineWidth(float firstWidth, int32_t firstLineCount, float restWidth,
-                std::vector<float>&& indents, int32_t indentsOffset)
+                const std::vector<float>& indents, const std::vector<float>& leftPaddings,
+                const std::vector<float>& rightPaddings, int32_t indentsAndPaddingsOffset)
             : mFirstWidth(firstWidth), mFirstLineCount(firstLineCount), mRestWidth(restWidth),
-              mIndents(std::move(indents)), mIndentsOffset(indentsOffset) {}
+              mIndents(indents), mLeftPaddings(leftPaddings),
+              mRightPaddings(rightPaddings), mOffset(indentsAndPaddingsOffset) {}
 
         float getLineWidth(size_t lineNo) override {
             const float width = ((ssize_t)lineNo < (ssize_t)mFirstLineCount)
                     ? mFirstWidth : mRestWidth;
-            if (mIndents.empty()) {
-                return width;
-            }
+            return width - get(mIndents, lineNo);
+        }
 
-            const size_t indentIndex = lineNo + mIndentsOffset;
-            if (indentIndex < mIndents.size()) {
-                return width - mIndents[indentIndex];
-            } else {
-                return width - mIndents.back();
-            }
+        float getLeftPadding(size_t lineNo) override {
+            return get(mLeftPaddings, lineNo);
+        }
+
+        float getRightPadding(size_t lineNo) override {
+            return get(mRightPaddings, lineNo);
         }
 
     private:
+        float get(const std::vector<float>& vec, size_t lineNo) {
+            if (vec.empty()) {
+                return 0;
+            }
+            const size_t index = lineNo + mOffset;
+            if (index < vec.size()) {
+                return vec[index];
+            } else {
+                return vec.back();
+            }
+        }
+
         const float mFirstWidth;
         const int32_t mFirstLineCount;
         const float mRestWidth;
-        const std::vector<float> mIndents;
-        const int32_t mIndentsOffset;
+        const std::vector<float>& mIndents;
+        const std::vector<float>& mLeftPaddings;
+        const std::vector<float>& mRightPaddings;
+        const int32_t mOffset;
 };
 
+static inline std::vector<float> jintArrayToFloatVector(JNIEnv* env, jintArray javaArray) {
+    if (javaArray == nullptr) {
+         return std::vector<float>();
+    } else {
+        ScopedIntArrayRO intArr(env, javaArray);
+        return std::vector<float>(intArr.get(), intArr.get() + intArr.size());
+    }
+}
+
+class Run {
+    public:
+        Run(int32_t start, int32_t end) : mStart(start), mEnd(end) {}
+        virtual ~Run() {}
+
+        virtual void addTo(minikin::LineBreaker* lineBreaker) = 0;
+
+    protected:
+        const int32_t mStart;
+        const int32_t mEnd;
+
+    private:
+        // Forbid copy and assign.
+        Run(const Run&) = delete;
+        void operator=(const Run&) = delete;
+};
+
+class StyleRun : public Run {
+    public:
+        StyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+                std::shared_ptr<minikin::FontCollection>&& collection,
+                minikin::FontStyle&& style, bool isRtl)
+            : Run(start, end), mPaint(std::move(paint)), mCollection(std::move(collection)),
+              mStyle(std::move(style)), mIsRtl(isRtl) {}
+
+        void addTo(minikin::LineBreaker* lineBreaker) override {
+            lineBreaker->addStyleRun(&mPaint, mCollection, mStyle, mStart, mEnd, mIsRtl);
+        }
+
+    private:
+        minikin::MinikinPaint mPaint;
+        std::shared_ptr<minikin::FontCollection> mCollection;
+        minikin::FontStyle mStyle;
+        const bool mIsRtl;
+};
+
+class Replacement : public Run {
+    public:
+        Replacement(int32_t start, int32_t end, float width, uint32_t localeListId)
+            : Run(start, end), mWidth(width), mLocaleListId(localeListId) {}
+
+        void addTo(minikin::LineBreaker* lineBreaker) override {
+            lineBreaker->addReplacement(mStart, mEnd, mWidth, mLocaleListId);
+        }
+
+    private:
+        const float mWidth;
+        const uint32_t mLocaleListId;
+};
+
+class StaticLayoutNative {
+    public:
+        StaticLayoutNative(
+                minikin::BreakStrategy strategy, minikin::HyphenationFrequency frequency,
+                bool isJustified, std::vector<float>&& indents, std::vector<float>&& leftPaddings,
+                std::vector<float>&& rightPaddings)
+            : mStrategy(strategy), mFrequency(frequency), mIsJustified(isJustified),
+              mIndents(std::move(indents)), mLeftPaddings(std::move(leftPaddings)),
+              mRightPaddings(std::move(rightPaddings)) {}
+
+        void addStyleRun(int32_t start, int32_t end, minikin::MinikinPaint&& paint,
+                         std::shared_ptr<minikin::FontCollection> collection,
+                         minikin::FontStyle&& style, bool isRtl) {
+            mRuns.emplace_back(std::make_unique<StyleRun>(
+                    start, end, std::move(paint), std::move(collection), std::move(style), isRtl));
+        }
+
+        void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
+            mRuns.emplace_back(std::make_unique<Replacement>(start, end, width, localeListId));
+        }
+
+        // Only valid while this instance is alive.
+        inline std::unique_ptr<minikin::LineBreaker::LineWidthDelegate> buildLineWidthDelegate(
+                float firstWidth, int32_t firstLineCount, float restWidth,
+                int32_t indentsAndPaddingsOffset) {
+            return std::make_unique<JNILineBreakerLineWidth>(
+                firstWidth, firstLineCount, restWidth, mIndents, mLeftPaddings, mRightPaddings,
+                indentsAndPaddingsOffset);
+        }
+
+        void addRuns(minikin::LineBreaker* lineBreaker) {
+            for (const auto& run : mRuns) {
+                run->addTo(lineBreaker);
+            }
+        }
+
+        void clearRuns() {
+            mRuns.clear();
+        }
+
+        inline minikin::BreakStrategy getStrategy() const { return mStrategy; }
+        inline minikin::HyphenationFrequency getFrequency() const { return mFrequency; }
+        inline bool isJustified() const { return mIsJustified; }
+
+    private:
+        const minikin::BreakStrategy mStrategy;
+        const minikin::HyphenationFrequency mFrequency;
+        const bool mIsJustified;
+        const std::vector<float> mIndents;
+        const std::vector<float> mLeftPaddings;
+        const std::vector<float> mRightPaddings;
+
+        std::vector<std::unique_ptr<Run>> mRuns;
+};
+
+static inline StaticLayoutNative* toNative(jlong ptr) {
+    return reinterpret_cast<StaticLayoutNative*>(ptr);
+}
+
 // set text and set a number of parameters for creating a layout (width, tabstops, strategy,
 // hyphenFrequency)
-static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
-        jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
-        jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency,
-        jboolean isJustified, jintArray indents, jint indentsOffset) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    b->resize(length);
-    env->GetCharArrayRegion(text, 0, length, b->buffer());
-    b->setText();
-    if (variableTabStops == nullptr) {
-        b->setTabStops(nullptr, 0, defaultTabStop);
-    } else {
-        ScopedIntArrayRO stops(env, variableTabStops);
-        b->setTabStops(stops.get(), stops.size(), defaultTabStop);
-    }
-    b->setStrategy(static_cast<minikin::BreakStrategy>(strategy));
-    b->setHyphenationFrequency(static_cast<minikin::HyphenationFrequency>(hyphenFrequency));
-    b->setJustified(isJustified);
+static jlong nInit(JNIEnv* env, jclass /* unused */,
+        jint breakStrategy, jint hyphenationFrequency, jboolean isJustified,
+        jintArray indents, jintArray leftPaddings, jintArray rightPaddings) {
+    return reinterpret_cast<jlong>(new StaticLayoutNative(
+            static_cast<minikin::BreakStrategy>(breakStrategy),
+            static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
+            isJustified,
+            jintArrayToFloatVector(env, indents),
+            jintArrayToFloatVector(env, leftPaddings),
+            jintArrayToFloatVector(env, rightPaddings)));
+}
 
-    std::vector<float> indentVec;
-    // TODO: copy indents only once when LineBreaker is started to be used.
-    if (indents != nullptr) {
-        ScopedIntArrayRO indentArr(env, indents);
-        indentVec.assign(indentArr.get(), indentArr.get() + indentArr.size());
-    }
-    b->setLineWidthDelegate(std::make_unique<JNILineBreakerLineWidth>(
-            firstWidth, firstWidthLineLimit, restWidth, std::move(indentVec), indentsOffset));
+// CriticalNative
+static void nFinish(jlong nativePtr) {
+    delete toNative(nativePtr);
 }
 
 static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
@@ -141,114 +263,125 @@
 }
 
 static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr,
-                               jobject recycle, jintArray recycleBreaks,
-                               jfloatArray recycleWidths, jfloatArray recycleAscents,
-                               jfloatArray recycleDescents, jintArray recycleFlags,
-                               jint recycleLength) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+        // Inputs
+        jcharArray text,
+        jint length,
+        jfloat firstWidth,
+        jint firstWidthLineCount,
+        jfloat restWidth,
+        jintArray variableTabStops,
+        jint defaultTabStop,
+        jint indentsOffset,
 
-    size_t nBreaks = b->computeBreaks();
+        // Outputs
+        jobject recycle,
+        jint recycleLength,
+        jintArray recycleBreaks,
+        jfloatArray recycleWidths,
+        jfloatArray recycleAscents,
+        jfloatArray recycleDescents,
+        jintArray recycleFlags,
+        jfloatArray charWidths) {
+
+    StaticLayoutNative* builder = toNative(nativePtr);
+
+    // TODO: Reorganize minikin APIs.
+    minikin::LineBreaker b;
+    b.resize(length);
+    env->GetCharArrayRegion(text, 0, length, b.buffer());
+    b.setText();
+    if (variableTabStops == nullptr) {
+        b.setTabStops(nullptr, 0, defaultTabStop);
+    } else {
+        ScopedIntArrayRO stops(env, variableTabStops);
+        b.setTabStops(stops.get(), stops.size(), defaultTabStop);
+    }
+    b.setStrategy(builder->getStrategy());
+    b.setHyphenationFrequency(builder->getFrequency());
+    b.setJustified(builder->isJustified());
+    b.setLineWidthDelegate(builder->buildLineWidthDelegate(
+            firstWidth, firstWidthLineCount, restWidth, indentsOffset));
+
+    builder->addRuns(&b);
+
+    size_t nBreaks = b.computeBreaks();
 
     recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents,
-            recycleFlags, recycleLength, nBreaks, b->getBreaks(), b->getWidths(), b->getAscents(),
-            b->getDescents(), b->getFlags());
+            recycleFlags, recycleLength, nBreaks, b.getBreaks(), b.getWidths(), b.getAscents(),
+            b.getDescents(), b.getFlags());
 
-    b->finish();
+    env->SetFloatArrayRegion(charWidths, 0, b.size(), b.charWidths());
+
+    b.finish();
+    builder->clearRuns();
 
     return static_cast<jint>(nBreaks);
 }
 
-static jlong nNewBuilder(JNIEnv*, jclass) {
-    return reinterpret_cast<jlong>(new minikin::LineBreaker);
-}
-
-static void nFreeBuilder(JNIEnv*, jclass, jlong nativePtr) {
-    delete reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-}
-
-static void nFinishBuilder(JNIEnv*, jclass, jlong nativePtr) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    b->finish();
-}
-
-static jlong nLoadHyphenator(JNIEnv* env, jclass, jobject buffer, jint offset,
-        jint minPrefix, jint minSuffix) {
-    const uint8_t* bytebuf = nullptr;
-    if (buffer != nullptr) {
-        void* rawbuf = env->GetDirectBufferAddress(buffer);
-        if (rawbuf != nullptr) {
-            bytebuf = reinterpret_cast<const uint8_t*>(rawbuf) + offset;
-        } else {
-            ALOGE("failed to get direct buffer address");
-        }
-    }
-    minikin::Hyphenator* hyphenator = minikin::Hyphenator::loadBinary(
-            bytebuf, minPrefix, minSuffix);
-    return reinterpret_cast<jlong>(hyphenator);
-}
-
-static void nSetLocales(JNIEnv* env, jclass, jlong nativePtr, jstring javaLocaleNames,
-        jlongArray nativeHyphenators) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-
-    ScopedUtfChars localeNames(env, javaLocaleNames);
-    ScopedLongArrayRO hyphArr(env, nativeHyphenators);
-    const size_t numLocales = hyphArr.size();
-    std::vector<minikin::Hyphenator*> hyphVec;
-    hyphVec.reserve(numLocales);
-    for (size_t i = 0; i < numLocales; i++) {
-        hyphVec.push_back(reinterpret_cast<minikin::Hyphenator*>(hyphArr[i]));
-    }
-    b->setLocales(localeNames.c_str(), hyphVec);
-}
-
 // Basically similar to Paint.getTextRunAdvances but with C++ interface
-static jfloat nAddStyleRun(JNIEnv* env, jclass, jlong nativePtr, jlong nativePaint, jint start,
-        jint end, jboolean isRtl) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
+// CriticalNative
+static void nAddStyleRun(jlong nativePtr, jlong nativePaint, jint start, jint end, jboolean isRtl) {
+    StaticLayoutNative* builder = toNative(nativePtr);
     Paint* paint = reinterpret_cast<Paint*>(nativePaint);
     const Typeface* typeface = paint->getAndroidTypeface();
     minikin::MinikinPaint minikinPaint;
     const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
     minikin::FontStyle style = MinikinUtils::prepareMinikinPaint(&minikinPaint, paint,
             typeface);
-    return b->addStyleRun(&minikinPaint, resolvedTypeface->fFontCollection, style, start, end,
-            isRtl);
+
+    builder->addStyleRun(
+        start, end, std::move(minikinPaint), resolvedTypeface->fFontCollection, std::move(style),
+        isRtl);
 }
 
-// Accept width measurements for the run, passed in from Java
-static void nAddMeasuredRun(JNIEnv* env, jclass, jlong nativePtr,
-        jint start, jint end, jfloatArray widths) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    env->GetFloatArrayRegion(widths, start, end - start, b->charWidths() + start);
-    b->addStyleRun(nullptr, nullptr, minikin::FontStyle{}, start, end, false);
-}
-
-static void nAddReplacementRun(JNIEnv* env, jclass, jlong nativePtr,
-        jint start, jint end, jfloat width) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    b->addReplacement(start, end, width);
-}
-
-static void nGetWidths(JNIEnv* env, jclass, jlong nativePtr, jfloatArray widths) {
-    minikin::LineBreaker* b = reinterpret_cast<minikin::LineBreaker*>(nativePtr);
-    env->SetFloatArrayRegion(widths, 0, b->size(), b->charWidths());
+// CriticalNative
+static void nAddReplacementRun(jlong nativePtr, jlong nativePaint, jint start, jint end,
+        jfloat width) {
+    StaticLayoutNative* builder = toNative(nativePtr);
+    Paint* paint = reinterpret_cast<Paint*>(nativePaint);
+    builder->addReplacementRun(start, end, width, paint->getMinikinLangListId());
 }
 
 static const JNINativeMethod gMethods[] = {
-    // TODO performance: many of these are candidates for fast jni, awaiting guidance
-    {"nNewBuilder", "()J", (void*) nNewBuilder},
-    {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
-    {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
-    {"nLoadHyphenator", "(Ljava/nio/ByteBuffer;III)J", (void*) nLoadHyphenator},
-    {"nSetLocales", "(JLjava/lang/String;[J)V", (void*) nSetLocales},
-    {"nSetupParagraph", "(J[CIFIF[IIIIZ[II)V", (void*) nSetupParagraph},
-    {"nAddStyleRun", "(JJIIZ)F", (void*) nAddStyleRun},
-    {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
-    {"nAddReplacementRun", "(JIIF)V", (void*) nAddReplacementRun},
-    {"nGetWidths", "(J[F)V", (void*) nGetWidths},
-    {"nComputeLineBreaks", "(JLandroid/text/StaticLayout$LineBreaks;[I[F[F[F[II)I",
-        (void*) nComputeLineBreaks}
+    // Fast Natives
+    {"nInit", "("
+        "I"  // breakStrategy
+        "I"  // hyphenationFrequency
+        "Z"  // isJustified
+        "[I"  // indents
+        "[I"  // left paddings
+        "[I"  // right paddings
+        ")J", (void*) nInit},
+
+    // Critical Natives
+    {"nFinish", "(J)V", (void*) nFinish},
+    {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
+    {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
+
+    // Regular JNI
+    {"nComputeLineBreaks", "("
+        "J"  // nativePtr
+
+        // Inputs
+        "[C"  // text
+        "I"  // length
+        "F"  // firstWidth
+        "I"  // firstWidthLineCount
+        "F"  // restWidth
+        "[I"  // variableTabStops
+        "I"  // defaultTabStop
+        "I"  // indentsOffset
+
+        // Outputs
+        "Landroid/text/StaticLayout$LineBreaks;"  // recycle
+        "I"  // recycleLength
+        "[I"  // recycleBreaks
+        "[F"  // recycleWidths
+        "[F"  // recycleAscents
+        "[F"  // recycleDescents
+        "[I"  // recycleFlags
+        "[F"  // charWidths
+        ")I", (void*) nComputeLineBreaks}
 };
 
 int register_android_text_StaticLayout(JNIEnv* env)
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b137da3..c6828c4 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -568,6 +568,35 @@
     return (res) ? (jint)cookie : 0;
 }
 
+static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz,
+                                                    jobject fileDescriptor, jstring debugPathName,
+                                                    jboolean appAsLib)
+{
+    ScopedUtfChars debugPathName8(env, debugPathName);
+
+    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+    if (fd < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+        return 0;
+    }
+
+    AssetManager* am = assetManagerForJavaObject(env, clazz);
+    if (am == NULL) {
+        return 0;
+    }
+
+    int dupfd = ::dup(fd);
+    if (dupfd < 0) {
+        jniThrowIOException(env, errno);
+        return 0;
+    }
+
+    int32_t cookie;
+    bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib);
+
+    return (res) ? static_cast<jint>(cookie) : 0;
+}
+
 static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
 {
     AssetManager* am = assetManagerForJavaObject(env, clazz);
@@ -1673,6 +1702,8 @@
         (void*) android_content_AssetManager_getAssetRemainingLength },
     { "addAssetPathNative", "(Ljava/lang/String;Z)I",
         (void*) android_content_AssetManager_addAssetPath },
+    { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I",
+        (void*) android_content_AssetManager_addAssetFd },
     { "addOverlayPathNative",   "(Ljava/lang/String;)I",
         (void*) android_content_AssetManager_addOverlayPath },
     { "isUpToDate",     "()Z",
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index af5d12b..dea38e8 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -20,6 +20,7 @@
 #include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
+#include <atomic>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <stdio.h>
@@ -32,6 +33,7 @@
 #include <binder/IServiceManager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/Parcel.h>
+#include <binder/BpBinder.h>
 #include <binder/ProcessState.h>
 #include <log/log.h>
 #include <utils/Atomic.h>
@@ -80,9 +82,17 @@
     // Class state.
     jclass mClass;
     jmethodID mForceGc;
+    jmethodID mProxyLimitCallback;
 
 } gBinderInternalOffsets;
 
+static struct sparseintarray_offsets_t
+{
+    jclass classObject;
+    jmethodID constructor;
+    jmethodID put;
+} gSparseIntArrayOffsets;
+
 // ----------------------------------------------------------------------------
 
 static struct error_offsets_t
@@ -96,14 +106,11 @@
 {
     // Class state.
     jclass mClass;
-    jmethodID mConstructor;
+    jmethodID mGetInstance;
     jmethodID mSendDeathNotice;
 
     // Object state.
-    jfieldID mObject;
-    jfieldID mSelf;
-    jfieldID mOrgue;
-
+    jfieldID mNativeData;  // Field holds native pointer to BinderProxyNativeData.
 } gBinderProxyOffsets;
 
 static struct class_offsets_t
@@ -144,20 +151,45 @@
 // ****************************************************************************
 // ****************************************************************************
 
-static volatile int32_t gNumRefsCreated = 0;
-static volatile int32_t gNumProxyRefs = 0;
-static volatile int32_t gNumLocalRefs = 0;
-static volatile int32_t gNumDeathRefs = 0;
+static constexpr int32_t PROXY_WARN_INTERVAL = 5000;
+static constexpr uint32_t GC_INTERVAL = 1000;
 
-static void incRefsCreated(JNIEnv* env)
+// Protected by gProxyLock. We warn if this gets too large.
+static int32_t gNumProxies = 0;
+static int32_t gProxiesWarned = 0;
+
+// Number of GlobalRefs held by JavaBBinders.
+static std::atomic<uint32_t> gNumLocalRefsCreated(0);
+static std::atomic<uint32_t> gNumLocalRefsDeleted(0);
+// Number of GlobalRefs held by JavaDeathRecipients.
+static std::atomic<uint32_t> gNumDeathRefsCreated(0);
+static std::atomic<uint32_t> gNumDeathRefsDeleted(0);
+
+// We collected after creating this many refs.
+static std::atomic<uint32_t> gCollectedAtRefs(0);
+
+// Garbage collect if we've allocated at least GC_INTERVAL refs since the last time.
+// TODO: Consider removing this completely. We should no longer be generating GlobalRefs
+// that are reclaimed as a result of GC action.
+static void gcIfManyNewRefs(JNIEnv* env)
 {
-    int old = android_atomic_inc(&gNumRefsCreated);
-    if (old == 200) {
-        android_atomic_and(0, &gNumRefsCreated);
-        env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
-                gBinderInternalOffsets.mForceGc);
+    uint32_t totalRefs = gNumLocalRefsCreated.load(std::memory_order_relaxed)
+            + gNumDeathRefsCreated.load(std::memory_order_relaxed);
+    uint32_t collectedAtRefs = gCollectedAtRefs.load(memory_order_relaxed);
+    // A bound on the number of threads that can have incremented gNum...RefsCreated before the
+    // following check is executed. Effectively a bound on #threads. Almost any value will do.
+    static constexpr uint32_t MAX_RACING = 100000;
+
+    if (totalRefs - (collectedAtRefs + GC_INTERVAL) /* modular arithmetic! */ < MAX_RACING) {
+        // Recently passed next GC interval.
+        if (gCollectedAtRefs.compare_exchange_strong(collectedAtRefs,
+                collectedAtRefs + GC_INTERVAL, std::memory_order_relaxed)) {
+            ALOGV("Binder forcing GC at %u created refs", totalRefs);
+            env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+                    gBinderInternalOffsets.mForceGc);
+        }  // otherwise somebody else beat us to it.
     } else {
-        ALOGV("Now have %d binder ops", old);
+        ALOGV("Now have %d binder ops", totalRefs - collectedAtRefs);
     }
 }
 
@@ -267,12 +299,12 @@
 class JavaBBinder : public BBinder
 {
 public:
-    JavaBBinder(JNIEnv* env, jobject object)
+    JavaBBinder(JNIEnv* env, jobject /* Java Binder */ object)
         : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
     {
         ALOGV("Creating JavaBBinder %p\n", this);
-        android_atomic_inc(&gNumLocalRefs);
-        incRefsCreated(env);
+        gNumLocalRefsCreated.fetch_add(1, std::memory_order_relaxed);
+        gcIfManyNewRefs(env);
     }
 
     bool    checkSubclass(const void* subclassID) const
@@ -289,7 +321,7 @@
     virtual ~JavaBBinder()
     {
         ALOGV("Destroying JavaBBinder %p\n", this);
-        android_atomic_dec(&gNumLocalRefs);
+        gNumLocalRefsDeleted.fetch_add(1, memory_order_relaxed);
         JNIEnv* env = javavm_to_jnienv(mVM);
         env->DeleteGlobalRef(mObject);
     }
@@ -351,12 +383,12 @@
 
 private:
     JavaVM* const   mVM;
-    jobject const   mObject;
+    jobject const   mObject;  // GlobalRef to Java Binder
 };
 
 // ----------------------------------------------------------------------------
 
-class JavaBBinderHolder : public RefBase
+class JavaBBinderHolder
 {
 public:
     sp<JavaBBinder> get(JNIEnv* env, jobject obj)
@@ -421,8 +453,8 @@
         LOGDEATH("Adding JDR %p to DRL %p", this, list.get());
         list->add(this);
 
-        android_atomic_inc(&gNumDeathRefs);
-        incRefsCreated(env);
+        gNumDeathRefsCreated.fetch_add(1, std::memory_order_relaxed);
+        gcIfManyNewRefs(env);
     }
 
     void binderDied(const wp<IBinder>& who)
@@ -502,7 +534,7 @@
     virtual ~JavaDeathRecipient()
     {
         //ALOGI("Removing death ref: recipient=%p\n", mObject);
-        android_atomic_dec(&gNumDeathRefs);
+        gNumDeathRefsDeleted.fetch_add(1, std::memory_order_relaxed);
         JNIEnv* env = javavm_to_jnienv(mVM);
         if (mObject != NULL) {
             env->DeleteGlobalRef(mObject);
@@ -513,8 +545,8 @@
 
 private:
     JavaVM* const mVM;
-    jobject mObject;
-    jweak mObjectWeak; // will be a weak ref to the same VM-side DeathRecipient after binderDied()
+    jobject mObject;  // Initial strong ref to Java-side DeathRecipient. Cleared on binderDied().
+    jweak mObjectWeak; // Weak ref to the same Java-side DeathRecipient after binderDied().
     wp<DeathRecipientList> mList;
 };
 
@@ -579,21 +611,40 @@
 
 namespace android {
 
-static void proxy_cleanup(const void* id, void* obj, void* cleanupCookie)
-{
-    android_atomic_dec(&gNumProxyRefs);
-    JNIEnv* env = javavm_to_jnienv((JavaVM*)cleanupCookie);
-    env->DeleteGlobalRef((jobject)obj);
+// We aggregate native pointer fields for BinderProxy in a single object to allow
+// management with a single NativeAllocationRegistry, and to reduce the number of JNI
+// Java field accesses. This costs us some extra indirections here.
+struct BinderProxyNativeData {
+    // Both fields are constant and not null once javaObjectForIBinder returns this as
+    // part of a BinderProxy.
+
+    // The native IBinder proxied by this BinderProxy.
+    sp<IBinder> mObject;
+
+    // Death recipients for mObject. Reference counted only because DeathRecipients
+    // hold a weak reference that can be temporarily promoted.
+    sp<DeathRecipientList> mOrgue;  // Death recipients for mObject.
+};
+
+BinderProxyNativeData* getBPNativeData(JNIEnv* env, jobject obj) {
+    return (BinderProxyNativeData *) env->GetLongField(obj, gBinderProxyOffsets.mNativeData);
 }
 
-static Mutex mProxyLock;
+static Mutex gProxyLock;
 
+// We may cache a single BinderProxyNativeData node to avoid repeat allocation.
+// All fields are null. Protected by gProxyLock.
+static BinderProxyNativeData *gNativeDataCache;
+
+// If the argument is a JavaBBinder, return the Java object that was used to create it.
+// Otherwise return a BinderProxy for the IBinder. If a previous call was passed the
+// same IBinder, and the original BinderProxy is still alive, return the same BinderProxy.
 jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
 {
     if (val == NULL) return NULL;
 
     if (val->checkSubclass(&gBinderOffsets)) {
-        // One of our own!
+        // It's a JavaBBinder created by ibinderForJavaObject. Already has Java object.
         jobject object = static_cast<JavaBBinder*>(val.get())->object();
         LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
         return object;
@@ -601,44 +652,33 @@
 
     // For the rest of the function we will hold this lock, to serialize
     // looking/creation/destruction of Java proxies for native Binder proxies.
-    AutoMutex _l(mProxyLock);
+    AutoMutex _l(gProxyLock);
 
-    // Someone else's...  do we know about it?
-    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
-    if (object != NULL) {
-        jobject res = jniGetReferent(env, object);
-        if (res != NULL) {
-            ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
-            return res;
-        }
-        LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
-        android_atomic_dec(&gNumProxyRefs);
-        val->detachObject(&gBinderProxyOffsets);
-        env->DeleteGlobalRef(object);
+    BinderProxyNativeData* nativeData = gNativeDataCache;
+    if (nativeData == nullptr) {
+        nativeData = new BinderProxyNativeData();
     }
-
-    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
-    if (object != NULL) {
-        LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
-        // The proxy holds a reference to the native object.
-        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
-        val->incStrong((void*)javaObjectForIBinder);
-
-        // The native object needs to hold a weak reference back to the
-        // proxy, so we can retrieve the same proxy if it is still active.
-        jobject refObject = env->NewGlobalRef(
-                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
-        val->attachObject(&gBinderProxyOffsets, refObject,
-                jnienv_to_javavm(env), proxy_cleanup);
-
-        // Also remember the death recipients registered on this proxy
-        sp<DeathRecipientList> drl = new DeathRecipientList;
-        drl->incStrong((void*)javaObjectForIBinder);
-        env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));
-
-        // Note that a new object reference has been created.
-        android_atomic_inc(&gNumProxyRefs);
-        incRefsCreated(env);
+    // gNativeDataCache is now logically empty.
+    jobject object = env->CallStaticObjectMethod(gBinderProxyOffsets.mClass,
+            gBinderProxyOffsets.mGetInstance, (jlong) nativeData, (jlong) val.get());
+    if (env->ExceptionCheck()) {
+        gNativeDataCache = nativeData;
+        return NULL;
+    }
+    BinderProxyNativeData* actualNativeData = getBPNativeData(env, object);
+    if (actualNativeData == nativeData) {
+        // New BinderProxy; we still have exclusive access.
+        nativeData->mOrgue = new DeathRecipientList;
+        nativeData->mObject = val;
+        gNativeDataCache = nullptr;
+        ++gNumProxies;
+        if (++gNumProxies >= gProxiesWarned + PROXY_WARN_INTERVAL) {
+            ALOGW("Unexpectedly many live BinderProxies: %d\n", gNumProxies);
+            gProxiesWarned = gNumProxies;
+        }
+    } else {
+        // nativeData wasn't used. Reuse it the next time.
+        gNativeDataCache = nativeData;
     }
 
     return object;
@@ -648,15 +688,16 @@
 {
     if (obj == NULL) return NULL;
 
+    // Instance of Binder?
     if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {
         JavaBBinderHolder* jbh = (JavaBBinderHolder*)
             env->GetLongField(obj, gBinderOffsets.mObject);
-        return jbh != NULL ? jbh->get(env, obj) : NULL;
+        return jbh->get(env, obj);
     }
 
+    // Instance of BinderProxy?
     if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) {
-        return (IBinder*)
-            env->GetLongField(obj, gBinderProxyOffsets.mObject);
+        return getBPNativeData(env, obj)->mObject;
     }
 
     ALOGW("ibinderForJavaObject: %p is not a Binder object", obj);
@@ -849,35 +890,21 @@
     IPCThreadState::self()->flushCommands();
 }
 
-static void android_os_Binder_init(JNIEnv* env, jobject obj)
+static jlong android_os_Binder_getNativeBBinderHolder(JNIEnv* env, jobject clazz)
 {
     JavaBBinderHolder* jbh = new JavaBBinderHolder();
-    if (jbh == NULL) {
-        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
-        return;
-    }
-    ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh);
-    jbh->incStrong((void*)android_os_Binder_init);
-    env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);
+    return (jlong) jbh;
 }
 
-static void android_os_Binder_destroyBinder(JNIEnv* env, jobject obj)
+static void Binder_destroy(void* rawJbh)
 {
-    JavaBBinderHolder* jbh = (JavaBBinderHolder*)
-        env->GetLongField(obj, gBinderOffsets.mObject);
-    if (jbh != NULL) {
-        env->SetLongField(obj, gBinderOffsets.mObject, 0);
-        ALOGV("Java Binder %p: removing ref on holder %p", obj, jbh);
-        jbh->decStrong((void*)android_os_Binder_init);
-    } else {
-        // Encountering an uninitialized binder is harmless.  All it means is that
-        // the Binder was only partially initialized when its finalizer ran and called
-        // destroyBinder().  The Binder could be partially initialized for several reasons.
-        // For example, a Binder subclass constructor might have thrown an exception before
-        // it could delegate to its superclass's constructor.  Consequently init() would
-        // not have been called and the holder pointer would remain NULL.
-        ALOGV("Java Binder %p: ignoring uninitialized binder", obj);
-    }
+    JavaBBinderHolder* jbh = (JavaBBinderHolder*) rawJbh;
+    ALOGV("Java Binder: deleting holder %p", jbh);
+    delete jbh;
+}
+
+JNIEXPORT jlong JNICALL android_os_Binder_getNativeFinalizer(JNIEnv*, jclass) {
+    return (jlong) Binder_destroy;
 }
 
 static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject clazz)
@@ -896,8 +923,8 @@
     { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
     { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
     { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
-    { "init", "()V", (void*)android_os_Binder_init },
-    { "destroyBinder", "()V", (void*)android_os_Binder_destroyBinder },
+    { "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
+    { "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
     { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
 };
 
@@ -924,17 +951,18 @@
 
 jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz)
 {
-    return gNumLocalRefs;
+    return gNumLocalRefsCreated - gNumLocalRefsDeleted;
 }
 
 jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz)
 {
-    return gNumProxyRefs;
+    AutoMutex _l(gProxyLock);
+    return gNumProxies;
 }
 
 jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz)
 {
-    return gNumDeathRefs;
+    return gNumDeathRefsCreated - gNumDeathRefsDeleted;
 }
 
 }
@@ -969,8 +997,45 @@
 
 static void android_os_BinderInternal_handleGc(JNIEnv* env, jobject clazz)
 {
-    ALOGV("Gc has executed, clearing binder ops");
-    android_atomic_and(0, &gNumRefsCreated);
+    ALOGV("Gc has executed, updating Refs count at GC");
+    gCollectedAtRefs = gNumLocalRefsCreated + gNumDeathRefsCreated;
+}
+
+static void android_os_BinderInternal_proxyLimitcallback(int uid)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    env->CallStaticVoidMethod(gBinderInternalOffsets.mClass,
+                              gBinderInternalOffsets.mProxyLimitCallback,
+                              uid);
+}
+
+static void android_os_BinderInternal_setBinderProxyCountEnabled(JNIEnv* env, jobject clazz,
+                                                                 jboolean enable)
+{
+    BpBinder::setCountByUidEnabled((bool) enable);
+}
+
+static jobject android_os_BinderInternal_getBinderProxyPerUidCounts(JNIEnv* env, jclass clazz)
+{
+    Vector<uint32_t> uids, counts;
+    BpBinder::getCountByUid(uids, counts);
+    jobject sparseIntArray = env->NewObject(gSparseIntArrayOffsets.classObject,
+                                            gSparseIntArrayOffsets.constructor);
+    for (size_t i = 0; i < uids.size(); i++) {
+        env->CallVoidMethod(sparseIntArray, gSparseIntArrayOffsets.put,
+                            static_cast<jint>(uids[i]), static_cast<jint>(counts[i]));
+    }
+    return sparseIntArray;
+}
+
+static jint android_os_BinderInternal_getBinderProxyCount(JNIEnv* env, jobject clazz, jint uid) {
+    return static_cast<jint>(BpBinder::getBinderProxyCount(static_cast<uint32_t>(uid)));
+}
+
+static void android_os_BinderInternal_setBinderProxyCountWatermarks(JNIEnv* env, jobject clazz,
+                                                                    jint high, jint low)
+{
+    BpBinder::setBinderProxyCountWatermarks(high, low);
 }
 
 // ----------------------------------------------------------------------------
@@ -981,7 +1046,11 @@
     { "joinThreadPool", "()V", (void*)android_os_BinderInternal_joinThreadPool },
     { "disableBackgroundScheduling", "(Z)V", (void*)android_os_BinderInternal_disableBackgroundScheduling },
     { "setMaxThreads", "(I)V", (void*)android_os_BinderInternal_setMaxThreads },
-    { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc }
+    { "handleGc", "()V", (void*)android_os_BinderInternal_handleGc },
+    { "nSetBinderProxyCountEnabled", "(Z)V", (void*)android_os_BinderInternal_setBinderProxyCountEnabled },
+    { "nGetBinderProxyPerUidCounts", "()Landroid/util/SparseIntArray;", (void*)android_os_BinderInternal_getBinderProxyPerUidCounts },
+    { "nGetBinderProxyCount", "(I)I", (void*)android_os_BinderInternal_getBinderProxyCount },
+    { "nSetBinderProxyCountWatermarks", "(II)V", (void*)android_os_BinderInternal_setBinderProxyCountWatermarks}
 };
 
 const char* const kBinderInternalPathName = "com/android/internal/os/BinderInternal";
@@ -992,6 +1061,16 @@
 
     gBinderInternalOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
     gBinderInternalOffsets.mForceGc = GetStaticMethodIDOrDie(env, clazz, "forceBinderGc", "()V");
+    gBinderInternalOffsets.mProxyLimitCallback = GetStaticMethodIDOrDie(env, clazz, "binderProxyLimitCallbackFromNative", "(I)V");
+
+    jclass SparseIntArrayClass = FindClassOrDie(env, "android/util/SparseIntArray");
+    gSparseIntArrayOffsets.classObject = MakeGlobalRefOrDie(env, SparseIntArrayClass);
+    gSparseIntArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject,
+                                                           "<init>", "()V");
+    gSparseIntArrayOffsets.put = GetMethodIDOrDie(env, gSparseIntArrayOffsets.classObject, "put",
+                                                   "(II)V");
+
+    BpBinder::setLimitCallback(android_os_BinderInternal_proxyLimitcallback);
 
     return RegisterMethodsOrDie(
         env, kBinderInternalPathName,
@@ -1004,8 +1083,7 @@
 
 static jboolean android_os_BinderProxy_pingBinder(JNIEnv* env, jobject obj)
 {
-    IBinder* target = (IBinder*)
-        env->GetLongField(obj, gBinderProxyOffsets.mObject);
+    IBinder* target = getBPNativeData(env, obj)->mObject.get();
     if (target == NULL) {
         return JNI_FALSE;
     }
@@ -1015,7 +1093,7 @@
 
 static jstring android_os_BinderProxy_getInterfaceDescriptor(JNIEnv* env, jobject obj)
 {
-    IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject);
+    IBinder* target = getBPNativeData(env, obj)->mObject.get();
     if (target != NULL) {
         const String16& desc = target->getInterfaceDescriptor();
         return env->NewString(reinterpret_cast<const jchar*>(desc.string()),
@@ -1028,8 +1106,7 @@
 
 static jboolean android_os_BinderProxy_isBinderAlive(JNIEnv* env, jobject obj)
 {
-    IBinder* target = (IBinder*)
-        env->GetLongField(obj, gBinderProxyOffsets.mObject);
+    IBinder* target = getBPNativeData(env, obj)->mObject.get();
     if (target == NULL) {
         return JNI_FALSE;
     }
@@ -1151,8 +1228,7 @@
         return JNI_FALSE;
     }
 
-    IBinder* target = (IBinder*)
-        env->GetLongField(obj, gBinderProxyOffsets.mObject);
+    IBinder* target = getBPNativeData(env, obj)->mObject.get();
     if (target == NULL) {
         jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");
         return JNI_FALSE;
@@ -1202,18 +1278,13 @@
         return;
     }
 
-    IBinder* target = (IBinder*)
-        env->GetLongField(obj, gBinderProxyOffsets.mObject);
-    if (target == NULL) {
-        ALOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient);
-        assert(false);
-    }
+    BinderProxyNativeData *nd = getBPNativeData(env, obj);
+    IBinder* target = nd->mObject.get();
 
     LOGDEATH("linkToDeath: binder=%p recipient=%p\n", target, recipient);
 
     if (!target->localBinder()) {
-        DeathRecipientList* list = (DeathRecipientList*)
-                env->GetLongField(obj, gBinderProxyOffsets.mOrgue);
+        DeathRecipientList* list = nd->mOrgue.get();
         sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient, list);
         status_t err = target->linkToDeath(jdr, NULL, flags);
         if (err != NO_ERROR) {
@@ -1234,8 +1305,8 @@
         return res;
     }
 
-    IBinder* target = (IBinder*)
-        env->GetLongField(obj, gBinderProxyOffsets.mObject);
+    BinderProxyNativeData* nd = getBPNativeData(env, obj);
+    IBinder* target = nd->mObject.get();
     if (target == NULL) {
         ALOGW("Binder has been finalized when calling linkToDeath() with recip=%p)\n", recipient);
         return JNI_FALSE;
@@ -1247,8 +1318,7 @@
         status_t err = NAME_NOT_FOUND;
 
         // If we find the matching recipient, proceed to unlink using that
-        DeathRecipientList* list = (DeathRecipientList*)
-                env->GetLongField(obj, gBinderProxyOffsets.mOrgue);
+        DeathRecipientList* list = nd->mOrgue.get();
         sp<JavaDeathRecipient> origJDR = list->find(recipient);
         LOGDEATH("   unlink found list %p and JDR %p", list, origJDR.get());
         if (origJDR != NULL) {
@@ -1274,25 +1344,21 @@
     return res;
 }
 
-static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj)
+static void BinderProxy_destroy(void* rawNativeData)
 {
     // Don't race with construction/initialization
-    AutoMutex _l(mProxyLock);
+    AutoMutex _l(gProxyLock);
 
-    IBinder* b = (IBinder*)
-            env->GetLongField(obj, gBinderProxyOffsets.mObject);
-    DeathRecipientList* drl = (DeathRecipientList*)
-            env->GetLongField(obj, gBinderProxyOffsets.mOrgue);
-
-    LOGDEATH("Destroying BinderProxy %p: binder=%p drl=%p\n", obj, b, drl);
-    if (b != nullptr) {
-        env->SetLongField(obj, gBinderProxyOffsets.mObject, 0);
-        env->SetLongField(obj, gBinderProxyOffsets.mOrgue, 0);
-        drl->decStrong((void*)javaObjectForIBinder);
-        b->decStrong((void*)javaObjectForIBinder);
-    }
-
+    BinderProxyNativeData * nativeData = (BinderProxyNativeData *) rawNativeData;
+    LOGDEATH("Destroying BinderProxy: binder=%p drl=%p\n",
+            nativeData->mObject.get(), nativeData->mOrgue.get());
+    delete nativeData;
     IPCThreadState::self()->flushCommands();
+    --gNumProxies;
+}
+
+JNIEXPORT jlong JNICALL android_os_BinderProxy_getNativeFinalizer(JNIEnv*, jclass) {
+    return (jlong) BinderProxy_destroy;
 }
 
 // ----------------------------------------------------------------------------
@@ -1305,7 +1371,7 @@
     {"transactNative",      "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
     {"linkToDeath",         "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},
     {"unlinkToDeath",       "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath},
-    {"destroy",             "()V", (void*)android_os_BinderProxy_destroy},
+    {"getNativeFinalizer",  "()J", (void*)android_os_BinderProxy_getNativeFinalizer},
 };
 
 const char* const kBinderProxyPathName = "android/os/BinderProxy";
@@ -1317,14 +1383,11 @@
 
     clazz = FindClassOrDie(env, kBinderProxyPathName);
     gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
-    gBinderProxyOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>", "()V");
+    gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
+            "(JJ)Landroid/os/BinderProxy;");
     gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
             "(Landroid/os/IBinder$DeathRecipient;)V");
-
-    gBinderProxyOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
-    gBinderProxyOffsets.mSelf = GetFieldIDOrDie(env, clazz, "mSelf",
-                                                "Ljava/lang/ref/WeakReference;");
-    gBinderProxyOffsets.mOrgue = GetFieldIDOrDie(env, clazz, "mOrgue", "J");
+    gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
 
     clazz = FindClassOrDie(env, "java/lang/Class");
     gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;");
diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp
index 9fd7c40..3b5a144 100644
--- a/core/jni/android_util_EventLog.cpp
+++ b/core/jni/android_util_EventLog.cpp
@@ -14,214 +14,20 @@
  * limitations under the License.
  */
 
-#include <fcntl.h>
-
-#include <log/log_event_list.h>
-
-#include <log/log.h>
+#include <android-base/macros.h>
+#include <log/log_id.h>
 
 #include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
 #include "jni.h"
 
-#define UNUSED  __attribute__((__unused__))
+#include "core_jni_helpers.h"
+#include "eventlog_helper.h"
 
 namespace android {
 
-static jclass gCollectionClass;
-static jmethodID gCollectionAddID;
-
-static jclass gEventClass;
-static jmethodID gEventInitID;
-
-static jclass gIntegerClass;
-static jfieldID gIntegerValueID;
-
-static jclass gLongClass;
-static jfieldID gLongValueID;
-
-static jclass gFloatClass;
-static jfieldID gFloatValueID;
-
-static jclass gStringClass;
-
-/*
- * In class android.util.EventLog:
- *  static native int writeEvent(int tag, int value)
- */
-static jint android_util_EventLog_writeEvent_Integer(JNIEnv* env UNUSED,
-                                                     jobject clazz UNUSED,
-                                                     jint tag, jint value)
-{
-    android_log_event_list ctx(tag);
-    ctx << (int32_t)value;
-    return ctx.write();
-}
-
-/*
- * In class android.util.EventLog:
- *  static native int writeEvent(long tag, long value)
- */
-static jint android_util_EventLog_writeEvent_Long(JNIEnv* env UNUSED,
-                                                  jobject clazz UNUSED,
-                                                  jint tag, jlong value)
-{
-    android_log_event_list ctx(tag);
-    ctx << (int64_t)value;
-    return ctx.write();
-}
-
-/*
- * In class android.util.EventLog:
- *  static native int writeEvent(long tag, float value)
- */
-static jint android_util_EventLog_writeEvent_Float(JNIEnv* env UNUSED,
-                                                  jobject clazz UNUSED,
-                                                  jint tag, jfloat value)
-{
-    android_log_event_list ctx(tag);
-    ctx << (float)value;
-    return ctx.write();
-}
-
-/*
- * In class android.util.EventLog:
- *  static native int writeEvent(int tag, String value)
- */
-static jint android_util_EventLog_writeEvent_String(JNIEnv* env,
-                                                    jobject clazz UNUSED,
-                                                    jint tag, jstring value) {
-    android_log_event_list ctx(tag);
-    // Don't throw NPE -- I feel like it's sort of mean for a logging function
-    // to be all crashy if you pass in NULL -- but make the NULL value explicit.
-    if (value != NULL) {
-        const char *str = env->GetStringUTFChars(value, NULL);
-        ctx << str;
-        env->ReleaseStringUTFChars(value, str);
-    } else {
-        ctx << "NULL";
-    }
-    return ctx.write();
-}
-
-/*
- * In class android.util.EventLog:
- *  static native int writeEvent(long tag, Object... value)
- */
-static jint android_util_EventLog_writeEvent_Array(JNIEnv* env, jobject clazz,
-                                                   jint tag, jobjectArray value) {
-    android_log_event_list ctx(tag);
-
-    if (value == NULL) {
-        ctx << "[NULL]";
-        return ctx.write();
-    }
-
-    jsize copied = 0, num = env->GetArrayLength(value);
-    for (; copied < num && copied < 255; ++copied) {
-        if (ctx.status()) break;
-        jobject item = env->GetObjectArrayElement(value, copied);
-        if (item == NULL) {
-            ctx << "NULL";
-        } else if (env->IsInstanceOf(item, gStringClass)) {
-            const char *str = env->GetStringUTFChars((jstring) item, NULL);
-            ctx << str;
-            env->ReleaseStringUTFChars((jstring) item, str);
-        } else if (env->IsInstanceOf(item, gIntegerClass)) {
-            ctx << (int32_t)env->GetIntField(item, gIntegerValueID);
-        } else if (env->IsInstanceOf(item, gLongClass)) {
-            ctx << (int64_t)env->GetLongField(item, gLongValueID);
-        } else if (env->IsInstanceOf(item, gFloatClass)) {
-            ctx << (float)env->GetFloatField(item, gFloatValueID);
-        } else {
-            jniThrowException(env,
-                    "java/lang/IllegalArgumentException",
-                    "Invalid payload item type");
-            return -1;
-        }
-        env->DeleteLocalRef(item);
-    }
-    return ctx.write();
-}
-
-static void readEvents(JNIEnv* env, int loggerMode, jintArray tags, jlong startTime, jobject out) {
-    struct logger_list *logger_list;
-    if (startTime) {
-        logger_list = android_logger_list_alloc_time(loggerMode,
-                log_time(startTime / NS_PER_SEC, startTime % NS_PER_SEC), 0);
-    } else {
-        logger_list = android_logger_list_alloc(loggerMode, 0, 0);
-    }
-    if (!logger_list) {
-        jniThrowIOException(env, errno);
-        return;
-    }
-
-    if (!android_logger_open(logger_list, LOG_ID_EVENTS)) {
-        jniThrowIOException(env, errno);
-        android_logger_list_free(logger_list);
-        return;
-    }
-
-    jsize tagLength = env->GetArrayLength(tags);
-    jint *tagValues = env->GetIntArrayElements(tags, NULL);
-
-    while (1) {
-        log_msg log_msg;
-        int ret = android_logger_list_read(logger_list, &log_msg);
-
-        if (ret == 0) {
-            break;
-        }
-        if (ret < 0) {
-            if (ret == -EINTR) {
-                continue;
-            }
-            if (ret == -EINVAL) {
-                jniThrowException(env, "java/io/IOException", "Event too short");
-            } else if (ret != -EAGAIN) {
-                jniThrowIOException(env, -ret);  // Will throw on return
-            }
-            break;
-        }
-
-        if (log_msg.id() != LOG_ID_EVENTS) {
-            continue;
-        }
-
-        int32_t tag = * (int32_t *) log_msg.msg();
-
-        int found = 0;
-        for (int i = 0; !found && i < tagLength; ++i) {
-            found = (tag == tagValues[i]);
-        }
-
-        if (found) {
-            jsize len = ret;
-            jbyteArray array = env->NewByteArray(len);
-            if (array == NULL) {
-                break;
-            }
-
-            jbyte *bytes = env->GetByteArrayElements(array, NULL);
-            memcpy(bytes, log_msg.buf, len);
-            env->ReleaseByteArrayElements(array, bytes, 0);
-
-            jobject event = env->NewObject(gEventClass, gEventInitID, array);
-            if (event == NULL) {
-                break;
-            }
-
-            env->CallBooleanMethod(out, gCollectionAddID, event);
-            env->DeleteLocalRef(event);
-            env->DeleteLocalRef(array);
-        }
-    }
-
-    android_logger_list_close(logger_list);
-
-    env->ReleaseIntArrayElements(tags, tagValues, 0);
-}
+constexpr char kEventLogEventClass[] = "android/util/EventLog$Event";
+template class EventLogHelper<log_id_t::LOG_ID_EVENTS, kEventLogEventClass>;
+using ELog = EventLogHelper<log_id_t::LOG_ID_EVENTS, kEventLogEventClass>;
 
 /*
  * In class android.util.EventLog:
@@ -229,7 +35,7 @@
  *
  *  Reads events from the event log
  */
-static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz UNUSED,
+static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED,
                                              jintArray tags,
                                              jobject out) {
 
@@ -238,7 +44,7 @@
         return;
     }
 
-    readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, tags, 0, out);
+    ELog::readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, tags, 0, out);
  }
 /*
  * In class android.util.EventLog:
@@ -246,7 +52,7 @@
  *
  *  Reads events from the event log, blocking until events after timestamp are to be overwritten.
  */
-static void android_util_EventLog_readEventsOnWrapping(JNIEnv* env, jobject clazz UNUSED,
+static void android_util_EventLog_readEventsOnWrapping(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED,
                                              jintArray tags,
                                              jlong timestamp,
                                              jobject out) {
@@ -254,8 +60,8 @@
         jniThrowNullPointerException(env, NULL);
         return;
     }
-    readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_WRAP,
-            tags, timestamp, out);
+    ELog::readEvents(env, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK | ANDROID_LOG_WRAP, tags,
+            timestamp, out);
 }
 
 /*
@@ -263,17 +69,11 @@
  */
 static const JNINativeMethod gRegisterMethods[] = {
     /* name, signature, funcPtr */
-    { "writeEvent", "(II)I", (void*) android_util_EventLog_writeEvent_Integer },
-    { "writeEvent", "(IJ)I", (void*) android_util_EventLog_writeEvent_Long },
-    { "writeEvent", "(IF)I", (void*) android_util_EventLog_writeEvent_Float },
-    { "writeEvent",
-      "(ILjava/lang/String;)I",
-      (void*) android_util_EventLog_writeEvent_String
-    },
-    { "writeEvent",
-      "(I[Ljava/lang/Object;)I",
-      (void*) android_util_EventLog_writeEvent_Array
-    },
+    { "writeEvent", "(II)I", (void*) ELog::writeEventInteger },
+    { "writeEvent", "(IJ)I", (void*) ELog::writeEventLong },
+    { "writeEvent", "(IF)I", (void*) ELog::writeEventFloat },
+    { "writeEvent", "(ILjava/lang/String;)I", (void*) ELog::writeEventString },
+    { "writeEvent", "(I[Ljava/lang/Object;)I", (void*) ELog::writeEventArray },
     { "readEvents",
       "([ILjava/util/Collection;)V",
       (void*) android_util_EventLog_readEvents
@@ -284,41 +84,8 @@
     },
 };
 
-static struct { const char *name; jclass *clazz; } gClasses[] = {
-    { "android/util/EventLog$Event", &gEventClass },
-    { "java/lang/Integer", &gIntegerClass },
-    { "java/lang/Long", &gLongClass },
-    { "java/lang/Float", &gFloatClass },
-    { "java/lang/String", &gStringClass },
-    { "java/util/Collection", &gCollectionClass },
-};
-
-static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
-    { &gIntegerClass, "value", "I", &gIntegerValueID },
-    { &gLongClass, "value", "J", &gLongValueID },
-    { &gFloatClass, "value", "F", &gFloatValueID },
-};
-
-static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = {
-    { &gEventClass, "<init>", "([B)V", &gEventInitID },
-    { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID },
-};
-
 int register_android_util_EventLog(JNIEnv* env) {
-    for (int i = 0; i < NELEM(gClasses); ++i) {
-        jclass clazz = FindClassOrDie(env, gClasses[i].name);
-        *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz);
-    }
-
-    for (int i = 0; i < NELEM(gFields); ++i) {
-        *gFields[i].id = GetFieldIDOrDie(env,
-                *gFields[i].c, gFields[i].name, gFields[i].ft);
-    }
-
-    for (int i = 0; i < NELEM(gMethods); ++i) {
-        *gMethods[i].id = GetMethodIDOrDie(env,
-                *gMethods[i].c, gMethods[i].name, gMethods[i].mt);
-    }
+    ELog::Init(env);
 
     return RegisterMethodsOrDie(
             env,
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 33c8304..dec6c02 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -219,7 +219,7 @@
         strcpy(cmdline, "unknown");
 
         sprintf(proc_path, "/proc/%d/cmdline", pid);
-        fd = open(proc_path, O_RDONLY);
+        fd = open(proc_path, O_RDONLY | O_CLOEXEC);
         if (fd >= 0) {
             int rc = read(fd, cmdline, sizeof(cmdline)-1);
             cmdline[rc] = 0;
@@ -555,7 +555,7 @@
         return false;
     }
 
-    int fd = open(text, O_WRONLY);
+    int fd = open(text, O_WRONLY | O_CLOEXEC);
     if (fd >= 0) {
         sprintf(text, "%" PRId32, pid);
         write(fd, text, strlen(text));
@@ -603,7 +603,7 @@
 
 static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], size_t num)
 {
-    int fd = open("/proc/meminfo", O_RDONLY);
+    int fd = open("/proc/meminfo", O_RDONLY | O_CLOEXEC);
 
     if (fd < 0) {
         ALOGW("Unable to open /proc/meminfo");
@@ -716,7 +716,7 @@
         sizesArray[i] = 0;
     }
 
-    int fd = open(file.string(), O_RDONLY);
+    int fd = open(file.string(), O_RDONLY | O_CLOEXEC);
 
     if (fd >= 0) {
         const size_t BUFFER_SIZE = 2048;
@@ -1023,7 +1023,7 @@
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return JNI_FALSE;
     }
-    int fd = open(file8, O_RDONLY);
+    int fd = open(file8, O_RDONLY | O_CLOEXEC);
 
     if (fd < 0) {
         if (kDebugProc) {
@@ -1157,7 +1157,7 @@
         char data[PATH_MAX];
         snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
 
-        int fd = open(path, O_RDONLY);
+        int fd = open(path, O_RDONLY | O_CLOEXEC);
         if (fd < 0) {
             continue;
         }
diff --git a/core/jni/android_util_StatsLog.cpp b/core/jni/android_util_StatsLog.cpp
deleted file mode 100644
index c992365..0000000
--- a/core/jni/android_util_StatsLog.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2007-2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <fcntl.h>
-#include <log/log_event_list.h>
-
-#include <log/log.h>
-
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-#include "jni.h"
-
-#define UNUSED  __attribute__((__unused__))
-
-namespace android {
-
-static jclass gCollectionClass;
-static jmethodID gCollectionAddID;
-
-static jclass gIntegerClass;
-static jfieldID gIntegerValueID;
-
-static jclass gLongClass;
-static jfieldID gLongValueID;
-
-static jclass gFloatClass;
-static jfieldID gFloatValueID;
-
-static jclass gStringClass;
-
-/*
- * In class android.util.StatsLog:
- *  static native int writeInt(int tag, int value)
- */
-static jint android_util_StatsLog_write_Integer(JNIEnv* env UNUSED,
-                                                     jobject clazz UNUSED,
-                                                     jint tag, jint value)
-{
-    android_log_event_list ctx(tag);
-    ctx << (int32_t)value;
-    return ctx.write(LOG_ID_STATS);
-}
-
-/*
- * In class android.util.StatsLog:
- *  static native int writeLong(long tag, long value)
- */
-static jint android_util_StatsLog_write_Long(JNIEnv* env UNUSED,
-                                                  jobject clazz UNUSED,
-                                                  jint tag, jlong value)
-{
-    android_log_event_list ctx(tag);
-    ctx << (int64_t)value;
-    return ctx.write(LOG_ID_STATS);
-}
-
-/*
- * In class android.util.StatsLog:
- *  static native int writeFloat(long tag, float value)
- */
-static jint android_util_StatsLog_write_Float(JNIEnv* env UNUSED,
-                                                  jobject clazz UNUSED,
-                                                  jint tag, jfloat value)
-{
-    android_log_event_list ctx(tag);
-    ctx << (float)value;
-    return ctx.write(LOG_ID_STATS);
-}
-
-/*
- * In class android.util.StatsLog:
- *  static native int writeString(int tag, String value)
- */
-static jint android_util_StatsLog_write_String(JNIEnv* env,
-                                                    jobject clazz UNUSED,
-                                                    jint tag, jstring value) {
-    android_log_event_list ctx(tag);
-    // Don't throw NPE -- I feel like it's sort of mean for a logging function
-    // to be all crashy if you pass in NULL -- but make the NULL value explicit.
-    if (value != NULL) {
-        const char *str = env->GetStringUTFChars(value, NULL);
-        ctx << str;
-        env->ReleaseStringUTFChars(value, str);
-    } else {
-        ctx << "NULL";
-    }
-    return ctx.write(LOG_ID_STATS);
-}
-
-/*
- * In class android.util.StatsLog:
- *  static native int writeArray(long tag, Object... value)
- */
-static jint android_util_StatsLog_write_Array(JNIEnv* env, jobject clazz,
-                                                   jint tag, jobjectArray value) {
-    android_log_event_list ctx(tag);
-
-    if (value == NULL) {
-        ctx << "[NULL]";
-        return ctx.write(LOG_ID_STATS);
-    }
-
-    jsize copied = 0, num = env->GetArrayLength(value);
-    for (; copied < num && copied < 255; ++copied) {
-        if (ctx.status()) break;
-        jobject item = env->GetObjectArrayElement(value, copied);
-        if (item == NULL) {
-            ctx << "NULL";
-        } else if (env->IsInstanceOf(item, gStringClass)) {
-            const char *str = env->GetStringUTFChars((jstring) item, NULL);
-            ctx << str;
-            env->ReleaseStringUTFChars((jstring) item, str);
-        } else if (env->IsInstanceOf(item, gIntegerClass)) {
-            ctx << (int32_t)env->GetIntField(item, gIntegerValueID);
-        } else if (env->IsInstanceOf(item, gLongClass)) {
-            ctx << (int64_t)env->GetLongField(item, gLongValueID);
-        } else if (env->IsInstanceOf(item, gFloatClass)) {
-            ctx << (float)env->GetFloatField(item, gFloatValueID);
-        } else {
-            jniThrowException(env,
-                    "java/lang/IllegalArgumentException",
-                    "Invalid payload item type");
-            return -1;
-        }
-        env->DeleteLocalRef(item);
-    }
-    return ctx.write(LOG_ID_STATS);
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gRegisterMethods[] = {
-    /* name, signature, funcPtr */
-    { "writeInt", "(II)I", (void*) android_util_StatsLog_write_Integer },
-    { "writeLong", "(IJ)I", (void*) android_util_StatsLog_write_Long },
-    { "writeFloat", "(IF)I", (void*) android_util_StatsLog_write_Float },
-    { "writeString",
-      "(ILjava/lang/String;)I",
-      (void*) android_util_StatsLog_write_String
-    },
-    { "writeArray",
-      "(I[Ljava/lang/Object;)I",
-      (void*) android_util_StatsLog_write_Array
-    },
-};
-
-static struct { const char *name; jclass *clazz; } gClasses[] = {
-    { "java/lang/Integer", &gIntegerClass },
-    { "java/lang/Long", &gLongClass },
-    { "java/lang/Float", &gFloatClass },
-    { "java/lang/String", &gStringClass },
-    { "java/util/Collection", &gCollectionClass },
-};
-
-static struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
-    { &gIntegerClass, "value", "I", &gIntegerValueID },
-    { &gLongClass, "value", "J", &gLongValueID },
-    { &gFloatClass, "value", "F", &gFloatValueID },
-};
-
-static struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = {
-    { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID },
-};
-
-int register_android_util_StatsLog(JNIEnv* env) {
-    for (int i = 0; i < NELEM(gClasses); ++i) {
-        jclass clazz = FindClassOrDie(env, gClasses[i].name);
-        *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz);
-    }
-
-    for (int i = 0; i < NELEM(gFields); ++i) {
-        *gFields[i].id = GetFieldIDOrDie(env,
-                *gFields[i].c, gFields[i].name, gFields[i].ft);
-    }
-
-    for (int i = 0; i < NELEM(gMethods); ++i) {
-        *gMethods[i].id = GetMethodIDOrDie(env,
-                *gMethods[i].c, gMethods[i].name, gMethods[i].mt);
-    }
-
-    return RegisterMethodsOrDie(
-            env,
-            "android/util/StatsLog",
-            gRegisterMethods, NELEM(gRegisterMethods));
-}
-
-}; // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index bf8bbf5..cfeba83 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -98,6 +98,18 @@
 
 // ----------------------------------------------------------------------------
 
+static jlong nativeCreateTransaction(JNIEnv* env, jclass clazz) {
+    return reinterpret_cast<jlong>(new SurfaceComposerClient::Transaction);
+}
+
+static void releaseTransaction(SurfaceComposerClient::Transaction* t) {
+    delete t;
+}
+
+static jlong nativeGetNativeTransactionFinalizer(JNIEnv* env, jclass clazz) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&releaseTransaction));
+}
+
 static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
         jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
         jint windowType, jint ownerUid) {
@@ -278,69 +290,88 @@
     }
 }
 
-static void nativeOpenTransaction(JNIEnv* env, jclass clazz) {
-    SurfaceComposerClient::openGlobalTransaction();
-}
+static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
+        jobject surfaceObj, int rotation) {
 
-
-static void nativeCloseTransaction(JNIEnv* env, jclass clazz, jboolean sync) {
-    SurfaceComposerClient::closeGlobalTransaction(sync);
-}
-
-static void nativeSetAnimationTransaction(JNIEnv* env, jclass clazz) {
-    SurfaceComposerClient::setAnimationTransaction();
-}
-
-static void nativeSetLayer(JNIEnv* env, jclass clazz, jlong nativeObject, jint zorder) {
-    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setLayer(zorder);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
+    sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
+    if (layerHandle == NULL) {
+        return;
     }
+
+    sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj);
+    if (consumer == NULL) {
+        return;
+    }
+
+    ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), rotation);
 }
 
-static void nativeSetRelativeLayer(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    transaction->apply(sync);
+}
+
+static void nativeSetAnimationTransaction(JNIEnv* env, jclass clazz, jlong transactionObj) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    transaction->setAnimationTransaction();
+}
+
+static void nativeSetLayer(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jint zorder) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    transaction->setLayer(ctrl, zorder);
+}
+
+static void nativeSetRelativeLayer(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jobject relativeTo, jint zorder) {
+
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     sp<IBinder> handle = ibinderForJavaObject(env, relativeTo);
 
-    ctrl->setRelativeLayer(handle, zorder);
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->setRelativeLayer(ctrl, handle, zorder);
+    }
 }
 
-static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong nativeObject, jfloat x, jfloat y) {
+static void nativeSetPosition(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jfloat x, jfloat y) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setPosition(x, y);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setPosition(ctrl, x, y);
 }
 
 static void nativeSetGeometryAppliesWithResize(JNIEnv* env, jclass clazz,
+jlong transactionObj,
         jlong nativeObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setGeometryAppliesWithResize();
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setGeometryAppliesWithResize(ctrl);
 }
 
-static void nativeSetSize(JNIEnv* env, jclass clazz, jlong nativeObject, jint w, jint h) {
+static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jint w, jint h) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setSize(w, h);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setSize(ctrl, w, h);
 }
 
-static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong nativeObject, jint flags, jint mask) {
+static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jint flags, jint mask) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setFlags(flags, mask);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setFlags(ctrl, flags, mask);
 }
 
-static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong nativeObject, jobject regionObj) {
+static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jobject regionObj) {
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     SkRegion* region = android_graphics_Region_getSkRegion(env, regionObj);
     if (!region) {
@@ -359,55 +390,65 @@
         }
     }
 
-    status_t err = ctrl->setTransparentRegionHint(reg);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->setTransparentRegionHint(ctrl, reg);
     }
 }
 
-static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong nativeObject, jfloat alpha) {
+static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jfloat alpha) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setAlpha(alpha);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setAlpha(ctrl, alpha);
 }
 
-static void nativeSetMatrix(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeSetColor(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jfloatArray fColor) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+
+    float* floatColors = env->GetFloatArrayElements(fColor, 0);
+    half3 color(floatColors[0], floatColors[1], floatColors[2]);
+    transaction->setColor(ctrl, color);
+}
+
+static void nativeSetMatrix(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jfloat dsdx, jfloat dtdx, jfloat dtdy, jfloat dsdy) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setMatrix(dsdx, dtdx, dtdy, dsdy);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setMatrix(ctrl, dsdx, dtdx, dtdy, dsdy);
 }
 
-static void nativeSetWindowCrop(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeSetWindowCrop(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jint l, jint t, jint r, jint b) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     Rect crop(l, t, r, b);
-    status_t err = ctrl->setCrop(crop);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setCrop(ctrl, crop);
 }
 
-static void nativeSetFinalCrop(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeSetFinalCrop(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jint l, jint t, jint r, jint b) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     Rect crop(l, t, r, b);
-    status_t err = ctrl->setFinalCrop(crop);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setFinalCrop(ctrl, crop);
 }
 
-static void nativeSetLayerStack(JNIEnv* env, jclass clazz, jlong nativeObject, jint layerStack) {
+static void nativeSetLayerStack(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject, jint layerStack) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    status_t err = ctrl->setLayerStack(layerStack);
-    if (err < 0 && err != NO_INIT) {
-        doThrowIAE(env);
-    }
+    transaction->setLayerStack(ctrl, layerStack);
 }
 
 static jobject nativeGetBuiltInDisplay(JNIEnv* env, jclass clazz, jint id) {
@@ -430,6 +471,7 @@
 }
 
 static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz,
+        jlong transactionObj,
         jobject tokenObj, jlong nativeSurfaceObject) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
@@ -438,8 +480,14 @@
     if (sur != NULL) {
         bufferProducer = sur->getIGraphicBufferProducer();
     }
-    status_t err = SurfaceComposerClient::setDisplaySurface(token,
-            bufferProducer);
+
+
+    status_t err = NO_ERROR;
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        err = transaction->setDisplaySurface(token,
+                bufferProducer);
+    }
     if (err != NO_ERROR) {
         doThrowIAE(env, "Illegal Surface, could not enable async mode. Was this"
                 " Surface created with singleBufferMode?");
@@ -447,14 +495,20 @@
 }
 
 static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz,
+        jlong transactionObj,
         jobject tokenObj, jint layerStack) {
+
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
 
-    SurfaceComposerClient::setDisplayLayerStack(token, layerStack);
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->setDisplayLayerStack(token, layerStack);
+    }
 }
 
 static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz,
+        jlong transactionObj,
         jobject tokenObj, jint orientation,
         jint layerStackRect_left, jint layerStackRect_top, jint layerStackRect_right, jint layerStackRect_bottom,
         jint displayRect_left, jint displayRect_top, jint displayRect_right, jint displayRect_bottom) {
@@ -462,14 +516,23 @@
     if (token == NULL) return;
     Rect layerStackRect(layerStackRect_left, layerStackRect_top, layerStackRect_right, layerStackRect_bottom);
     Rect displayRect(displayRect_left, displayRect_top, displayRect_right, displayRect_bottom);
-    SurfaceComposerClient::setDisplayProjection(token, orientation, layerStackRect, displayRect);
+
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect);
+    }
 }
 
 static void nativeSetDisplaySize(JNIEnv* env, jclass clazz,
+        jlong transactionObj,
         jobject tokenObj, jint width, jint height) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
-    SurfaceComposerClient::setDisplaySize(token, width, height);
+
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->setDisplaySize(token, width, height);
+    }
 }
 
 static jobjectArray nativeGetDisplayConfigs(JNIEnv* env, jclass clazz,
@@ -712,52 +775,73 @@
     return JNI_TRUE;
 }
 
-static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jobject handleObject, jlong frameNumber) {
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     sp<IBinder> handle = ibinderForJavaObject(env, handleObject);
 
-    ctrl->deferTransactionUntil(handle, frameNumber);
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->deferTransactionUntil(ctrl, handle, frameNumber);
+    }
 }
 
-static void nativeDeferTransactionUntilSurface(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeDeferTransactionUntilSurface(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jlong surfaceObject, jlong frameNumber) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     sp<Surface> barrier = reinterpret_cast<Surface *>(surfaceObject);
 
-    ctrl->deferTransactionUntil(barrier, frameNumber);
+    transaction->deferTransactionUntil(ctrl, barrier, frameNumber);
 }
 
-static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jobject newParentObject) {
+
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     sp<IBinder> handle = ibinderForJavaObject(env, newParentObject);
 
-    ctrl->reparentChildren(handle);
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->reparentChildren(ctrl, handle);
+    }
 }
 
-static void nativeReparent(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeReparent(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jobject newParentObject) {
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
     sp<IBinder> parentHandle = ibinderForJavaObject(env, newParentObject);
-    ctrl->reparent(parentHandle);
+
+    {
+        auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+        transaction->reparent(ctrl, parentHandle);
+    }
 }
 
-static void nativeSeverChildren(JNIEnv* env, jclass clazz, jlong nativeObject) {
+static void nativeSeverChildren(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    ctrl->detachChildren();
+    transaction->detachChildren(ctrl);
 }
 
-static void nativeSetOverrideScalingMode(JNIEnv* env, jclass clazz, jlong nativeObject,
+static void nativeSetOverrideScalingMode(JNIEnv* env, jclass clazz, jlong transactionObj,
+        jlong nativeObject,
         jint scalingMode) {
-    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
 
-    ctrl->setOverrideScalingMode(scalingMode);
+    auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    transaction->setOverrideScalingMode(ctrl, scalingMode);
 }
 
 static jobject nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-
     return javaObjectForIBinder(env, ctrl->getHandle());
 }
 
@@ -792,35 +876,39 @@
             (void*)nativeScreenshotBitmap },
     {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;IIIIZZ)V",
             (void*)nativeScreenshot },
-    {"nativeOpenTransaction", "()V",
-            (void*)nativeOpenTransaction },
-    {"nativeCloseTransaction", "(Z)V",
-            (void*)nativeCloseTransaction },
-    {"nativeSetAnimationTransaction", "()V",
+    {"nativeCreateTransaction", "()J",
+            (void*)nativeCreateTransaction },
+    {"nativeApplyTransaction", "(JZ)V",
+            (void*)nativeApplyTransaction },
+    {"nativeGetNativeTransactionFinalizer", "()J",
+            (void*)nativeGetNativeTransactionFinalizer },
+    {"nativeSetAnimationTransaction", "(J)V",
             (void*)nativeSetAnimationTransaction },
-    {"nativeSetLayer", "(JI)V",
+    {"nativeSetLayer", "(JJI)V",
             (void*)nativeSetLayer },
-    {"nativeSetRelativeLayer", "(JLandroid/os/IBinder;I)V",
+    {"nativeSetRelativeLayer", "(JJLandroid/os/IBinder;I)V",
             (void*)nativeSetRelativeLayer },
-    {"nativeSetPosition", "(JFF)V",
+    {"nativeSetPosition", "(JJFF)V",
             (void*)nativeSetPosition },
-    {"nativeSetGeometryAppliesWithResize", "(J)V",
+    {"nativeSetGeometryAppliesWithResize", "(JJ)V",
             (void*)nativeSetGeometryAppliesWithResize },
-    {"nativeSetSize", "(JII)V",
+    {"nativeSetSize", "(JJII)V",
             (void*)nativeSetSize },
-    {"nativeSetTransparentRegionHint", "(JLandroid/graphics/Region;)V",
+    {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
             (void*)nativeSetTransparentRegionHint },
-    {"nativeSetAlpha", "(JF)V",
+    {"nativeSetAlpha", "(JJF)V",
             (void*)nativeSetAlpha },
-    {"nativeSetMatrix", "(JFFFF)V",
+    {"nativeSetColor", "(JJ[F)V",
+            (void*)nativeSetColor },
+    {"nativeSetMatrix", "(JJFFFF)V",
             (void*)nativeSetMatrix },
-    {"nativeSetFlags", "(JII)V",
+    {"nativeSetFlags", "(JJII)V",
             (void*)nativeSetFlags },
-    {"nativeSetWindowCrop", "(JIIII)V",
+    {"nativeSetWindowCrop", "(JJIIII)V",
             (void*)nativeSetWindowCrop },
-    {"nativeSetFinalCrop", "(JIIII)V",
+    {"nativeSetFinalCrop", "(JJIIII)V",
             (void*)nativeSetFinalCrop },
-    {"nativeSetLayerStack", "(JI)V",
+    {"nativeSetLayerStack", "(JJI)V",
             (void*)nativeSetLayerStack },
     {"nativeGetBuiltInDisplay", "(I)Landroid/os/IBinder;",
             (void*)nativeGetBuiltInDisplay },
@@ -828,13 +916,13 @@
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
             (void*)nativeDestroyDisplay },
-    {"nativeSetDisplaySurface", "(Landroid/os/IBinder;J)V",
+    {"nativeSetDisplaySurface", "(JLandroid/os/IBinder;J)V",
             (void*)nativeSetDisplaySurface },
-    {"nativeSetDisplayLayerStack", "(Landroid/os/IBinder;I)V",
+    {"nativeSetDisplayLayerStack", "(JLandroid/os/IBinder;I)V",
             (void*)nativeSetDisplayLayerStack },
-    {"nativeSetDisplayProjection", "(Landroid/os/IBinder;IIIIIIIII)V",
+    {"nativeSetDisplayProjection", "(JLandroid/os/IBinder;IIIIIIIII)V",
             (void*)nativeSetDisplayProjection },
-    {"nativeSetDisplaySize", "(Landroid/os/IBinder;II)V",
+    {"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V",
             (void*)nativeSetDisplaySize },
     {"nativeGetDisplayConfigs", "(Landroid/os/IBinder;)[Landroid/view/SurfaceControl$PhysicalDisplayInfo;",
             (void*)nativeGetDisplayConfigs },
@@ -860,23 +948,25 @@
             (void*)nativeGetAnimationFrameStats },
     {"nativeSetDisplayPowerMode", "(Landroid/os/IBinder;I)V",
             (void*)nativeSetDisplayPowerMode },
-    {"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
+    {"nativeDeferTransactionUntil", "(JJLandroid/os/IBinder;J)V",
             (void*)nativeDeferTransactionUntil },
-    {"nativeDeferTransactionUntilSurface", "(JJJ)V",
+    {"nativeDeferTransactionUntilSurface", "(JJJJ)V",
             (void*)nativeDeferTransactionUntilSurface },
-    {"nativeReparentChildren", "(JLandroid/os/IBinder;)V",
+    {"nativeReparentChildren", "(JJLandroid/os/IBinder;)V",
             (void*)nativeReparentChildren } ,
-    {"nativeReparent", "(JLandroid/os/IBinder;)V",
+    {"nativeReparent", "(JJLandroid/os/IBinder;)V",
             (void*)nativeReparent },
-    {"nativeSeverChildren", "(J)V",
+    {"nativeSeverChildren", "(JJ)V",
             (void*)nativeSeverChildren } ,
-    {"nativeSetOverrideScalingMode", "(JI)V",
+    {"nativeSetOverrideScalingMode", "(JJI)V",
             (void*)nativeSetOverrideScalingMode },
     {"nativeGetHandle", "(J)Landroid/os/IBinder;",
             (void*)nativeGetHandle },
     {"nativeScreenshotToBuffer",
      "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
      (void*)nativeScreenshotToBuffer },
+    {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;I)V",
+            (void*)nativeCaptureLayers },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/eventlog_helper.h b/core/jni/eventlog_helper.h
new file mode 100644
index 0000000..3a05195
--- /dev/null
+++ b/core/jni/eventlog_helper.h
@@ -0,0 +1,272 @@
+/*
+ * 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 FRAMEWORKS_BASE_CORE_JNI_EVENTLOG_HELPER_H_
+#define FRAMEWORKS_BASE_CORE_JNI_EVENTLOG_HELPER_H_
+
+#include <memory>
+
+#include <fcntl.h>
+
+#include <android-base/macros.h>
+#include <log/log_event_list.h>
+
+#include <log/log.h>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include "core_jni_helpers.h"
+#include "jni.h"
+
+namespace android {
+
+template <log_id_t LogID, const char* EventClassDescriptor>
+class EventLogHelper {
+public:
+    static void Init(JNIEnv* env) {
+        struct { const char *name; jclass *clazz; } gClasses[] = {
+                { EventClassDescriptor, &gEventClass },
+                { "java/lang/Integer", &gIntegerClass },
+                { "java/lang/Long", &gLongClass },
+                { "java/lang/Float", &gFloatClass },
+                { "java/lang/String", &gStringClass },
+                { "java/util/Collection", &gCollectionClass },
+        };
+        struct { jclass *c; const char *name, *ft; jfieldID *id; } gFields[] = {
+                { &gIntegerClass, "value", "I", &gIntegerValueID },
+                { &gLongClass, "value", "J", &gLongValueID },
+                { &gFloatClass, "value", "F", &gFloatValueID },
+        };
+        struct { jclass *c; const char *name, *mt; jmethodID *id; } gMethods[] = {
+                { &gEventClass, "<init>", "([B)V", &gEventInitID },
+                { &gCollectionClass, "add", "(Ljava/lang/Object;)Z", &gCollectionAddID },
+        };
+
+        for (size_t i = 0; i < NELEM(gClasses); ++i) {
+            ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, gClasses[i].name));
+            *gClasses[i].clazz = MakeGlobalRefOrDie(env, clazz.get());
+        }
+        for (size_t i = 0; i < NELEM(gFields); ++i) {
+            *gFields[i].id = GetFieldIDOrDie(env,
+                    *gFields[i].c, gFields[i].name, gFields[i].ft);
+        }
+
+        for (size_t i = 0; i < NELEM(gMethods); ++i) {
+            *gMethods[i].id = GetMethodIDOrDie(env,
+                    *gMethods[i].c, gMethods[i].name, gMethods[i].mt);
+        }
+    }
+
+    static jint writeEventInteger(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED,
+            jint tag, jint value) {
+        android_log_event_list ctx(tag);
+        ctx << (int32_t)value;
+        return ctx.write(LogID);
+    }
+    static jint writeEventLong(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED,
+            jint tag, jlong value) {
+        android_log_event_list ctx(tag);
+        ctx << (int64_t)value;
+        return ctx.write(LogID);
+    }
+    static jint writeEventFloat(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED,
+            jint tag, jfloat value) {
+        android_log_event_list ctx(tag);
+        ctx << (float)value;
+        return ctx.write(LogID);
+    }
+    static jint writeEventString(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED, jint tag,
+            jstring value) {
+        android_log_event_list ctx(tag);
+        // Don't throw NPE -- I feel like it's sort of mean for a logging function
+        // to be all crashy if you pass in NULL -- but make the NULL value explicit.
+        ctx << (value != nullptr ? ScopedUtfChars(env, value).c_str() : "NULL");
+        return ctx.write(LogID);
+    }
+    static jint writeEventArray(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED, jint tag,
+            jobjectArray value) {
+        android_log_event_list ctx(tag);
+
+        if (value == nullptr) {
+            ctx << "[NULL]";
+            return ctx.write(LogID);
+        }
+
+        jsize copied = 0, num = env->GetArrayLength(value);
+        for (; copied < num && copied < 255; ++copied) {
+            if (ctx.status()) break;
+            ScopedLocalRef<jobject> item(env, env->GetObjectArrayElement(value, copied));
+            if (item == nullptr) {
+                ctx << "NULL";
+            } else if (env->IsInstanceOf(item.get(), gStringClass)) {
+                ctx << ScopedUtfChars(env, (jstring) item.get()).c_str();
+            } else if (env->IsInstanceOf(item.get(), gIntegerClass)) {
+                ctx << (int32_t)env->GetIntField(item.get(), gIntegerValueID);
+            } else if (env->IsInstanceOf(item.get(), gLongClass)) {
+                ctx << (int64_t)env->GetLongField(item.get(), gLongValueID);
+            } else if (env->IsInstanceOf(item.get(), gFloatClass)) {
+                ctx << (float)env->GetFloatField(item.get(), gFloatValueID);
+            } else {
+                jniThrowException(env,
+                        "java/lang/IllegalArgumentException",
+                        "Invalid payload item type");
+                return -1;
+            }
+        }
+        return ctx.write(LogID);
+    }
+
+    static void readEvents(JNIEnv* env, int loggerMode, jlong startTime, jobject out) {
+        readEvents(env, loggerMode, nullptr, startTime, out);
+    }
+
+    static void readEvents(JNIEnv* env, int loggerMode, jintArray jTags, jlong startTime,
+            jobject out) {
+        std::unique_ptr<struct logger_list, decltype(&android_logger_list_close)> logger_list(
+                nullptr, android_logger_list_close);
+        if (startTime) {
+            logger_list.reset(android_logger_list_alloc_time(loggerMode,
+                    log_time(startTime / NS_PER_SEC, startTime % NS_PER_SEC), 0));
+        } else {
+            logger_list.reset(android_logger_list_alloc(loggerMode, 0, 0));
+        }
+        if (!logger_list) {
+            jniThrowIOException(env, errno);
+            return;
+        }
+
+        if (!android_logger_open(logger_list.get(), LogID)) {
+            jniThrowIOException(env, errno);
+            return;
+        }
+
+        ScopedIntArrayRO tags(env);
+        if (jTags != nullptr) {
+            tags.reset(jTags);
+        }
+
+        while (1) {
+            log_msg log_msg;
+            int ret = android_logger_list_read(logger_list.get(), &log_msg);
+
+            if (ret == 0) {
+                return;
+            }
+            if (ret < 0) {
+                if (ret == -EINTR) {
+                    continue;
+                }
+                if (ret == -EINVAL) {
+                    jniThrowException(env, "java/io/IOException", "Event too short");
+                } else if (ret != -EAGAIN) {
+                    jniThrowIOException(env, -ret);  // Will throw on return
+                }
+                return;
+            }
+
+            if (log_msg.id() != LogID) {
+                continue;
+            }
+
+            int32_t tag = * (int32_t *) log_msg.msg();
+
+            if (jTags != nullptr) {
+                bool found = false;
+                for (size_t i = 0; !found && i < tags.size(); ++i) {
+                    found = (tag == tags[i]);
+                }
+                if (!found) {
+                    continue;
+                }
+            }
+
+            jsize len = ret;
+            ScopedLocalRef<jbyteArray> array(env, env->NewByteArray(len));
+            if (array == nullptr) {
+                return;
+            }
+
+            {
+                ScopedByteArrayRW bytes(env, array.get());
+                memcpy(bytes.get(), log_msg.buf, len);
+            }
+
+            ScopedLocalRef<jobject> event(env,
+                    env->NewObject(gEventClass, gEventInitID, array.get()));
+            if (event == nullptr) {
+                return;
+            }
+
+            env->CallBooleanMethod(out, gCollectionAddID, event.get());
+            if (env->ExceptionCheck() == JNI_TRUE) {
+                return;
+            }
+        }
+    }
+
+private:
+    static jclass gCollectionClass;
+    static jmethodID gCollectionAddID;
+
+    static jclass gEventClass;
+    static jmethodID gEventInitID;
+
+    static jclass gIntegerClass;
+    static jfieldID gIntegerValueID;
+
+    static jclass gLongClass;
+    static jfieldID gLongValueID;
+
+    static jclass gFloatClass;
+    static jfieldID gFloatValueID;
+
+    static jclass gStringClass;
+};
+
+// Explicit instantiation declarations.
+template <log_id_t LogID, const char* EventClassDescriptor>
+jclass EventLogHelper<LogID, EventClassDescriptor>::gCollectionClass;
+template <log_id_t LogID, const char* EventClassDescriptor>
+jmethodID EventLogHelper<LogID, EventClassDescriptor>::gCollectionAddID;
+
+template <log_id_t LogID, const char* EventClassDescriptor>
+jclass EventLogHelper<LogID, EventClassDescriptor>::gEventClass;
+template <log_id_t LogID, const char* EventClassDescriptor>
+jmethodID EventLogHelper<LogID, EventClassDescriptor>::gEventInitID;
+
+template <log_id_t LogID, const char* EventClassDescriptor>
+jclass EventLogHelper<LogID, EventClassDescriptor>::gIntegerClass;
+template <log_id_t LogID, const char* EventClassDescriptor>
+jfieldID EventLogHelper<LogID, EventClassDescriptor>::gIntegerValueID;
+
+template <log_id_t LogID, const char* EventClassDescriptor>
+jclass EventLogHelper<LogID, EventClassDescriptor>::gLongClass;
+template <log_id_t LogID, const char* EventClassDescriptor>
+jfieldID EventLogHelper<LogID, EventClassDescriptor>::gLongValueID;
+
+template <log_id_t LogID, const char* EventClassDescriptor>
+jclass EventLogHelper<LogID, EventClassDescriptor>::gFloatClass;
+template <log_id_t LogID, const char* EventClassDescriptor>
+jfieldID EventLogHelper<LogID, EventClassDescriptor>::gFloatValueID;
+
+template <log_id_t LogID, const char* EventClassDescriptor>
+jclass EventLogHelper<LogID, EventClassDescriptor>::gStringClass;
+
+}  // namespace android
+
+#endif  // FRAMEWORKS_BASE_CORE_JNI_EVENTLOG_HELPER_H_
diff --git a/core/proto/android/app/activitymanager.proto b/core/proto/android/app/activitymanager.proto
new file mode 100644
index 0000000..e87499e
--- /dev/null
+++ b/core/proto/android/app/activitymanager.proto
@@ -0,0 +1,80 @@
+/*
+ * 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.app;
+
+option java_multiple_files = true;
+
+message ActivityManagerProto {
+
+    // ActivityManager.java PROCESS_STATEs
+    enum ProcessState {
+      // Order matters for process states, so values have been spaced to provide
+      // room for future additions.
+
+      // Not a real process state.
+      PROCESS_STATE_UNKNOWN = -100;
+      // Process is a persistent system process.
+      PROCESS_STATE_PERSISTENT = 0;
+      // Process is a persistent system process and is doing UI.
+      PROCESS_STATE_PERSISTENT_UI = 100;
+      // Process is hosting the current top activities. Note that this covers
+      // all activities that are visible to the user.
+      PROCESS_STATE_TOP = 200;
+      // Process is hosting a foreground service due to a system binding.
+      PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 300;
+      // Process is hosting a foreground service.
+      PROCESS_STATE_FOREGROUND_SERVICE = 400;
+      // Same as PROCESS_STATE_TOP but while device is sleeping.
+      PROCESS_STATE_TOP_SLEEPING = 500;
+      // Process is important to the user, and something they are aware of.
+      PROCESS_STATE_IMPORTANT_FOREGROUND = 600;
+      // Process is important to the user, but not something they are aware of.
+      PROCESS_STATE_IMPORTANT_BACKGROUND = 700;
+      // Process is in the background transient so we will try to keep running.
+      PROCESS_STATE_TRANSIENT_BACKGROUND = 800;
+      // Process is in the background running a backup/restore operation.
+      PROCESS_STATE_BACKUP = 900;
+      // Process is in the background, but it can't restore its state so we want
+      // to try to avoid killing it.
+      PROCESS_STATE_HEAVY_WEIGHT = 1000;
+      // Process is in the background running a service. Unlike oom_adj, this
+      // level is used for both the normal running in background state and the
+      // executing operations state.
+      PROCESS_STATE_SERVICE = 1100;
+      // Process is in the background running a receiver. Note that from the
+      // perspective of oom_adj, receivers run at a higher foreground level, but
+      // for our prioritization here that is not necessary and putting them
+      // below services means many fewer changes in some process states as they
+      // receive broadcasts.
+      PROCESS_STATE_RECEIVER = 1200;
+      // Process is in the background but hosts the home activity.
+      PROCESS_STATE_HOME = 1300;
+      // Process is in the background but hosts the last shown activity.
+      PROCESS_STATE_LAST_ACTIVITY = 1400;
+      // Process is being cached for later use and contains activities.
+      PROCESS_STATE_CACHED_ACTIVITY = 1500;
+      // Process is being cached for later use and is a client of another cached
+      // process that contains activities.
+      PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 1600;
+      // Process is being cached for later use and is empty.
+      PROCESS_STATE_CACHED_EMPTY = 1700;
+      // Process does not exist.
+      PROCESS_STATE_NONEXISTENT = 1800;
+    }
+}
diff --git a/core/proto/android/app/alarmmanager.proto b/core/proto/android/app/alarmmanager.proto
new file mode 100644
index 0000000..789e3d6
--- /dev/null
+++ b/core/proto/android/app/alarmmanager.proto
@@ -0,0 +1,54 @@
+/*
+ * 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";
+
+import "frameworks/base/core/proto/android/app/pendingintent.proto";
+
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.AlarmManager object.
+ */
+message AlarmManagerProto {
+  enum AlarmType {
+    // Alarm time in System.currentTimeMillis() (wall clock time in UTC), which
+    // will wake up the device when it goes off.
+    RTC_WAKEUP = 0;
+    // Alarm time in System.currentTimeMillis() (wall clock time in UTC).  This
+    // alarm does not wake the device up; if it goes off while the device is
+    // asleep, it will not be delivered until the next time the device wakes up.
+    RTC = 1;
+    // Alarm time in SystemClock.elapsedRealtime() (time since boot, including
+    // sleep), which will wake up the device when it goes off.
+    ELAPSED_REALTIME_WAKEUP = 2;
+    // Alarm time in SystemClock.elapsedRealtime() (time since boot, including
+    // sleep). This alarm does not wake the device up; if it goes off while the
+    // device is asleep, it will not be delivered until the next time the device
+    // wakes up.
+    ELAPSED_REALTIME = 3;
+  }
+}
+
+// An android.app.AlarmManager.AlarmClockInfo object.
+message AlarmClockInfoProto {
+  // This value is UTC wall clock time in milliseconds, as returned by
+  // System#currentTimeMillis() for example.
+  optional int64 trigger_time_ms = 1;
+  optional android.app.PendingIntentProto show_intent = 2;
+}
diff --git a/core/proto/android/app/jobparameters.proto b/core/proto/android/app/jobparameters.proto
new file mode 100644
index 0000000..4f6a2a2
--- /dev/null
+++ b/core/proto/android/app/jobparameters.proto
@@ -0,0 +1,33 @@
+/*
+ * 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";
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.JobParameters object.
+ */
+message JobParametersProto {
+    enum CancelReason {
+        REASON_CANCELLED = 0;
+        REASON_CONSTRAINTS_NOT_SATISFIED = 1;
+        REASON_PREEMPT = 2;
+        REASON_TIMEOUT = 3;
+        REASON_DEVICE_IDLE = 4;
+    }
+}
diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto
new file mode 100644
index 0000000..0388547
--- /dev/null
+++ b/core/proto/android/app/notification_channel.proto
@@ -0,0 +1,53 @@
+/*
+ * 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";
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/media/audioattributes.proto";
+
+/**
+ * An android.app.NotificationChannel object.
+ */
+message NotificationChannelProto {
+    optional string id = 1;
+    optional string name = 2;
+    optional string description = 3;
+    optional int32 importance = 4;
+    optional bool can_bypass_dnd = 5;
+    // Default is VISIBILITY_NO_OVERRIDE (-1000).
+    optional int32 lockscreen_visibility = 6;
+    optional string sound = 7;
+    optional bool use_lights = 8;
+    // Default is 0.
+    optional int32 light_color = 9;
+    repeated int64 vibration = 10;
+    // Bitwise representation of fields that have been changed by the user,
+    // preventing the app from making changes to these fields.
+    optional int32 user_locked_fields = 11;
+    optional bool is_vibration_enabled = 12;
+    // Default is true.
+    optional bool show_badge = 13;
+    // Default is false.
+    optional bool is_deleted = 14;
+    optional string group = 15;
+    optional android.media.AudioAttributesProto audio_attributes = 16;
+    // If this is a blockable system notification channel.
+    optional bool is_blockable_system = 17;
+}
diff --git a/core/proto/android/app/notification_channel_group.proto b/core/proto/android/app/notification_channel_group.proto
new file mode 100644
index 0000000..89a540f
--- /dev/null
+++ b/core/proto/android/app/notification_channel_group.proto
@@ -0,0 +1,34 @@
+/*
+ * 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";
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+import "frameworks/base/core/proto/android/app/notification_channel.proto";
+
+/**
+ * An android.app.NotificationChannelGroup object.
+ */
+message NotificationChannelGroupProto {
+    optional string id = 1;
+    optional string name = 2;
+    optional string description = 3;
+    optional bool is_blocked = 4;
+    repeated android.app.NotificationChannelProto channels = 5;
+}
diff --git a/core/proto/android/app/notificationmanager.proto b/core/proto/android/app/notificationmanager.proto
new file mode 100644
index 0000000..7d774ae
--- /dev/null
+++ b/core/proto/android/app/notificationmanager.proto
@@ -0,0 +1,64 @@
+/*
+ * 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";
+option java_package = "android.app";
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.NotificationMananger.Policy object.
+ */
+message PolicyProto {
+    enum Category {
+        CATEGORY_UNKNOWN = 0;
+        // Reminder notifications are prioritized.
+        REMINDERS = 1;
+        // Event notifications are prioritized.
+        EVENTS = 2;
+        // Message notifications are prioritized.
+        MESSAGES = 3;
+        // Calls are prioritized.
+        CALLS = 4;
+        // Calls from repeat callers are prioritized.
+        REPEAT_CALLERS = 5;
+    }
+    repeated Category priority_categories = 1;
+
+    enum Sender {
+        // Any sender is prioritized.
+        ANY = 0;
+        // Saved contacts are prioritized.
+        CONTACTS = 1;
+        // Only starred contacts are prioritized.
+        STARRED = 2;
+    }
+    optional Sender priority_call_sender = 2;
+    optional Sender priority_message_sender = 3;
+
+    enum SuppressedVisualEffect {
+        SVE_UNKNOWN = 0;
+        // Whether notifications suppressed by DND should not interrupt visually
+        // (e.g. with notification lights or by turning the screen on) when the
+        // screen is off.
+        SCREEN_OFF = 1;
+        // Whether notifications suppressed by DND should not interrupt visually
+        // when the screen is on (e.g. by peeking onto the screen).
+        SCREEN_ON = 2;
+    }
+    repeated SuppressedVisualEffect suppressed_visual_effects = 4;
+}
diff --git a/core/proto/android/app/pendingintent.proto b/core/proto/android/app/pendingintent.proto
new file mode 100644
index 0000000..b562c0b
--- /dev/null
+++ b/core/proto/android/app/pendingintent.proto
@@ -0,0 +1,28 @@
+/*
+ * 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";
+
+option java_multiple_files = true;
+
+package android.app;
+
+/**
+ * An android.app.PendingIntent object.
+ */
+message PendingIntentProto {
+  optional string target = 1;
+}
diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto
index 03910df..4d748e8 100644
--- a/core/proto/android/app/window_configuration.proto
+++ b/core/proto/android/app/window_configuration.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_package = "android.app";
 option java_multiple_files = true;
 
@@ -25,7 +24,7 @@
 
 /** Proto representation for WindowConfiguration.java class. */
 message WindowConfigurationProto {
-  .android.graphics.RectProto app_bounds = 1;
-  int32 windowing_mode = 2;
-  int32 activity_type = 3;
+  optional .android.graphics.RectProto app_bounds = 1;
+  optional int32 windowing_mode = 2;
+  optional int32 activity_type = 3;
 }
diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto
index 90f6ffb..fc0c8c5 100644
--- a/core/proto/android/content/component_name.proto
+++ b/core/proto/android/content/component_name.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_package = "android.content";
 option java_multiple_files = true;
 
@@ -25,7 +24,7 @@
  * An android.content.ComponentName object.
  */
 message ComponentNameProto {
-    string package_name = 1;
-    string class_name = 2;
+    optional string package_name = 1;
+    optional string class_name = 2;
 }
 
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index 804e0b4..111b27f 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_package = "android.content";
 option java_multiple_files = true;
 
@@ -28,22 +27,22 @@
  * An android resource configuration.
  */
 message ConfigurationProto {
-  float font_scale = 1;
-  uint32 mcc = 2;
-  uint32 mnc = 3;
+  optional float font_scale = 1;
+  optional uint32 mcc = 2;
+  optional uint32 mnc = 3;
   repeated LocaleProto locales = 4;
-  uint32 screen_layout = 5;
-  uint32 touchscreen = 6;
-  uint32 keyboard_hidden = 7;
-  uint32 hard_keyboard_hidden = 8;
-  uint32 navigation = 9;
-  uint32 navigation_hidden = 10;
-  uint32 orientation = 11;
-  uint32 ui_mode = 12;
-  uint32 screen_width_dp = 13;
-  uint32 screen_height_dp = 14;
-  uint32 smallest_screen_width_dp = 15;
-  uint32 density_dpi = 16;
-  .android.app.WindowConfigurationProto window_configuration = 17;
+  optional uint32 screen_layout = 5;
+  optional uint32 touchscreen = 6;
+  optional uint32 keyboard_hidden = 7;
+  optional uint32 hard_keyboard_hidden = 8;
+  optional uint32 navigation = 9;
+  optional uint32 navigation_hidden = 10;
+  optional uint32 orientation = 11;
+  optional uint32 ui_mode = 12;
+  optional uint32 screen_width_dp = 13;
+  optional uint32 screen_height_dp = 14;
+  optional uint32 smallest_screen_width_dp = 15;
+  optional uint32 density_dpi = 16;
+  optional .android.app.WindowConfigurationProto window_configuration = 17;
 }
 
diff --git a/core/proto/android/content/featureinfo.proto b/core/proto/android/content/featureinfo.proto
new file mode 100644
index 0000000..a750120
--- /dev/null
+++ b/core/proto/android/content/featureinfo.proto
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 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";
+
+option java_package = "android.content.pm";
+option java_multiple_files = true;
+
+package android.content.pm;
+
+message FeatureInfoProto {
+    optional string name = 1;
+    optional int32 version = 2;
+    optional string gles_version = 3;
+    optional int32 flags = 4;
+}
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
new file mode 100644
index 0000000..3e5265a
--- /dev/null
+++ b/core/proto/android/content/intent.proto
@@ -0,0 +1,81 @@
+/*
+ * 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.content;
+
+option java_package = "android.content";
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/os/patternmatcher.proto";
+
+// Next Tag: 13
+message IntentProto {
+    enum DockState {
+        // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+        // the phone is not in any dock.
+        DOCK_STATE_UNDOCKED = 0;
+
+        // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+        // the phone is in a desk dock.
+        DOCK_STATE_DESK = 1;
+
+        // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+        // the phone is in a car dock.
+        DOCK_STATE_CAR = 2;
+
+        // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+        // the phone is in a analog (low end) dock.
+        DOCK_STATE_LE_DESK = 3;
+
+        // Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
+        // the phone is in a digital (high end) dock.
+        DOCK_STATE_HE_DESK = 4;
+    }
+
+    optional string action = 1;
+    repeated string categories = 2;
+    optional string data = 3;
+    optional string type = 4;
+    optional string flag = 5;
+    optional string package = 6;
+    optional string component = 7;
+    optional string source_bounds = 8;
+    optional string clip_data = 9;
+    optional string extras = 10;
+    optional int32 content_user_hint = 11;
+    optional string selector = 12;
+}
+
+// Next Tag: 11
+message IntentFilterProto {
+    repeated string actions = 1;
+    repeated string categories = 2;
+    repeated string data_schemes = 3;
+    repeated android.os.PatternMatcherProto data_scheme_specs = 4;
+    repeated AuthorityEntryProto data_authorities = 5;
+    repeated android.os.PatternMatcherProto data_paths = 6;
+    repeated string data_types = 7;
+    optional int32 priority = 8;
+    optional bool has_partial_types = 9;
+    optional bool get_auto_verify = 10;
+}
+
+message AuthorityEntryProto {
+    optional string host = 1;
+    optional bool wild = 2;
+    optional int32 port = 3;
+}
diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto
index 961b10b..f0de31c 100644
--- a/core/proto/android/content/locale.proto
+++ b/core/proto/android/content/locale.proto
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_package = "android.content";
 option java_multiple_files = true;
 
 package android.content;
 
 message LocaleProto {
-  string language = 1;
-  string country = 2;
-  string variant = 3;
+  optional string language = 1;
+  optional string country = 2;
+  optional string variant = 3;
 }
 
diff --git a/core/proto/android/graphics/rect.proto b/core/proto/android/graphics/rect.proto
index a65d331..562ffce 100644
--- a/core/proto/android/graphics/rect.proto
+++ b/core/proto/android/graphics/rect.proto
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.graphics;
 
 option java_multiple_files = true;
 
 message RectProto {
-  int32 left = 1;
-  int32 top = 2;
-  int32 right = 3;
-  int32 bottom = 4;
+  optional int32 left = 1;
+  optional int32 top = 2;
+  optional int32 right = 3;
+  optional int32 bottom = 4;
 }
 
diff --git a/core/proto/android/internal/locallog.proto b/core/proto/android/internal/locallog.proto
new file mode 100644
index 0000000..51f6c1c
--- /dev/null
+++ b/core/proto/android/internal/locallog.proto
@@ -0,0 +1,24 @@
+/*
+ * 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 com.android.internal.util;
+
+option java_multiple_files = true;
+
+message LocalLogProto {
+  repeated string lines = 1;
+}
diff --git a/core/proto/android/media/audioattributes.proto b/core/proto/android/media/audioattributes.proto
new file mode 100644
index 0000000..860d608
--- /dev/null
+++ b/core/proto/android/media/audioattributes.proto
@@ -0,0 +1,99 @@
+/*
+ * 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";
+option java_package = "android.media";
+option java_multiple_files = true;
+
+package android.media;
+
+/**
+ * An android.media.AudioAttributes object.
+ */
+message AudioAttributesProto {
+    optional Usage usage = 1;
+    optional ContentType content_type = 2;
+    // Bit representation of set flags.
+    optional int32 flags = 3;
+    repeated string tags = 4;
+}
+
+enum ContentType {
+    // Content type value to use when the content type is unknown, or other than
+    // the ones defined.
+    CONTENT_TYPE_UNKNOWN = 0;
+    // Content type value to use when the content type is speech.
+    SPEECH = 1;
+    // Content type value to use when the content type is music.
+    MUSIC = 2;
+    // Content type value to use when the content type is a soundtrack,
+    // typically accompanying a movie or TV program.
+    MOVIE = 3;
+    // Content type value to use when the content type is a sound used to
+    // accompany a user action, such as a beep or sound effect expressing a key
+    // click, or event, such as the type of a sound for a bonus being received
+    // in a game. These sounds are mostly synthesized or short Foley sounds.
+    SONIFICATION = 4;
+}
+
+enum Usage {
+    // Usage value to use when the usage is unknown.
+    USAGE_UNKNOWN = 0;
+    // Usage value to use when the usage is media, such as music, or movie
+    // soundtracks.
+    MEDIA = 1;
+    // Usage value to use when the usage is voice communications, such as
+    // telephony or VoIP.
+    VOICE_COMMUNICATION = 2;
+    // Usage value to use when the usage is in-call signalling, such as with a
+    // "busy" beep, or DTMF tones.
+    VOICE_COMMUNICATION_SIGNALLING = 3;
+    // Usage value to use when the usage is an alarm (e.g. wake-up alarm).
+    ALARM = 4;
+    // Usage value to use when the usage is notification. Other notification
+    // usages are for more specialized uses.
+    NOTIFICATION = 5;
+    // Usage value to use when the usage is telephony ringtone.
+    NOTIFICATION_RINGTONE = 6;
+    // Usage value to use when the usage is a request to enter/end a
+    // communication, such as a VoIP communication or video-conference.
+    NOTIFICATION_COMMUNICATION_REQUEST = 7;
+    // Usage value to use when the usage is notification for an "instant"
+    // communication such as a chat, or SMS.
+    NOTIFICATION_COMMUNICATION_INSTANT = 8;
+    // Usage value to use when the usage is notification for a non-immediate
+    // type of communication such as e-mail.
+    NOTIFICATION_COMMUNICATION_DELAYED = 9;
+    // Usage value to use when the usage is to attract the user's attention,
+    // such as a reminder or low battery warning.
+    NOTIFICATION_EVENT = 10;
+    // Usage value to use when the usage is for accessibility, such as with a
+    // screen reader.
+    ASSISTANCE_ACCESSIBILITY = 11;
+    // Usage value to use when the usage is driving or navigation directions.
+    ASSISTANCE_NAVIGATION_GUIDANCE = 12;
+    // Usage value to use when the usage is sonification, such as  with user
+    // interface sounds.
+    ASSISTANCE_SONIFICATION = 13;
+    // Usage value to use when the usage is for game audio.
+    GAME = 14;
+    // Usage value to use when feeding audio to the platform and replacing
+    // "traditional" audio source, such as audio capture devices.
+    VIRTUAL_SOURCE = 15;
+    // Usage value to use for audio responses to user queries, audio
+    // instructions or help utterances.
+    ASSISTANT = 16;
+}
diff --git a/core/proto/android/os/batterymanager.proto b/core/proto/android/os/batterymanager.proto
new file mode 100644
index 0000000..669bf2d
--- /dev/null
+++ b/core/proto/android/os/batterymanager.proto
@@ -0,0 +1,29 @@
+/*
+ * 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 BatteryManagerProto {
+    enum PlugType {
+        PLUG_TYPE_NONE = 0;
+        PLUG_TYPE_AC = 1;
+        PLUG_TYPE_USB = 2;
+        PLUG_TYPE_WIRELESS = 4;
+    }
+}
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
new file mode 100644
index 0000000..c33c0a0
--- /dev/null
+++ b/core/proto/android/os/batterystats.proto
@@ -0,0 +1,770 @@
+/*
+ * Copyright (C) 2016 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";
+option java_multiple_files = true;
+
+package android.os;
+
+import "frameworks/base/core/proto/android/app/jobparameters.proto";
+import "frameworks/base/core/proto/android/os/powermanager.proto";
+import "frameworks/base/core/proto/android/telephony/signalstrength.proto";
+
+message BatteryStatsProto {
+  optional int32 report_version = 1;
+  optional int64 parcel_version = 2;
+  optional string start_platform_version = 3;
+  optional string end_platform_version = 4;
+  optional BatteryHistoryProto history = 5;
+  repeated UidProto uids = 6;
+  optional SystemProto system = 7;
+}
+
+message BatteryHistoryProto {
+}
+
+message ControllerActivityProto {
+  // Time (milliseconds) spent in the idle state.
+  optional int64 idle_duration_ms = 1;
+  // Time (milliseconds) spent in the receive state.
+  optional int64 rx_duration_ms = 2;
+  // Total power (mAh) consumed by the controller in all states. The value may
+  // always be 0 if the device doesn't support power calculations.
+  optional int64 power_mah = 3;
+
+  // Represents a transmit level, where each level may draw a different amount
+  // of power. The levels themselves are controller-specific (and may possibly
+  // be device specific...yet to be confirmed).
+  message TxLevel {
+    // Transmit level. Higher levels draw more power.
+    optional int32 level = 1;
+    // Time spent in this specific transmit level state.
+    optional int64 duration_ms = 2;
+  }
+  repeated TxLevel tx = 4;
+}
+
+message SystemProto {
+  message Battery {
+    // Wall clock time when the data collection started.
+    // In case of device time manually reset by users:
+    //   start_clock_time_ms keeps the same value in the current collection
+    //   period and changes for later collection periods.
+    optional int64 start_clock_time_ms = 1;
+    // #times the device has been started since start_clock_time_millis.
+    optional int64 start_count = 2;
+    // Total realtime duration (= SINCE_UNPLUGGED battery_realtime_millis.)
+    optional int64 total_realtime_ms = 3;
+    optional int64 total_uptime_ms = 4;
+    // Realtime duration on battery.
+    optional int64 battery_realtime_ms = 5;
+    // Uptime duration (i.e., not suspend).
+    // Uptime is anytime the CPUs were on. The radio and Wifi chip
+    // can be running while the CPUs are off.
+    optional int64 battery_uptime_ms = 6;
+    // Total realtime duration measured with screen off or dozing.
+    optional int64 screen_off_realtime_ms = 7;
+    // Total uptime duration measured with screen off or dozing.
+    optional int64 screen_off_uptime_ms = 8;
+    // Total time the screen was dozing while the device was running on battery.
+    // For historical reasons, screen_doze_duration_msec is a subset of
+    // screen_off_realtime_msec.
+    optional int64 screen_doze_duration_ms = 9;
+    // The estimated real battery capacity, which may be less than the declared
+    // battery capacity (for example, because of battery aging). This field is
+    // less reliable than min(max)_learned_battery_capacity_uah, use those two
+    // fields whenever possible.
+    optional int64 estimated_battery_capacity_mah = 10;
+    // The minimum learned battery capacity in uAh.
+    optional int64 min_learned_battery_capacity_uah = 11;
+    // The maximum learned battery capacity in uAh.
+    optional int64 max_learned_battery_capacity_uah = 12;
+  };
+  optional Battery battery = 1;
+
+  message BatteryDischarge {
+    // Discharged battery percentage points since the stats were last reset
+    // after charging (lower bound approximation).
+    optional int32 lower_bound_since_charge = 1;
+    // Upper bound approximation.
+    optional int32 upper_bound_since_charge = 2;
+    // Discharged points while screen is on.
+    optional int32 screen_on_since_charge = 3;
+    // Discharged points while screen is off.
+    optional int32 screen_off_since_charge = 4;
+    // Discharged points while screen was dozing. For historical reasons,
+    // screen_doze_since_charge is a subset of screen_off_since_charge.
+    optional int32 screen_doze_since_charge = 5;
+    // Total amount of battery discharged in mAh. This will only be non-zero for
+    // devices that report battery discharge via a coulomb counter.
+    optional int64 total_mah = 6;
+    // Total amount of battery discharged while the screen was off in mAh.
+    // This will only be non-zero for devices that report battery discharge
+    // via a coulomb counter.
+    optional int64 total_mah_screen_off = 7;
+    // Total amount of battery discharged while the screen was dozing in mAh.
+    // This will only be non-zero for devices that report battery discharge
+    // via a coulomb counter. For historical reasons, total_mah_screen_doze is
+    // a subset of total_mah_screen_off.
+    optional int64 total_mah_screen_doze = 8;
+  };
+  optional BatteryDischarge battery_discharge = 2;
+
+  oneof time_remaining {
+    // Approximation for how much time remains until the battery is fully
+    // charged. The device will print -1 if there wasn't enough data to
+    // calculate an estimate, or if the battery is currently discharging.
+    int64 charge_time_remaining_ms = 3;
+    // Approximation for how much time remains until the battery is fully
+    // discharged. The device will print -1 if there wasn't enough data to
+    // calculate an estimate, or if the battery is currently charging.
+    int64 discharge_time_remaining_ms = 4;
+  }
+
+  // BatteryLevelStep tracks data for which conditions were continuously held for
+  // the entire duration. Field for which the conditions were not consistent
+  // for the entire duration should be marked MIXED.
+  message BatteryLevelStep {
+    // How long the battery was at the current level.
+    optional int64 duration_ms = 1;
+    // Battery level
+    optional int32 level = 2;
+
+    // State of the display. A special enum is used rather than
+    // DisplayProto.State because a MIXED value needs to be in the enum, and
+    // batterystats doesn't care about all of the different display states.
+    enum DisplayState {
+      DS_MIXED = 0;
+      DS_ON = 1;
+      DS_OFF = 2;
+      DS_DOZE = 3;
+      DS_DOZE_SUSPEND = 4;
+      // Any display state error that comes through should be sent to hackbod@.
+      DS_ERROR = 5;
+    }
+    // The state of the display for the entire battery level step. MIXED is used
+    // if there were multiple states for this step.
+    optional DisplayState display_state = 3;
+
+    // Indicates status in power save mode.
+    enum PowerSaveMode {
+      PSM_MIXED = 0;
+      PSM_ON = 1;
+      PSM_OFF = 2;
+    }
+    // Battery Saver mode for the entire battery level step. MIXED is used
+    // if there were multiple states for this step.
+    optional PowerSaveMode power_save_mode = 4;
+
+    // Indicates status in idle mode.
+    enum IdleMode {
+      IM_MIXED = 0;
+      IM_ON = 2;
+      IM_OFF = 3;
+    }
+    // Doze mode for the entire battery level step. MIXED is used if there were
+    // multiple states for this step.
+    optional IdleMode idle_mode = 5;
+  };
+  // Battery level steps when the device was charging.
+  repeated BatteryLevelStep charge_step = 5;
+  // Battery level steps when the device was discharging.
+  repeated BatteryLevelStep discharge_step = 6;
+
+  // All CPU frequencies of the device.
+  repeated int64 cpu_frequency = 7;
+
+  message DataConnection {
+    enum Name {
+      NONE = 0;
+      GPRS = 1;
+      EDGE = 2;
+      UMTS = 3;
+      CDMA = 4;
+      EVDO_0 = 5;
+      EVDO_A = 6;
+      ONE_X_RTT = 7; // 1xRTT.
+      HSDPA = 8;
+      HSUPA = 9;
+      HSPA = 10;
+      IDEN = 11;
+      EVDO_B = 12;
+      LTE = 13;
+      EHRPD = 14;
+      HSPAP = 15;
+      OTHER = 16;
+    };
+    optional Name name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated DataConnection data_connection = 8;
+
+  optional ControllerActivityProto global_bluetooth_controller = 9;
+  optional ControllerActivityProto global_modem_controller = 10;
+  optional ControllerActivityProto global_wifi_controller = 11;
+
+  message GlobalNetwork {
+    // Total Bytes received on mobile connections.
+    optional int64 mobile_bytes_rx = 1;
+    // Total Bytes transmitted on mobile connections.
+    optional int64 mobile_bytes_tx = 2;
+    // Total Bytes received on wifi connections.
+    optional int64 wifi_bytes_rx = 3;
+    // Total Bytes transmitted on wifi connections.
+    optional int64 wifi_bytes_tx = 4;
+    // Total Packets received on mobile connections.
+    optional int64 mobile_packets_rx = 5;
+    // Total Packets transmitted on mobile connections.
+    optional int64 mobile_packets_tx = 6;
+    // Total Packets received on wifi connections.
+    optional int64 wifi_packets_rx = 7;
+    // Total Packets transmitted on wifi connections.
+    optional int64 wifi_packets_tx = 8;
+    // Total Bytes received on bluetooth connections.
+    optional int64 bt_bytes_rx = 9;
+    // Total Bytes transmitted on bluetooth connections.
+    optional int64 bt_bytes_tx = 10;
+  };
+  optional GlobalNetwork global_network = 12;
+
+  message GlobalWifi {
+    // The amount of time that wifi has been on while the device was running on
+    // battery.
+    optional int64 on_duration_ms = 1;
+    // The amount of time that wifi has been on and the driver has been in the
+    // running state while the device was running on battery.
+    optional int64 running_duration_ms = 2;
+  }
+  optional GlobalWifi global_wifi = 13;
+
+  // Kernel wakelock metrics are only recorded when the device is unplugged
+  // *and* the screen is off.
+  message KernelWakelock {
+    optional string name = 1;
+    // Kernel wakelock stats aren't apportioned across all kernel wakelocks (as
+    // app wakelocks stats are).
+    optional TimerProto total = 2;
+    // The kernel doesn't have the data to enable printing out current and max
+    // durations.
+  };
+  repeated KernelWakelock kernel_wakelock = 14;
+
+  message Misc {
+    optional int64 screen_on_duration_ms = 1;
+    optional int64 phone_on_duration_ms = 2;
+    optional int64 full_wakelock_total_duration_ms = 3;
+    // The total elapsed time that a partial wakelock was held. This duration
+    // does not double count wakelocks held at the same time.
+    optional int64 partial_wakelock_total_duration_ms = 4;
+    optional int64 mobile_radio_active_duration_ms = 5;
+    // The time that is the difference between the mobile radio time we saw
+    // based on the elapsed timestamp when going down vs. the given time stamp
+    // from the radio.
+    optional int64 mobile_radio_active_adjusted_time_ms = 6;
+    optional int32 mobile_radio_active_count = 7;
+    // The amount of time that the mobile network has been active (in a high
+    // power state) but not being able to blame on an app.
+    optional int32 mobile_radio_active_unknown_duration_ms = 8;
+    // Total amount of time the device was in the interactive state.
+    optional int64 interactive_duration_ms = 9;
+    optional int64 battery_saver_mode_enabled_duration_ms = 10;
+    optional int32 num_connectivity_changes = 11;
+    // Amount of time the device was in deep Doze.
+    optional int64 deep_doze_enabled_duration_ms = 12;
+    // How many times the device went into deep Doze mode.
+    optional int32 deep_doze_count = 13;
+    // Amount of time the device was idling in deep Doze. Idling time
+    // encompasses "doze" time and the maintenance windows that allow apps to
+    // operate.
+    optional int64 deep_doze_idling_duration_ms = 14;
+    // How many times the device idling for deep Doze mode.
+    optional int32 deep_doze_idling_count = 15;
+    optional int64 longest_deep_doze_duration_ms = 16;
+    // Amount of time the device was in Doze Light.
+    optional int64 light_doze_enabled_duration_ms = 17;
+    // How many times the device went into Doze Light mode.
+    optional int32 light_doze_count = 18;
+    // Amount of time the device was idling in Doze Light. Idling time
+    // encompasses "doze" time and the maintenance windows that allow apps to
+    // operate.
+    optional int64 light_doze_idling_duration_ms = 19;
+    // How many times the device idling for Doze Light mode.
+    optional int32 light_doze_idling_count = 20;
+    optional int64 longest_light_doze_duration_ms = 21;
+  }
+  optional Misc misc = 15;
+
+  message PhoneSignalStrength {
+    optional android.telephony.SignalStrengthProto.StrengthName name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated PhoneSignalStrength phone_signal_strength = 16;
+
+  message PowerUseItem {
+    enum Sipper {
+      UNKNOWN_SIPPER = 0;
+      IDLE = 1;
+      CELL = 2;
+      PHONE = 3;
+      WIFI = 4;
+      BLUETOOTH = 5;
+      FLASHLIGHT = 6;
+      SCREEN = 7;
+      USER = 8;
+      UNACCOUNTED = 9;
+      OVERCOUNTED = 10;
+      CAMERA = 11;
+      MEMORY = 12;
+    };
+    optional Sipper name = 1;
+    // UID, only valid for the USER sipper.
+    optional int32 uid = 2;
+    // Estimated power use in mAh.
+    optional double computed_power_mah = 3;
+    // Starting in Oreo, Battery Settings has two modes to display the battery
+    // info. The first is "app usage list". In this mode, items with should_hide
+    // enabled are hidden.
+    optional bool should_hide = 4;
+    // Smeared power from screen usage. Screen usage power is split and smeared
+    // among apps, based on activity time.
+    optional double screen_power_mah = 5;
+    // Smeared power using proportional method. Power usage from hidden sippers
+    // is smeared to all apps proportionally (except for screen usage).
+    optional double proportional_smear_mah = 6;
+  };
+  repeated PowerUseItem power_use_item = 17;
+
+  message PowerUseSummary {
+    optional double battery_capacity_mah = 1;
+    optional double computed_power_mah = 2;
+    // Lower bound of actual power drained.
+    optional double min_drained_power_mah = 3;
+    // Upper bound of actual power drained.
+    optional double max_drained_power_mah = 4;
+  };
+  optional PowerUseSummary power_use_summary = 18;
+
+  message ResourcePowerManager {
+    // Either StateName or StateName.VoterName.
+    optional string name = 1;
+    optional TimerProto total = 2;
+    optional TimerProto screen_off = 3;
+  }
+  repeated ResourcePowerManager resource_power_manager = 19;
+
+  message ScreenBrightness {
+    enum Name {
+      DARK = 0; // Not screen-off.
+      DIM = 1;
+      MEDIUM = 2;
+      LIGHT = 3;
+      BRIGHT = 4;
+    };
+    optional Name name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated ScreenBrightness screen_brightness = 20;
+
+  // Duration and number of times trying to acquire a signal
+  optional TimerProto signal_scanning = 21;
+
+  message WakeupReason {
+    optional string name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated WakeupReason wakeup_reason = 22;
+
+  message WifiSignalStrength {
+    enum Name {
+      NONE = 0;
+      POOR = 1;
+      MODERATE = 2;
+      GOOD = 3;
+      GREAT = 4;
+    };
+    optional Name name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated WifiSignalStrength wifi_signal_strength = 23;
+
+  message WifiState {
+    enum Name {
+      OFF = 0;
+      OFF_SCANNING = 1;
+      ON_NO_NETWORKS = 2;
+      ON_DISCONNECTED = 3;
+      ON_CONNECTED_STA = 4;
+      ON_CONNECTED_P2P = 5;
+      ON_CONNECTED_STA_P2P = 6;
+      SOFT_AP = 7;
+    };
+    optional Name name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated WifiState wifi_state = 24;
+
+  message WifiSupplicantState {
+    enum Name {
+      INVALID = 0;
+      DISCONNECTED = 1;
+      INTERFACE_DISABLED = 2;
+      INACTIVE = 3;
+      SCANNING = 4;
+      AUTHENTICATING = 5;
+      ASSOCIATING = 6;
+      ASSOCIATED = 7;
+      FOUR_WAY_HANDSHAKE = 8;
+      GROUP_HANDSHAKE = 9;
+      COMPLETED = 10;
+      DORMANT = 11;
+      UNINITIALIZED = 12;
+    };
+    optional Name name = 1;
+    optional TimerProto total = 2;
+  };
+  repeated WifiSupplicantState wifi_supplicant_state = 25;
+}
+
+message TimerProto {
+  // This may be an apportioned time.
+  optional int64 duration_ms = 1;
+  optional int64 count = 2;
+  // The max duration if it is being tracked. Not all Timer subclasses
+  // track the max duration.
+  optional int64 max_duration_ms = 3;
+  // The current time the timer has been active, if it is being tracked.
+  // Not all Timer subclasses track the current duration.
+  optional int64 current_duration_ms = 4;
+  // The total cumulative duration (i.e. sum of past durations) that this timer
+  // has been on since reset. This may differ from duration_ms since, depending
+  // on the Timer, getTotalTimeLocked may represent the total 'blamed' or
+  // 'pooled' time, rather than the actual time. By contrast, total_duration_ms
+  // always gives the actual total time. Not all Timer subclasses track the
+  // total duration.
+  optional int64 total_duration_ms = 5;
+}
+
+message UidProto {
+  // Combination of app ID and user ID.
+  optional int32 uid = 1;
+
+  // The statistics associated with a particular package.
+  message Package {
+    optional string name = 1;
+
+    message Service {
+      optional string name = 1;
+      // Time spent started.
+      optional int64 start_duration_ms = 2;
+      optional int32 start_count = 3;
+      optional int32 launch_count = 4;
+    }
+    repeated Service services = 2;
+  }
+  repeated Package packages = 2;
+
+  optional ControllerActivityProto bluetooth_controller = 3;
+  optional ControllerActivityProto modem_controller = 4;
+  optional ControllerActivityProto wifi_controller = 5;
+
+  // Bluetooth misc data.
+  message BluetoothMisc {
+    // Duration spent BLE scanning blamed on this App (i.e. apportioned to this
+    // app amongst all apps doing BLE scanning; see explanation of 'apportioned'
+    // in App's comment).
+    optional TimerProto apportioned_ble_scan = 1;
+    // Background times aren't apportioned.
+    optional TimerProto background_ble_scan = 2;
+    // Running unoptimized BLE scanning, as defined by Bluetooth's
+    // AppScanStats.recordScanStart. As of May 2017, these are unfiltered,
+    // non-opportunistic, non-first-match scans. Durations are not
+    // pooled/apportioned.
+    optional TimerProto unoptimized_ble_scan = 3;
+    // Running unoptimized BLE scanning when app is in background. Durations are
+    // not pooled/apportioned.
+    optional TimerProto background_unoptimized_ble_scan = 4;
+    // Count of results returned by BLE scanning.
+    optional int32 ble_scan_result_count = 5;
+    // Count of results returned by BLE scans when app is in background.
+    // (Included in ble_scan_result_count.)
+    optional int32 background_ble_scan_result_count = 6;
+  }
+  optional BluetoothMisc bluetooth_misc = 6;
+
+  message Cpu {
+    // Total CPU time with processes executing in userspace. Summed up across
+    // multiple cores.
+    optional int64 user_duration_ms = 1;
+    // Total CPU time with processes executing kernel syscalls. Summed up across
+    // multiple cores.
+    optional int64 system_duration_ms = 2;
+
+    // CPU time broken down by CPU frequency (go/cpu-battery-metrics).
+    //
+    // These are real CPU time measurement from the kernel, so their sum can
+    // be different from the sum of user_duration_millis and
+    // system_duration_millis, which are just approximations. Data is not
+    // tracked when device is charging.
+    message ByFrequency {
+      // Index of the frequency in system.cpu_frequency. It starts from 1, to
+      // make it easier to analyze.
+      optional int32 frequency_index = 1;
+      // CPU time in milliseconds.
+      optional int64 total_duration_ms = 2;
+      // Screen-off CPU time in milliseconds.
+      optional int64 screen_off_duration_ms = 3;
+    }
+    repeated ByFrequency by_frequency = 3;
+  }
+  optional Cpu cpu = 7;
+
+  // Duration is pooled/apportioned.
+  optional TimerProto audio = 8;
+  // Duration is pooled/apportioned.
+  optional TimerProto camera = 9;
+  // Duration is pooled/apportioned.
+  optional TimerProto flashlight = 10;
+  // Duration is not pooled/apportioned.
+  optional TimerProto foreground_activity = 11;
+  // Duration is not pooled/apportioned.
+  optional TimerProto foreground_service = 12;
+  // Duration is not pooled/apportioned.
+  optional TimerProto vibrator = 13;
+  // Duration is pooled/apportioned.
+  optional TimerProto video = 14;
+
+  message Job {
+    optional string name = 1;
+    // Job times aren't apportioned.
+    optional TimerProto total = 2;
+    optional TimerProto background = 3;
+  }
+  repeated Job jobs = 15;
+
+  message JobCompletion {
+    // Job name.
+    optional string name = 1;
+
+    message ReasonCount {
+      optional android.app.JobParametersProto.CancelReason name = 1;
+      optional int32 count = 2;
+    }
+    repeated ReasonCount reason_count = 2;
+  };
+  repeated JobCompletion job_completion = 16;
+
+  message Network {
+    // Mobile data traffic (total, background + foreground).
+    optional int64 mobile_bytes_rx = 1;
+    optional int64 mobile_bytes_tx = 2;
+    // Wifi data traffic (total, background + foreground).
+    optional int64 wifi_bytes_rx = 3;
+    optional int64 wifi_bytes_tx = 4;
+    // Bluetooth data traffic (total, background + foreground).
+    optional int64 bt_bytes_rx = 5;
+    optional int64 bt_bytes_tx = 6;
+    // In packets (total, background + foreground).
+    optional int64 mobile_packets_rx = 7;
+    optional int64 mobile_packets_tx = 8;
+    optional int64 wifi_packets_rx = 9;
+    optional int64 wifi_packets_tx = 10;
+    // Radio active duration.
+    optional int64 mobile_active_duration_ms = 11;
+    optional int32 mobile_active_count = 12;
+    // Number of times the app woke up the mobile radio.
+    optional int32 mobile_wakeup_count = 13;
+    // Number of times the app woke up the wifi radio.
+    optional int32 wifi_wakeup_count = 14;
+    // Mobile data traffic in the background only, included in total above.
+    optional int64 mobile_bytes_bg_rx = 15;
+    optional int64 mobile_bytes_bg_tx = 16;
+    // Wifi data traffic in the background only, included in total above.
+    optional int64 wifi_bytes_bg_rx = 17;
+    optional int64 wifi_bytes_bg_tx = 18;
+    // In packets (background only, included in total packets above).
+    optional int64 mobile_packets_bg_rx = 19;
+    optional int64 mobile_packets_bg_tx = 20;
+    optional int64 wifi_packets_bg_rx = 21;
+    optional int64 wifi_packets_bg_tx = 22;
+  };
+  optional Network network = 17;
+
+  // TODO: combine System and App messages?
+  message PowerUseItem {
+    // Estimated power use in mAh.
+    optional double computed_power_mah = 1;
+    // Starting in Oreo, Battery Settings has two modes to display the battery
+    // info. The first is "app usage list". In this mode, items with should_hide
+    // enabled are hidden.
+    optional bool should_hide = 2;
+    // Smeared power from screen usage. Screen usage power is split and smeared
+    // among apps, based on activity time.
+    optional double screen_power_mah = 3;
+    // Smeared power using proportional method. Power usage from hidden sippers
+    // is smeared to all apps proportionally (except for screen usage).
+    optional double proportional_smear_mah = 4;
+  };
+  optional PowerUseItem power_use_item = 18;
+
+  // Durations are not pooled/apportioned.
+  message Process {
+    optional string name = 1;
+    // Time spent executing in user code.
+    optional int64 user_duration_ms = 2;
+    // Time spent executing in kernel code.
+    optional int64 system_duration_ms = 3;
+    // Time the process was running in the foreground.
+    optional int64 foreground_duration_ms = 4;
+    // Number of times the process has been started.
+    optional int32 start_count = 5;
+    // Number of times the process has had an ANR.
+    optional int32 anr_count = 6;
+    // Number of times the process has crashed.
+    optional int32 crash_count = 7;
+  };
+  repeated Process process = 19;
+
+  message StateTime {
+    // All of these (non-deprecated) states are mutually exclusive and can be
+    // added together to find the total time a uid has had any processes running
+    // at all.
+
+    // In approximate order or priority (top being what the framework considers
+    // most important and is thus least likely to kill when resources are
+    // needed:
+    // top > foreground service > top sleeping > foreground > background > cache
+    enum State {
+      // Time this uid has any processes in the top state (or above such as
+      // persistent).
+      PROCESS_STATE_TOP = 0;
+      // Time this uid has any process with a started out bound foreground
+      // service, but none in the "top" state.
+      PROCESS_STATE_FOREGROUND_SERVICE = 1;
+      // Time this uid has any process that is top while the device is sleeping,
+      // but none in the "foreground service" or better state. Sleeping is
+      // mostly screen off, but also includes the time when the screen is on but
+      // the device has not yet been unlocked.
+      PROCESS_STATE_TOP_SLEEPING = 2;
+      // Time this uid has any process in an active foreground state, but none
+      // in the "top sleeping" or better state.
+      PROCESS_STATE_FOREGROUND = 3;
+      // Time this uid has any process in an active background state, but none
+      // in the "foreground" or better state.
+      PROCESS_STATE_BACKGROUND = 4;
+      // Time this uid has any processes that are sitting around cached, not in
+      // one of the other active states.
+      PROCESS_STATE_CACHED = 5;
+    }
+    optional State state = 1;
+    optional int64 duration_ms = 2;
+  }
+  repeated StateTime states = 20;
+
+  message Sensor {
+    optional int32 id = 1;
+    optional TimerProto apportioned = 2;
+    // Background times aren't apportioned.
+    optional TimerProto background = 3;
+  }
+  repeated Sensor sensors = 21;
+
+  message Sync {
+    optional string name = 1;
+    // Sync times aren't apportioned.
+    optional TimerProto total = 2;
+    optional TimerProto background = 3;
+  }
+  repeated Sync syncs = 22;
+
+  message UserActivity {
+    optional android.os.PowerManagerProto.UserActivityEvent name = 1;
+    optional int32 count = 2;
+  };
+  repeated UserActivity user_activity = 23;
+
+  // Aggregated wakelock data for an app overall, across all of its wakelocks.
+  // The Wakelock message holds data about each *individual* wakelock, but it
+  // cannot be used to ascertain the aggregated time the app spent holding
+  // wakelocks, since merely summing Wakelock data will either underestimate (in
+  // the case of wakelock.partial.duration_ms) or overestimate (in the case of
+  // wakelock.partial.total_duration_ms) the total time, due to overlapping
+  // wakelocks. AggregatedWakelock, on the other hand, holds overall per-app
+  // wakelock data.
+  message AggregatedWakelock {
+    // The total duration that the app spent holding partial wakelocks.
+    // It includes both foreground + background use.
+    optional int64 partial_duration_ms = 1;
+    // The total duration that the app spent holding partial wakelocks while the
+    // app was in the background. Subtracting from partial_duration_ms will
+    // yield foreground usage.
+    optional int64 background_partial_duration_ms = 2;
+  };
+  optional AggregatedWakelock aggregated_wakelock = 24;
+
+  message Wakelock {
+    optional string name = 1;
+
+    // Full wakelocks keep the screen on. Based on
+    // PowerManager.SCREEN_BRIGHT_WAKE_LOCK (deprecated in API 13) and
+    // PowerManager.SCREEN_DIM_WAKE_LOCK (deprecated in API 17). Current, max,
+    // and total durations are not tracked for full wakelocks.
+    optional TimerProto full = 2;
+
+    // Partial wakelocks ensure the CPU is running while allowing the screen
+    // to turn off. Based on PowerManager.PARTIAL_WAKE_LOCK.
+    // Partial wakelock metrics are only recorded when the device is unplugged
+    // *and* the screen is off. Current, max, and total durations are tracked
+    // for partial wakelocks.
+    optional TimerProto partial = 3;
+
+    // These fields are for tracking partial wakelocks (see above), but only
+    // the time the wakelock was held while the app was in a background state.
+    // Since all background tracking is 'actual', not 'apportioned',
+    // background_partial.duration_ms is identical to
+    // background_partial.total_duration_ms.
+    optional TimerProto background_partial = 4;
+
+    // Window wakelocks keep the screen on. Current, max, and total durations
+    // are not tracked for window wakelocks.
+    optional TimerProto window = 5;
+  };
+  repeated Wakelock wakelocks = 25;
+
+  message WakeupAlarm {
+    // Wakeup alarm name.
+    optional string name = 1;
+    // Only includes counts when screen-off (& on battery).
+    optional int32 count = 2;
+  }
+  repeated WakeupAlarm wakeup_alarm = 26;
+
+  message Wifi {
+    // Duration holding Wifi-lock. This time is apportioned.
+    optional int64 full_wifi_lock_duration_ms = 1;
+    // Duration running Wifi. This time is apportioned.
+    optional int64 running_duration_ms = 2;
+    // Duration performing Wifi-scan blamed on this App (i.e. apportioned to
+    // this app amongst all apps doing Wifi-scanning; see explanation of
+    // 'apportioned' in App's comment).
+    optional TimerProto apportioned_scan = 3;
+    // Scans performed when app is in background. (Included in
+    // apportioned_scan). This value is not apportioned. Subtracting
+    // background_scan.total_duration_ms from apportioned_scan.total_duration_ms
+    // will yield foreground usage.
+    optional TimerProto background_scan = 4;
+  };
+  optional Wifi wifi = 27;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 7331a64..50e811d 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -14,27 +14,30 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_multiple_files = true;
 option java_outer_classname = "IncidentProtoMetadata";
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 import "frameworks/base/libs/incident/proto/android/section.proto";
-import "frameworks/base/core/proto/android/service/appwidget.proto";
-import "frameworks/base/core/proto/android/service/battery.proto";
-import "frameworks/base/core/proto/android/service/fingerprint.proto";
-import "frameworks/base/core/proto/android/service/diskstats.proto";
-import "frameworks/base/core/proto/android/service/netstats.proto";
-import "frameworks/base/core/proto/android/service/notification.proto";
-import "frameworks/base/core/proto/android/service/package.proto";
-import "frameworks/base/core/proto/android/service/power.proto";
-import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/providers/settings.proto";
 import "frameworks/base/core/proto/android/os/incidentheader.proto";
 import "frameworks/base/core/proto/android/os/kernelwake.proto";
 import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
 import "frameworks/base/core/proto/android/os/procrank.proto";
+import "frameworks/base/core/proto/android/server/activitymanagerservice.proto";
+import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto";
+import "frameworks/base/core/proto/android/server/fingerprint.proto";
+import "frameworks/base/core/proto/android/server/powermanagerservice.proto";
+import "frameworks/base/core/proto/android/service/appwidget.proto";
+import "frameworks/base/core/proto/android/service/battery.proto";
+import "frameworks/base/core/proto/android/service/batterystats.proto";
+import "frameworks/base/core/proto/android/service/diskstats.proto";
+import "frameworks/base/core/proto/android/service/netstats.proto";
+import "frameworks/base/core/proto/android/service/notification.proto";
+import "frameworks/base/core/proto/android/service/package.proto";
+import "frameworks/base/core/proto/android/service/print.proto";
+import "frameworks/base/core/proto/android/service/procstats.proto";
 
 package android.os;
 
@@ -49,35 +52,87 @@
     //SystemProperties system_properties = 1000;
 
     // Linux services
-    Procrank procrank = 2000 [
+    optional Procrank procrank = 2000 [
         (section).type = SECTION_COMMAND,
         (section).args = "/system/xbin/procrank"
     ];
 
-    PageTypeInfo page_type_info = 2001 [
+    optional PageTypeInfo page_type_info = 2001 [
         (section).type = SECTION_FILE,
         (section).args = "/proc/pagetypeinfo"
     ];
 
-    KernelWakeSources kernel_wake_sources = 2002 [
+    optional KernelWakeSources kernel_wake_sources = 2002 [
         (section).type = SECTION_FILE,
         (section).args = "/d/wakeup_sources"
     ];
 
 
     // System Services
-    android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
+    optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
         (section).type = SECTION_DUMPSYS,
         (section).args = "fingerprint --proto --incident"
     ];
 
-    android.service.NetworkStatsServiceDumpProto netstats = 3001;
-    android.providers.settings.SettingsServiceDumpProto settings = 3002;
-    android.service.appwidget.AppWidgetServiceDumpProto appwidget = 3003;
-    android.service.battery.BatteryServiceDumpProto battery = 3006;
-    android.service.diskstats.DiskStatsServiceDumpProto diskstats = 3007;
-    android.service.notification.NotificationServiceDumpProto notification = 3004;
-    android.service.pm.PackageServiceDumpProto package = 3008;
-    android.service.power.PowerServiceDumpProto power = 3009;
-    android.service.print.PrintServiceDumpProto print = 3010;
+    optional android.service.NetworkStatsServiceDumpProto netstats = 3001 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "netstats --proto"
+    ];
+
+    optional android.providers.settings.SettingsServiceDumpProto settings = 3002;
+    optional android.service.appwidget.AppWidgetServiceDumpProto appwidget = 3003;
+    optional android.service.notification.NotificationServiceDumpProto notification = 3004 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "notification --proto"
+    ];
+
+    optional android.service.batterystats.BatteryStatsServiceDumpProto batterystats = 3005 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "batterystats --proto"
+    ];
+
+    optional android.service.battery.BatteryServiceDumpProto battery = 3006 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "battery --proto"
+    ];
+
+    optional android.service.diskstats.DiskStatsServiceDumpProto diskstats = 3007 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "diskstats --proto"
+    ];
+
+    optional android.service.pm.PackageServiceDumpProto package = 3008 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "package --proto"
+    ];
+
+    optional com.android.server.power.PowerManagerServiceDumpProto power = 3009 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "power --proto"
+    ];
+
+    optional android.service.print.PrintServiceDumpProto print = 3010;
+
+    optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "procstats --proto"
+    ];
+
+    optional com.android.server.am.proto.ActivityStackSupervisorProto activities = 3012 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "activity --proto activities"
+    ];
+
+    optional com.android.server.am.proto.BroadcastProto broadcasts = 3013 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "activity --proto broadcasts"
+    ];
+
+    optional com.android.server.am.proto.ServiceProto amservices = 3014;
+    optional com.android.server.am.proto.ProcessProto amprocesses = 3015;
+
+    optional com.android.server.AlarmManagerServiceProto alarm = 3016 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "alarm --proto"
+    ];
 }
diff --git a/core/proto/android/os/incidentheader.proto b/core/proto/android/os/incidentheader.proto
index 55a0616..ce924da 100644
--- a/core/proto/android/os/incidentheader.proto
+++ b/core/proto/android/os/incidentheader.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_multiple_files = true;
 option java_outer_classname = "IncidentHeaderProtoMetadata";
 
@@ -29,6 +28,6 @@
         CAUSE_CRASH = 3;
     }
 
-    Cause cause = 1;
+    optional Cause cause = 1;
 }
 
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index e0b62aa..12649e1 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_multiple_files = true;
 option java_outer_classname = "WakeupSourcesProto";
 
@@ -29,23 +28,23 @@
 // Next Tag: 11
 message WakeupSourceProto {
     // Name of the event which triggers application processor
-    string name = 1;
+    optional string name = 1;
 
-    int32 active_count = 2;
+    optional int32 active_count = 2;
 
-    int32 event_count = 3;
+    optional int32 event_count = 3;
 
-    int32 wakeup_count = 4;
+    optional int32 wakeup_count = 4;
 
-    int32 expire_count = 5;
+    optional int32 expire_count = 5;
 
-    int64 active_since = 6;
+    optional int64 active_since = 6;
 
-    int64 total_time = 7;
+    optional int64 total_time = 7;
 
-    int64 max_time = 8;
+    optional int64 max_time = 8;
 
-    int64 last_change = 9;
+    optional int64 last_change = 9;
 
-    int64 prevent_suspend_time = 10;
+    optional int64 prevent_suspend_time = 10;
 }
diff --git a/core/proto/android/os/looper.proto b/core/proto/android/os/looper.proto
index 9fcc781..ef84bb1 100644
--- a/core/proto/android/os/looper.proto
+++ b/core/proto/android/os/looper.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.os;
 
 option java_multiple_files = true;
@@ -23,8 +22,8 @@
 import "frameworks/base/core/proto/android/os/messagequeue.proto";
 
 message LooperProto {
-    string thread_name = 1;
-    int64 thread_id = 2;
-    int32 identity_hash_code = 3;
-    android.os.MessageQueueProto queue = 4;
+    optional string thread_name = 1;
+    optional int64 thread_id = 2;
+    optional int32 identity_hash_code = 3;
+    optional android.os.MessageQueueProto queue = 4;
 }
diff --git a/core/proto/android/os/message.proto b/core/proto/android/os/message.proto
index 604935d..38e27a1 100644
--- a/core/proto/android/os/message.proto
+++ b/core/proto/android/os/message.proto
@@ -14,24 +14,23 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.os;
 
 option java_multiple_files = true;
 
 message MessageProto {
-    int64 when = 1;
+    optional int64 when = 1;
     // Name of callback class.
-    string callback = 2;
+    optional string callback = 2;
     // User-defined message code so that the recipient can identify what this
     // message is about.
-    int32 what = 3;
-    int32 arg1 = 4;
-    int32 arg2 = 5;
+    optional int32 what = 3;
+    optional int32 arg1 = 4;
+    optional int32 arg2 = 5;
     // String representation of an arbitrary object to send to the recipient.
-    string obj = 6;
+    optional string obj = 6;
     // Name of target class.
-    string target = 7;
-    int32 barrier = 8;
+    optional string target = 7;
+    optional int32 barrier = 8;
 }
diff --git a/core/proto/android/os/messagequeue.proto b/core/proto/android/os/messagequeue.proto
index 9bff13e..5d4bff0 100644
--- a/core/proto/android/os/messagequeue.proto
+++ b/core/proto/android/os/messagequeue.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.os;
 
 option java_multiple_files = true;
@@ -24,6 +23,6 @@
 
 message MessageQueueProto {
     repeated android.os.MessageProto messages = 1;
-    bool is_polling_locked = 2;
-    bool is_quitting = 3;
+    optional bool is_polling_locked = 2;
+    optional bool is_quitting = 3;
 }
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index fbb4ee5..f82ea76 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -13,8 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-syntax = "proto3";
+syntax = "proto2";
 
 option java_multiple_files = true;
 option java_outer_classname = "PageTypeInfoProto";
@@ -38,9 +37,9 @@
  */
 message PageTypeInfo {
 
-    int32 page_block_order = 1;
+    optional int32 page_block_order = 1;
 
-    int32 pages_per_block = 2;
+    optional int32 pages_per_block = 2;
 
     repeated MigrateTypeProto migrate_types = 3;
 
@@ -50,11 +49,11 @@
 // Next tag: 5
 message MigrateTypeProto {
 
-    int32 node = 1;
+    optional int32 node = 1;
 
-    string zone = 2;
+    optional string zone = 2;
 
-    string type = 3;
+    optional string type = 3;
 
     // order level starts from 0 for 4KB to page_block_order defined above, e.g. 10 for 4096KB
     repeated int32 free_pages_count = 4;
@@ -63,19 +62,19 @@
 // Next tag: 9
 message BlockProto {
 
-    int32 node = 1;
+    optional int32 node = 1;
 
-    string zone = 2;
+    optional string zone = 2;
 
-    int32 unmovable = 3;
+    optional int32 unmovable = 3;
 
-    int32 reclaimable = 4;
+    optional int32 reclaimable = 4;
 
-    int32 movable = 5;
+    optional int32 movable = 5;
 
-    int32 cma = 6;
+    optional int32 cma = 6;
 
-    int32 reserve = 7;
+    optional int32 reserve = 7;
 
-    int32 isolate = 8;
+    optional int32 isolate = 8;
 }
diff --git a/core/proto/android/os/patternmatcher.proto b/core/proto/android/os/patternmatcher.proto
new file mode 100644
index 0000000..d30315b
--- /dev/null
+++ b/core/proto/android/os/patternmatcher.proto
@@ -0,0 +1,35 @@
+/*
+ * 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";
+option java_multiple_files = true;
+
+package android.os;
+
+message PatternMatcherProto {
+    optional string pattern = 1;
+
+    enum Type {
+        TYPE_LITERAL = 0;
+        TYPE_PREFIX = 1;
+        TYPE_SIMPLE_GLOB = 2;
+        TYPE_ADVANCED_GLOB = 3;
+    }
+    optional Type type = 2;
+
+    // This data is too much for dump
+    // repeated int32 parsed_pattern = 3;
+}
diff --git a/core/proto/android/os/powermanager.proto b/core/proto/android/os/powermanager.proto
new file mode 100644
index 0000000..e9f409d
--- /dev/null
+++ b/core/proto/android/os/powermanager.proto
@@ -0,0 +1,114 @@
+/*
+ * 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 PowerManagerProto {
+    /* User activity events in PowerManager.java. */
+    enum UserActivityEvent {
+        // Unspecified event type.
+        USER_ACTIVITY_EVENT_OTHER = 0;
+        // Button or key pressed or released.
+        USER_ACTIVITY_EVENT_BUTTON = 1;
+        // Touch down, move or up.
+        USER_ACTIVITY_EVENT_TOUCH = 2;
+        // Accessibility taking action on behalf of user.
+        USER_ACTIVITY_EVENT_ACCESSIBILITY = 3;
+    }
+
+    enum WakeLockLevel {
+        // NOTE: Wake lock levels were previously defined as a bit field, except
+        // that only a few combinations were actually supported so the bit field
+        // was removed. This explains why the numbering scheme is so odd. If
+        // adding a new wake lock level, any unused value can be used.
+
+        // Ensures that the CPU is running; the screen and keyboard backlight
+        // will be allowed to go off.
+        PARTIAL_WAKE_LOCK = 1;
+
+        // Ensures that the screen is on (but may be dimmed); the keyboard
+        // backlight will be allowed to go off. If the user presses the power
+        // button, then the SCREEN_DIM_WAKE_LOCK will be implicitly released by
+        // the system, causing both the screen and the CPU to be turned off.
+        SCREEN_DIM_WAKE_LOCK = 6 [deprecated = true];
+
+        // Ensures that the screen is on at full brightness; the keyboard
+        // backlight will be allowed to go off. If the user presses the power
+        // button, then the SCREEN_BRIGHT_WAKE_LOCK will be implicitly released
+        // by the system, causing both the screen and the CPU to be turned off.
+        SCREEN_BRIGHT_WAKE_LOCK = 10 [deprecated = true];
+
+        // Ensures that the screen and keyboard backlight are on at full
+        // brightness. If the user presses the power button, then the
+        // FULL_WAKE_LOCK will be implicitly released by the system, causing
+        // both the screen and the CPU to be turned off.
+        FULL_WAKE_LOCK = 26 [deprecated = true];
+
+        // Turns the screen off when the proximity sensor activates. If the
+        // proximity sensor detects that an object is nearby, the screen turns
+        // off immediately. Shortly after the object moves away, the screen
+        // turns on again.
+        // A proximity wake lock does not prevent the device from falling asleep
+        // unlike FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK and
+        // SCREEN_DIM_WAKE_LOCK. If there is no user activity and no other wake
+        // locks are held, then the device will fall asleep (and lock) as usual.
+        // However, the device will not fall asleep while the screen has been
+        // turned off by the proximity sensor because it effectively counts as
+        // ongoing user activity.
+        PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
+
+        // Put the screen in a low power state and allow the CPU to suspend if
+        // no other wake locks are held. This is used by the dream manager to
+        // implement doze mode. It currently has no effect unless the power
+        // manager is in the dozing state.
+        DOZE_WAKE_LOCK = 64;
+
+        // Keep the device awake enough to allow drawing to occur. This is used
+        // by the window manager to allow applications to draw while the system
+        // is dozing. It currently has no effect unless the power manager is in
+        // the dozing state.
+        DRAW_WAKE_LOCK = 128;
+    }
+}
+
+message PowerManagerInternalProto {
+    // Enum values gotten from PowerManagerInternal.java
+    enum Wakefulness {
+        // The device is asleep. It can only be awoken by a call to wakeUp().
+        // The screen should be off or in the process of being turned off by the
+        // display controller. The device typically passes through the dozing
+        // state first.
+        WAKEFULNESS_ASLEEP = 0;
+        // The device is fully awake. It can be put to sleep by a call to
+        // goToSleep(). When the user activity timeout expires, the device may
+        // start dreaming or go to sleep.
+        WAKEFULNESS_AWAKE = 1;
+        // The device is dreaming. It can be awoken by a call to wakeUp(), which
+        // ends the dream. The device goes to sleep when goToSleep() is called,
+        // when the dream ends, or when unplugged. User activity may brighten
+        // the screen but does not end the dream.
+        WAKEFULNESS_DREAMING = 2;
+        // The device is dozing. It is almost asleep but is allowing a special
+        // low-power "doze" dream to run which keeps the display on but lets the
+        // application processor suspend. It can be awoken by a call to wakeUp()
+        // which ends the dream. The device fully goes to sleep if the dream
+        // cannot be started or ends on its own.
+        WAKEFULNESS_DOZING = 3;
+    }
+}
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index c7dbf4d..ab6a6a3 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 option java_multiple_files = true;
 option java_outer_classname = "ProcrankProto";
 
@@ -27,56 +26,56 @@
     repeated ProcessProto processes = 1;
 
     // Summary
-    SummaryProto summary = 2;
+    optional SummaryProto summary = 2;
 }
 
 // Next Tag: 11
 message ProcessProto {
     // ID of the process
-    int32 pid = 1;
+    optional int32 pid = 1;
 
     // virtual set size, unit KB
-    int64 vss = 2;
+    optional int64 vss = 2;
 
     // resident set size, unit KB
-    int64 rss = 3;
+    optional int64 rss = 3;
 
     // proportional set size, unit KB
-    int64 pss = 4;
+    optional int64 pss = 4;
 
     // unique set size, unit KB
-    int64 uss = 5;
+    optional int64 uss = 5;
 
     // swap size, unit KB
-    int64 swap = 6;
+    optional int64 swap = 6;
 
     // proportional swap size, unit KB
-    int64 pswap = 7;
+    optional int64 pswap = 7;
 
     // unique swap size, unit KB
-    int64 uswap = 8;
+    optional int64 uswap = 8;
 
     // zswap size, unit KB
-    int64 zswap = 9;
+    optional int64 zswap = 9;
 
     // process command
-    string cmdline = 10;
+    optional string cmdline = 10;
 }
 
 // Next Tag: 3
 message SummaryProto {
-    ProcessProto total = 1;
+    optional ProcessProto total = 1;
 
-    ZramProto zram = 2;
+    optional ZramProto zram = 2;
 
-    RamProto ram = 3;
+    optional RamProto ram = 3;
 }
 
 // TODO: sync on how to use these values
 message ZramProto {
-    string raw_text = 1;
+    optional string raw_text = 1;
 }
 
 message RamProto {
-    string raw_text = 1;
+    optional string raw_text = 1;
 }
diff --git a/core/proto/android/os/worksource.proto b/core/proto/android/os/worksource.proto
index c2aa5cb..c60edfc 100644
--- a/core/proto/android/os/worksource.proto
+++ b/core/proto/android/os/worksource.proto
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.os;
 
 option java_multiple_files = true;
 
 message WorkSourceProto {
     message WorkSourceContentProto {
-        int32 uid = 1;
-        string name = 2;
+        optional int32 uid = 1;
+        optional string name = 2;
     }
 
     repeated WorkSourceContentProto work_source_contents = 1;
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index 3db4df0..3411c6a 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.providers.settings;
 
 option java_multiple_files = true;
@@ -26,587 +25,595 @@
     repeated UserSettingsProto user_settings = 1;
 
     // Global settings
-    GlobalSettingsProto global_settings = 2;
+    optional GlobalSettingsProto global_settings = 2;
 }
 
 message UserSettingsProto {
     // Should be 0, 10, 11, 12, etc. where 0 is the owner.
-    int32 user_id = 1;
+    optional int32 user_id = 1;
 
     // The secure settings for this user
-    SecureSettingsProto secure_settings = 2;
+    optional SecureSettingsProto secure_settings = 2;
 
     // The system settings for this user
-    SystemSettingsProto system_settings = 3;
+    optional SystemSettingsProto system_settings = 3;
 }
 
 message GlobalSettingsProto {
     // Historical operations
     repeated SettingsOperationProto historical_op = 1;
 
-    SettingProto add_users_when_locked = 2;
-    SettingProto enable_accessibility_global_gesture_enabled = 3;
-    SettingProto airplane_mode_on = 4;
-    SettingProto theater_mode_on = 5;
-    SettingProto radio_bluetooth = 6;
-    SettingProto radio_wifi = 7;
-    SettingProto radio_wimax = 8;
-    SettingProto radio_cell = 9;
-    SettingProto radio_nfc = 10;
-    SettingProto airplane_mode_radios = 11;
-    SettingProto airplane_mode_toggleable_radios = 12;
-    SettingProto bluetooth_disabled_profiles = 13;
-    SettingProto bluetooth_interoperability_list = 14;
-    SettingProto wifi_sleep_policy = 15;
-    SettingProto auto_time = 16;
-    SettingProto auto_time_zone = 17;
-    SettingProto car_dock_sound = 18;
-    SettingProto car_undock_sound = 19;
-    SettingProto desk_dock_sound = 20;
-    SettingProto desk_undock_sound = 21;
-    SettingProto dock_sounds_enabled = 22;
-    SettingProto dock_sounds_enabled_when_accessibility = 23;
-    SettingProto lock_sound = 24;
-    SettingProto unlock_sound = 25;
-    SettingProto trusted_sound = 26;
-    SettingProto low_battery_sound = 27;
-    SettingProto power_sounds_enabled = 28;
-    SettingProto wireless_charging_started_sound = 29;
-    SettingProto charging_sounds_enabled = 30;
-    SettingProto stay_on_while_plugged_in = 31;
-    SettingProto bugreport_in_power_menu = 32;
-    SettingProto adb_enabled = 33;
-    SettingProto debug_view_attributes = 34;
-    SettingProto assisted_gps_enabled = 35;
-    SettingProto bluetooth_on = 36;
-    SettingProto cdma_cell_broadcast_sms = 37;
-    SettingProto cdma_roaming_mode = 38;
-    SettingProto cdma_subscription_mode = 39;
-    SettingProto data_activity_timeout_mobile = 40;
-    SettingProto data_activity_timeout_wifi = 41;
-    SettingProto data_roaming = 42;
-    SettingProto mdc_initial_max_retry = 43;
-    SettingProto force_allow_on_external = 44;
-    SettingProto development_force_resizable_activities = 45;
-    SettingProto development_enable_freeform_windows_support = 46;
-    SettingProto development_settings_enabled = 47;
-    SettingProto device_provisioned = 48;
-    SettingProto device_provisioning_mobile_data_enabled = 49;
-    SettingProto display_size_forced = 50;
-    SettingProto display_scaling_force = 51;
-    SettingProto download_max_bytes_over_mobile = 52;
-    SettingProto download_recommended_max_bytes_over_mobile = 53;
-    SettingProto hdmi_control_enabled = 54;
-    SettingProto hdmi_system_audio_control_enabled = 55;
-    SettingProto hdmi_control_auto_wakeup_enabled = 56;
-    SettingProto hdmi_control_auto_device_off_enabled = 57;
-    SettingProto mhl_input_switching_enabled = 58;
-    SettingProto mhl_power_charge_enabled = 59;
-    SettingProto mobile_data = 60;
-    SettingProto mobile_data_always_on = 61;
-    SettingProto connectivity_metrics_buffer_size = 62;
-    SettingProto netstats_enabled = 63;
-    SettingProto netstats_poll_interval = 64;
-    SettingProto netstats_time_cache_max_age = 65;
-    SettingProto netstats_global_alert_bytes = 66;
-    SettingProto netstats_sample_enabled = 67;
-    SettingProto netstats_dev_bucket_duration = 68;
-    SettingProto netstats_dev_persist_bytes = 69;
-    SettingProto netstats_dev_rotate_age = 70;
-    SettingProto netstats_dev_delete_age = 71;
-    SettingProto netstats_uid_bucket_duration = 72;
-    SettingProto netstats_uid_persist_bytes = 73;
-    SettingProto netstats_uid_rotate_age = 74;
-    SettingProto netstats_uid_delete_age = 75;
-    SettingProto netstats_uid_tag_bucket_duration = 76;
-    SettingProto netstats_uid_tag_persist_bytes = 77;
-    SettingProto netstats_uid_tag_rotate_age = 78;
-    SettingProto netstats_uid_tag_delete_age = 79;
-    SettingProto network_preference = 80;
-    SettingProto network_scorer_app = 81;
-    SettingProto nitz_update_diff = 82;
-    SettingProto nitz_update_spacing = 83;
-    SettingProto ntp_server = 84;
-    SettingProto ntp_timeout = 85;
-    SettingProto storage_benchmark_interval = 86;
-    SettingProto dns_resolver_sample_validity_seconds = 87;
-    SettingProto dns_resolver_success_threshold_percent = 88;
-    SettingProto dns_resolver_min_samples = 89;
-    SettingProto dns_resolver_max_samples = 90;
-    SettingProto ota_disable_automatic_update = 91;
-    SettingProto package_verifier_enable = 92;
-    SettingProto package_verifier_timeout = 93;
-    SettingProto package_verifier_default_response = 94;
-    SettingProto package_verifier_setting_visible = 95;
-    SettingProto package_verifier_include_adb = 96;
-    SettingProto fstrim_mandatory_interval = 97;
-    SettingProto pdp_watchdog_poll_interval_ms = 98;
-    SettingProto pdp_watchdog_long_poll_interval_ms = 99;
-    SettingProto pdp_watchdog_error_poll_interval_ms = 100;
-    SettingProto pdp_watchdog_trigger_packet_count = 101;
-    SettingProto pdp_watchdog_error_poll_count = 102;
-    SettingProto pdp_watchdog_max_pdp_reset_fail_count = 103;
-    SettingProto setup_prepaid_data_service_url = 105;
-    SettingProto setup_prepaid_detection_target_url = 106;
-    SettingProto setup_prepaid_detection_redir_host = 107;
-    SettingProto sms_outgoing_check_interval_ms = 108;
-    SettingProto sms_outgoing_check_max_count = 109;
-    SettingProto sms_short_code_confirmation = 110;
-    SettingProto sms_short_code_rule = 111;
-    SettingProto tcp_default_init_rwnd = 112;
-    SettingProto tether_supported = 113;
-    SettingProto tether_dun_required = 114;
-    SettingProto tether_dun_apn = 115;
-    SettingProto carrier_app_whitelist = 116;
-    SettingProto usb_mass_storage_enabled = 117;
-    SettingProto use_google_mail = 118;
-    SettingProto webview_data_reduction_proxy_key = 119;
-    SettingProto webview_fallback_logic_enabled = 120;
-    SettingProto webview_provider = 121;
-    SettingProto webview_multiprocess = 122;
-    SettingProto network_switch_notification_daily_limit = 123;
-    SettingProto network_switch_notification_rate_limit_millis = 124;
-    SettingProto network_avoid_bad_wifi = 125;
-    SettingProto wifi_display_on = 126;
-    SettingProto wifi_display_certification_on = 127;
-    SettingProto wifi_display_wps_config = 128;
-    SettingProto wifi_networks_available_notification_on = 129;
-    SettingProto wimax_networks_available_notification_on = 130;
-    SettingProto wifi_networks_available_repeat_delay = 131;
-    SettingProto wifi_country_code = 132;
-    SettingProto wifi_framework_scan_interval_ms = 133;
-    SettingProto wifi_idle_ms = 134;
-    SettingProto wifi_num_open_networks_kept = 135;
-    SettingProto wifi_on = 136;
-    SettingProto wifi_scan_always_available = 137;
-    SettingProto wifi_wakeup_enabled = 138;
-    SettingProto network_recommendations_enabled = 139;
-    SettingProto ble_scan_always_available = 140;
-    SettingProto wifi_saved_state = 141;
-    SettingProto wifi_supplicant_scan_interval_ms = 142;
-    SettingProto wifi_enhanced_auto_join = 143;
-    SettingProto wifi_network_show_rssi = 144;
-    SettingProto wifi_scan_interval_when_p2p_connected_ms = 145;
-    SettingProto wifi_watchdog_on = 146;
-    SettingProto wifi_watchdog_poor_network_test_enabled = 147;
-    SettingProto wifi_suspend_optimizations_enabled = 148;
-    SettingProto wifi_verbose_logging_enabled = 149;
-    SettingProto wifi_max_dhcp_retry_count = 150;
-    SettingProto wifi_mobile_data_transition_wakelock_timeout_ms = 151;
-    SettingProto wifi_device_owner_configs_lockdown = 152;
-    SettingProto wifi_frequency_band = 153;
-    SettingProto wifi_p2p_device_name = 154;
-    SettingProto wifi_reenable_delay_ms = 155;
-    SettingProto wifi_ephemeral_out_of_range_timeout_ms = 156;
-    SettingProto data_stall_alarm_non_aggressive_delay_in_ms = 157;
-    SettingProto data_stall_alarm_aggressive_delay_in_ms = 158;
-    SettingProto provisioning_apn_alarm_delay_in_ms = 159;
-    SettingProto gprs_register_check_period_ms = 160;
-    SettingProto wtf_is_fatal = 161;
-    SettingProto mode_ringer = 162;
-    SettingProto overlay_display_devices = 163;
-    SettingProto battery_discharge_duration_threshold = 164;
-    SettingProto battery_discharge_threshold = 165;
-    SettingProto send_action_app_error = 166;
-    SettingProto dropbox_age_seconds = 167;
-    SettingProto dropbox_max_files = 168;
-    SettingProto dropbox_quota_kb = 169;
-    SettingProto dropbox_quota_percent = 170;
-    SettingProto dropbox_reserve_percent = 171;
-    SettingProto dropbox_tag_prefix = 172;
-    SettingProto error_logcat_prefix = 173;
-    SettingProto sys_free_storage_log_interval = 174;
-    SettingProto disk_free_change_reporting_threshold = 175;
-    SettingProto sys_storage_threshold_percentage = 176;
-    SettingProto sys_storage_threshold_max_bytes = 177;
-    SettingProto sys_storage_full_threshold_bytes = 178;
-    SettingProto sync_max_retry_delay_in_seconds = 179;
-    SettingProto connectivity_change_delay = 180;
-    SettingProto connectivity_sampling_interval_in_seconds = 181;
-    SettingProto pac_change_delay = 182;
-    SettingProto captive_portal_mode = 183;
-    SettingProto captive_portal_server = 184;
-    SettingProto captive_portal_https_url = 185;
-    SettingProto captive_portal_http_url = 186;
-    SettingProto captive_portal_fallback_url = 187;
-    SettingProto captive_portal_use_https = 188;
-    SettingProto captive_portal_user_agent = 189;
-    SettingProto nsd_on = 190;
-    SettingProto set_install_location = 191;
-    SettingProto default_install_location = 192;
-    SettingProto inet_condition_debounce_up_delay = 193;
-    SettingProto inet_condition_debounce_down_delay = 194;
-    SettingProto read_external_storage_enforced_default = 195;
-    SettingProto http_proxy = 196;
-    SettingProto global_http_proxy_host = 197;
-    SettingProto global_http_proxy_port = 198;
-    SettingProto global_http_proxy_exclusion_list = 199;
-    SettingProto global_http_proxy_pac = 200;
-    SettingProto set_global_http_proxy = 201;
-    SettingProto default_dns_server = 202;
-    SettingProto bluetooth_headset_priority_prefix = 203;
-    SettingProto bluetooth_a2dp_sink_priority_prefix = 204;
-    SettingProto bluetooth_a2dp_src_priority_prefix = 205;
-    SettingProto bluetooth_input_device_priority_prefix = 206;
-    SettingProto bluetooth_map_priority_prefix = 207;
-    SettingProto bluetooth_map_client_priority_prefix = 208;
-    SettingProto bluetooth_pbap_client_priority_prefix = 209;
-    SettingProto bluetooth_sap_priority_prefix = 210;
-    SettingProto bluetooth_pan_priority_prefix = 211;
-    SettingProto device_idle_constants = 212;
-    SettingProto device_idle_constants_watch = 213;
-    SettingProto app_idle_constants = 214;
-    SettingProto alarm_manager_constants = 215;
-    SettingProto job_scheduler_constants = 216;
-    SettingProto shortcut_manager_constants = 217;
-    SettingProto window_animation_scale = 218;
-    SettingProto transition_animation_scale = 219;
-    SettingProto animator_duration_scale = 220;
-    SettingProto fancy_ime_animations = 221;
-    SettingProto compatibility_mode = 222;
-    SettingProto emergency_tone = 223;
-    SettingProto call_auto_retry = 224;
-    SettingProto emergency_affordance_needed = 225;
-    SettingProto preferred_network_mode = 226;
-    SettingProto debug_app = 227;
-    SettingProto wait_for_debugger = 228;
-    SettingProto low_power_mode = 229;
-    SettingProto low_power_mode_trigger_level = 230;
-    SettingProto always_finish_activities = 231;
-    SettingProto dock_audio_media_enabled = 232;
-    SettingProto encoded_surround_output = 233;
-    SettingProto audio_safe_volume_state = 234;
-    SettingProto tzinfo_update_content_url = 235;
-    SettingProto tzinfo_update_metadata_url = 236;
-    SettingProto selinux_update_content_url = 237;
-    SettingProto selinux_update_metadata_url = 238;
-    SettingProto sms_short_codes_update_content_url = 239;
-    SettingProto sms_short_codes_update_metadata_url = 240;
-    SettingProto apn_db_update_content_url = 241;
-    SettingProto apn_db_update_metadata_url = 242;
-    SettingProto cert_pin_update_content_url = 243;
-    SettingProto cert_pin_update_metadata_url = 244;
-    SettingProto intent_firewall_update_content_url = 245;
-    SettingProto intent_firewall_update_metadata_url = 246;
-    SettingProto selinux_status = 247;
-    SettingProto development_force_rtl = 248;
-    SettingProto low_battery_sound_timeout = 249;
-    SettingProto wifi_bounce_delay_override_ms = 250;
-    SettingProto policy_control = 251;
-    SettingProto zen_mode = 252;
-    SettingProto zen_mode_ringer_level = 253;
-    SettingProto zen_mode_config_etag = 254;
-    SettingProto heads_up_notifications_enabled = 255;
-    SettingProto device_name = 256;
-    SettingProto network_scoring_provisioned = 257;
-    SettingProto require_password_to_decrypt = 258;
-    SettingProto enhanced_4g_mode_enabled = 259;
-    SettingProto vt_ims_enabled = 260;
-    SettingProto wfc_ims_enabled = 261;
-    SettingProto wfc_ims_mode = 262;
-    SettingProto wfc_ims_roaming_mode = 263;
-    SettingProto wfc_ims_roaming_enabled = 264;
-    SettingProto lte_service_forced = 265;
-    SettingProto ephemeral_cookie_max_size_bytes = 266;
-    SettingProto enable_ephemeral_feature = 267;
-    SettingProto installed_instant_app_min_cache_period = 268;
-    SettingProto allow_user_switching_when_system_user_locked = 269;
-    SettingProto boot_count = 270;
-    SettingProto safe_boot_disallowed = 271;
-    SettingProto device_demo_mode = 272;
-    SettingProto database_downgrade_reason = 274;
-    SettingProto contacts_database_wal_enabled = 275;
-    SettingProto multi_sim_voice_call_subscription = 276;
-    SettingProto multi_sim_voice_prompt = 277;
-    SettingProto multi_sim_data_call_subscription = 278;
-    SettingProto multi_sim_sms_subscription = 279;
-    SettingProto multi_sim_sms_prompt = 280;
-    SettingProto new_contact_aggregator = 281;
-    SettingProto contact_metadata_sync_enabled = 282;
-    SettingProto enable_cellular_on_boot = 283;
-    SettingProto max_notification_enqueue_rate = 284;
-    SettingProto cell_on = 285;
-    SettingProto network_recommendations_package = 286;
-    SettingProto bluetooth_a2dp_supports_optional_codecs_prefix = 287;
-    SettingProto bluetooth_a2dp_optional_codecs_enabled_prefix = 288;
-    SettingProto installed_instant_app_max_cache_period = 289;
-    SettingProto uninstalled_instant_app_min_cache_period = 290;
-    SettingProto uninstalled_instant_app_max_cache_period = 291;
-    SettingProto unused_static_shared_lib_min_cache_period = 292;
+    optional SettingProto add_users_when_locked = 2;
+    optional SettingProto enable_accessibility_global_gesture_enabled = 3;
+    optional SettingProto airplane_mode_on = 4;
+    optional SettingProto theater_mode_on = 5;
+    optional SettingProto radio_bluetooth = 6;
+    optional SettingProto radio_wifi = 7;
+    optional SettingProto radio_wimax = 8;
+    optional SettingProto radio_cell = 9;
+    optional SettingProto radio_nfc = 10;
+    optional SettingProto airplane_mode_radios = 11;
+    optional SettingProto airplane_mode_toggleable_radios = 12;
+    optional SettingProto bluetooth_disabled_profiles = 13;
+    optional SettingProto bluetooth_interoperability_list = 14;
+    optional SettingProto wifi_sleep_policy = 15;
+    optional SettingProto auto_time = 16;
+    optional SettingProto auto_time_zone = 17;
+    optional SettingProto car_dock_sound = 18;
+    optional SettingProto car_undock_sound = 19;
+    optional SettingProto desk_dock_sound = 20;
+    optional SettingProto desk_undock_sound = 21;
+    optional SettingProto dock_sounds_enabled = 22;
+    optional SettingProto dock_sounds_enabled_when_accessibility = 23;
+    optional SettingProto lock_sound = 24;
+    optional SettingProto unlock_sound = 25;
+    optional SettingProto trusted_sound = 26;
+    optional SettingProto low_battery_sound = 27;
+    optional SettingProto power_sounds_enabled = 28;
+    optional SettingProto wireless_charging_started_sound = 29;
+    optional SettingProto charging_sounds_enabled = 30;
+    optional SettingProto stay_on_while_plugged_in = 31;
+    optional SettingProto bugreport_in_power_menu = 32;
+    optional SettingProto adb_enabled = 33;
+    optional SettingProto debug_view_attributes = 34;
+    optional SettingProto assisted_gps_enabled = 35;
+    optional SettingProto bluetooth_on = 36;
+    optional SettingProto cdma_cell_broadcast_sms = 37;
+    optional SettingProto cdma_roaming_mode = 38;
+    optional SettingProto cdma_subscription_mode = 39;
+    optional SettingProto data_activity_timeout_mobile = 40;
+    optional SettingProto data_activity_timeout_wifi = 41;
+    optional SettingProto data_roaming = 42;
+    optional SettingProto mdc_initial_max_retry = 43;
+    optional SettingProto force_allow_on_external = 44;
+    optional SettingProto development_force_resizable_activities = 45;
+    optional SettingProto development_enable_freeform_windows_support = 46;
+    optional SettingProto development_settings_enabled = 47;
+    optional SettingProto device_provisioned = 48;
+    optional SettingProto device_provisioning_mobile_data_enabled = 49;
+    optional SettingProto display_size_forced = 50;
+    optional SettingProto display_scaling_force = 51;
+    optional SettingProto download_max_bytes_over_mobile = 52;
+    optional SettingProto download_recommended_max_bytes_over_mobile = 53;
+    optional SettingProto hdmi_control_enabled = 54;
+    optional SettingProto hdmi_system_audio_control_enabled = 55;
+    optional SettingProto hdmi_control_auto_wakeup_enabled = 56;
+    optional SettingProto hdmi_control_auto_device_off_enabled = 57;
+    optional SettingProto mhl_input_switching_enabled = 58;
+    optional SettingProto mhl_power_charge_enabled = 59;
+    optional SettingProto mobile_data = 60;
+    optional SettingProto mobile_data_always_on = 61;
+    optional SettingProto connectivity_metrics_buffer_size = 62;
+    optional SettingProto netstats_enabled = 63;
+    optional SettingProto netstats_poll_interval = 64;
+    optional SettingProto netstats_time_cache_max_age = 65;
+    optional SettingProto netstats_global_alert_bytes = 66;
+    optional SettingProto netstats_sample_enabled = 67;
+    optional SettingProto netstats_dev_bucket_duration = 68;
+    optional SettingProto netstats_dev_persist_bytes = 69;
+    optional SettingProto netstats_dev_rotate_age = 70;
+    optional SettingProto netstats_dev_delete_age = 71;
+    optional SettingProto netstats_uid_bucket_duration = 72;
+    optional SettingProto netstats_uid_persist_bytes = 73;
+    optional SettingProto netstats_uid_rotate_age = 74;
+    optional SettingProto netstats_uid_delete_age = 75;
+    optional SettingProto netstats_uid_tag_bucket_duration = 76;
+    optional SettingProto netstats_uid_tag_persist_bytes = 77;
+    optional SettingProto netstats_uid_tag_rotate_age = 78;
+    optional SettingProto netstats_uid_tag_delete_age = 79;
+    optional SettingProto network_preference = 80;
+    optional SettingProto network_scorer_app = 81;
+    optional SettingProto nitz_update_diff = 82;
+    optional SettingProto nitz_update_spacing = 83;
+    optional SettingProto ntp_server = 84;
+    optional SettingProto ntp_timeout = 85;
+    optional SettingProto storage_benchmark_interval = 86;
+    optional SettingProto dns_resolver_sample_validity_seconds = 87;
+    optional SettingProto dns_resolver_success_threshold_percent = 88;
+    optional SettingProto dns_resolver_min_samples = 89;
+    optional SettingProto dns_resolver_max_samples = 90;
+    optional SettingProto ota_disable_automatic_update = 91;
+    optional SettingProto package_verifier_enable = 92;
+    optional SettingProto package_verifier_timeout = 93;
+    optional SettingProto package_verifier_default_response = 94;
+    optional SettingProto package_verifier_setting_visible = 95;
+    optional SettingProto package_verifier_include_adb = 96;
+    optional SettingProto fstrim_mandatory_interval = 97;
+    optional SettingProto pdp_watchdog_poll_interval_ms = 98;
+    optional SettingProto pdp_watchdog_long_poll_interval_ms = 99;
+    optional SettingProto pdp_watchdog_error_poll_interval_ms = 100;
+    optional SettingProto pdp_watchdog_trigger_packet_count = 101;
+    optional SettingProto pdp_watchdog_error_poll_count = 102;
+    optional SettingProto pdp_watchdog_max_pdp_reset_fail_count = 103;
+    optional SettingProto setup_prepaid_data_service_url = 105;
+    optional SettingProto setup_prepaid_detection_target_url = 106;
+    optional SettingProto setup_prepaid_detection_redir_host = 107;
+    optional SettingProto sms_outgoing_check_interval_ms = 108;
+    optional SettingProto sms_outgoing_check_max_count = 109;
+    optional SettingProto sms_short_code_confirmation = 110;
+    optional SettingProto sms_short_code_rule = 111;
+    optional SettingProto tcp_default_init_rwnd = 112;
+    optional SettingProto tether_supported = 113;
+    optional SettingProto tether_dun_required = 114;
+    optional SettingProto tether_dun_apn = 115;
+    optional SettingProto carrier_app_whitelist = 116;
+    optional SettingProto usb_mass_storage_enabled = 117;
+    optional SettingProto use_google_mail = 118;
+    optional SettingProto webview_data_reduction_proxy_key = 119;
+    optional SettingProto webview_fallback_logic_enabled = 120;
+    optional SettingProto webview_provider = 121;
+    optional SettingProto webview_multiprocess = 122;
+    optional SettingProto network_switch_notification_daily_limit = 123;
+    optional SettingProto network_switch_notification_rate_limit_millis = 124;
+    optional SettingProto network_avoid_bad_wifi = 125;
+    optional SettingProto wifi_display_on = 126;
+    optional SettingProto wifi_display_certification_on = 127;
+    optional SettingProto wifi_display_wps_config = 128;
+    optional SettingProto wifi_networks_available_notification_on = 129;
+    optional SettingProto wimax_networks_available_notification_on = 130;
+    optional SettingProto wifi_networks_available_repeat_delay = 131;
+    optional SettingProto wifi_country_code = 132;
+    optional SettingProto wifi_framework_scan_interval_ms = 133;
+    optional SettingProto wifi_idle_ms = 134;
+    optional SettingProto wifi_num_open_networks_kept = 135;
+    optional SettingProto wifi_on = 136;
+    optional SettingProto wifi_scan_always_available = 137;
+    optional SettingProto wifi_wakeup_enabled = 138;
+    optional SettingProto network_recommendations_enabled = 139;
+    optional SettingProto ble_scan_always_available = 140;
+    optional SettingProto wifi_saved_state = 141;
+    optional SettingProto wifi_supplicant_scan_interval_ms = 142;
+    optional SettingProto wifi_enhanced_auto_join = 143;
+    optional SettingProto wifi_network_show_rssi = 144;
+    optional SettingProto wifi_scan_interval_when_p2p_connected_ms = 145;
+    optional SettingProto wifi_watchdog_on = 146;
+    optional SettingProto wifi_watchdog_poor_network_test_enabled = 147;
+    optional SettingProto wifi_suspend_optimizations_enabled = 148;
+    optional SettingProto wifi_verbose_logging_enabled = 149;
+    optional SettingProto wifi_max_dhcp_retry_count = 150;
+    optional SettingProto wifi_mobile_data_transition_wakelock_timeout_ms = 151;
+    optional SettingProto wifi_device_owner_configs_lockdown = 152;
+    optional SettingProto wifi_frequency_band = 153;
+    optional SettingProto wifi_p2p_device_name = 154;
+    optional SettingProto wifi_reenable_delay_ms = 155;
+    optional SettingProto wifi_ephemeral_out_of_range_timeout_ms = 156;
+    optional SettingProto data_stall_alarm_non_aggressive_delay_in_ms = 157;
+    optional SettingProto data_stall_alarm_aggressive_delay_in_ms = 158;
+    optional SettingProto provisioning_apn_alarm_delay_in_ms = 159;
+    optional SettingProto gprs_register_check_period_ms = 160;
+    optional SettingProto wtf_is_fatal = 161;
+    optional SettingProto mode_ringer = 162;
+    optional SettingProto overlay_display_devices = 163;
+    optional SettingProto battery_discharge_duration_threshold = 164;
+    optional SettingProto battery_discharge_threshold = 165;
+    optional SettingProto send_action_app_error = 166;
+    optional SettingProto dropbox_age_seconds = 167;
+    optional SettingProto dropbox_max_files = 168;
+    optional SettingProto dropbox_quota_kb = 169;
+    optional SettingProto dropbox_quota_percent = 170;
+    optional SettingProto dropbox_reserve_percent = 171;
+    optional SettingProto dropbox_tag_prefix = 172;
+    optional SettingProto error_logcat_prefix = 173;
+    optional SettingProto sys_free_storage_log_interval = 174;
+    optional SettingProto disk_free_change_reporting_threshold = 175;
+    optional SettingProto sys_storage_threshold_percentage = 176;
+    optional SettingProto sys_storage_threshold_max_bytes = 177;
+    optional SettingProto sys_storage_full_threshold_bytes = 178;
+    optional SettingProto sync_max_retry_delay_in_seconds = 179;
+    optional SettingProto connectivity_change_delay = 180;
+    optional SettingProto connectivity_sampling_interval_in_seconds = 181;
+    optional SettingProto pac_change_delay = 182;
+    optional SettingProto captive_portal_mode = 183;
+    optional SettingProto captive_portal_server = 184;
+    optional SettingProto captive_portal_https_url = 185;
+    optional SettingProto captive_portal_http_url = 186;
+    optional SettingProto captive_portal_fallback_url = 187;
+    optional SettingProto captive_portal_use_https = 188;
+    optional SettingProto captive_portal_user_agent = 189;
+    optional SettingProto nsd_on = 190;
+    optional SettingProto set_install_location = 191;
+    optional SettingProto default_install_location = 192;
+    optional SettingProto inet_condition_debounce_up_delay = 193;
+    optional SettingProto inet_condition_debounce_down_delay = 194;
+    optional SettingProto read_external_storage_enforced_default = 195;
+    optional SettingProto http_proxy = 196;
+    optional SettingProto global_http_proxy_host = 197;
+    optional SettingProto global_http_proxy_port = 198;
+    optional SettingProto global_http_proxy_exclusion_list = 199;
+    optional SettingProto global_http_proxy_pac = 200;
+    optional SettingProto set_global_http_proxy = 201;
+    optional SettingProto default_dns_server = 202;
+    optional SettingProto bluetooth_headset_priority_prefix = 203;
+    optional SettingProto bluetooth_a2dp_sink_priority_prefix = 204;
+    optional SettingProto bluetooth_a2dp_src_priority_prefix = 205;
+    optional SettingProto bluetooth_input_device_priority_prefix = 206;
+    optional SettingProto bluetooth_map_priority_prefix = 207;
+    optional SettingProto bluetooth_map_client_priority_prefix = 208;
+    optional SettingProto bluetooth_pbap_client_priority_prefix = 209;
+    optional SettingProto bluetooth_sap_priority_prefix = 210;
+    optional SettingProto bluetooth_pan_priority_prefix = 211;
+    optional SettingProto device_idle_constants = 212;
+    optional SettingProto device_idle_constants_watch = 213;
+    optional SettingProto app_idle_constants = 214;
+    optional SettingProto alarm_manager_constants = 215;
+    optional SettingProto job_scheduler_constants = 216;
+    optional SettingProto shortcut_manager_constants = 217;
+    optional SettingProto window_animation_scale = 218;
+    optional SettingProto transition_animation_scale = 219;
+    optional SettingProto animator_duration_scale = 220;
+    optional SettingProto fancy_ime_animations = 221;
+    optional SettingProto compatibility_mode = 222;
+    optional SettingProto emergency_tone = 223;
+    optional SettingProto call_auto_retry = 224;
+    optional SettingProto emergency_affordance_needed = 225;
+    optional SettingProto preferred_network_mode = 226;
+    optional SettingProto debug_app = 227;
+    optional SettingProto wait_for_debugger = 228;
+    optional SettingProto low_power_mode = 229;
+    optional SettingProto low_power_mode_trigger_level = 230;
+    optional SettingProto always_finish_activities = 231;
+    optional SettingProto dock_audio_media_enabled = 232;
+    optional SettingProto encoded_surround_output = 233;
+    optional SettingProto audio_safe_volume_state = 234;
+    optional SettingProto tzinfo_update_content_url = 235;
+    optional SettingProto tzinfo_update_metadata_url = 236;
+    optional SettingProto selinux_update_content_url = 237;
+    optional SettingProto selinux_update_metadata_url = 238;
+    optional SettingProto sms_short_codes_update_content_url = 239;
+    optional SettingProto sms_short_codes_update_metadata_url = 240;
+    optional SettingProto apn_db_update_content_url = 241;
+    optional SettingProto apn_db_update_metadata_url = 242;
+    optional SettingProto cert_pin_update_content_url = 243;
+    optional SettingProto cert_pin_update_metadata_url = 244;
+    optional SettingProto intent_firewall_update_content_url = 245;
+    optional SettingProto intent_firewall_update_metadata_url = 246;
+    optional SettingProto selinux_status = 247;
+    optional SettingProto development_force_rtl = 248;
+    optional SettingProto low_battery_sound_timeout = 249;
+    optional SettingProto wifi_bounce_delay_override_ms = 250;
+    optional SettingProto policy_control = 251;
+    optional SettingProto zen_mode = 252;
+    optional SettingProto zen_mode_ringer_level = 253;
+    optional SettingProto zen_mode_config_etag = 254;
+    optional SettingProto heads_up_notifications_enabled = 255;
+    optional SettingProto device_name = 256;
+    optional SettingProto network_scoring_provisioned = 257;
+    optional SettingProto require_password_to_decrypt = 258;
+    optional SettingProto enhanced_4g_mode_enabled = 259;
+    optional SettingProto vt_ims_enabled = 260;
+    optional SettingProto wfc_ims_enabled = 261;
+    optional SettingProto wfc_ims_mode = 262;
+    optional SettingProto wfc_ims_roaming_mode = 263;
+    optional SettingProto wfc_ims_roaming_enabled = 264;
+    optional SettingProto lte_service_forced = 265;
+    optional SettingProto ephemeral_cookie_max_size_bytes = 266;
+    optional SettingProto enable_ephemeral_feature = 267;
+    optional SettingProto installed_instant_app_min_cache_period = 268;
+    optional SettingProto allow_user_switching_when_system_user_locked = 269;
+    optional SettingProto boot_count = 270;
+    optional SettingProto safe_boot_disallowed = 271;
+    optional SettingProto device_demo_mode = 272;
+    optional SettingProto database_downgrade_reason = 274;
+    optional SettingProto contacts_database_wal_enabled = 275;
+    optional SettingProto multi_sim_voice_call_subscription = 276;
+    optional SettingProto multi_sim_voice_prompt = 277;
+    optional SettingProto multi_sim_data_call_subscription = 278;
+    optional SettingProto multi_sim_sms_subscription = 279;
+    optional SettingProto multi_sim_sms_prompt = 280;
+    optional SettingProto new_contact_aggregator = 281;
+    optional SettingProto contact_metadata_sync_enabled = 282;
+    optional SettingProto enable_cellular_on_boot = 283;
+    optional SettingProto max_notification_enqueue_rate = 284;
+    optional SettingProto cell_on = 285;
+    optional SettingProto network_recommendations_package = 286;
+    optional SettingProto bluetooth_a2dp_supports_optional_codecs_prefix = 287;
+    optional SettingProto bluetooth_a2dp_optional_codecs_enabled_prefix = 288;
+    optional SettingProto installed_instant_app_max_cache_period = 289;
+    optional SettingProto uninstalled_instant_app_min_cache_period = 290;
+    optional SettingProto uninstalled_instant_app_max_cache_period = 291;
+    optional SettingProto unused_static_shared_lib_min_cache_period = 292;
 }
 
 message SecureSettingsProto {
     // Historical operations
     repeated SettingsOperationProto historical_op = 1;
 
-    SettingProto android_id = 2;
-    SettingProto default_input_method = 3;
-    SettingProto selected_input_method_subtype = 4;
-    SettingProto input_methods_subtype_history = 5;
-    SettingProto input_method_selector_visibility = 6;
-    SettingProto voice_interaction_service = 7;
-    SettingProto autofill_service = 8;
-    SettingProto bluetooth_hci_log = 9;
-    SettingProto user_setup_complete = 10;
-    SettingProto completed_category_prefix = 11;
-    SettingProto enabled_input_methods = 12;
-    SettingProto disabled_system_input_methods = 13;
-    SettingProto show_ime_with_hard_keyboard = 14;
-    SettingProto always_on_vpn_app = 15;
-    SettingProto always_on_vpn_lockdown = 16;
-    SettingProto install_non_market_apps = 17;
-    SettingProto location_mode = 18;
-    SettingProto location_previous_mode = 19;
-    SettingProto lock_to_app_exit_locked = 20;
-    SettingProto lock_screen_lock_after_timeout = 21;
-    SettingProto lock_screen_allow_remote_input = 22;
-    SettingProto show_note_about_notification_hiding = 23;
-    SettingProto trust_agents_initialized = 24;
-    SettingProto parental_control_enabled = 25;
-    SettingProto parental_control_last_update = 26;
-    SettingProto parental_control_redirect_url = 27;
-    SettingProto settings_classname = 28;
-    SettingProto accessibility_enabled = 29;
-    SettingProto touch_exploration_enabled = 30;
-    SettingProto enabled_accessibility_services = 31;
-    SettingProto touch_exploration_granted_accessibility_services = 32;
-    SettingProto accessibility_speak_password = 33;
-    SettingProto accessibility_high_text_contrast_enabled = 34;
-    SettingProto accessibility_script_injection = 35;
-    SettingProto accessibility_screen_reader_url = 36;
-    SettingProto accessibility_web_content_key_bindings = 37;
-    SettingProto accessibility_display_magnification_enabled = 38;
-    SettingProto accessibility_display_magnification_scale = 39;
-    SettingProto accessibility_soft_keyboard_mode = 40;
-    SettingProto accessibility_captioning_enabled = 41;
-    SettingProto accessibility_captioning_locale = 42;
-    SettingProto accessibility_captioning_preset = 43;
-    SettingProto accessibility_captioning_background_color = 44;
-    SettingProto accessibility_captioning_foreground_color = 45;
-    SettingProto accessibility_captioning_edge_type = 46;
-    SettingProto accessibility_captioning_edge_color = 47;
-    SettingProto accessibility_captioning_window_color = 48;
-    SettingProto accessibility_captioning_typeface = 49;
-    SettingProto accessibility_captioning_font_scale = 50;
-    SettingProto accessibility_display_inversion_enabled = 51;
-    SettingProto accessibility_display_daltonizer_enabled = 52;
-    SettingProto accessibility_display_daltonizer = 53;
-    SettingProto accessibility_autoclick_enabled = 54;
-    SettingProto accessibility_autoclick_delay = 55;
-    SettingProto accessibility_large_pointer_icon = 56;
-    SettingProto long_press_timeout = 57;
-    SettingProto multi_press_timeout = 58;
-    SettingProto enabled_print_services = 59;
-    SettingProto disabled_print_services = 60;
-    SettingProto display_density_forced = 61;
-    SettingProto tts_default_rate = 62;
-    SettingProto tts_default_pitch = 63;
-    SettingProto tts_default_synth = 64;
-    SettingProto tts_default_locale = 65;
-    SettingProto tts_enabled_plugins = 66;
-    SettingProto connectivity_release_pending_intent_delay_ms = 67;
-    SettingProto allowed_geolocation_origins = 68;
-    SettingProto preferred_tty_mode = 69;
-    SettingProto enhanced_voice_privacy_enabled = 70;
-    SettingProto tty_mode_enabled = 71;
-    SettingProto backup_enabled = 72;
-    SettingProto backup_auto_restore = 73;
-    SettingProto backup_provisioned = 74;
-    SettingProto backup_transport = 75;
-    SettingProto last_setup_shown = 76;
-    SettingProto search_global_search_activity = 77;
-    SettingProto search_num_promoted_sources = 78;
-    SettingProto search_max_results_to_display = 79;
-    SettingProto search_max_results_per_source = 80;
-    SettingProto search_web_results_override_limit = 81;
-    SettingProto search_promoted_source_deadline_millis = 82;
-    SettingProto search_source_timeout_millis = 83;
-    SettingProto search_prefill_millis = 84;
-    SettingProto search_max_stat_age_millis = 85;
-    SettingProto search_max_source_event_age_millis = 86;
-    SettingProto search_min_impressions_for_source_ranking = 87;
-    SettingProto search_min_clicks_for_source_ranking = 88;
-    SettingProto search_max_shortcuts_returned = 89;
-    SettingProto search_query_thread_core_pool_size = 90;
-    SettingProto search_query_thread_max_pool_size = 91;
-    SettingProto search_shortcut_refresh_core_pool_size = 92;
-    SettingProto search_shortcut_refresh_max_pool_size = 93;
-    SettingProto search_thread_keepalive_seconds = 94;
-    SettingProto search_per_source_concurrent_query_limit = 95;
-    SettingProto mount_play_notification_snd = 96;
-    SettingProto mount_ums_autostart = 97;
-    SettingProto mount_ums_prompt = 98;
-    SettingProto mount_ums_notify_enabled = 99;
-    SettingProto anr_show_background = 100;
-    SettingProto voice_recognition_service = 101;
-    SettingProto package_verifier_user_consent = 102;
-    SettingProto selected_spell_checker = 103;
-    SettingProto selected_spell_checker_subtype = 104;
-    SettingProto spell_checker_enabled = 105;
-    SettingProto incall_power_button_behavior = 106;
-    SettingProto incall_back_button_behavior = 107;
-    SettingProto wake_gesture_enabled = 108;
-    SettingProto doze_enabled = 109;
-    SettingProto doze_always_on = 110;
-    SettingProto doze_pulse_on_pick_up = 111;
-    SettingProto doze_pulse_on_double_tap = 112;
-    SettingProto ui_night_mode = 113;
-    SettingProto screensaver_enabled = 114;
-    SettingProto screensaver_components = 115;
-    SettingProto screensaver_activate_on_dock = 116;
-    SettingProto screensaver_activate_on_sleep = 117;
-    SettingProto screensaver_default_component = 118;
-    SettingProto nfc_payment_default_component = 119;
-    SettingProto nfc_payment_foreground = 120;
-    SettingProto sms_default_application = 121;
-    SettingProto dialer_default_application = 122;
-    SettingProto emergency_assistance_application = 123;
-    SettingProto assist_structure_enabled = 124;
-    SettingProto assist_screenshot_enabled = 125;
-    SettingProto assist_disclosure_enabled = 126;
-    SettingProto enabled_notification_assistant = 127;
-    SettingProto enabled_notification_listeners = 128;
-    SettingProto enabled_notification_policy_access_packages = 129;
-    SettingProto sync_parent_sounds = 130;
-    SettingProto immersive_mode_confirmations = 131;
-    SettingProto print_service_search_uri = 132;
-    SettingProto payment_service_search_uri = 133;
-    SettingProto skip_first_use_hints = 134;
-    SettingProto unsafe_volume_music_active_ms = 135;
-    SettingProto lock_screen_show_notifications = 136;
-    SettingProto tv_input_hidden_inputs = 137;
-    SettingProto tv_input_custom_labels = 138;
-    SettingProto usb_audio_automatic_routing_disabled = 139;
-    SettingProto sleep_timeout = 140;
-    SettingProto double_tap_to_wake = 141;
-    SettingProto assistant = 142;
-    SettingProto camera_gesture_disabled = 143;
-    SettingProto camera_double_tap_power_gesture_disabled = 144;
-    SettingProto camera_double_twist_to_flip_enabled = 145;
-    SettingProto night_display_activated = 146;
-    SettingProto night_display_auto_mode = 147;
-    SettingProto night_display_custom_start_time = 148;
-    SettingProto night_display_custom_end_time = 149;
-    SettingProto brightness_use_twilight = 150;
-    SettingProto enabled_vr_listeners = 151;
-    SettingProto vr_display_mode = 152;
-    SettingProto carrier_apps_handled = 153;
-    SettingProto managed_profile_contact_remote_search = 154;
-    SettingProto automatic_storage_manager_enabled = 155;
-    SettingProto automatic_storage_manager_days_to_retain = 156;
-    SettingProto automatic_storage_manager_bytes_cleared = 157;
-    SettingProto automatic_storage_manager_last_run = 158;
-    SettingProto system_navigation_keys_enabled = 159;
-    SettingProto downloads_backup_enabled = 160;
-    SettingProto downloads_backup_allow_metered = 161;
-    SettingProto downloads_backup_charging_only = 162;
-    SettingProto automatic_storage_manager_downloads_days_to_retain = 163;
-    SettingProto qs_tiles = 164;
-    SettingProto demo_user_setup_complete = 165;
-    SettingProto instant_apps_enabled = 166;
-    SettingProto device_paired = 167;
-    SettingProto notification_badging = 168;
-    SettingProto backup_manager_constants = 169;
+    optional SettingProto android_id = 2;
+    optional SettingProto default_input_method = 3;
+    optional SettingProto selected_input_method_subtype = 4;
+    optional SettingProto input_methods_subtype_history = 5;
+    optional SettingProto input_method_selector_visibility = 6;
+    optional SettingProto voice_interaction_service = 7;
+    optional SettingProto autofill_service = 8;
+    optional SettingProto bluetooth_hci_log = 9;
+    optional SettingProto user_setup_complete = 10;
+    optional SettingProto completed_category_prefix = 11;
+    optional SettingProto enabled_input_methods = 12;
+    optional SettingProto disabled_system_input_methods = 13;
+    optional SettingProto show_ime_with_hard_keyboard = 14;
+    optional SettingProto always_on_vpn_app = 15;
+    optional SettingProto always_on_vpn_lockdown = 16;
+    optional SettingProto install_non_market_apps = 17;
+    optional SettingProto location_mode = 18;
+    optional SettingProto location_previous_mode = 19;
+    optional SettingProto lock_to_app_exit_locked = 20;
+    optional SettingProto lock_screen_lock_after_timeout = 21;
+    optional SettingProto lock_screen_allow_remote_input = 22;
+    optional SettingProto show_note_about_notification_hiding = 23;
+    optional SettingProto trust_agents_initialized = 24;
+    optional SettingProto parental_control_enabled = 25;
+    optional SettingProto parental_control_last_update = 26;
+    optional SettingProto parental_control_redirect_url = 27;
+    optional SettingProto settings_classname = 28;
+    optional SettingProto accessibility_enabled = 29;
+    optional SettingProto touch_exploration_enabled = 30;
+    optional SettingProto enabled_accessibility_services = 31;
+    optional SettingProto touch_exploration_granted_accessibility_services = 32;
+    optional SettingProto accessibility_speak_password = 33;
+    optional SettingProto accessibility_high_text_contrast_enabled = 34;
+    optional SettingProto accessibility_script_injection = 35;
+    optional SettingProto accessibility_screen_reader_url = 36;
+    optional SettingProto accessibility_web_content_key_bindings = 37;
+    optional SettingProto accessibility_display_magnification_enabled = 38;
+    optional SettingProto accessibility_display_magnification_scale = 39;
+    optional SettingProto accessibility_soft_keyboard_mode = 40;
+    optional SettingProto accessibility_captioning_enabled = 41;
+    optional SettingProto accessibility_captioning_locale = 42;
+    optional SettingProto accessibility_captioning_preset = 43;
+    optional SettingProto accessibility_captioning_background_color = 44;
+    optional SettingProto accessibility_captioning_foreground_color = 45;
+    optional SettingProto accessibility_captioning_edge_type = 46;
+    optional SettingProto accessibility_captioning_edge_color = 47;
+    optional SettingProto accessibility_captioning_window_color = 48;
+    optional SettingProto accessibility_captioning_typeface = 49;
+    optional SettingProto accessibility_captioning_font_scale = 50;
+    optional SettingProto accessibility_display_inversion_enabled = 51;
+    optional SettingProto accessibility_display_daltonizer_enabled = 52;
+    optional SettingProto accessibility_display_daltonizer = 53;
+    optional SettingProto accessibility_autoclick_enabled = 54;
+    optional SettingProto accessibility_autoclick_delay = 55;
+    optional SettingProto accessibility_large_pointer_icon = 56;
+    optional SettingProto long_press_timeout = 57;
+    optional SettingProto multi_press_timeout = 58;
+    optional SettingProto enabled_print_services = 59;
+    optional SettingProto disabled_print_services = 60;
+    optional SettingProto display_density_forced = 61;
+    optional SettingProto tts_default_rate = 62;
+    optional SettingProto tts_default_pitch = 63;
+    optional SettingProto tts_default_synth = 64;
+    optional SettingProto tts_default_locale = 65;
+    optional SettingProto tts_enabled_plugins = 66;
+    optional SettingProto connectivity_release_pending_intent_delay_ms = 67;
+    optional SettingProto allowed_geolocation_origins = 68;
+    optional SettingProto preferred_tty_mode = 69;
+    optional SettingProto enhanced_voice_privacy_enabled = 70;
+    optional SettingProto tty_mode_enabled = 71;
+    optional SettingProto backup_enabled = 72;
+    optional SettingProto backup_auto_restore = 73;
+    optional SettingProto backup_provisioned = 74;
+    optional SettingProto backup_transport = 75;
+    optional SettingProto last_setup_shown = 76;
+    optional SettingProto search_global_search_activity = 77;
+    optional SettingProto search_num_promoted_sources = 78;
+    optional SettingProto search_max_results_to_display = 79;
+    optional SettingProto search_max_results_per_source = 80;
+    optional SettingProto search_web_results_override_limit = 81;
+    optional SettingProto search_promoted_source_deadline_millis = 82;
+    optional SettingProto search_source_timeout_millis = 83;
+    optional SettingProto search_prefill_millis = 84;
+    optional SettingProto search_max_stat_age_millis = 85;
+    optional SettingProto search_max_source_event_age_millis = 86;
+    optional SettingProto search_min_impressions_for_source_ranking = 87;
+    optional SettingProto search_min_clicks_for_source_ranking = 88;
+    optional SettingProto search_max_shortcuts_returned = 89;
+    optional SettingProto search_query_thread_core_pool_size = 90;
+    optional SettingProto search_query_thread_max_pool_size = 91;
+    optional SettingProto search_shortcut_refresh_core_pool_size = 92;
+    optional SettingProto search_shortcut_refresh_max_pool_size = 93;
+    optional SettingProto search_thread_keepalive_seconds = 94;
+    optional SettingProto search_per_source_concurrent_query_limit = 95;
+    optional SettingProto mount_play_notification_snd = 96;
+    optional SettingProto mount_ums_autostart = 97;
+    optional SettingProto mount_ums_prompt = 98;
+    optional SettingProto mount_ums_notify_enabled = 99;
+    optional SettingProto anr_show_background = 100;
+    optional SettingProto voice_recognition_service = 101;
+    optional SettingProto package_verifier_user_consent = 102;
+    optional SettingProto selected_spell_checker = 103;
+    optional SettingProto selected_spell_checker_subtype = 104;
+    optional SettingProto spell_checker_enabled = 105;
+    optional SettingProto incall_power_button_behavior = 106;
+    optional SettingProto incall_back_button_behavior = 107;
+    optional SettingProto wake_gesture_enabled = 108;
+    optional SettingProto doze_enabled = 109;
+    optional SettingProto doze_always_on = 110;
+    optional SettingProto doze_pulse_on_pick_up = 111;
+    optional SettingProto doze_pulse_on_double_tap = 112;
+    optional SettingProto ui_night_mode = 113;
+    optional SettingProto screensaver_enabled = 114;
+    optional SettingProto screensaver_components = 115;
+    optional SettingProto screensaver_activate_on_dock = 116;
+    optional SettingProto screensaver_activate_on_sleep = 117;
+    optional SettingProto screensaver_default_component = 118;
+    optional SettingProto nfc_payment_default_component = 119;
+    optional SettingProto nfc_payment_foreground = 120;
+    optional SettingProto sms_default_application = 121;
+    optional SettingProto dialer_default_application = 122;
+    optional SettingProto emergency_assistance_application = 123;
+    optional SettingProto assist_structure_enabled = 124;
+    optional SettingProto assist_screenshot_enabled = 125;
+    optional SettingProto assist_disclosure_enabled = 126;
+    optional SettingProto enabled_notification_assistant = 127;
+    optional SettingProto enabled_notification_listeners = 128;
+    optional SettingProto enabled_notification_policy_access_packages = 129;
+    optional SettingProto sync_parent_sounds = 130;
+    optional SettingProto immersive_mode_confirmations = 131;
+    optional SettingProto print_service_search_uri = 132;
+    optional SettingProto payment_service_search_uri = 133;
+    optional SettingProto skip_first_use_hints = 134;
+    optional SettingProto unsafe_volume_music_active_ms = 135;
+    optional SettingProto lock_screen_show_notifications = 136;
+    optional SettingProto tv_input_hidden_inputs = 137;
+    optional SettingProto tv_input_custom_labels = 138;
+    optional SettingProto usb_audio_automatic_routing_disabled = 139;
+    optional SettingProto sleep_timeout = 140;
+    optional SettingProto double_tap_to_wake = 141;
+    optional SettingProto assistant = 142;
+    optional SettingProto camera_gesture_disabled = 143;
+    optional SettingProto camera_double_tap_power_gesture_disabled = 144;
+    optional SettingProto camera_double_twist_to_flip_enabled = 145;
+    optional SettingProto night_display_activated = 146;
+    optional SettingProto night_display_auto_mode = 147;
+    optional SettingProto night_display_custom_start_time = 148;
+    optional SettingProto night_display_custom_end_time = 149;
+    optional SettingProto brightness_use_twilight = 150;
+    optional SettingProto enabled_vr_listeners = 151;
+    optional SettingProto vr_display_mode = 152;
+    optional SettingProto carrier_apps_handled = 153;
+    optional SettingProto managed_profile_contact_remote_search = 154;
+    optional SettingProto automatic_storage_manager_enabled = 155;
+    optional SettingProto automatic_storage_manager_days_to_retain = 156;
+    optional SettingProto automatic_storage_manager_bytes_cleared = 157;
+    optional SettingProto automatic_storage_manager_last_run = 158;
+    optional SettingProto system_navigation_keys_enabled = 159;
+    optional SettingProto downloads_backup_enabled = 160;
+    optional SettingProto downloads_backup_allow_metered = 161;
+    optional SettingProto downloads_backup_charging_only = 162;
+    optional SettingProto automatic_storage_manager_downloads_days_to_retain = 163;
+    optional SettingProto qs_tiles = 164;
+    optional SettingProto demo_user_setup_complete = 165;
+    optional SettingProto instant_apps_enabled = 166;
+    optional SettingProto device_paired = 167;
+    optional SettingProto notification_badging = 168;
+    optional SettingProto backup_manager_constants = 169;
 }
 
 message SystemSettingsProto {
     // Historical operations
     repeated SettingsOperationProto historical_op = 1;
 
-    SettingProto end_button_behavior = 2;
-    SettingProto advanced_settings = 3;
-    SettingProto bluetooth_discoverability = 4;
-    SettingProto bluetooth_discoverability_timeout = 5;
-    SettingProto font_scale = 6;
-    SettingProto system_locales = 7;
-    SettingProto screen_off_timeout = 8;
-    SettingProto screen_brightness = 9;
-    SettingProto screen_brightness_for_vr = 10;
-    SettingProto screen_brightness_mode = 11;
-    SettingProto screen_auto_brightness_adj = 12;
-    SettingProto mode_ringer_streams_affected = 13;
-    SettingProto mute_streams_affected = 14;
-    SettingProto vibrate_on = 15;
-    SettingProto vibrate_input_devices = 16;
-    SettingProto volume_ring = 17;
-    SettingProto volume_system = 18;
-    SettingProto volume_voice = 19;
-    SettingProto volume_music = 20;
-    SettingProto volume_alarm = 21;
-    SettingProto volume_notification = 22;
-    SettingProto volume_bluetooth_sco = 23;
-    SettingProto volume_master = 24;
-    SettingProto master_mono = 25;
-    SettingProto vibrate_in_silent = 26;
-    SettingProto append_for_last_audible = 27;
-    SettingProto ringtone = 28;
-    SettingProto ringtone_cache = 29;
-    SettingProto notification_sound = 30;
-    SettingProto notification_sound_cache = 31;
-    SettingProto alarm_alert = 32;
-    SettingProto alarm_alert_cache = 33;
-    SettingProto media_button_receiver = 34;
-    SettingProto text_auto_replace = 35;
-    SettingProto text_auto_caps = 36;
-    SettingProto text_auto_punctuate = 37;
-    SettingProto text_show_password = 38;
-    SettingProto show_gtalk_service_status = 39;
-    SettingProto time_12_24 = 40;
-    SettingProto date_format = 41;
-    SettingProto setup_wizard_has_run = 42;
-    SettingProto accelerometer_rotation = 43;
-    SettingProto user_rotation = 44;
-    SettingProto hide_rotation_lock_toggle_for_accessibility = 45;
-    SettingProto vibrate_when_ringing = 46;
-    SettingProto dtmf_tone_when_dialing = 47;
-    SettingProto dtmf_tone_type_when_dialing = 48;
-    SettingProto hearing_aid = 49;
-    SettingProto tty_mode = 50;
-    SettingProto sound_effects_enabled = 51;
-    SettingProto haptic_feedback_enabled = 52;
-    SettingProto notification_light_pulse = 53;
-    SettingProto pointer_location = 54;
-    SettingProto show_touches = 55;
-    SettingProto window_orientation_listener_log = 56;
-    SettingProto lockscreen_sounds_enabled = 57;
-    SettingProto lockscreen_disabled = 58;
-    SettingProto sip_receive_calls = 59;
-    SettingProto sip_call_options = 60;
-    SettingProto sip_always = 61;
-    SettingProto sip_address_only = 62;
-    SettingProto pointer_speed = 63;
-    SettingProto lock_to_app_enabled = 64;
-    SettingProto egg_mode = 65;
-    SettingProto when_to_make_wifi_calls = 66;
+    optional SettingProto end_button_behavior = 2;
+    optional SettingProto advanced_settings = 3;
+    optional SettingProto bluetooth_discoverability = 4;
+    optional SettingProto bluetooth_discoverability_timeout = 5;
+    optional SettingProto font_scale = 6;
+    optional SettingProto system_locales = 7;
+    optional SettingProto screen_off_timeout = 8;
+    optional SettingProto screen_brightness = 9;
+    optional SettingProto screen_brightness_for_vr = 10;
+    optional SettingProto screen_brightness_mode = 11;
+    optional SettingProto screen_auto_brightness_adj = 12;
+    optional SettingProto mode_ringer_streams_affected = 13;
+    optional SettingProto mute_streams_affected = 14;
+    optional SettingProto vibrate_on = 15;
+    optional SettingProto vibrate_input_devices = 16;
+    optional SettingProto volume_ring = 17;
+    optional SettingProto volume_system = 18;
+    optional SettingProto volume_voice = 19;
+    optional SettingProto volume_music = 20;
+    optional SettingProto volume_alarm = 21;
+    optional SettingProto volume_notification = 22;
+    optional SettingProto volume_bluetooth_sco = 23;
+    optional SettingProto volume_master = 24;
+    optional SettingProto master_mono = 25;
+    optional SettingProto vibrate_in_silent = 26;
+    optional SettingProto append_for_last_audible = 27;
+    optional SettingProto ringtone = 28;
+    optional SettingProto ringtone_cache = 29;
+    optional SettingProto notification_sound = 30;
+    optional SettingProto notification_sound_cache = 31;
+    optional SettingProto alarm_alert = 32;
+    optional SettingProto alarm_alert_cache = 33;
+    optional SettingProto media_button_receiver = 34;
+    optional SettingProto text_auto_replace = 35;
+    optional SettingProto text_auto_caps = 36;
+    optional SettingProto text_auto_punctuate = 37;
+    optional SettingProto text_show_password = 38;
+    optional SettingProto show_gtalk_service_status = 39;
+    optional SettingProto time_12_24 = 40;
+    optional SettingProto date_format = 41;
+    optional SettingProto setup_wizard_has_run = 42;
+    optional SettingProto accelerometer_rotation = 43;
+    optional SettingProto user_rotation = 44;
+    optional SettingProto hide_rotation_lock_toggle_for_accessibility = 45;
+    optional SettingProto vibrate_when_ringing = 46;
+    optional SettingProto dtmf_tone_when_dialing = 47;
+    optional SettingProto dtmf_tone_type_when_dialing = 48;
+    optional SettingProto hearing_aid = 49;
+    optional SettingProto tty_mode = 50;
+    optional SettingProto sound_effects_enabled = 51;
+    optional SettingProto haptic_feedback_enabled = 52;
+    optional SettingProto notification_light_pulse = 53;
+    optional SettingProto pointer_location = 54;
+    optional SettingProto show_touches = 55;
+    optional SettingProto window_orientation_listener_log = 56;
+    optional SettingProto lockscreen_sounds_enabled = 57;
+    optional SettingProto lockscreen_disabled = 58;
+    optional SettingProto sip_receive_calls = 59;
+    optional SettingProto sip_call_options = 60;
+    optional SettingProto sip_always = 61;
+    optional SettingProto sip_address_only = 62;
+    optional SettingProto pointer_speed = 63;
+    optional SettingProto lock_to_app_enabled = 64;
+    optional SettingProto egg_mode = 65;
+    optional SettingProto when_to_make_wifi_calls = 66;
 }
 
 message SettingProto {
     // ID of the setting
-    string id = 1;
+    optional string id = 1;
 
     // Name of the setting
-    string name = 2;
+    optional string name = 2;
 
     // Package name of the setting
-    string pkg = 3;
+    optional string pkg = 3;
 
     // Value of this setting
-    string value = 4;
+    optional string value = 4;
 
     // Default value of this setting
-    string default_value = 5;
+    optional string default_value = 5;
 
     // Whether the default is set by the system
-    bool default_from_system = 6;
+    optional bool default_from_system = 6;
+}
+
+message SettingsProto {
+    // Enum values gotten from Settings.java
+    enum ScreenBrightnessMode {
+        SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
+        SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
+    }
 }
 
 message SettingsOperationProto {
     // When the operation happened
-    int64 timestamp = 1;
+    optional int64 timestamp = 1;
 
     // Type of the operation
-    string operation = 2;
+    optional string operation = 2;
 
     // Name of the setting that was affected (optional)
-    string setting = 3;
+    optional string setting = 3;
 }
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index d5ecacc..c57cb72 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -14,71 +14,160 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
+import "frameworks/base/core/proto/android/content/intent.proto";
+import "frameworks/base/core/proto/android/server/intentresolver.proto";
 import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
 import "frameworks/base/core/proto/android/graphics/rect.proto";
+import "frameworks/base/core/proto/android/os/looper.proto";
 
 package com.android.server.am.proto;
 
 option java_multiple_files = true;
 
 message ActivityManagerServiceProto {
-  ActivityStackSupervisorProto activities = 1;
+  optional ActivityStackSupervisorProto activities = 1;
+
+  optional BroadcastProto broadcasts = 2;
+
+  optional ServiceProto services = 3;
+
+  optional ProcessProto processes = 4;
 }
 
 message ActivityStackSupervisorProto {
-  .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
+  optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
   repeated ActivityDisplayProto displays = 2;
-  KeyguardControllerProto keyguard_controller = 3;
-  int32 focused_stack_id = 4;
-  .com.android.server.wm.proto.IdentifierProto resumed_activity = 5;
+  optional KeyguardControllerProto keyguard_controller = 3;
+  optional int32 focused_stack_id = 4;
+  optional .com.android.server.wm.proto.IdentifierProto resumed_activity = 5;
 }
 
 /* represents ActivityStackSupervisor.ActivityDisplay */
 message ActivityDisplayProto {
-  .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
-  int32 id = 2;
+  optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
+  optional int32 id = 2;
   repeated ActivityStackProto stacks = 3;
 }
 
 message ActivityStackProto {
-  .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
-  int32 id = 2;
+  optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
+  optional int32 id = 2;
   repeated TaskRecordProto tasks = 3;
-  .com.android.server.wm.proto.IdentifierProto resumed_activity = 4;
-  int32 display_id = 5;
-  bool fullscreen = 6;
-  .android.graphics.RectProto bounds = 7;
+  optional .com.android.server.wm.proto.IdentifierProto resumed_activity = 4;
+  optional int32 display_id = 5;
+  optional bool fullscreen = 6;
+  optional .android.graphics.RectProto bounds = 7;
 }
 
 message TaskRecordProto {
-  .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
-  int32 id = 2;
+  optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
+  optional int32 id = 2;
   repeated ActivityRecordProto activities = 3;
-  int32 stack_id = 4;
-  .android.graphics.RectProto last_non_fullscreen_bounds = 5;
-  string real_activity = 6;
-  string orig_activity = 7;
-  int32 activity_type = 8;
-  int32 return_to_type = 9;
-  int32 resize_mode = 10;
-  bool fullscreen = 11;
-  .android.graphics.RectProto bounds = 12;
-  int32 min_width = 13;
-  int32 min_height = 14;
+  optional int32 stack_id = 4;
+  optional .android.graphics.RectProto last_non_fullscreen_bounds = 5;
+  optional string real_activity = 6;
+  optional string orig_activity = 7;
+  optional int32 activity_type = 8;
+  optional int32 resize_mode = 9;
+  optional bool fullscreen = 10;
+  optional .android.graphics.RectProto bounds = 11;
+  optional int32 min_width = 12;
+  optional int32 min_height = 13;
 }
 
 message ActivityRecordProto {
-  .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
-  .com.android.server.wm.proto.IdentifierProto identifier = 2;
-  string state = 3;
-  bool visible = 4;
-  bool front_of_task = 5;
-  int32 proc_id = 6;
+  optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
+  optional .com.android.server.wm.proto.IdentifierProto identifier = 2;
+  optional string state = 3;
+  optional bool visible = 4;
+  optional bool front_of_task = 5;
+  optional int32 proc_id = 6;
 }
 
 message KeyguardControllerProto {
-  bool keyguard_showing = 1;
-  bool keyguard_occluded = 2;
-}
\ No newline at end of file
+  optional bool keyguard_showing = 1;
+  optional bool keyguard_occluded = 2;
+}
+
+message BroadcastProto {
+  repeated ReceiverListProto  receiver_list = 1;
+
+  optional .com.android.server.IntentResolverProto receiver_resolver = 2;
+
+  repeated BroadcastQueueProto broadcast_queue = 3;
+
+  repeated StickyBroadcastProto sticky_broadcasts = 4;
+
+  message MainHandler {
+    optional string handler = 1;
+    optional .android.os.LooperProto looper = 2;
+  }
+  optional MainHandler handler = 5;
+}
+
+message ReceiverListProto {
+  optional ProcessRecordProto app = 1;
+  optional int32 pid = 2;
+  optional int32 uid = 3;
+  optional int32 user = 4;
+  optional BroadcastRecordProto current = 5;
+  optional bool linked_to_death = 6;
+  repeated BroadcastFilterProto filters = 7;
+  optional string hex_hash = 8; // this hash is used to find the object in IntentResolver
+}
+
+message ProcessRecordProto {
+  optional int32 pid = 1;
+  optional string process_name = 2;
+  optional int32 uid = 3;
+  optional int32 user_id = 4;
+  optional int32 app_id = 5;
+  optional int32 isolated_app_id = 6;
+}
+
+message BroadcastRecordProto {
+  optional int32 user_id = 1;
+  optional string intent_action = 2;
+}
+
+message BroadcastFilterProto {
+  optional .android.content.IntentFilterProto intent_filter = 1;
+  optional string required_permission = 2;
+  optional string hex_hash = 3; // used to find the object in IntentResolver
+  optional int32 owning_user_id = 4;
+}
+
+message BroadcastQueueProto {
+  optional string queue_name = 1;
+  repeated BroadcastRecordProto parallel_broadcasts = 2;
+  repeated BroadcastRecordProto ordered_broadcasts = 3;
+  optional BroadcastRecordProto pending_broadcast = 4;
+  repeated BroadcastRecordProto historical_broadcasts = 5;
+
+  message BroadcastSummary {
+    optional .android.content.IntentProto intent = 1;
+    optional int64 enqueue_clock_time_ms = 2;
+    optional int64 dispatch_clock_time_ms = 3;
+    optional int64 finish_clock_time_ms = 4;
+  }
+  repeated BroadcastSummary historical_broadcasts_summary = 6;
+}
+
+message StickyBroadcastProto {
+  optional int32 user = 1;
+
+  message StickyAction {
+    optional string name = 1;
+    repeated .android.content.IntentProto intents = 2;
+  }
+  repeated StickyAction actions = 2;
+}
+
+message ServiceProto {
+  // TODO: "dumpsys activity --proto services"
+}
+
+message ProcessProto {
+  // TODO: "dumpsys activity --proto processes"
+}
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
new file mode 100644
index 0000000..d2cd190
--- /dev/null
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -0,0 +1,247 @@
+/*
+ * 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";
+
+import "frameworks/base/core/proto/android/app/alarmmanager.proto";
+import "frameworks/base/core/proto/android/app/pendingintent.proto";
+import "frameworks/base/core/proto/android/internal/locallog.proto";
+import "frameworks/base/core/proto/android/os/worksource.proto";
+
+package com.android.server;
+
+option java_multiple_files = true;
+
+message AlarmManagerServiceProto {
+  optional int64 current_time = 1;
+  optional int64 elapsed_realtime = 2;
+  optional int64 last_time_change_clock_time = 3;
+  optional int64 last_time_change_realtime = 4;
+  // Current settings
+  optional ConstantsProto settings = 5;
+  // UIDs currently in the foreground.
+  repeated int32 foreground_uids = 6;
+  // Packages forced into app standby.
+  repeated string forced_app_standby_packages = 7;
+
+  optional bool is_interactive = 8;
+  // Only valid if is_interactive is false.
+  optional int64 time_since_non_interactive_ms = 9;
+  // Only valid if is_interactive is false.
+  optional int64 max_wakeup_delay_ms = 10;
+  // Only valid if is_interactive is false.
+  optional int64 time_since_last_dispatch_ms = 11;
+  // Only valid if is_interactive is false.
+  optional int64 time_until_next_non_wakeup_delivery_ms = 12;
+
+  optional int64 time_until_next_non_wakeup_alarm_ms = 13;
+  optional int64 time_until_next_wakeup_ms = 14;
+  optional int64 time_since_last_wakeup_ms = 15;
+  // Time since the last wakeup was set.
+  optional int64 time_since_last_wakeup_set_ms = 16;
+  optional int64 time_change_event_count = 17;
+  // The current set of user whitelisted apps for device idle mode, meaning
+  // these are allowed to freely schedule alarms. These are app IDs, not UIDs.
+  repeated int32 device_idle_user_whitelist_app_ids = 18;
+
+  repeated AlarmClockMetadataProto next_alarm_clock_metadata = 19;
+
+  repeated BatchProto pending_alarm_batches = 20;
+
+  // List of alarms per uid deferred due to user applied background restrictions
+  // on the source app.
+  repeated AlarmProto pending_user_blocked_background_alarms = 21;
+
+  // When idling mode will end. Will be empty if the device is not currently
+  // idling.
+  optional AlarmProto pending_idle_until = 22;
+
+  // Any alarms that we don't want to run during idle mode. Will be empty if the
+  // device is not currently idling.
+  repeated AlarmProto pending_while_idle_alarms = 23;
+
+  // This is a special alarm that will put the system into idle until it goes
+  // off. The caller has given the time they want this to happen at.
+  optional AlarmProto next_wake_from_idle = 24;
+
+  repeated AlarmProto past_due_non_wakeup_alarms = 25;
+
+  // Number of delayed alarms.
+  optional int32 delayed_alarm_count = 26;
+  // The total amount of time alarms had been delayed. Overlapping alarms are
+  // only counted once (ie. If two alarms were meant to trigger at the same time
+  // but were delayed by 5 seconds, the total time would be 5 seconds).
+  optional int64 total_delay_time_ms = 27;
+  optional int64 max_delay_duration_ms = 28;
+  optional int64 max_non_interactive_duration_ms = 29;
+
+  optional int32 broadcast_ref_count = 30;
+  // Canonical count of (operation.send() - onSendFinished()) and listener
+  // send/complete/timeout invocations.
+  optional int32 pending_intent_send_count = 31;
+  optional int32 pending_intent_finish_count = 32;
+  optional int32 listener_send_count = 33;
+  optional int32 listener_finish_count = 34;
+
+  repeated InFlightProto outstanding_deliveries = 35;
+
+  // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. It
+  // should be either CosntantsProto.allow_while_idle_short_duration_ms or
+  // ConstantsProto.allow_while_idle_long_duration_ms.
+  optional int64 allow_while_idle_min_duration_ms = 36;
+
+  message LastAllowWhileIdleDispatch {
+    optional int32 uid = 1;
+    // In the 'elapsed' timebase.
+    optional int64 time_ms = 2;
+  }
+  // For each uid, this is the last time we dispatched an "allow while idle"
+  // alarm, used to determine the earliest we can dispatch the next such alarm.
+  repeated LastAllowWhileIdleDispatch last_allow_while_idle_dispatch_times = 37;
+
+  optional com.android.internal.util.LocalLogProto recent_problems = 38;
+
+  message TopAlarm {
+    optional int32 uid = 1;
+    optional string package_name = 2;
+    optional FilterStatsProto filter = 3;
+  }
+  repeated TopAlarm top_alarms = 39;
+
+  message AlarmStat {
+    optional BroadcastStatsProto broadcast = 1;
+    repeated FilterStatsProto filters = 2;
+  }
+  repeated AlarmStat alarm_stats = 40;
+
+  repeated IdleDispatchEntryProto allow_while_idle_dispatches = 41;
+  repeated WakeupEventProto recent_wakeup_history = 42;
+}
+
+// This is a soft wrapper for alarm clock information. It is not representative
+// of an android.app.AlarmManager.AlarmClockInfo object.
+message AlarmClockMetadataProto {
+  optional int32 user = 1;
+  optional bool is_pending_send = 2;
+  // This value is UTC wall clock time in milliseconds, as returned by
+  // System#currentTimeMillis() for example.
+  optional int64 trigger_time_ms = 3;
+}
+
+// A com.android.server.AlarmManagerService.Alarm object.
+message AlarmProto {
+  optional string tag = 1;
+  optional .android.app.AlarmManagerProto.AlarmType type = 2;
+  // How long until the alarm goes off, in the 'elapsed' timebase.
+  optional int64 when_elapsed_ms = 3;
+  optional int64 window_length_ms = 4;
+  optional int64 repeat_interval_ms = 5;
+  optional int32 count = 6;
+  optional int32 flags = 7;
+  optional .android.app.AlarmClockInfoProto alarm_clock = 8;
+  optional .android.app.PendingIntentProto operation = 9;
+  optional string listener = 10;
+}
+
+// A com.android.server.AlarmManagerService.Batch object.
+message BatchProto {
+  // Start time in terms of elapsed realtime.
+  optional int64 start_realtime = 1;
+  // End time in terms of elapsed realtime.
+  optional int64 end_realtime = 2;
+  optional int32 flags = 3;
+  repeated AlarmProto alarms = 4;
+}
+
+// A com.android.server.AlarmManagerService.BroadcastStats object.
+message BroadcastStatsProto {
+  optional int32 uid = 1;
+  optional string package_name = 2;
+  // The total amount of time this broadcast was in flight.
+  optional int64 total_flight_duration_ms = 3;
+  optional int32 count = 4;
+  optional int32 wakeup_count = 5;
+  // The last time this first became active (when nesting changed from 0 to 1)
+  // in terms of elapsed realtime.
+  optional int64 start_time_realtime = 6;
+  // The broadcast is active if nesting > 0.
+  optional int32 nesting = 7;
+}
+
+// A com.android.server.AlarmManagerService.Constants object.
+message ConstantsProto {
+  // Minimum futurity of a new alarm.
+  optional int64 min_futurity_duration_ms = 1;
+  // Minimum alarm recurrence interval.
+  optional int64 min_interval_duration_ms = 2;
+  // Direct alarm listener callback timeout.
+  optional int64 listener_timeout_duration_ms = 3;
+  // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle.
+  optional int64 allow_while_idle_short_duration_ms = 4;
+  // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling.
+  optional int64 allow_while_idle_long_duration_ms = 5;
+  // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE.
+  optional int64 allow_while_idle_whitelist_duration_ms = 6;
+}
+
+// A com.android.server.AlarmManagerService.FilterStats object.
+message FilterStatsProto {
+  optional string tag = 1;
+  // The last time this filter when in flight, in terms of elapsed realtime.
+  optional int64 last_flight_time_realtime = 2;
+  // The total amount of time this filter was in flight.
+  optional int64 total_flight_duration_ms = 3;
+  optional int32 count = 4;
+  optional int32 wakeup_count = 5;
+  // The last time this first became active (when nesting changed from 0 to 1)
+  // in terms of elapsed realtime.
+  optional int64 start_time_realtime = 6;
+  // The filter is active if nesting > 0.
+  optional int32 nesting = 7;
+}
+
+// A com.android.server.AlarmManagerService.IdleDispatchEntry object.
+message IdleDispatchEntryProto {
+  optional int32 uid = 1;
+  optional string pkg = 2;
+  optional string tag = 3;
+  optional string op = 4;
+  // Time when this entry was created, in terms of elapsed realtime.
+  optional int64 entry_creation_realtime = 5;
+  // For a RESCHEDULED op, this is the last time we dispatched an "allow while
+  // idle" alarm for the UID. For a SET op, this is when the alarm was
+  // triggered. Times are in the 'elapsed' timebase.
+  optional int64 arg_realtime = 6;
+}
+
+// A com.android.server.AlarmManagerService.InFlight object.
+message InFlightProto {
+  optional int32 uid = 1;
+  optional string tag = 2;
+  optional int64 when_elapsed_ms = 3;
+  optional .android.app.AlarmManagerProto.AlarmType alarm_type = 4;
+  optional .android.app.PendingIntentProto pending_intent = 5;
+  optional BroadcastStatsProto broadcast_stats = 6;
+  optional FilterStatsProto filter_stats = 7;
+  optional .android.os.WorkSourceProto work_source = 8;
+}
+
+// A com.android.server.AlarmManagerService.WakeupEvent object.
+message WakeupEventProto {
+  optional int32 uid = 1;
+  optional string action = 2;
+  optional int64 when = 3;
+}
diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto
new file mode 100644
index 0000000..ec4ffe0
--- /dev/null
+++ b/core/proto/android/server/fingerprint.proto
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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 com.android.server.fingerprint;
+
+option java_multiple_files = true;
+option java_outer_classname = "FingerprintServiceProto";
+
+message FingerprintServiceDumpProto {
+    // Each log may include multiple tuples of (user_id, num_fingerprints).
+    repeated FingerprintUserStatsProto users = 1;
+}
+
+message FingerprintUserStatsProto {
+    // Should be 0, 10, 11, 12, etc. where 0 is the owner.
+    optional int32 user_id = 1;
+
+    // The number of fingerprints registered to this user.
+    optional int32 num_fingerprints = 2;
+
+    // Normal fingerprint authentications (e.g. lockscreen).
+    optional PerformanceStatsProto normal = 3;
+
+    // Crypto authentications (e.g. to unlock password storage, make secure
+    // purchases, etc).
+    optional PerformanceStatsProto crypto = 4;
+}
+
+// A com.android.server.fingerprint.FingerpintService.PerformanceStats object.
+message PerformanceStatsProto {
+    // Number of accepted fingerprints.
+    optional int32 accept = 1;
+
+    // Number of rejected fingerprints.
+    optional int32 reject = 2;
+
+    // Total number of acquisitions. Should be >= accept+reject due to poor
+    // image acquisition in some cases (too fast, too slow, dirty sensor, etc.)
+    optional int32 acquire = 3;
+
+    // Total number of lockouts.
+    optional int32 lockout = 4;
+
+    // Total number of permanent lockouts.
+    optional int32 permanent_lockout = 5;
+}
diff --git a/core/proto/android/server/intentresolver.proto b/core/proto/android/server/intentresolver.proto
new file mode 100644
index 0000000..60c060c
--- /dev/null
+++ b/core/proto/android/server/intentresolver.proto
@@ -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.
+ */
+
+syntax = "proto2";
+option java_multiple_files = true;
+
+package com.android.server;
+
+message IntentResolverProto {
+
+    message ArrayMapEntry {
+        optional string key = 1;
+        repeated string values = 2;
+    }
+
+    repeated ArrayMapEntry full_mime_types = 1;
+    repeated ArrayMapEntry base_mime_types = 2;
+    repeated ArrayMapEntry wild_mime_types = 3;
+    repeated ArrayMapEntry schemes = 4;
+    repeated ArrayMapEntry non_data_actions = 5;
+    repeated ArrayMapEntry mime_typed_actions = 6;
+}
+
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
new file mode 100644
index 0000000..d442acf
--- /dev/null
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -0,0 +1,314 @@
+/*
+ * 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 com.android.server.power;
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/app/activitymanager.proto";
+import "frameworks/base/core/proto/android/content/intent.proto";
+import "frameworks/base/core/proto/android/os/batterymanager.proto";
+import "frameworks/base/core/proto/android/os/looper.proto";
+import "frameworks/base/core/proto/android/os/powermanager.proto";
+import "frameworks/base/core/proto/android/os/worksource.proto";
+import "frameworks/base/core/proto/android/providers/settings.proto";
+import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
+import "frameworks/base/core/proto/android/view/display.proto";
+
+message PowerManagerServiceDumpProto {
+    // A com.android.server.power.PowerManagerService.Constants object.
+    message ConstantsProto {
+        optional bool is_no_cached_wake_locks = 1;
+    }
+    message ActiveWakeLocksProto {
+        optional bool is_cpu = 1;
+        optional bool is_screen_bright = 2;
+        optional bool is_screen_dim = 3;
+        optional bool is_button_bright = 4;
+        optional bool is_proximity_screen_off = 5;
+        // only set if already awake
+        optional bool is_stay_awake = 6;
+        optional bool is_doze = 7;
+        optional bool is_draw = 8;
+    }
+    message UserActivityProto {
+        optional bool is_screen_bright = 1;
+        optional bool is_screen_dim = 2;
+        optional bool is_screen_dream = 3;
+    }
+    // A com.android.server.power.PowerManagerService.UidState object.
+    message UidStateProto {
+        optional int32 uid = 1;
+        optional string uid_string = 2;
+        optional bool is_active = 3;
+        optional int32 num_wake_locks = 4;
+        optional bool is_process_state_unknown = 5;
+        optional .android.app.ActivityManagerProto.ProcessState process_state = 6;
+    }
+
+    optional ConstantsProto constants = 1;
+    // A bitfield that indicates what parts of the power state have
+    // changed and need to be recalculated.
+    optional int32 dirty = 2;
+    // Indicates whether the device is awake or asleep or somewhere in between.
+    optional .android.os.PowerManagerInternalProto.Wakefulness wakefulness = 3;
+    optional bool is_wakefulness_changing = 4;
+    // True if the device is plugged into a power source.
+    optional bool is_powered = 5;
+    // The current plug type
+    optional .android.os.BatteryManagerProto.PlugType plug_type = 6;
+    // The current battery level percentage.
+    optional int32 battery_level = 7;
+    // The battery level percentage at the time the dream started.
+    optional int32 battery_level_when_dream_started = 8;
+    // The current dock state.
+    optional .android.content.IntentProto.DockState dock_state = 9;
+    // True if the device should stay on.
+    optional bool is_stay_on = 10;
+    // True if the proximity sensor reads a positive result.
+    optional bool is_proximity_positive = 11;
+    // True if boot completed occurred.  We keep the screen on until this happens.
+    optional bool is_boot_completed = 12;
+    // True if systemReady() has been called.
+    optional bool is_system_ready = 13;
+    // True if auto-suspend mode is enabled.
+    optional bool is_hal_auto_suspend_mode_enabled = 14;
+    // True if interactive mode is enabled.
+    optional bool is_hal_auto_interactive_mode_enabled = 15;
+    // Summarizes the state of all active wakelocks.
+    optional ActiveWakeLocksProto active_wake_locks = 16;
+    // Have we scheduled a message to check for long wake locks?  This is when
+    // we will check. (In milliseconds timestamp)
+    optional int64 notify_long_scheduled_ms = 17;
+    // Last time we checked for long wake locks. (In milliseconds timestamp)
+    optional int64 notify_long_dispatched_ms = 18;
+    // The time we decided to do next long check. (In milliseconds timestamp)
+    optional int64 notify_long_next_check_ms = 19;
+    // Summarizes the effect of the user activity timer.
+    optional UserActivityProto user_activity = 20;
+    // If true, instructs the display controller to wait for the proximity
+    // sensor to go negative before turning the screen on.
+    optional bool is_request_wait_for_negative_proximity = 21;
+    // True if MSG_SANDMAN has been scheduled.
+    optional bool is_sandman_scheduled = 22;
+    // True if the sandman has just been summoned for the first time since entering
+    // the dreaming or dozing state.  Indicates whether a new dream should begin.
+    optional bool is_sandman_summoned = 23;
+    // If true, the device is in low power mode.
+    optional bool is_low_power_mode_enabled = 24;
+    // True if the battery level is currently considered low.
+    optional bool is_battery_level_low = 25;
+    // True if we are currently in light device idle mode.
+    optional bool is_light_device_idle_mode = 26;
+    // True if we are currently in device idle mode.
+    optional bool is_device_idle_mode = 27;
+    // Set of app ids that we will always respect the wake locks for.
+    repeated int32 device_idle_whitelist = 28;
+    // Set of app ids that are temporarily allowed to acquire wakelocks due to
+    // high-pri message
+    repeated int32 device_idle_temp_whitelist = 29;
+    // Timestamp of the last time the device was awoken.
+    optional int64 last_wake_time_ms = 30;
+    // Timestamp of the last time the device was put to sleep.
+    optional int64 last_sleep_time_ms = 31;
+    // Timestamp of the last call to user activity.
+    optional int64 last_user_activity_time_ms = 32;
+    optional int64 last_user_activity_time_no_change_lights_ms = 33;
+    // Timestamp of last interactive power hint.
+    optional int64 last_interactive_power_hint_time_ms = 34;
+    // Timestamp of the last screen brightness boost.
+    optional int64 last_screen_brightness_boost_time_ms = 35;
+    // True if screen brightness boost is in progress.
+    optional bool is_screen_brightness_boost_in_progress = 36;
+    // True if the display power state has been fully applied, which means the
+    // display is actually on or actually off or whatever was requested.
+    optional bool is_display_ready = 37;
+    // True if the wake lock suspend blocker has been acquired.
+    optional bool is_holding_wake_lock_suspend_blocker = 38;
+    // The suspend blocker used to keep the CPU alive when the display is on, the
+    // display is getting ready or there is user activity (in which case the
+    // display must be on).
+    optional bool is_holding_display_suspend_blocker = 39;
+    // Settings and configuration
+    optional PowerServiceSettingsAndConfigurationDumpProto settings_and_configuration = 40;
+    // Sleep timeout in ms
+    optional sint32 sleep_timeout_ms = 41;
+    // Screen off timeout in ms
+    optional int32 screen_off_timeout_ms = 42;
+    // Screen dim duration in ms
+    optional int32 screen_dim_duration_ms = 43;
+    // We are currently in the middle of a batch change of uids.
+    optional bool are_uids_changing = 44;
+    // Some uids have actually changed while mUidsChanging was true.
+    optional bool are_uids_changed = 45;
+    // List of UIDs and their states
+    repeated UidStateProto uid_states = 46;
+    optional .android.os.LooperProto looper = 47;
+    // List of all wake locks acquired by applications.
+    repeated WakeLockProto wake_locks = 48;
+    // List of all suspend blockers.
+    repeated SuspendBlockerProto suspend_blockers = 49;
+    optional WirelessChargerDetectorProto wireless_charger_detector = 50;
+}
+
+// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
+message SuspendBlockerProto {
+    optional string name = 1;
+    optional int32 reference_count = 2;
+}
+
+// A com.android.server.power.PowerManagerService.WakeLock object.
+message WakeLockProto {
+    message WakeLockFlagsProto {
+        // Turn the screen on when the wake lock is acquired.
+        optional bool is_acquire_causes_wakeup = 1;
+        // When this wake lock is released, poke the user activity timer
+        // so the screen stays on for a little longer.
+        optional bool is_on_after_release = 2;
+    }
+
+    optional .android.os.PowerManagerProto.WakeLockLevel lock_level = 1;
+    optional string tag = 2;
+    optional WakeLockFlagsProto flags = 3;
+    optional bool is_disabled = 4;
+    // Acquire time in ms
+    optional int64 acq_ms = 5;
+    optional bool is_notified_long = 6;
+    // Owner UID
+    optional int32 uid = 7;
+    // Owner PID
+    optional int32 pid = 8;
+    optional .android.os.WorkSourceProto work_source = 9;
+}
+
+message PowerServiceSettingsAndConfigurationDumpProto {
+    message StayOnWhilePluggedInProto {
+        optional bool is_stay_on_while_plugged_in_ac = 1;
+        optional bool is_stay_on_while_plugged_in_usb = 2;
+        optional bool is_stay_on_while_plugged_in_wireless = 3;
+    }
+    message ScreenBrightnessSettingLimitsProto {
+        optional int32 setting_minimum = 1;
+        optional int32 setting_maximum = 2;
+        optional int32 setting_default = 3;
+        optional int32 setting_for_vr_default = 4;
+    }
+
+    // True to decouple auto-suspend mode from the display state.
+    optional bool is_decouple_hal_auto_suspend_mode_from_display_config = 1;
+    // True to decouple interactive mode from the display state.
+    optional bool is_decouple_hal_interactive_mode_from_display_config = 2;
+    // True if the device should wake up when plugged or unplugged.
+    optional bool is_wake_up_when_plugged_or_unplugged_config = 3;
+    // True if the device should wake up when plugged or unplugged in theater mode.
+    optional bool is_wake_up_when_plugged_or_unplugged_in_theater_mode_config = 4;
+    // True if theater mode is enabled
+    optional bool is_theater_mode_enabled = 5;
+    // True if the device should suspend when the screen is off due to proximity.
+    optional bool is_suspend_when_screen_off_due_to_proximity_config = 6;
+    // True if dreams are supported on this device.
+    optional bool are_dreams_supported_config = 7;
+    // Default value for dreams enabled
+    optional bool are_dreams_enabled_by_default_config = 8;
+    // Default value for dreams activate-on-sleep
+    optional bool are_dreams_activated_on_sleep_by_default_config = 9;
+    // Default value for dreams activate-on-dock
+    optional bool are_dreams_activated_on_dock_by_default_config = 10;
+    // True if dreams can run while not plugged in.
+    optional bool are_dreams_enabled_on_battery_config = 11;
+    // Minimum battery level to allow dreaming when powered.
+    // Use -1 to disable this safety feature.
+    optional sint32 dreams_battery_level_minimum_when_powered_config = 12;
+    // Minimum battery level to allow dreaming when not powered.
+    // Use -1 to disable this safety feature.
+    optional sint32 dreams_battery_level_minimum_when_not_powered_config = 13;
+    // If the battery level drops by this percentage and the user activity
+    // timeout has expired, then assume the device is receiving insufficient
+    // current to charge effectively and terminate the dream.  Use -1 to disable
+    // this safety feature.
+    optional sint32 dreams_battery_level_drain_cutoff_config = 14;
+    // True if dreams are enabled by the user.
+    optional bool are_dreams_enabled_setting = 15;
+    // True if dreams should be activated on sleep.
+    optional bool are_dreams_activate_on_sleep_setting = 16;
+    // True if dreams should be activated on dock.
+    optional bool are_dreams_activate_on_dock_setting = 17;
+    // True if doze should not be started until after the screen off transition.
+    optional bool is_doze_after_screen_off_config = 18;
+    // If true, the device is in low power mode.
+    optional bool is_low_power_mode_setting = 19;
+    // Current state of whether the settings are allowing auto low power mode.
+    optional bool is_auto_low_power_mode_configured = 20;
+    // The user turned off low power mode below the trigger level
+    optional bool is_auto_low_power_mode_snoozing = 21;
+    // The minimum screen off timeout, in milliseconds.
+    optional int32 minimum_screen_off_timeout_config_ms = 22;
+    // The screen dim duration, in milliseconds.
+    optional int32 maximum_screen_dim_duration_config_ms = 23;
+    // The maximum screen dim time expressed as a ratio relative to the screen off timeout.
+    optional float maximum_screen_dim_ratio_config = 24;
+    // The screen off timeout setting value in milliseconds.
+    optional int32 screen_off_timeout_setting_ms = 25;
+    // The sleep timeout setting value in milliseconds.
+    optional sint32 sleep_timeout_setting_ms = 26;
+    // The maximum allowable screen off timeout according to the device administration policy.
+    optional int32 maximum_screen_off_timeout_from_device_admin_ms = 27;
+    optional bool is_maximum_screen_off_timeout_from_device_admin_enforced_locked = 28;
+    // The stay on while plugged in setting.
+    // A set of battery conditions under which to make the screen stay on.
+    optional StayOnWhilePluggedInProto stay_on_while_plugged_in = 29;
+    // The screen brightness setting, from 0 to 255.
+    // Use -1 if no value has been set.
+    optional sint32 screen_brightness_setting = 30;
+    // The screen auto-brightness adjustment setting, from -1 to 1.
+    // Use 0 if there is no adjustment.
+    optional float screen_auto_brightness_adjustment_setting = 31;
+    // The screen brightness mode.
+    optional .android.providers.settings.SettingsProto.ScreenBrightnessMode screen_brightness_mode_setting = 32;
+    // The screen brightness setting override from the window manager
+    // to allow the current foreground activity to override the brightness.
+    // Use -1 to disable.
+    optional sint32 screen_brightness_override_from_window_manager = 33;
+    // The user activity timeout override from the window manager
+    // to allow the current foreground activity to override the user activity
+    // timeout. Use -1 to disable.
+    optional sint64 user_activity_timeout_override_from_window_manager_ms = 34;
+    // The window manager has determined the user to be inactive via other means.
+    // Set this to false to disable.
+    optional bool is_user_inactive_override_from_window_manager = 35;
+    // The screen brightness setting override from the settings application
+    // to temporarily adjust the brightness until next updated,
+    // Use -1 to disable.
+    optional sint32 temporary_screen_brightness_setting_override = 36;
+    // The screen brightness adjustment setting override from the settings
+    // application to temporarily adjust the auto-brightness adjustment factor
+    // until next updated, in the range -1..1.
+    // Use NaN to disable.
+    optional float temporary_screen_auto_brightness_adjustment_setting_override = 37;
+    // The screen state to use while dozing.
+    optional .android.view.DisplayProto.DisplayState doze_screen_state_override_from_dream_manager = 38;
+    // The screen brightness to use while dozing.
+    optional float dozed_screen_brightness_override_from_dream_manager = 39;
+    // Screen brightness settings limits.
+    optional ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 40;
+    // The screen brightness setting, from 0 to 255, to be used while in VR Mode.
+    optional int32 screen_brightness_for_vr_setting = 41;
+    // True if double tap to wake is enabled
+    optional bool is_double_tap_wake_enabled = 42;
+    // True if we are currently in VR Mode.
+    optional bool is_vr_mode_enabled = 43;
+}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index d177f1c..064523a 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 import "frameworks/base/core/proto/android/content/configuration.proto";
 import "frameworks/base/core/proto/android/graphics/rect.proto";
 import "frameworks/base/core/proto/android/view/displayinfo.proto";
@@ -26,21 +25,21 @@
 option java_multiple_files = true;
 
 message WindowManagerServiceProto {
-  WindowManagerPolicyProto policy = 1;
+  optional WindowManagerPolicyProto policy = 1;
   /* window hierarchy root */
-  RootWindowContainerProto root_window_container = 2;
-  IdentifierProto focused_window = 3;
-  string focused_app = 4;
-  IdentifierProto input_method_window = 5;
-  bool display_frozen = 6;
-  int32 rotation = 7;
-  int32 last_orientation = 8;
-  AppTransitionProto app_transition = 9;
+  optional RootWindowContainerProto root_window_container = 2;
+  optional IdentifierProto focused_window = 3;
+  optional string focused_app = 4;
+  optional IdentifierProto input_method_window = 5;
+  optional bool display_frozen = 6;
+  optional int32 rotation = 7;
+  optional int32 last_orientation = 8;
+  optional AppTransitionProto app_transition = 9;
 }
 
 /* represents DisplayContent */
 message RootWindowContainerProto {
-  WindowContainerProto window_container = 1;
+  optional WindowContainerProto window_container = 1;
   repeated DisplayProto displays = 2;
   /* window references in top down z order */
   repeated IdentifierProto windows = 3;
@@ -48,7 +47,7 @@
 
 /* represents PhoneWindowManager */
 message WindowManagerPolicyProto {
-  .android.graphics.RectProto stable_bounds = 1;
+  optional .android.graphics.RectProto stable_bounds = 1;
 }
 
 /* represents AppTransition */
@@ -59,7 +58,7 @@
     APP_STATE_RUNNING = 2;
     APP_STATE_TIMEOUT = 3;
   }
-  AppState app_transition_state = 1;
+  optional AppState app_transition_state = 1;
   /* definitions for constants found in {@link com.android.server.wm.AppTransition} */
   enum TransitionType {
     TRANSIT_NONE = 0;
@@ -83,124 +82,124 @@
     TRANSIT_KEYGUARD_OCCLUDE = 22;
     TRANSIT_KEYGUARD_UNOCCLUDE = 23;
   }
-  TransitionType last_used_app_transition = 2;
+  optional TransitionType last_used_app_transition = 2;
 }
 
 /* represents DisplayContent */
 message DisplayProto {
-  WindowContainerProto window_container = 1;
-  int32 id = 2;
+  optional WindowContainerProto window_container = 1;
+  optional int32 id = 2;
   repeated StackProto stacks = 3;
-  DockedStackDividerControllerProto docked_stack_divider_controller = 4;
-  PinnedStackControllerProto pinned_stack_controller = 5;
+  optional DockedStackDividerControllerProto docked_stack_divider_controller = 4;
+  optional PinnedStackControllerProto pinned_stack_controller = 5;
   /* non app windows */
   repeated WindowTokenProto above_app_windows = 6;
   repeated WindowTokenProto below_app_windows = 7;
   repeated WindowTokenProto ime_windows = 8;
-  int32 dpi = 9;
-  .android.view.DisplayInfoProto display_info = 10;
-  int32 rotation = 11;
-  ScreenRotationAnimationProto screen_rotation_animation = 12;
+  optional int32 dpi = 9;
+  optional .android.view.DisplayInfoProto display_info = 10;
+  optional int32 rotation = 11;
+  optional ScreenRotationAnimationProto screen_rotation_animation = 12;
 }
 
 
 /* represents DockedStackDividerController */
 message DockedStackDividerControllerProto {
-  bool minimized_dock = 1;
+  optional bool minimized_dock = 1;
 }
 
 /* represents PinnedStackController */
 message PinnedStackControllerProto {
-  .android.graphics.RectProto default_bounds = 1;
-  .android.graphics.RectProto movement_bounds = 2;
+  optional .android.graphics.RectProto default_bounds = 1;
+  optional .android.graphics.RectProto movement_bounds = 2;
 }
 
 /* represents TaskStack */
 message StackProto {
-  WindowContainerProto window_container = 1;
-  int32 id = 2;
+  optional WindowContainerProto window_container = 1;
+  optional int32 id = 2;
   repeated TaskProto tasks = 3;
-  bool fills_parent = 4;
-  .android.graphics.RectProto bounds = 5;
-  bool animation_background_surface_is_dimming = 6;
+  optional bool fills_parent = 4;
+  optional .android.graphics.RectProto bounds = 5;
+  optional bool animation_background_surface_is_dimming = 6;
 }
 
 /* represents Task */
 message TaskProto {
-  WindowContainerProto window_container = 1;
-  int32 id = 2;
+  optional WindowContainerProto window_container = 1;
+  optional int32 id = 2;
   repeated AppWindowTokenProto app_window_tokens = 3;
-  bool fills_parent = 4;
-  .android.graphics.RectProto bounds = 5;
-  .android.graphics.RectProto temp_inset_bounds = 6;
+  optional bool fills_parent = 4;
+  optional .android.graphics.RectProto bounds = 5;
+  optional .android.graphics.RectProto temp_inset_bounds = 6;
 }
 
 /* represents AppWindowToken */
 message AppWindowTokenProto {
   /* obtained from ActivityRecord */
-  string name = 1;
-  WindowTokenProto window_token = 2;
+  optional string name = 1;
+  optional WindowTokenProto window_token = 2;
 }
 
 /* represents WindowToken */
 message WindowTokenProto {
-  WindowContainerProto window_container = 1;
-  int32 hash_code = 2;
+  optional WindowContainerProto window_container = 1;
+  optional int32 hash_code = 2;
   repeated WindowStateProto windows = 3;
 }
 
 /* represents WindowState */
 message WindowStateProto {
-  WindowContainerProto window_container = 1;
-  IdentifierProto identifier = 2;
-  int32 display_id = 3;
-  int32 stack_id = 4;
-  .android.view.WindowLayoutParamsProto attributes = 5;
-  .android.graphics.RectProto given_content_insets = 6;
-  .android.graphics.RectProto frame = 7;
-  .android.graphics.RectProto containing_frame = 8;
-  .android.graphics.RectProto parent_frame = 9;
-  .android.graphics.RectProto content_frame = 10;
-  .android.graphics.RectProto content_insets = 11;
-  .android.graphics.RectProto surface_insets = 12;
-  WindowStateAnimatorProto animator = 13;
-  bool animating_exit = 14;
+  optional WindowContainerProto window_container = 1;
+  optional IdentifierProto identifier = 2;
+  optional int32 display_id = 3;
+  optional int32 stack_id = 4;
+  optional .android.view.WindowLayoutParamsProto attributes = 5;
+  optional .android.graphics.RectProto given_content_insets = 6;
+  optional .android.graphics.RectProto frame = 7;
+  optional .android.graphics.RectProto containing_frame = 8;
+  optional .android.graphics.RectProto parent_frame = 9;
+  optional .android.graphics.RectProto content_frame = 10;
+  optional .android.graphics.RectProto content_insets = 11;
+  optional .android.graphics.RectProto surface_insets = 12;
+  optional WindowStateAnimatorProto animator = 13;
+  optional bool animating_exit = 14;
   repeated WindowStateProto child_windows = 15;
 }
 
 message IdentifierProto {
-  int32 hash_code = 1;
-  int32 user_id = 2;
-  string title = 3;
+  optional int32 hash_code = 1;
+  optional int32 user_id = 2;
+  optional string title = 3;
 }
 
 /* represents WindowStateAnimator */
 message WindowStateAnimatorProto {
-  .android.graphics.RectProto last_clip_rect = 1;
-  WindowSurfaceControllerProto surface = 2;
+  optional .android.graphics.RectProto last_clip_rect = 1;
+  optional WindowSurfaceControllerProto surface = 2;
 }
 
 /* represents WindowSurfaceController */
 message WindowSurfaceControllerProto {
-  bool shown = 1;
-  int32 layer = 2;
+  optional bool shown = 1;
+  optional int32 layer = 2;
 }
 
 /* represents ScreenRotationAnimation */
 message ScreenRotationAnimationProto {
-  bool started = 1;
-  bool animation_running = 2;
+  optional bool started = 1;
+  optional bool animation_running = 2;
 }
 
 /* represents WindowContainer */
 message WindowContainerProto {
-  ConfigurationContainerProto configuration_container = 1;
-  int32 orientation = 2;
+  optional ConfigurationContainerProto configuration_container = 1;
+  optional int32 orientation = 2;
 }
 
 /* represents ConfigurationContainer */
 message ConfigurationContainerProto {
-  .android.content.ConfigurationProto override_configuration = 1;
-  .android.content.ConfigurationProto full_configuration = 2;
-  .android.content.ConfigurationProto merged_override_configuration = 3;
+  optional .android.content.ConfigurationProto override_configuration = 1;
+  optional .android.content.ConfigurationProto full_configuration = 2;
+  optional .android.content.ConfigurationProto merged_override_configuration = 3;
 }
diff --git a/core/proto/android/server/wirelesschargerdetector.proto b/core/proto/android/server/wirelesschargerdetector.proto
new file mode 100644
index 0000000..89cf2f8
--- /dev/null
+++ b/core/proto/android/server/wirelesschargerdetector.proto
@@ -0,0 +1,49 @@
+/*
+ * 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 com.android.server.power;
+
+option java_multiple_files = true;
+
+message WirelessChargerDetectorProto {
+    message VectorProto {
+        optional float x = 1;
+        optional float y = 2;
+        optional float z = 3;
+    }
+
+    // Previously observed wireless power state.
+    optional bool is_powered_wirelessly = 1;
+    // True if the device is thought to be at rest on a wireless charger.
+    optional bool is_at_rest = 2;
+    // The gravity vector most recently observed while at rest.
+    optional VectorProto rest = 3;
+    // True if detection is in progress.
+    optional bool is_detection_in_progress = 4;
+    // The time when detection was last performed.
+    optional int64 detection_start_time_ms = 5;
+    // True if the rest position should be updated if at rest.
+    optional bool is_must_update_rest_position = 6;
+    // The total number of samples collected.
+    optional int32 total_samples = 7;
+    // The number of samples collected that showed evidence of not being at rest.
+    optional int32 moving_samples = 8;
+    // The value of the first sample that was collected.
+    optional VectorProto first_sample = 9;
+    // The value of the last sample that was collected.
+    optional VectorProto last_sample = 10;
+}
diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto
index 1f04f71..3f46d2b 100644
--- a/core/proto/android/service/appwidget.proto
+++ b/core/proto/android/service/appwidget.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service.appwidget;
 
 option java_multiple_files = true;
@@ -28,13 +27,13 @@
 
 // represents a bound widget
 message WidgetProto {
-  bool isCrossProfile = 1; // true if host and provider belong to diff users
-  bool isHostStopped = 2; // true if host has not called startListening yet
-  string hostPackage = 3;
-  string providerPackage = 4;
-  string providerClass = 5;
-  int32 minWidth = 6;
-  int32 minHeight = 7;
-  int32 maxWidth = 8;
-  int32 maxHeight = 9;
+  optional bool isCrossProfile = 1; // true if host and provider belong to diff users
+  optional bool isHostStopped = 2; // true if host has not called startListening yet
+  optional string hostPackage = 3;
+  optional string providerPackage = 4;
+  optional string providerClass = 5;
+  optional int32 minWidth = 6;
+  optional int32 minHeight = 7;
+  optional int32 maxWidth = 8;
+  optional int32 maxHeight = 9;
 }
diff --git a/core/proto/android/service/battery.proto b/core/proto/android/service/battery.proto
index 33ad682b..8382b82 100644
--- a/core/proto/android/service/battery.proto
+++ b/core/proto/android/service/battery.proto
@@ -14,20 +14,15 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service.battery;
 
 option java_multiple_files = true;
 option java_outer_classname = "BatteryServiceProto";
 
+import "frameworks/base/core/proto/android/os/batterymanager.proto";
+
 message BatteryServiceDumpProto {
-    enum BatteryPlugged {
-        BATTERY_PLUGGED_NONE = 0;
-        BATTERY_PLUGGED_AC = 1;
-        BATTERY_PLUGGED_USB = 2;
-        BATTERY_PLUGGED_WIRELESS = 4;
-    }
     enum BatteryStatus {
         BATTERY_STATUS_INVALID = 0;
         BATTERY_STATUS_UNKNOWN = 1;
@@ -48,29 +43,29 @@
     }
 
     // If true: UPDATES STOPPED -- use 'reset' to restart
-    bool are_updates_stopped = 1;
+    optional bool are_updates_stopped = 1;
     // Plugged status of power sources
-    BatteryPlugged plugged = 2;
+    optional android.os.BatteryManagerProto.PlugType plugged = 2;
     // Max current in microamperes
-    int32 max_charging_current = 3;
+    optional int32 max_charging_current = 3;
     // Max voltage
-    int32 max_charging_voltage = 4;
+    optional int32 max_charging_voltage = 4;
     // Battery capacity in microampere-hours
-    int32 charge_counter = 5;
+    optional int32 charge_counter = 5;
     // Charging status
-    BatteryStatus status = 6;
+    optional BatteryStatus status = 6;
     // Battery health
-    BatteryHealth health = 7;
+    optional BatteryHealth health = 7;
     // True if the battery is present
-    bool is_present = 8;
+    optional bool is_present = 8;
     // Charge level, from 0 through "scale" inclusive
-    int32 level = 9;
+    optional int32 level = 9;
     // The maximum value for the charge level
-    int32 scale = 10;
+    optional int32 scale = 10;
     // Battery voltage in millivolts
-    int32 voltage = 11;
+    optional int32 voltage = 11;
     // Battery temperature in tenths of a degree Centigrade
-    int32 temperature = 12;
+    optional int32 temperature = 12;
     // The type of battery installed, e.g. "Li-ion"
-    string technology = 13;
+    optional string technology = 13;
 }
diff --git a/core/proto/android/service/batterystats.proto b/core/proto/android/service/batterystats.proto
new file mode 100644
index 0000000..54d3f40
--- /dev/null
+++ b/core/proto/android/service/batterystats.proto
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.service.batterystats;
+
+option java_multiple_files = true;
+option java_outer_classname = "BatteryStatsServiceProto";
+
+import "frameworks/base/core/proto/android/os/batterystats.proto";
+
+message BatteryStatsServiceDumpProto {
+  optional android.os.BatteryStatsProto batterystats = 1;
+}
diff --git a/core/proto/android/service/diskstats.proto b/core/proto/android/service/diskstats.proto
index 4d86526..f725e8a 100644
--- a/core/proto/android/service/diskstats.proto
+++ b/core/proto/android/service/diskstats.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service.diskstats;
 
 option java_multiple_files = true;
@@ -33,47 +32,51 @@
         ENCRYPTION_FILE_BASED = 3;
     }
     // Whether the latency test resulted in an error
-    bool has_test_error = 1;
+    optional bool has_test_error = 1;
     // If the test errored, error message is contained here
-    string error_message = 2;
+    optional string error_message = 2;
     // 512B write latency in milliseconds, if the test was successful
-    int32 write_512b_latency_millis = 3;
+    optional int32 write_512b_latency_millis = 3;
     // Free Space in the major partitions
     repeated DiskStatsFreeSpaceProto partitions_free_space = 4;
     // Is the device using file-based encryption, full disk encryption or other
-    EncryptionType encryption = 5;
+    optional EncryptionType encryption = 5;
     // Cached values of folder sizes, etc.
-    DiskStatsCachedValuesProto cached_folder_sizes = 6;
+    optional DiskStatsCachedValuesProto cached_folder_sizes = 6;
 }
 
 message DiskStatsCachedValuesProto {
-    // Total app data size, in kilobytes
-    int64 agg_apps_size = 1;
+    // Total app code size, in kilobytes
+    optional int64 agg_apps_size = 1;
     // Total app cache size, in kilobytes
-    int64 agg_apps_cache_size = 2;
+    optional int64 agg_apps_cache_size = 2;
     // Size of image files, in kilobytes
-    int64 photos_size = 3;
+    optional int64 photos_size = 3;
     // Size of video files, in kilobytes
-    int64 videos_size = 4;
+    optional int64 videos_size = 4;
     // Size of audio files, in kilobytes
-    int64 audio_size = 5;
+    optional int64 audio_size = 5;
     // Size of downloads, in kilobytes
-    int64 downloads_size = 6;
+    optional int64 downloads_size = 6;
     // Size of system directory, in kilobytes
-    int64 system_size = 7;
+    optional int64 system_size = 7;
     // Size of other files, in kilobytes
-    int64 other_size = 8;
+    optional int64 other_size = 8;
     // Sizes of individual packages
     repeated DiskStatsAppSizesProto app_sizes = 9;
+    // Total app data size, in kilobytes
+    optional int64 agg_apps_data_size = 10;
 }
 
 message DiskStatsAppSizesProto {
     // Name of the package
-    string package_name = 1;
-    // App's data size in kilobytes
-    int64 app_size = 2;
+    optional string package_name = 1;
+    // App's code size in kilobytes
+    optional int64 app_size = 2;
     // App's cache size in kilobytes
-    int64 cache_size = 3;
+    optional int64 cache_size = 3;
+    // App's data size in kilobytes
+    optional int64 app_data_size = 4;
 }
 
 message DiskStatsFreeSpaceProto {
@@ -86,9 +89,9 @@
         FOLDER_SYSTEM = 2;
     }
     // Which folder?
-    Folder folder = 1;
+    optional Folder folder = 1;
     // Available space, in kilobytes
-    int64 available_space = 2;
+    optional int64 available_space = 2;
     // Total space, in kilobytes
-    int64 total_space = 3;
+    optional int64 total_space = 3;
 }
diff --git a/core/proto/android/service/fingerprint.proto b/core/proto/android/service/fingerprint.proto
deleted file mode 100644
index f88b762..0000000
--- a/core/proto/android/service/fingerprint.proto
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2016 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 = "proto3";
-
-package android.service.fingerprint;
-
-option java_multiple_files = true;
-option java_outer_classname = "FingerprintServiceProto";
-
-message FingerprintServiceDumpProto {
-    // Each log may include multiple tuples of (user_id, num_fingerprints).
-    repeated FingerprintUserStatsProto users = 1;
-}
-
-message FingerprintUserStatsProto {
-    // Should be 0, 10, 11, 12, etc. where 0 is the owner.
-    int32 user_id = 1;
-
-    // The number of fingerprints registered to this user.
-    int32 num_fingerprints = 2;
-
-    // Normal fingerprint authentications (e.g. lockscreen).
-    FingerprintActionStatsProto normal = 3;
-
-    // Crypto authentications (e.g. to unlock password storage, make secure
-    // purchases, etc).
-    FingerprintActionStatsProto crypto = 4;
-}
-
-message FingerprintActionStatsProto {
-    // Number of accepted fingerprints.
-    int32 accept = 1;
-
-    // Number of rejected fingerprints.
-    int32 reject = 2;
-
-    // Total number of acquisitions. Should be >= accept+reject due to poor
-    // image acquisition in some cases (too fast, too slow, dirty sensor, etc.)
-    int32 acquire = 3;
-
-    // Total number of lockouts.
-    int32 lockout = 4;
-
-    // Total number of permanent lockouts.
-    int32 lockout_permanent = 5;
-}
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index b8679b0..ee9d6fc 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service;
 
 option java_multiple_files = true;
@@ -29,19 +28,19 @@
 message GraphicsStatsProto {
 
     // The package name of the app
-    string package_name = 1;
+    optional string package_name = 1;
 
     // The version code of the app
-    int32 version_code = 2;
+    optional int32 version_code = 2;
 
     // The start & end timestamps in UTC as
     // milliseconds since January 1, 1970
     // Compatible with java.util.Date#setTime()
-    int64 stats_start = 3;
-    int64 stats_end = 4;
+    optional int64 stats_start = 3;
+    optional int64 stats_end = 4;
 
     // The aggregated statistics for the package
-    GraphicsStatsJankSummaryProto summary = 5;
+    optional GraphicsStatsJankSummaryProto summary = 5;
 
     // The frame time histogram for the package
     repeated GraphicsStatsHistogramBucketProto histogram = 6;
@@ -49,31 +48,31 @@
 
 message GraphicsStatsJankSummaryProto {
     // Distinct frame count.
-    int32 total_frames = 1;
+    optional int32 total_frames = 1;
 
     // Number of frames with slow render time. Frames are considered janky if
     // they took more than a vsync interval (typically 16.667ms) to be rendered.
-    int32 janky_frames = 2;
+    optional int32 janky_frames = 2;
 
     // Number of "missed vsync" events.
-    int32 missed_vsync_count = 3;
+    optional int32 missed_vsync_count = 3;
 
     // Number of "high input latency" events.
-    int32 high_input_latency_count = 4;
+    optional int32 high_input_latency_count = 4;
 
     // Number of "slow UI thread" events.
-    int32 slow_ui_thread_count = 5;
+    optional int32 slow_ui_thread_count = 5;
 
     // Number of "slow bitmap upload" events.
-    int32 slow_bitmap_upload_count = 6;
+    optional int32 slow_bitmap_upload_count = 6;
 
     // Number of "slow draw" events.
-    int32 slow_draw_count = 7;
+    optional int32 slow_draw_count = 7;
 }
 
 message GraphicsStatsHistogramBucketProto {
     // Lower bound of render time in milliseconds.
-    int32 render_millis = 1;
+    optional int32 render_millis = 1;
     // Number of frames in the bucket.
-    int32 frame_count = 2;
+    optional int32 frame_count = 2;
 }
diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto
index 5a577b1..23613fd 100644
--- a/core/proto/android/service/netstats.proto
+++ b/core/proto/android/service/netstats.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service;
 
 option java_multiple_files = true;
@@ -28,23 +27,23 @@
     repeated NetworkInterfaceProto active_uid_interfaces = 2;
 
     // Device level network stats, which may include non-IP layer traffic.
-    NetworkStatsRecorderProto dev_stats = 3;
+    optional NetworkStatsRecorderProto dev_stats = 3;
 
     // IP-layer traffic stats.
-    NetworkStatsRecorderProto xt_stats = 4;
+    optional NetworkStatsRecorderProto xt_stats = 4;
 
     // Per-UID network stats.
-    NetworkStatsRecorderProto uid_stats = 5;
+    optional NetworkStatsRecorderProto uid_stats = 5;
 
     // Per-UID, per-tag network stats, excluding the default tag (i.e. tag=0).
-    NetworkStatsRecorderProto uid_tag_stats = 6;
+    optional NetworkStatsRecorderProto uid_tag_stats = 6;
 }
 
 // Corresponds to NetworkStatsService.mActiveIfaces/mActiveUidIfaces.
 message NetworkInterfaceProto {
-    string interface = 1;
+    optional string interface = 1;
 
-    NetworkIdentitySetProto identities = 2;
+    optional NetworkIdentitySetProto identities = 2;
 }
 
 // Corresponds to NetworkIdentitySet.
@@ -55,22 +54,22 @@
 // Corresponds to NetworkIdentity.
 message NetworkIdentityProto {
     // Constats from ConnectivityManager.TYPE_*.
-    int32 type = 1;
+    optional int32 type = 1;
 
-    string subscriber_id = 2;
+    optional string subscriber_id = 2;
 
-    string network_id = 3;
+    optional string network_id = 3;
 
-    bool roaming = 4;
+    optional bool roaming = 4;
 
-    bool metered = 5;
+    optional bool metered = 5;
 }
 
 // Corresponds to NetworkStatsRecorder.
 message NetworkStatsRecorderProto {
-    int64 pending_total_bytes = 1;
+    optional int64 pending_total_bytes = 1;
 
-    NetworkStatsCollectionProto complete_history = 2;
+    optional NetworkStatsCollectionProto complete_history = 2;
 }
 
 // Corresponds to NetworkStatsCollection.
@@ -80,26 +79,26 @@
 
 // Corresponds to NetworkStatsCollection.mStats.
 message NetworkStatsCollectionStatsProto {
-    NetworkStatsCollectionKeyProto key = 1;
+    optional NetworkStatsCollectionKeyProto key = 1;
 
-    NetworkStatsHistoryProto history = 2;
+    optional NetworkStatsHistoryProto history = 2;
 }
 
 // Corresponds to NetworkStatsCollection.Key.
 message NetworkStatsCollectionKeyProto {
-    NetworkIdentitySetProto identity = 1;
+    optional NetworkIdentitySetProto identity = 1;
 
-    int32 uid = 2;
+    optional int32 uid = 2;
 
-    int32 set = 3;
+    optional int32 set = 3;
 
-    int32 tag = 4;
+    optional int32 tag = 4;
 }
 
 // Corresponds to NetworkStatsHistory.
 message NetworkStatsHistoryProto {
     // Duration for this bucket in milliseconds.
-    int64 bucket_duration_ms = 1;
+    optional int64 bucket_duration_ms = 1;
 
     repeated NetworkStatsHistoryBucketProto buckets = 2;
 }
@@ -107,15 +106,15 @@
 // Corresponds to each bucket in NetworkStatsHistory.
 message NetworkStatsHistoryBucketProto {
     // Bucket start time in milliseconds since epoch.
-    int64 bucket_start_ms = 1;
+    optional int64 bucket_start_ms = 1;
 
-    int64 rx_bytes = 2;
+    optional int64 rx_bytes = 2;
 
-    int64 rx_packets = 3;
+    optional int64 rx_packets = 3;
 
-    int64 tx_bytes = 4;
+    optional int64 tx_bytes = 4;
 
-    int64 tx_packets = 5;
+    optional int64 tx_packets = 5;
 
-    int64 operations = 6;
+    optional int64 operations = 6;
 }
diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto
index 819460e..7a0e152 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -14,51 +14,119 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service.notification;
 
 option java_multiple_files = true;
 option java_outer_classname = "NotificationServiceProto";
 
+import "frameworks/base/core/proto/android/app/notification_channel.proto";
+import "frameworks/base/core/proto/android/app/notification_channel_group.proto";
+import "frameworks/base/core/proto/android/app/notificationmanager.proto";
+import "frameworks/base/core/proto/android/content/component_name.proto";
+
 message NotificationServiceDumpProto {
     repeated NotificationRecordProto records = 1;
 
-    ZenModeProto zen = 2;
+    optional ZenModeProto zen = 2;
+
+    optional ManagedServicesProto notification_listeners = 3;
+
+    optional int32 listener_hints = 4;
+
+    repeated ListenersDisablingEffectsProto listeners_disabling_effects = 5;
+
+    optional ManagedServicesProto notification_assistants = 6;
+
+    optional ManagedServicesProto condition_providers = 7;
+
+    optional RankingHelperProto ranking_config = 8;
 }
 
 message NotificationRecordProto {
-    string key = 1;
-    State state = 2;
-    int32 flags = 3;
-    string channelId = 4;
-    string sound = 5;
-    int32 sound_usage = 6;
-    bool can_vibrate = 7;
-    bool can_show_light = 8;
-    string group_key = 9;
-    int32 importance = 10;
+    optional string key = 1;
+
+    enum State {
+        ENQUEUED = 0;
+        POSTED = 1;
+        SNOOZED = 2;
+    }
+    optional State state = 2;
+    optional int32 flags = 3;
+    optional string channelId = 4;
+    optional string sound = 5;
+    optional int32 sound_usage = 6;
+    optional bool can_vibrate = 7;
+    optional bool can_show_light = 8;
+    optional string group_key = 9;
+    optional int32 importance = 10;
 }
 
-enum State {
-    ENQUEUED = 0;
+message ListenersDisablingEffectsProto {
+    optional int32 hint = 1;
+    repeated ManagedServiceInfoProto listeners = 2;
+}
 
-    POSTED = 1;
+message ManagedServiceInfoProto {
+    optional android.content.ComponentNameProto component = 1;
+    optional int32 user_id = 2;
+    optional string service = 3;
+    optional bool is_system = 4;
+    optional bool is_guest = 5;
+}
 
-    SNOOZED = 2;
+message ManagedServicesProto {
+    optional string caption = 1;
+
+    message ServiceProto {
+        repeated string name = 1;
+        optional int32 user_id = 2;
+        optional bool is_primary = 3;
+    }
+    repeated ServiceProto approved = 2;
+
+    // All of this type/caption enabled for current profiles.
+    repeated android.content.ComponentNameProto enabled = 3;
+
+
+    repeated ManagedServiceInfoProto live_services = 4;
+
+    // Snoozed for current profiles.
+    repeated android.content.ComponentNameProto snoozed = 5;
+}
+
+message RankingHelperProto {
+    repeated string notification_signal_extractors = 1;
+
+    message RecordProto {
+        optional string package = 1;
+        // Default value is UNKNOWN_UID = USER_NULL = -10000.
+        optional int32 uid = 2;
+        // Default is IMPORTANCE_UNSPECIFIED (-1000).
+        optional int32 importance = 3;
+        // Default is PRIORITY_DEFAULT (0).
+        optional int32 priority = 4;
+        // Default is VISIBILITY_NO_OVERRIDE (-1000).
+        optional int32 visibility = 5;
+        // Default is true.
+        optional bool show_badge = 6;
+        repeated android.app.NotificationChannelProto channels = 7;
+        repeated android.app.NotificationChannelGroupProto channel_groups = 8;
+    }
+    repeated RecordProto records = 2;
+    repeated RecordProto records_restored_without_uid = 3;
 }
 
 message ZenModeProto {
-    ZenMode zen_mode = 1;
+    enum ZenMode {
+        ZEN_MODE_OFF = 0;
+        ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
+        ZEN_MODE_NO_INTERRUPTIONS = 2;
+        ZEN_MODE_ALARMS = 3;
+    }
+    optional ZenMode zen_mode = 1;
     repeated string enabled_active_conditions = 2;
-    int32 suppressed_effects = 3;
+    optional int32 suppressed_effects = 3;
     repeated string suppressors = 4;
-    string policy = 5;
+    optional android.app.PolicyProto policy = 5;
 }
-
-enum ZenMode {
-    ZEN_MODE_OFF = 0;
-    ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
-    ZEN_MODE_NO_INTERRUPTIONS = 2;
-    ZEN_MODE_ALARMS = 3;
-}
\ No newline at end of file
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 326b0eb..aa1a575 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -14,43 +14,40 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service.pm;
 
+import "frameworks/base/core/proto/android/content/featureinfo.proto";
+
 option java_multiple_files = true;
 option java_outer_classname = "PackageServiceProto";
 
 message PackageServiceDumpProto {
     message PackageShortProto {
         // Name of package. e.g. "com.android.providers.telephony".
-        string name = 1;
+        optional string name = 1;
         // UID for this package as assigned by Android OS.
-        int32 uid = 2;
+        optional int32 uid = 2;
     }
     message SharedLibraryProto {
-        string name = 1;
+        optional string name = 1;
         // True if library path is not null (jar), false otherwise (apk)
-        bool is_jar = 2;
+        optional bool is_jar = 2;
         // Should be filled if is_jar is true
-        string path = 3;
+        optional string path = 3;
         // Should be filled if is_jar is false
-        string apk = 4;
-    }
-    message FeatureProto {
-        string name = 1;
-        int32 version = 2;
+        optional string apk = 4;
     }
     message SharedUserProto {
-        int32 user_id = 1;
-        string name = 2;
+        optional int32 user_id = 1;
+        optional string name = 2;
     }
 
     // Installed packages.
-    PackageShortProto required_verifier_package = 1;
-    PackageShortProto verifier_package = 2;
+    optional PackageShortProto required_verifier_package = 1;
+    optional PackageShortProto verifier_package = 2;
     repeated SharedLibraryProto shared_libraries = 3;
-    repeated FeatureProto features = 4;
+    repeated android.content.pm.FeatureInfoProto features = 4;
     repeated PackageProto packages = 5;
     repeated SharedUserProto shared_users = 6;
     // Messages from the settings problem file
@@ -59,8 +56,8 @@
 
 message PackageProto {
     message SplitProto {
-        string name = 1;
-        int32 revision_code = 2;
+        optional string name = 1;
+        optional int32 revision_code = 2;
     }
     message UserInfoProto {
         enum InstallType {
@@ -87,32 +84,32 @@
             COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
         }
 
-        int32 id = 1;
-        InstallType install_type = 2;
+        optional int32 id = 1;
+        optional InstallType install_type = 2;
         // Is the app restricted by owner / admin
-        bool is_hidden = 3;
-        bool is_suspended = 4;
-        bool is_stopped = 5;
-        bool is_launched = 6;
-        EnabledState enabled_state = 7;
-        string last_disabled_app_caller = 8;
+        optional bool is_hidden = 3;
+        optional bool is_suspended = 4;
+        optional bool is_stopped = 5;
+        optional bool is_launched = 6;
+        optional EnabledState enabled_state = 7;
+        optional string last_disabled_app_caller = 8;
     }
 
     // Name of package. e.g. "com.android.providers.telephony".
-    string name = 1;
+    optional string name = 1;
     // UID for this package as assigned by Android OS.
-    int32 uid = 2;
+    optional int32 uid = 2;
     // Package's reported version.
-    int32 version_code = 3;
+    optional int32 version_code = 3;
     // Package's reported version string (what's displayed to the user).
-    string version_string = 4;
+    optional string version_string = 4;
     // UTC timestamp of install
-    int64 install_time_ms = 5;
+    optional int64 install_time_ms = 5;
     // Millisecond UTC timestamp of latest update adjusted to Google's server clock.
-    int64 update_time_ms = 6;
+    optional int64 update_time_ms = 6;
     // From "dumpsys package" - name of package which installed this one.
     // Typically "" if system app or "com.android.vending" if Play Store.
-    string installer_name = 7;
+    optional string installer_name = 7;
     // Split APKs.
     repeated SplitProto splits = 8;
     // Per-user package info.
diff --git a/core/proto/android/service/power.proto b/core/proto/android/service/power.proto
deleted file mode 100644
index 1830dbf..0000000
--- a/core/proto/android/service/power.proto
+++ /dev/null
@@ -1,408 +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.
- */
-
-syntax = "proto3";
-
-package android.service.power;
-
-option java_multiple_files = true;
-option java_outer_classname = "PowerServiceProto";
-
-import "frameworks/base/core/proto/android/os/looper.proto";
-import "frameworks/base/core/proto/android/os/worksource.proto";
-import "frameworks/base/core/proto/android/service/wirelesschargerdetector.proto";
-
-message PowerServiceDumpProto {
-    message ConstantsProto {
-        bool is_no_cached_wake_locks = 1;
-    }
-    message ActiveWakeLocksProto {
-        bool is_cpu = 1;
-        bool is_screen_bright = 2;
-        bool is_screen_dim = 3;
-        bool is_button_bright = 4;
-        bool is_proximity_screen_off = 5;
-        // only set if already awake
-        bool is_stay_awake = 6;
-        bool is_doze = 7;
-        bool is_draw = 8;
-    }
-    message UserActivityProto {
-        bool is_screen_bright = 1;
-        bool is_screen_dim = 2;
-        bool is_screen_dream = 3;
-    }
-    message UidProto {
-        // Enum values gotten from ActivityManager.java
-        enum ProcessState {
-            // Process is a persistent system process.
-            PROCESS_STATE_PERSISTENT = 0;
-            // Process is a persistent system process and is doing UI.
-            PROCESS_STATE_PERSISTENT_UI = 1;
-            // Process is hosting the current top activities. Note that this
-            // covers all activities that are visible to the user.
-            PROCESS_STATE_TOP = 2;
-            // Process is hosting a foreground service due to a system binding.
-            PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;
-            // Process is hosting a foreground service.
-            PROCESS_STATE_FOREGROUND_SERVICE = 4;
-            // Same as {@link #PROCESS_STATE_TOP} but while device is sleeping.
-            PROCESS_STATE_TOP_SLEEPING = 5;
-            // Process is important to the user, and something they are aware of.
-            PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
-            // Process is important to the user, but not something they are aware of.
-            PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
-            // Process is in the background running a backup/restore operation.
-            PROCESS_STATE_BACKUP = 8;
-            // Process is in the background, but it can't restore its state so
-            // we want to try to avoid killing it.
-            PROCESS_STATE_HEAVY_WEIGHT = 9;
-            // Process is in the background running a service.
-            PROCESS_STATE_SERVICE = 10;
-            // Process is in the background running a receiver.
-            PROCESS_STATE_RECEIVER = 11;
-            // Process is in the background but hosts the home activity.
-            PROCESS_STATE_HOME = 12;
-            // Process is in the background but hosts the last shown activity.
-            PROCESS_STATE_LAST_ACTIVITY = 13;
-            // Process is being cached for later use and contains activities.
-            PROCESS_STATE_CACHED_ACTIVITY = 14;
-            // Process is being cached for later use and is a client of another
-            // cached process that contains activities.
-            PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15;
-            // Process is being cached for later use and is empty.
-            PROCESS_STATE_CACHED_EMPTY = 16;
-            // Process does not exist.
-            PROCESS_STATE_NONEXISTENT = 17;
-        }
-        int32 uid = 1;
-        string uid_string = 2;
-        bool is_active = 3;
-        int32 num_wake_locks = 4;
-        bool is_process_state_unknown = 5;
-        ProcessState process_state = 6;
-    }
-
-    // Enum values gotten from PowerManagerInternal.java
-    enum Wakefulness {
-        WAKEFULNESS_ASLEEP = 0;
-        WAKEFULNESS_AWAKE = 1;
-        WAKEFULNESS_DREAMING = 2;
-        WAKEFULNESS_DOZING = 3;
-        WAKEFULNESS_UNKNOWN = 4;
-    }
-    // Enum values gotten from BatteryManager.java
-    enum PlugType {
-        PLUG_TYPE_NONE = 0;
-        PLUG_TYPE_PLUGGED_AC = 1;
-        PLUG_TYPE_PLUGGED_USB = 2;
-        PLUG_TYPE_PLUGGED_WIRELESS = 4;
-    }
-    // Enum values gotten from Intent.java
-    enum DockState {
-        DOCK_STATE_UNDOCKED = 0;
-        DOCK_STATE_DESK = 1;
-        DOCK_STATE_CAR = 2;
-        DOCK_STATE_LE_DESK = 3;
-        DOCK_STATE_HE_DESK = 4;
-    }
-
-    ConstantsProto constants = 1;
-    // A bitfield that indicates what parts of the power state have
-    // changed and need to be recalculated.
-    int32 dirty = 2;
-    // Indicates whether the device is awake or asleep or somewhere in between.
-    Wakefulness wakefulness = 3;
-    bool is_wakefulness_changing = 4;
-    // True if the device is plugged into a power source.
-    bool is_powered = 5;
-    // The current plug type
-    PlugType plug_type = 6;
-    // The current battery level percentage.
-    int32 battery_level = 7;
-    // The battery level percentage at the time the dream started.
-    int32 battery_level_when_dream_started = 8;
-    // The current dock state.
-    DockState dock_state = 9;
-    // True if the device should stay on.
-    bool is_stay_on = 10;
-    // True if the proximity sensor reads a positive result.
-    bool is_proximity_positive = 11;
-    // True if boot completed occurred.  We keep the screen on until this happens.
-    bool is_boot_completed = 12;
-    // True if systemReady() has been called.
-    bool is_system_ready = 13;
-    // True if auto-suspend mode is enabled.
-    bool is_hal_auto_suspend_mode_enabled = 14;
-    // True if interactive mode is enabled.
-    bool is_hal_auto_interactive_mode_enabled = 15;
-    // Summarizes the state of all active wakelocks.
-    ActiveWakeLocksProto active_wake_locks = 16;
-    // Have we scheduled a message to check for long wake locks?  This is when
-    // we will check. (In milliseconds timestamp)
-    int64 notify_long_scheduled_ms = 17;
-    // Last time we checked for long wake locks. (In milliseconds timestamp)
-    int64 notify_long_dispatched_ms = 18;
-    // The time we decided to do next long check. (In milliseconds timestamp)
-    int64 notify_long_next_check_ms = 19;
-    // Summarizes the effect of the user activity timer.
-    UserActivityProto user_activity = 20;
-    // If true, instructs the display controller to wait for the proximity
-    // sensor to go negative before turning the screen on.
-    bool is_request_wait_for_negative_proximity = 21;
-    // True if MSG_SANDMAN has been scheduled.
-    bool is_sandman_scheduled = 22;
-    // True if the sandman has just been summoned for the first time since entering
-    // the dreaming or dozing state.  Indicates whether a new dream should begin.
-    bool is_sandman_summoned = 23;
-    // If true, the device is in low power mode.
-    bool is_low_power_mode_enabled = 24;
-    // True if the battery level is currently considered low.
-    bool is_battery_level_low = 25;
-    // True if we are currently in light device idle mode.
-    bool is_light_device_idle_mode = 26;
-    // True if we are currently in device idle mode.
-    bool is_device_idle_mode = 27;
-    // Set of app ids that we will always respect the wake locks for.
-    repeated int32 device_idle_whitelist = 28;
-    // Set of app ids that are temporarily allowed to acquire wakelocks due to
-    // high-pri message
-    repeated int32 device_idle_temp_whitelist = 29;
-    // Timestamp of the last time the device was awoken.
-    int64 last_wake_time_ms = 30;
-    // Timestamp of the last time the device was put to sleep.
-    int64 last_sleep_time_ms = 31;
-    // Timestamp of the last call to user activity.
-    int64 last_user_activity_time_ms = 32;
-    int64 last_user_activity_time_no_change_lights_ms = 33;
-    // Timestamp of last interactive power hint.
-    int64 last_interactive_power_hint_time_ms = 34;
-    // Timestamp of the last screen brightness boost.
-    int64 last_screen_brightness_boost_time_ms = 35;
-    // True if screen brightness boost is in progress.
-    bool is_screen_brightness_boost_in_progress = 36;
-    // True if the display power state has been fully applied, which means the
-    // display is actually on or actually off or whatever was requested.
-    bool is_display_ready = 37;
-    // True if the wake lock suspend blocker has been acquired.
-    bool is_holding_wake_lock_suspend_blocker = 38;
-    // The suspend blocker used to keep the CPU alive when the display is on, the
-    // display is getting ready or there is user activity (in which case the
-    // display must be on).
-    bool is_holding_display_suspend_blocker = 39;
-    // Settings and configuration
-    PowerServiceSettingsAndConfigurationDumpProto settings_and_configuration = 40;
-    // Sleep timeout in ms
-    sint32 sleep_timeout_ms = 41;
-    // Screen off timeout in ms
-    int32 screen_off_timeout_ms = 42;
-    // Screen dim duration in ms
-    int32 screen_dim_duration_ms = 43;
-    // We are currently in the middle of a batch change of uids.
-    bool are_uids_changing = 44;
-    // Some uids have actually changed while mUidsChanging was true.
-    bool are_uids_changed = 45;
-    // List of UIDs and their states
-    repeated UidProto uids = 46;
-    android.os.LooperProto looper = 47;
-    // List of all wake locks acquired by applications.
-    repeated WakeLockProto wake_locks = 48;
-    // List of all suspend blockers.
-    repeated SuspendBlockerProto suspend_blockers = 49;
-    WirelessChargerDetectorProto wireless_charger_detector = 50;
-}
-
-message SuspendBlockerProto {
-    string name = 1;
-    int32 reference_count = 2;
-}
-
-message WakeLockProto {
-    message WakeLockFlagsProto {
-        // Turn the screen on when the wake lock is acquired.
-        bool is_acquire_causes_wakeup = 1;
-        // When this wake lock is released, poke the user activity timer
-        // so the screen stays on for a little longer.
-        bool is_on_after_release = 2;
-    }
-
-    // Enum values gotten from PowerManager.java
-    enum LockLevel {
-        WAKE_LOCK_INVALID = 0;
-        // Ensures that the CPU is running.
-        PARTIAL_WAKE_LOCK = 1;
-        // Ensures that the screen is on (but may be dimmed).
-        SCREEN_DIM_WAKE_LOCK = 6;
-        // Ensures that the screen is on at full brightness.
-        SCREEN_BRIGHT_WAKE_LOCK = 10;
-        // Ensures that the screen and keyboard backlight are on at full brightness.
-        FULL_WAKE_LOCK = 26;
-        // Turns the screen off when the proximity sensor activates.
-        PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
-        // Put the screen in a low power state and allow the CPU to suspend
-        // if no other wake locks are held.
-        DOZE_WAKE_LOCK = 64;
-        // Keep the device awake enough to allow drawing to occur.
-        DRAW_WAKE_LOCK = 128;
-    }
-
-    LockLevel lock_level = 1;
-    string tag = 2;
-    WakeLockFlagsProto flags = 3;
-    bool is_disabled = 4;
-    // Acquire time in ms
-    int64 acq_ms = 5;
-    bool is_notified_long = 6;
-    // Owner UID
-    int32 uid = 7;
-    // Owner PID
-    int32 pid = 8;
-    android.os.WorkSourceProto work_source = 9;
-}
-
-message PowerServiceSettingsAndConfigurationDumpProto {
-    message StayOnWhilePluggedInProto {
-        bool is_stay_on_while_plugged_in_ac = 1;
-        bool is_stay_on_while_plugged_in_usb = 2;
-        bool is_stay_on_while_plugged_in_wireless = 3;
-    }
-    message ScreenBrightnessSettingLimitsProto {
-        int32 setting_minimum = 1;
-        int32 setting_maximum = 2;
-        int32 setting_default = 3;
-        int32 setting_for_vr_default = 4;
-    }
-
-    // Enum values gotten from Settings.java
-    enum ScreenBrightnessMode {
-        SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
-        SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1;
-    }
-    // Enum values gotten from Display.java
-    enum DisplayState {
-        DISPLAY_STATE_UNKNOWN = 0;
-        DISPLAY_STATE_OFF = 1;
-        DISPLAY_STATE_ON = 2;
-        DISPLAY_STATE_DOZE = 3;
-        DISPLAY_STATE_DOZE_SUSPEND = 4;
-        DISPLAY_STATE_VR = 5;
-    }
-
-
-    // True to decouple auto-suspend mode from the display state.
-    bool is_decouple_hal_auto_suspend_mode_from_display_config = 1;
-    // True to decouple interactive mode from the display state.
-    bool is_decouple_hal_interactive_mode_from_display_config = 2;
-    // True if the device should wake up when plugged or unplugged.
-    bool is_wake_up_when_plugged_or_unplugged_config = 3;
-    // True if the device should wake up when plugged or unplugged in theater mode.
-    bool is_wake_up_when_plugged_or_unplugged_in_theater_mode_config = 4;
-    // True if theater mode is enabled
-    bool is_theater_mode_enabled = 5;
-    // True if the device should suspend when the screen is off due to proximity.
-    bool is_suspend_when_screen_off_due_to_proximity_config = 6;
-    // True if dreams are supported on this device.
-    bool are_dreams_supported_config = 7;
-    // Default value for dreams enabled
-    bool are_dreams_enabled_by_default_config = 8;
-    // Default value for dreams activate-on-sleep
-    bool are_dreams_activated_on_sleep_by_default_config = 9;
-    // Default value for dreams activate-on-dock
-    bool are_dreams_activated_on_dock_by_default_config = 10;
-    // True if dreams can run while not plugged in.
-    bool are_dreams_enabled_on_battery_config = 11;
-    // Minimum battery level to allow dreaming when powered.
-    // Use -1 to disable this safety feature.
-    sint32 dreams_battery_level_minimum_when_powered_config = 12;
-    // Minimum battery level to allow dreaming when not powered.
-    // Use -1 to disable this safety feature.
-    sint32 dreams_battery_level_minimum_when_not_powered_config = 13;
-    // If the battery level drops by this percentage and the user activity
-    // timeout has expired, then assume the device is receiving insufficient
-    // current to charge effectively and terminate the dream.  Use -1 to disable
-    // this safety feature.
-    sint32 dreams_battery_level_drain_cutoff_config = 14;
-    // True if dreams are enabled by the user.
-    bool are_dreams_enabled_setting = 15;
-    // True if dreams should be activated on sleep.
-    bool are_dreams_activate_on_sleep_setting = 16;
-    // True if dreams should be activated on dock.
-    bool are_dreams_activate_on_dock_setting = 17;
-    // True if doze should not be started until after the screen off transition.
-    bool is_doze_after_screen_off_config = 18;
-    // If true, the device is in low power mode.
-    bool is_low_power_mode_setting = 19;
-    // Current state of whether the settings are allowing auto low power mode.
-    bool is_auto_low_power_mode_configured = 20;
-    // The user turned off low power mode below the trigger level
-    bool is_auto_low_power_mode_snoozing = 21;
-    // The minimum screen off timeout, in milliseconds.
-    int32 minimum_screen_off_timeout_config_ms = 22;
-    // The screen dim duration, in milliseconds.
-    int32 maximum_screen_dim_duration_config_ms = 23;
-    // The maximum screen dim time expressed as a ratio relative to the screen off timeout.
-    float maximum_screen_dim_ratio_config = 24;
-    // The screen off timeout setting value in milliseconds.
-    int32 screen_off_timeout_setting_ms = 25;
-    // The sleep timeout setting value in milliseconds.
-    sint32 sleep_timeout_setting_ms = 26;
-    // The maximum allowable screen off timeout according to the device administration policy.
-    int32 maximum_screen_off_timeout_from_device_admin_ms = 27;
-    bool is_maximum_screen_off_timeout_from_device_admin_enforced_locked = 28;
-    // The stay on while plugged in setting.
-    // A set of battery conditions under which to make the screen stay on.
-    StayOnWhilePluggedInProto stay_on_while_plugged_in = 29;
-    // The screen brightness setting, from 0 to 255.
-    // Use -1 if no value has been set.
-    sint32 screen_brightness_setting = 30;
-    // The screen auto-brightness adjustment setting, from -1 to 1.
-    // Use 0 if there is no adjustment.
-    float screen_auto_brightness_adjustment_setting = 31;
-    // The screen brightness mode.
-    ScreenBrightnessMode screen_brightness_mode_setting = 32;
-    // The screen brightness setting override from the window manager
-    // to allow the current foreground activity to override the brightness.
-    // Use -1 to disable.
-    sint32 screen_brightness_override_from_window_manager = 33;
-    // The user activity timeout override from the window manager
-    // to allow the current foreground activity to override the user activity
-    // timeout. Use -1 to disable.
-    sint64 user_activity_timeout_override_from_window_manager_ms = 34;
-    // The window manager has determined the user to be inactive via other means.
-    // Set this to false to disable.
-    bool is_user_inactive_override_from_window_manager = 35;
-    // The screen brightness setting override from the settings application
-    // to temporarily adjust the brightness until next updated,
-    // Use -1 to disable.
-    sint32 temporary_screen_brightness_setting_override = 36;
-    // The screen brightness adjustment setting override from the settings
-    // application to temporarily adjust the auto-brightness adjustment factor
-    // until next updated, in the range -1..1.
-    // Use NaN to disable.
-    float temporary_screen_auto_brightness_adjustment_setting_override = 37;
-    // The screen state to use while dozing.
-    DisplayState doze_screen_state_override_from_dream_manager = 38;
-    // The screen brightness to use while dozing.
-    float dozed_screen_brightness_override_from_dream_manager = 39;
-    // Screen brightness settings limits.
-    ScreenBrightnessSettingLimitsProto screen_brightness_setting_limits = 40;
-    // The screen brightness setting, from 0 to 255, to be used while in VR Mode.
-    int32 screen_brightness_for_vr_setting = 41;
-    // True if double tap to wake is enabled
-    bool is_double_tap_wake_enabled = 42;
-    // True if we are currently in VR Mode.
-    bool is_vr_mode_enabled = 43;
-}
diff --git a/core/proto/android/service/print.proto b/core/proto/android/service/print.proto
index f099872..c2be7f1 100644
--- a/core/proto/android/service/print.proto
+++ b/core/proto/android/service/print.proto
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.service.print;
 
 option java_multiple_files = true;
@@ -30,7 +29,7 @@
 
 message PrintUserStateProto {
     // Should be 0, 10, 11, 12, etc. where 0 is the owner.
-    int32 user_id = 1;
+    optional int32 user_id = 1;
 
     // The installed print services
     repeated InstalledPrintServiceProto installed_services = 2;
@@ -48,18 +47,18 @@
     repeated PrinterDiscoverySessionProto discovery_sessions = 6;
 
     // The print spooler state
-    PrintSpoolerStateProto print_spooler_state = 7;
+    optional PrintSpoolerStateProto print_spooler_state = 7;
 }
 
 message PrintSpoolerStateProto {
     // Is the print spooler destroyed?
-    bool is_destroyed = 1;
+    optional bool is_destroyed = 1;
 
     // Is the print spooler bound?
-    bool is_bound = 2;
+    optional bool is_bound = 2;
 
     // State internal to the print spooler
-    PrintSpoolerInternalStateProto internal_state = 3;
+    optional PrintSpoolerInternalStateProto internal_state = 3;
 }
 
 message PrintSpoolerInternalStateProto {
@@ -75,7 +74,7 @@
 
 message PrinterCapabilitiesProto {
     // Minimum margins of the printer
-    MarginsProto min_margins = 1;
+    optional MarginsProto min_margins = 1;
 
     // List of supported media sizes
     repeated MediaSizeProto media_sizes = 2;
@@ -92,10 +91,10 @@
 
 message PrinterInfoProto {
     // The id of the printer
-    PrinterIdProto id = 1;
+    optional PrinterIdProto id = 1;
 
     // The name of the printer
-    string name = 2;
+    optional string name = 2;
 
     enum Status {
         // unused
@@ -111,21 +110,21 @@
         STATUS_UNAVAILABLE = 3;
     }
     // The status of the printer
-    Status status = 3;
+    optional Status status = 3;
 
     // The description of the printer
-    string description = 4;
+    optional string description = 4;
 
     // The capabilities of the printer
-    PrinterCapabilitiesProto capabilities = 5;
+    optional PrinterCapabilitiesProto capabilities = 5;
 }
 
 message PrinterDiscoverySessionProto {
     // Is this session destroyed?
-    bool is_destroyed = 1;
+    optional bool is_destroyed = 1;
 
     // Is printer discovery in progress?
-    bool is_printer_discovery_in_progress = 2;
+    optional bool is_printer_discovery_in_progress = 2;
 
     // List of printer discovery observers
     repeated string printer_discovery_observers = 3;
@@ -142,44 +141,44 @@
 
 message InstalledPrintServiceProto {
     // Component name of the service
-    android.content.ComponentNameProto component_name = 1;
+    optional android.content.ComponentNameProto component_name = 1;
 
     // Settings activity for this service
-    string settings_activity = 2;
+    optional string settings_activity = 2;
 
     // Add printers activity for this service
-    string add_printers_activity = 3;
+    optional string add_printers_activity = 3;
 
     // Advances options activity for this service
-    string advanced_options_activity = 4;
+    optional string advanced_options_activity = 4;
 }
 
 message PrinterIdProto {
     // Component name of the service that reported the printer
-    android.content.ComponentNameProto service_name = 1;
+    optional android.content.ComponentNameProto service_name = 1;
 
     // Local id of the printer
-    string local_id = 2;
+    optional string local_id = 2;
 }
 
 message ActivePrintServiceProto {
     // Component name of the service
-    android.content.ComponentNameProto component_name = 1;
+    optional android.content.ComponentNameProto component_name = 1;
 
     // Is the active service destroyed
-    bool is_destroyed = 2;
+    optional bool is_destroyed = 2;
 
     // Is the active service bound
-    bool is_bound = 3;
+    optional bool is_bound = 3;
 
     // Has the active service a discovery session
-    bool has_discovery_session = 4;
+    optional bool has_discovery_session = 4;
 
     // Has the active service a active print jobs
-    bool has_active_print_jobs = 5;
+    optional bool has_active_print_jobs = 5;
 
     // Is the active service discovering printers
-    bool is_discovering_printers = 6;
+    optional bool is_discovering_printers = 6;
 
     // The tracked printers of this active service
     repeated PrinterIdProto tracked_printers = 7;
@@ -187,58 +186,58 @@
 
 message MediaSizeProto {
     // Id of this media size
-    string id = 1;
+    optional string id = 1;
 
     // Label of this media size
-    string label = 2;
+    optional string label = 2;
 
     // Height of the media
-    int32 height_mils = 3;
+    optional int32 height_mils = 3;
 
     // Width of the media
-    int32 width_mils = 4;
+    optional int32 width_mils = 4;
 }
 
 message ResolutionProto {
     // Id of this resolution
-    string id = 1;
+    optional string id = 1;
 
     // Label for this resoltion
-    string label = 2;
+    optional string label = 2;
 
     // Resolution in horizontal orientation
-    int32 horizontal_dpi = 3;
+    optional int32 horizontal_dpi = 3;
 
     // Resolution in vertical orientation
-    int32 vertical_dpi = 4;
+    optional int32 vertical_dpi = 4;
 }
 
 message MarginsProto {
     // Space at the top
-    int32 top_mils = 1;
+    optional int32 top_mils = 1;
 
     // Space at the left
-    int32 left_mils = 2;
+    optional int32 left_mils = 2;
 
     // Space at the right
-    int32 right_mils = 3;
+    optional int32 right_mils = 3;
 
     // Space at the bottom
-    int32 bottom_mils = 4;
+    optional int32 bottom_mils = 4;
 }
 
 message PrintAttributesProto {
     // Media to use
-    ResolutionProto media_size = 1;
+    optional ResolutionProto media_size = 1;
 
     // Is the media in portrait mode?
-    bool is_portrait = 2;
+    optional bool is_portrait = 2;
 
     // Resolution to use
-    ResolutionProto resolution = 3;
+    optional ResolutionProto resolution = 3;
 
     // Margins around the document
-    MarginsProto min_margins = 4;
+    optional MarginsProto min_margins = 4;
 
     enum ColorMode {
         // unused
@@ -251,7 +250,7 @@
         COLOR_MODE_COLOR = 2;
     }
     // Color mode to use
-    ColorMode color_mode = 5;
+    optional ColorMode color_mode = 5;
 
     enum DuplexMode {
         // unused
@@ -267,37 +266,37 @@
         DUPLEX_MODE_SHORT_EDGE = 4;
     }
     // Duplex mode to use
-    DuplexMode duplex_mode = 6;
+    optional DuplexMode duplex_mode = 6;
 }
 
 message PrintDocumentInfoProto {
     // Name of the document to print
-    string name = 1;
+    optional string name = 1;
 
     // Number of pages in the doc
-    int32 page_count = 2;
+    optional int32 page_count = 2;
 
     // Type of content (see PrintDocumentInfo.ContentType)
-    int32 content_type = 3;
+    optional int32 content_type = 3;
 
     // The size of the the document
-    int64 data_size = 4;
+    optional int64 data_size = 4;
 }
 
 message PageRangeProto {
     // Start of the range
-    int32 start = 1;
+    optional int32 start = 1;
 
     // End of the range (included)
-    int32 end = 2;
+    optional int32 end = 2;
 }
 
 message PrintJobInfoProto {
     // Label of the job
-    string label = 1;
+    optional string label = 1;
 
     // Id of the job
-    string print_job_id = 2;
+    optional string print_job_id = 2;
 
     enum State {
         // Unknown state
@@ -326,43 +325,43 @@
     }
 
     // State of the job
-    State state = 3;
+    optional State state = 3;
 
     // Printer handling the job
-    PrinterIdProto printer = 4;
+    optional PrinterIdProto printer = 4;
 
     // Tag assigned to the job
-    string tag = 5;
+    optional string tag = 5;
 
     // Time the job was created
-    int64 creation_time = 6;
+    optional int64 creation_time = 6;
 
     // Attributes of the job
-    PrintAttributesProto attributes = 7;
+    optional PrintAttributesProto attributes = 7;
 
     // Document info of the job
-    PrintDocumentInfoProto document_info = 8;
+    optional PrintDocumentInfoProto document_info = 8;
 
     // If the job current getting canceled
-    bool is_canceling = 9;
+    optional bool is_canceling = 9;
 
     // The selected ranges of the job
     repeated PageRangeProto pages = 10;
 
     // Does the job have any advanced options
-    bool has_advanced_options = 11;
+    optional bool has_advanced_options = 11;
 
     // Progress of the job
-    float progress = 12;
+    optional float progress = 12;
 
     // The current service set state
-    string status = 13;
+    optional string status = 13;
 }
 
 message CachedPrintJobProto {
     // The id of the app the job belongs to
-    int32 app_id = 1;
+    optional int32 app_id = 1;
 
     // The print job
-    PrintJobInfoProto print_job = 2;
+    optional PrintJobInfoProto print_job = 2;
 }
\ No newline at end of file
diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto
new file mode 100644
index 0000000..b2e0373
--- /dev/null
+++ b/core/proto/android/service/procstats.proto
@@ -0,0 +1,164 @@
+/*
+ * 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";
+option java_multiple_files = true;
+option java_outer_classname = "ProcessStatsServiceProto";
+
+import "frameworks/base/core/proto/android/util/common.proto";
+
+package android.service.procstats;
+
+/**
+ * Data from ProcStatsService Dumpsys
+ *
+ * Next Tag: 4
+ */
+message ProcessStatsServiceDumpProto {
+
+    optional ProcessStatsSectionProto procstats_now = 1;
+
+    optional ProcessStatsSectionProto procstats_over_3hrs = 2;
+
+    optional ProcessStatsSectionProto procstats_over_24hrs = 3;
+}
+
+/**
+ * Data model from /frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
+ * This proto is defined based on the writeToParcel method.
+ *
+ * Next Tag: 9
+ */
+message ProcessStatsSectionProto {
+
+    // Elapsed realtime at start of report.
+    optional int64 start_realtime_ms = 1;
+
+    // Elapsed realtime at end of report.
+    optional int64 end_realtime_ms = 2;
+
+    // CPU uptime at start of report.
+    optional int64 start_uptime_ms = 3;
+
+    // CPU uptime at end of report.
+    optional int64 end_uptime_ms = 4;
+
+    // System runtime library. e.g. "libdvm.so", "libart.so".
+    optional string runtime = 5;
+
+    // whether kernel reports swapped pss.
+    optional bool has_swapped_pss = 6;
+
+    // Data completeness. e.g. "complete", "partial", shutdown", or "sysprops".
+    enum Status {
+        STATUS_UNKNOWN = 0;
+        STATUS_COMPLETE = 1;
+        STATUS_PARTIAL = 2;
+        STATUS_SHUTDOWN = 3;
+        STATUS_SYSPROPS = 4;
+    }
+    repeated Status status = 7;
+
+    // Stats for each process.
+    repeated ProcessStatsProto process_stats = 8;
+}
+
+// Next Tag: 6
+message ProcessStatsProto {
+
+    // Name of process.
+    optional string process = 1;
+
+    // Uid of the process.
+    optional int32 uid = 2;
+
+    // Information about how often kills occurred
+    message Kill {
+      // Count of excessive CPU kills
+      optional int32 cpu = 1;
+
+      // Count of kills when cached
+      optional int32 cached = 2;
+
+      // PSS stats during cached kill
+      optional android.util.AggStats cached_pss = 3;
+    }
+    optional Kill kill = 3;
+
+    message State {
+        enum ScreenState {
+            SCREEN_UNKNOWN = 0;
+            OFF = 1;
+            ON = 2;
+        }
+        optional ScreenState screen_state = 1;
+
+        enum MemoryState {
+            MEMORY_UNKNOWN = 0;
+            NORMAL = 1;     // normal.
+            MODERATE = 2;   // moderate memory pressure.
+            LOW = 3;        // low memory.
+            CRITICAL = 4;   // critical memory.
+        }
+        optional MemoryState memory_state = 2;
+
+        enum ProcessState {
+            PROCESS_UNKNOWN = 0;
+            // Persistent system process.
+            PERSISTENT = 1;
+            // Top activity; actually any visible activity.
+            TOP = 2;
+            // Important foreground process (ime, wallpaper, etc).
+            IMPORTANT_FOREGROUND = 3;
+            // Important background process.
+            IMPORTANT_BACKGROUND = 4;
+            // Performing backup operation.
+            BACKUP = 5;
+            // Heavy-weight process (currently not used).
+            HEAVY_WEIGHT = 6;
+            // Background process running a service.
+            SERVICE = 7;
+            // Process not running, but would be if there was enough RAM.
+            SERVICE_RESTARTING = 8;
+            // Process running a receiver.
+            RECEIVER = 9;
+            // Process hosting home/launcher app when not on top.
+            HOME = 10;
+            // Process hosting the last app the user was in.
+            LAST_ACTIVITY = 11;
+            // Cached process hosting a previous activity.
+            CACHED_ACTIVITY = 12;
+            // Cached process hosting a client activity.
+            CACHED_ACTIVITY_CLIENT = 13;
+            // Cached process that is empty.
+            CACHED_EMPTY = 14;
+        }
+        optional ProcessState process_state = 3;
+
+        // Millisecond duration spent in this state
+        optional int64 duration_ms = 4;
+
+        // # of samples taken
+        optional int32 sample_size = 5;
+
+        // PSS is memory reserved for this process
+        optional android.util.AggStats pss = 6;
+
+        // USS is memory shared between processes, divided evenly for accounting
+        optional android.util.AggStats uss = 7;
+    }
+    repeated State states = 5;
+}
diff --git a/core/proto/android/service/wirelesschargerdetector.proto b/core/proto/android/service/wirelesschargerdetector.proto
deleted file mode 100644
index 7ba7c17..0000000
--- a/core/proto/android/service/wirelesschargerdetector.proto
+++ /dev/null
@@ -1,50 +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.
- */
-
-syntax = "proto3";
-
-package android.service.power;
-
-option java_multiple_files = true;
-
-message WirelessChargerDetectorProto {
-    message VectorProto {
-        float x = 1;
-        float y = 2;
-        float z = 3;
-    }
-
-    // Previously observed wireless power state.
-    bool is_powered_wirelessly = 1;
-    // True if the device is thought to be at rest on a wireless charger.
-    bool is_at_rest = 2;
-    // The gravity vector most recently observed while at rest.
-    VectorProto rest = 3;
-    // True if detection is in progress.
-    bool is_detection_in_progress = 4;
-    // The time when detection was last performed.
-    int64 detection_start_time_ms = 5;
-    // True if the rest position should be updated if at rest.
-    bool is_must_update_rest_position = 6;
-    // The total number of samples collected.
-    int32 total_samples = 7;
-    // The number of samples collected that showed evidence of not being at rest.
-    int32 moving_samples = 8;
-    // The value of the first sample that was collected.
-    VectorProto first_sample = 9;
-    // The value of the last sample that was collected.
-    VectorProto last_sample = 10;
-}
\ No newline at end of file
diff --git a/core/proto/android/telephony/signalstrength.proto b/core/proto/android/telephony/signalstrength.proto
new file mode 100644
index 0000000..366f1d1
--- /dev/null
+++ b/core/proto/android/telephony/signalstrength.proto
@@ -0,0 +1,34 @@
+/*
+ * 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";
+option java_package = "android.telephony";
+option java_multiple_files = true;
+
+package android.telephony;
+
+/**
+ * An android.telephony.SignalStrength object.
+ */
+message SignalStrengthProto {
+  enum StrengthName {
+    SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+    SIGNAL_STRENGTH_POOR = 1;
+    SIGNAL_STRENGTH_MODERATE = 2;
+    SIGNAL_STRENGTH_GOOD = 3;
+    SIGNAL_STRENGTH_GREAT = 4;
+  }
+}
diff --git a/core/proto/android/util/common.proto b/core/proto/android/util/common.proto
new file mode 100644
index 0000000..429c3cad
--- /dev/null
+++ b/core/proto/android/util/common.proto
@@ -0,0 +1,32 @@
+/*
+ * 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.util;
+
+option java_multiple_files = true;
+
+/**
+ * Very basic data structure used by aggregated stats.
+ */
+message AggStats {
+
+    optional int64 min = 1;
+
+    optional int64 average = 2;
+
+    optional int64 max = 3;
+}
diff --git a/core/proto/android/view/display.proto b/core/proto/android/view/display.proto
new file mode 100644
index 0000000..210c6d1
--- /dev/null
+++ b/core/proto/android/view/display.proto
@@ -0,0 +1,41 @@
+/*
+ * 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.view;
+
+option java_multiple_files = true;
+
+message DisplayProto {
+    enum DisplayState {
+        // The display state is unknown.
+        DISPLAY_STATE_UNKNOWN = 0;
+        // The display state is off.
+        DISPLAY_STATE_OFF = 1;
+        // The display state is on.
+        DISPLAY_STATE_ON = 2;
+        // The display is dozing in a low power state; it is still on but is
+        // optimized for showing system-provided content while the device is
+        // non-interactive.
+        DISPLAY_STATE_DOZE = 3;
+        // The display is dozing in a suspended low power state; it is still on
+        // but is optimized for showing static system-provided content while the
+        // device is non-interactive.
+        DISPLAY_STATE_DOZE_SUSPEND = 4;
+        // The display is on and optimized for VR mode.
+        DISPLAY_STATE_VR = 5;
+    }
+}
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 8583868..9ca4046 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.view;
 
 option java_multiple_files = true;
 
 /* represents DisplayInfo */
 message DisplayInfoProto {
-  int32 logical_width = 1;
-  int32 logical_height = 2;
-  int32 app_width = 3;
-  int32 app_height = 4;
+  optional int32 logical_width = 1;
+  optional int32 logical_height = 2;
+  optional int32 app_width = 3;
+  optional int32 app_height = 4;
 }
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index 5bb84dc..7821212 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-syntax = "proto3";
-
+syntax = "proto2";
 package android.view;
 
 option java_multiple_files = true;
 
 /* represents WindowManager.LayoutParams */
 message WindowLayoutParamsProto {
-  int32 type = 1;
+  optional int32 type = 1;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 695fdac..77bfec3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -183,15 +183,23 @@
     <protected-broadcast
         android:name="android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.BROWSE_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" />
+    <protected-broadcast
+        android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" />
+    <protected-broadcast
         android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.input.profile.action.IDLE_TIME_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
     <protected-broadcast
-        android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" />
+        android:name="android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
@@ -204,8 +212,8 @@
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
-    <protected-broadcast android:name="android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.btopp.intent.action.INCOMING_FILE_NOTIFICATION" />
     <protected-broadcast android:name="android.btopp.intent.action.USER_CONFIRMATION_TIMEOUT" />
@@ -318,6 +326,7 @@
     <protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
     <protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
+    <protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
     <protected-broadcast android:name="android.net.wifi.RSSI_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
@@ -551,6 +560,9 @@
     <protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
     <protected-broadcast android:name="com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
 
+    <!-- Made protected in P (was introduced in JB-MR2) -->
+    <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
@@ -963,7 +975,7 @@
         android:permissionGroup="android.permission-group.MICROPHONE"
         android:label="@string/permlab_recordAudio"
         android:description="@string/permdesc_recordAudio"
-        android:protectionLevel="dangerous"/>
+        android:protectionLevel="dangerous|instant"/>
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the UCE Service                              -->
@@ -1873,11 +1885,11 @@
 
     <!-- @SystemApi @hide Allows an application to create/manage/remove stacks -->
     <permission android:name="android.permission.MANAGE_ACTIVITY_STACKS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|development" />
 
     <!-- @SystemApi @hide Allows an application to embed other activities -->
     <permission android:name="android.permission.ACTIVITY_EMBEDDING"
-                android:protectionLevel="signature|privileged" />
+                android:protectionLevel="signature|privileged|development" />
 
     <!-- Allows an application to start any activity, regardless of permission
          protection or exported state.
@@ -3106,6 +3118,13 @@
     <permission android:name="android.permission.BIND_APPWIDGET"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to bind app's slices and get their
+         content. This content will be surfaced to the
+         user and not to leave the device.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.BIND_SLICE"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- @SystemApi Private permission, to restrict who can bring up a dialog to add a new
          keyguard widget
          @hide -->
@@ -3577,6 +3596,10 @@
     <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
         android:protectionLevel="signature|development|instant|appop" />
 
+    <!-- @hide Allows system components to access all app shortcuts. -->
+    <permission android:name="android.permission.ACCESS_SHORTCUTS"
+        android:protectionLevel="signature" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/drawable/ic_ab_back_material_settings.xml b/core/res/res/drawable/ic_ab_back_material_settings.xml
new file mode 100644
index 0000000..7325a41
--- /dev/null
+++ b/core/res/res/drawable/ic_ab_back_material_settings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:autoMirrored="true"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@color/white"
+        android:pathData="M20,11H7.62l4.88,-4.88a0.996,0.996 0,1 0,-1.41 -1.41l-6.94,6.94c-0.2,0.2 -0.2,0.51 0,0.71l6.94,6.94a0.996,0.996 0,1 0,1.41 -1.41L7.62,13H20c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z"/>
+</vector>
diff --git a/core/res/res/drawable/ic_slice_send.xml b/core/res/res/drawable/ic_slice_send.xml
new file mode 100644
index 0000000..b8b6954
--- /dev/null
+++ b/core/res/res/drawable/ic_slice_send.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:autoMirrored="true"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/slice_remote_input_bg.xml b/core/res/res/drawable/slice_remote_input_bg.xml
new file mode 100644
index 0000000..3120679
--- /dev/null
+++ b/core/res/res/drawable/slice_remote_input_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#ff6c6c6c" />
+    <corners
+        android:bottomRightRadius="16dp"
+        android:bottomLeftRadius="16dp"/>
+</shape>
diff --git a/core/res/res/drawable/slice_ripple_drawable.xml b/core/res/res/drawable/slice_ripple_drawable.xml
new file mode 100644
index 0000000..5ba1f07
--- /dev/null
+++ b/core/res/res/drawable/slice_ripple_drawable.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight" />
\ No newline at end of file
diff --git a/core/res/res/layout/floating_popup_menu_button.xml b/core/res/res/layout/floating_popup_menu_button.xml
index 6cbe8c8..c419e46 100644
--- a/core/res/res/layout/floating_popup_menu_button.xml
+++ b/core/res/res/layout/floating_popup_menu_button.xml
@@ -53,7 +53,6 @@
         android:ellipsize="end"
         android:fontFamily="sans-serif-medium"
         android:textSize="@dimen/floating_toolbar_text_size"
-        android:textAllCaps="true"
         android:textColor="?attr/floatingToolbarForegroundColor"
         android:background="@null"
         android:focusable="false"
diff --git a/core/res/res/layout/magnifier.xml b/core/res/res/layout/magnifier.xml
new file mode 100644
index 0000000..f3344c7
--- /dev/null
+++ b/core/res/res/layout/magnifier.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/magnifier_inner"
+        android:layout_width="@android:dimen/magnifier_width"
+        android:layout_height="@android:dimen/magnifier_height"
+        android:elevation="@android:dimen/magnifier_elevation"
+        android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"
+        android:scaleType="fitXY">
+        <ImageView
+            android:id="@+id/magnifier_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 76b3528..ee5c758 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -70,6 +70,8 @@
                 android:textSize="16sp"
                 android:textColor="#eeffffff"
                 android:layout_marginTop="4dp"
+                android:ellipsize="end"
+                android:maxLines="7"
             />
         </LinearLayout>
     </LinearLayout>
diff --git a/core/res/res/layout/slice_grid.xml b/core/res/res/layout/slice_grid.xml
new file mode 100644
index 0000000..4cb78e1
--- /dev/null
+++ b/core/res/res/layout/slice_grid.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<android.app.slice.widget.GridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+</android.app.slice.widget.GridView>
diff --git a/core/res/res/layout/slice_message.xml b/core/res/res/layout/slice_message.xml
new file mode 100644
index 0000000..9e0cfe7
--- /dev/null
+++ b/core/res/res/layout/slice_message.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<android.app.slice.widget.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <!-- TODO: Support text source -->
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxWidth="48dp"
+            android:maxHeight="48dp" />
+    </LinearLayout>
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:maxLines="10" />
+</android.app.slice.widget.MessageView>
diff --git a/core/res/res/layout/slice_message_local.xml b/core/res/res/layout/slice_message_local.xml
new file mode 100644
index 0000000..d806ddd
--- /dev/null
+++ b/core/res/res/layout/slice_message_local.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<android.app.slice.widget.MessageView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical|end"
+    android:paddingTop="12dp"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <TextView android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignStart="@android:id/title"
+        android:layout_gravity="end"
+        android:gravity="end"
+        android:padding="8dp"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:background="#ffeeeeee"
+        android:maxLines="10" />
+
+</android.app.slice.widget.MessageView>
diff --git a/core/res/res/layout/slice_remote_input.xml b/core/res/res/layout/slice_remote_input.xml
new file mode 100644
index 0000000..670c1e9
--- /dev/null
+++ b/core/res/res/layout/slice_remote_input.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+<!-- LinearLayout -->
+<android.app.slice.widget.RemoteInputView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/remote_input"
+        android:background="@drawable/slice_remote_input_bg"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+    <view class="com.android.internal.app.slice.widget.RemoteInputView$RemoteEditText"
+            android:id="@+id/remote_input_text"
+            android:layout_height="match_parent"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:paddingTop="2dp"
+            android:paddingBottom="4dp"
+            android:paddingStart="16dp"
+            android:paddingEnd="12dp"
+            android:gravity="start|center_vertical"
+            android:textAppearance="?android:attr/textAppearance"
+            android:textColor="#FFFFFFFF"
+            android:textColorHint="#99ffffff"
+            android:textSize="16sp"
+            android:background="@null"
+            android:singleLine="true"
+            android:ellipsize="start"
+            android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
+            android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
+
+    <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical">
+
+        <ImageButton
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:paddingStart="12dp"
+                android:paddingEnd="24dp"
+                android:paddingTop="16dp"
+                android:paddingBottom="16dp"
+                android:id="@+id/remote_input_send"
+                android:src="@drawable/ic_slice_send"
+                android:tint="#FFFFFF"
+                android:tintMode="src_in"
+                android:background="@drawable/slice_ripple_drawable" />
+
+        <ProgressBar
+                android:id="@+id/remote_input_progress"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:layout_marginEnd="6dp"
+                android:layout_gravity="center"
+                android:visibility="invisible"
+                android:indeterminate="true"
+                style="?android:attr/progressBarStyleSmall" />
+
+    </FrameLayout>
+
+</android.app.slice.widget.RemoteInputView>
\ No newline at end of file
diff --git a/core/res/res/layout/slice_secondary_text.xml b/core/res/res/layout/slice_secondary_text.xml
new file mode 100644
index 0000000..80a1574
--- /dev/null
+++ b/core/res/res/layout/slice_secondary_text.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+<!-- LinearLayout -->
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/core/res/res/layout/slice_small_template.xml b/core/res/res/layout/slice_small_template.xml
new file mode 100644
index 0000000..cced42b
--- /dev/null
+++ b/core/res/res/layout/slice_small_template.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:clipToPadding="false">
+
+    <LinearLayout
+        android:id="@android:id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-4dp"
+        android:gravity="start|center_vertical"
+        android:orientation="horizontal"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:gravity="center_vertical"
+        android:orientation="vertical">
+
+        <TextView android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:textAppearance="?android:attr/textAppearanceListItem" />
+
+        <TextView android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="10" />
+
+    </LinearLayout>
+
+    <LinearLayout android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="end|center_vertical"
+        android:orientation="horizontal" />
+
+</LinearLayout>
diff --git a/core/res/res/layout/slice_title.xml b/core/res/res/layout/slice_title.xml
new file mode 100644
index 0000000..455d59f
--- /dev/null
+++ b/core/res/res/layout/slice_title.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<!-- LinearLayout -->
+<TextView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textColor="?android:attr/textColorPrimary"
+        android:gravity="center"
+        android:layout_height="wrap_content"
+        android:padding="4dp"
+        android:layout_width="match_parent" />
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index d3ba7ef..5f65553 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Werkprofiel is weens ontbrekende administrasieprogram uitgevee"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Die werkprofiel se administrasieprogram ontbreek of is korrup. Gevolglik is jou werkprofiel en verwante data uitgevee. Kontak jou administrateur vir bystand."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Jou werkprofiel is nie meer op hierdie toestel beskikbaar nie"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Te veel wagwoordpogings"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Toestel word bestuur"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Jou organisasie bestuur hierdie toestel en kan netwerkverkeer monitor. Tik vir besonderhede."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Jou toestel sal uitgevee word"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Opletberigte"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Kleinhandeldemonstrasie"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-verbinding"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Program loop tans"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Programme wat batterykrag gebruik"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans batterykrag"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> programme gebruik tans batterykrag"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Invoermetode"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksaksies"</string>
     <string name="email" msgid="4560673117055050403">"E-pos"</string>
-    <string name="dial" msgid="4204975095406423102">"Foon"</string>
-    <string name="map" msgid="6068210738233985748">"Kaarte"</string>
-    <string name="browse" msgid="6993590095938149861">"Blaaier"</string>
+    <string name="dial" msgid="1253998302767701559">"Bel"</string>
+    <string name="map" msgid="6521159124535543457">"Spoor op"</string>
+    <string name="browse" msgid="1245903488306147205">"Maak oop"</string>
+    <string name="sms" msgid="4560537514610063430">"SMS"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Voeg by"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Bergingspasie word min"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Sommige stelselfunksies werk moontlik nie"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Nie genoeg berging vir die stelsel nie. Maak seker jy het 250 MB spasie beskikbaar en herbegin."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Foon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Oorfone"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokluidsprekers"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Oorfone"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Stelsel"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-oudio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Draadlose skerm"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Noodboodskappetoets"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Antwoord"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM word nie toegelaat nie"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM is nie opgestel nie"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM word nie toegelaat nie"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Foon word nie toegelaat nie"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Opspringvenster"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Hierdie kortpad vereis die jongste program"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Kon nie die kortpad teruglaai nie omdat die program nie rugsteun en teruglaai steun nie"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Kon nie teruglaai nie omdat programondertekening nie ooreenstem nie"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Kon nie kortpad teruglaai nie"</string>
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index a3c2cab..c4be6f4 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"በጎደለ የአስተዳዳሪ መተግበሪያ ምክንያት የሥራ መገለጫ ተሰርዟል።"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"የሥራ መገለጫ አስተዳዳሪ መተግበሪያው ወይም ይጎድላል ወይም ተበላሽቷል። በዚህ ምክንያት የሥራ መገለጫዎ እና ተዛማጅ ውሂብ ተሰርዘዋል። እርዳታን ለማግኘት አስተዳዳሪዎን ያነጋግሩ።"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"የሥራ መገለጫዎ ከዚህ በኋላ በዚህ መሣሪያ ላይ አይገኝም"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"በጣም ብዙ የይለፍ ቃል ሙከራዎች"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"መሣሪያው የሚተዳደር ነው"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"የእርስዎ ድርጅት ይህን መሣሪያ ያስተዳድራል፣ እና የአውታረ መረብ ትራፊክን ሊከታተል ይችላል። ዝርዝሮችን ለማግኘት መታ ያድርጉ።"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"የእርስዎ መሣሪያ ይደመሰሳል"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ማንቂያዎች"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"የችርቻሮ ማሳያ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"የዩኤስቢ ግንኙነት"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"APP እየሠራ ነው"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ባትሪ በመፍጀት ላይ ያሉ መተግበሪያዎች"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ባትሪ እየተጠቀመ ነው"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> መተግበሪያዎች ባትሪ እየተጠቀሙ ነው"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ግቤት ስልት"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"የፅሁፍ እርምጃዎች"</string>
     <string name="email" msgid="4560673117055050403">"ኢሜይል"</string>
-    <string name="dial" msgid="4204975095406423102">"ስልክ"</string>
-    <string name="map" msgid="6068210738233985748">"ካርታዎች"</string>
-    <string name="browse" msgid="6993590095938149861">"አሳሽ"</string>
+    <string name="dial" msgid="1253998302767701559">"ጥሪ"</string>
+    <string name="map" msgid="6521159124535543457">"ቦታውን አግኝ"</string>
+    <string name="browse" msgid="1245903488306147205">"ክፈት"</string>
+    <string name="sms" msgid="4560537514610063430">"መልዕክት"</string>
+    <string name="add_contact" msgid="7867066569670597203">"አክል"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"የማከማቻ ቦታ እያለቀ ነው"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"አንዳንድ የስርዓት ተግባራት ላይሰሩ ይችላሉ"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"ለስርዓቱ የሚሆን በቂ ቦታ የለም። 250 ሜባ ነጻ ቦታ እንዳለዎት ያረጋግጡና ዳግም ያስጀምሩ።"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ጡባዊ ተኮ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ቴሌቪዥን"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ስልክ"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"የጆሮ ማዳመጫዎች"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"የትከል ድምፅ ማጉያዎች"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"ኤችዲኤምአይ"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"የጆሮ ማዳመጫዎች"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"ዩ ኤስ ቢ"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ስርዓት"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"የብሉቱዝ ድምጽ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ገመድ አልባ ማሳያ"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"የአስቸኳይ አደጋ መልእክቶች ሙከራ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"ምላሽ ስጥ"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"ሲም አይፈቀድም"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"ሲም አልቀረበም"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"ሲም አይፈቀድም"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ስልክ አይፈቀድም"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"ብቅ-ባይ መስኮት"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ይህ አቋራጭ በጣም የቅርብ ጊዜውን መተግበሪያ ይፈልጋል"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"መተግበሪያ ምትኬን እና ወደ ነበረበት መመለስን ሳለማይደግፍ አቋራጭ ወደ ነበረበት ሊመለስ አልቻለም"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"በመተግበሪያ ፊርማ አለመዛመድ አቋራጭን ወደነበረበት መመለስ አልተቻለም"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"አቋራጭን ወደ ነበረበት መመለስ አልተቻለም"</string>
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 5ff51f2..dddd52b 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -180,6 +180,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"تم حذف الملف الشخصي للعمل نتيجة فقد تطبيق المشرف"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"تطبيق المشرف للملف الشخصي للعمل مفقود أو تالف لذا تم حذف الملف الشخصي للعمل والبيانات ذات الصلة. اتصل بالمشرف للحصول على المساعدة."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"لم يعد ملفك الشخصي للعمل متاحًا على هذا الجهاز"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"تم إجراء محاولات كثيرة جدًا لإدخال كلمة المرور"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"تتم إدارة الجهاز"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"تدير مؤسستك هذا الجهاز ويمكنها مراقبة حركة بيانات الشبكة. يمكنك النقر للحصول على تفاصيل."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"سيتم محو بيانات جهازك."</string>
@@ -260,6 +261,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"التنبيهات"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"عرض توضيحي لبائع التجزئة"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"‏اتصال USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"التطبيق قيد التشغيل"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"التطبيقات التي تستهلك البطارية"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"يستخدم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> البطارية"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"تستخدم <xliff:g id="NUMBER">%1$d</xliff:g> من التطبيقات البطارية"</string>
@@ -1057,9 +1059,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"طريقة الإرسال"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"إجراءات النص"</string>
     <string name="email" msgid="4560673117055050403">"بريد إلكتروني"</string>
-    <string name="dial" msgid="4204975095406423102">"الهاتف"</string>
-    <string name="map" msgid="6068210738233985748">"الخرائط"</string>
-    <string name="browse" msgid="6993590095938149861">"المتصفح"</string>
+    <string name="dial" msgid="1253998302767701559">"اتصال"</string>
+    <string name="map" msgid="6521159124535543457">"تحديد الموقع"</string>
+    <string name="browse" msgid="1245903488306147205">"فتح"</string>
+    <string name="sms" msgid="4560537514610063430">"رسالة"</string>
+    <string name="add_contact" msgid="7867066569670597203">"إضافة"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"مساحة التخزين منخفضة"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"قد لا تعمل بعض وظائف النظام"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"ليست هناك سعة تخزينية كافية للنظام. تأكد من أنه لديك مساحة خالية تبلغ ٢٥٠ ميغابايت وأعد التشغيل."</string>
@@ -1512,9 +1516,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"الجهاز اللوحي"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"التلفزيون"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"الهاتف"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"سماعات رأس"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"مكبرات صوت للإرساء"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"سماعات رأس"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"النظام"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"صوت بلوتوث"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"عرض شاشة لاسلكي"</string>
@@ -1721,7 +1726,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"العمل الثاني <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"العمل الثالث <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"لإزالة تثبيت هذه الشاشة، يمكنك لمس زرّي \"رجوع\" و\"نظرة عامة\" مع الاستمرار"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"لا يمكن إزالة تثبيت هذا التطبيق"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"تم تثبيت الشاشة"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"تم إلغاء تثبيت الشاشة"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"المطالبة برقم التعريف الشخصي قبل إزالة التثبيت"</string>
@@ -1917,9 +1921,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"اختبار رسائل الطوارئ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"الرد"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"‏غير مسموح باستخدام SIM"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"‏لم يتم تقديم SIM"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"‏غير مسموح باستخدام SIM"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"غير مسموح باستخدام الهاتف"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"‏غير مسموح باستخدام SIM للصوت"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"‏لم يتم توفير SIM للصوت"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"‏غير مسموح باستخدام SIM للصوت"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"غير مسموح باستخدام الهاتف للصوت"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"نافذة منبثقة"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"يتطلب هذا الاختصار أحدث تطبيق"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"تعذّرت استعادة الاختصار لأن التطبيق لا يوفِّر إمكانية النسخ الاحتياطي والاستعادة"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"تعذّرت استعادة الاختصار بسبب عدم تطابق توقيع التطبيق"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"تعذّرت استعادة الاختصار"</string>
 </resources>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index bd63c27..40b5473 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Admin tətbiqi olmadığından iş profili silindi"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"İş profili admin tətbiqi ya yoxdur, ya da korlanıb. Nəticədə iş profili və onunla bağlı data silinib. Kömək üçün admin ilə əlaqə saxlayın."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"İş profili artıq bu cihazda əlçatan deyil"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Həddindən çox parol cəhdi"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Cihaz idarə olunur"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Təşkilat bu cihazı idarə edir və şəbəkənin ötürülməsinə nəzarət edə bilər. Detallar üçün klikləyin."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Cihazınız təmizlənəcəkdir"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Siqnallar"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Pərakəndə demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB əlaqə"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Tətbiq işləyir"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Batareyadan istifadə edən tətbiqlər"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> batareyadan istifadə edir"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> tətbiq batareyadan istifadə edir"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Daxiletmə metodu"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Mətn əməliyyatları"</string>
     <string name="email" msgid="4560673117055050403">"E-poçt"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Xəritə"</string>
-    <string name="browse" msgid="6993590095938149861">"Brauzer"</string>
+    <string name="dial" msgid="1253998302767701559">"Zəng"</string>
+    <string name="map" msgid="6521159124535543457">"Tapmaq"</string>
+    <string name="browse" msgid="1245903488306147205">"Açın"</string>
+    <string name="sms" msgid="4560537514610063430">"Mesaj"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Əlavə edin"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Yaddaş yeri bitir"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Bəzi sistem funksiyaları işləməyə bilər"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Sistem üçün yetərincə yaddaş ehtiyatı yoxdur. 250 MB yaddaş ehtiyatının olmasına əmin olun və yenidən başladın."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Planşet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Qulaqlıq"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dok spikerlər"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Qulaqlıq"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Simsiz ekran"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Təcili mesaj testi"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Cavablayın"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-ə icazə verilmir"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM təmin edilməyib"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-ə icazə verilmir"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefona icazə verilmir"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Popap Pəncərəsi"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Bu qısayol ən son tətbiqi tələb edir"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Qısayolu bərpa etmək mümkün olmadı, çünki tətbiq yedəkləməni və bərpa etməyi dəstəkləmir"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Tətbiqin imza uyğunsuzluğu səbəbilə qısayolu bərpa etmək mümkün olmadı"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Qısayolu bərpa etmək mümkün olmadı"</string>
 </resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index e362a87..7a90719 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -174,6 +174,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profil za Work je izbrisan jer nedostaje aplikacija za administratore"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Aplikacija za administratore na profilu za Work nedostaje ili je oštećena. Zbog toga su profil za Work i povezani podaci izbrisani. Obratite se administratoru za pomoć."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Profil za Work više nije dostupan na ovom uređaju"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Previše pokušaja unosa lozinke"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Uređajem se upravlja"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organizacija upravlja ovim uređajem i može da nadgleda mrežni saobraćaj. Dodirnite za detalje."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Uređaj će biti obrisan"</string>
@@ -251,6 +252,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Obaveštenja"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Režim demonstracije za maloprodajne objekte"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB veza"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikacija je pokrenuta"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacije koje troše bateriju"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Aplikacije (<xliff:g id="NUMBER">%1$d</xliff:g>) koriste bateriju"</string>
@@ -276,8 +278,8 @@
     <string name="permgroupdesc_storage" msgid="637758554581589203">"pristupa slikama, medijima i datotekama na uređaju"</string>
     <string name="permgrouprequest_storage" msgid="7429669910547860218">"Dozvolite &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da pristupa slikama, medijskim datotekama i datotekama na uređaju"</string>
     <string name="permgrouplab_microphone" msgid="171539900250043464">"Mikrofon"</string>
-    <string name="permgroupdesc_microphone" msgid="4988812113943554584">"snima audio"</string>
-    <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Dozvolite da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snima audio snimke"</string>
+    <string name="permgroupdesc_microphone" msgid="4988812113943554584">"snima zvuk"</string>
+    <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Dozvolite da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snima zvuk"</string>
     <string name="permgrouplab_camera" msgid="4820372495894586615">"Kamera"</string>
     <string name="permgroupdesc_camera" msgid="3250611594678347720">"snima slike i video"</string>
     <string name="permgrouprequest_camera" msgid="810824326507258410">"Dozvolite da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snima slike i video snimke"</string>
@@ -997,9 +999,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metod unosa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Radnje u vezi sa tekstom"</string>
     <string name="email" msgid="4560673117055050403">"Pošalji imejl"</string>
-    <string name="dial" msgid="4204975095406423102">"Pozovi"</string>
-    <string name="map" msgid="6068210738233985748">"Mape"</string>
-    <string name="browse" msgid="6993590095938149861">"Pregledač"</string>
+    <string name="dial" msgid="1253998302767701559">"Pozovi"</string>
+    <string name="map" msgid="6521159124535543457">"Pronađi"</string>
+    <string name="browse" msgid="1245903488306147205">"Otvori"</string>
+    <string name="sms" msgid="4560537514610063430">"Pošalji SMS"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Dodaj"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Memorijski prostor je na izmaku"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Neke sistemske funkcije možda ne funkcionišu"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Nema dovoljno memorijskog prostora za sistem. Uverite se da imate 250 MB slobodnog prostora i ponovo pokrenite."</string>
@@ -1443,9 +1447,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalice"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Zvučnici bazne stanice"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalice"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Bežični ekran"</string>
@@ -1646,7 +1651,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3. poslovni imejl <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Da biste otkačili ovaj ekran, dodirnite i zadržite dugmad Nazad i Pregled"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Ova aplikacija ne može da se otkači"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Ekran je zakačen"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran je otkačen"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Traži PIN pre otkačinjanja"</string>
@@ -1812,9 +1816,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Testiranje poruka u hitnim slučajevima"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odgovori"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM kartica nije dozvoljena"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM kartica nije podešena"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM kartica nije dozvoljena"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon nije dozvoljen"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM kartica nije prilagođena za glasovne usluge"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM kartica nije podešena za glasovne usluge"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM kartica nije prilagođena za glasovne usluge"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefon nije prilagođen za glasovne usluge"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Iskačući prozor"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"i još <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ova prečica zahteva najnoviju aplikaciju"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Vraćanje prečice nije uspelo jer aplikacija ne podržava pravljenje rezervne kopije i vraćanje"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Vraćanje prečice nije uspelo jer se potpisi aplikacija ne podudaraju"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Vraćanje prečice nije uspelo"</string>
 </resources>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index fdbe702..039b6eb 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Працоўны профіль выдалены з-за адсутнасці праграмы адміністратара"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Праграма адміністратара для працоўнага профілю адсутнічае або пашкоджана. У выніку гэтага ваш працоўны профіль і звязаныя з ім даныя былі выдалены. Звярніцеся па дапамогу да адміністратара."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Ваш працоўны профіль больш не даступны на гэтай прыладзе"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Занадта шмат спроб уводу пароля"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Прылада знаходзіцца пад кіраваннем"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ваша арганізацыя кіруе гэтай прыладай і можа сачыць за сеткавым трафікам. Дакраніцеся для атрымання дадатковай інфармацыі."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Даныя вашай прылады будуць сцерты"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Абвесткi"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Дэманстрацыйны рэжым для пунктаў продажу"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Падключэнне USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Праграма працуе"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Праграмы, якія выкарыстоўваюць акумулятар"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> выкарыстоўвае акумулятар"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Наступная колькасць праграм выкарыстоўваюць акумулятар: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Метад уводу"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Дзеянні з тэкстам"</string>
     <string name="email" msgid="4560673117055050403">"Электронная пошта"</string>
-    <string name="dial" msgid="4204975095406423102">"Тэлефон"</string>
-    <string name="map" msgid="6068210738233985748">"Карты"</string>
-    <string name="browse" msgid="6993590095938149861">"Браўзер"</string>
+    <string name="dial" msgid="1253998302767701559">"Выклікаць"</string>
+    <string name="map" msgid="6521159124535543457">"Знайсці"</string>
+    <string name="browse" msgid="1245903488306147205">"Адкрыць"</string>
+    <string name="sms" msgid="4560537514610063430">"Паведамленне"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Дадаць"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Месца для захавання на зыходзе"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Некаторыя сістэмныя функцыі могуць не працаваць"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Не хапае сховішча для сістэмы. Пераканайцеся, што ў вас ёсць 250 МБ свабоднага месца, і перазапусціце."</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Планшэт"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ТБ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Тэлефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Навушнікі"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Дынамікі станцыi"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Навушнікі"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Сістэма"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-аўдыё"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Бесправадны дысплей"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Праверка экстранных паведамленняў"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Адказаць"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-карта не дапускаецца"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-карты няма"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-карта не дапускаецца"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Тэлефон не дапускаецца"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Выплыўное акно"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Для гэтага ярлыка патрабуецца найноўшая версія праграмы"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Не атрымалася аднавіць ярлык, бо праграма не падтрымлівае рэзервовае капіраванне і аднаўленне"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Не атрымалася аднавіць ярлык з-за несупадзення подпісаў праграм"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Не атрымалася аднавіць ярлык"</string>
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index ae4cc61..ccdeb24 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Служебният потребителски профил е изтрит поради липса на приложение за администриране"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Приложението за администриране на служебния потребителски профил липсва или е повредено. В резултат на това той и свързаните с него данни са изтрити. За съдействие се свържете с администратора си."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Служебният ви потребителски профил вече не е налице на това устройство"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Опитите за паролата са твърде много"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Устройството се управлява"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Организацията ви управлява това устройство и може да наблюдава мрежовия трафик. Докоснете за подробности."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Данните на устройството ви ще бъдат изтрити"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Сигнали"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Демонстрационен режим за магазини"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB връзка"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Приложението работи"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Приложения, използващи батерията"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва батерията"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> приложения използват батерията"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Метод на въвеждане"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Действия с текста"</string>
     <string name="email" msgid="4560673117055050403">"Имейл"</string>
-    <string name="dial" msgid="4204975095406423102">"Телефон"</string>
-    <string name="map" msgid="6068210738233985748">"Карти"</string>
-    <string name="browse" msgid="6993590095938149861">"Браузър"</string>
+    <string name="dial" msgid="1253998302767701559">"Обаждане"</string>
+    <string name="map" msgid="6521159124535543457">"Намиране"</string>
+    <string name="browse" msgid="1245903488306147205">"Отваряне"</string>
+    <string name="sms" msgid="4560537514610063430">"Съобщение"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Добавяне"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Мястото в хранилището е на изчерпване"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Възможно е някои функции на системата да не работят"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"За системата няма достатъчно място в хранилището. Уверете се, че имате свободни 250 МБ, и рестартирайте."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Таблет"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Телевизор"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Слушалки"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Докинг станц.: Високогов."</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Слушалки"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Система"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Звук през Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Безжичен дисплей"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Тест за спешни съобщения"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Отговор"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM картата не е разрешена"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM картата не е обезпечена"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM картата не е разрешена"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Телефонът не е разрешен"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Изскачащ прозорец"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"За този пряк път се изисква най-новата версия на приложението"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Прекият път не можа да бъде възстановен, защото приложението не поддържа създаването на резервно копие и възстановяването"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Прекият път не можа да бъде възстановен поради несъответствие в подписа на приложението"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Прекият път не можа да бъде възстановен"</string>
 </resources>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index e3ecfda..8e1b5b4 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"প্রশাসক অ্যাপ না থাকায় কর্মস্থলের প্রোফাইল মুছে ফেলা হয়েছে"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"কর্মস্থলের প্রোফাইলের প্রশাসক অ্যাপটি হয় নেই, অথবা সেটি ক্ষতিগ্রস্ত হয়েছে৷ এর ফলে আপনার কর্মস্থলের প্রোফাইল এবং সম্পর্কিত ডেটা মুছে ফেলা হয়েছে৷ সহায়তার জন্য আপনার প্রশাসকের সাথে যোগাযোগ করুন৷"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"আপনার কর্মস্থলের প্রোফাইলটি আর এই ডিভাইসে নেই"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"বহুবার ভুল পাসওয়ার্ড দিয়েছেন"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ডিভাইসটি পরিচালনা করা হচ্ছে"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"আপনার প্রতিষ্ঠান এই ডিভাইসটি পরিচালনা করে এবং এটির নেটওয়ার্ক ট্রাফিকের উপরে নজর রাখতে পারে। বিশদ বিবরণের জন্য ট্যাপ করুন।,"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"আপনার ডিভাইসটি মুছে ফেলা হবে"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"সতর্কতা"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"খুচরা বিক্রয়ের ডেমো"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB সংযোগ"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"অ্যাপ চলছে"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"কিছু অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপটি ব্যাটারি ব্যবহার করছে"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g>টি অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
@@ -401,7 +403,7 @@
     <string name="permlab_vibrate" msgid="7696427026057705834">"ভাইব্রেশন নিয়ন্ত্রণ করুন"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"অ্যাপ্লিকেশানকে কম্পক নিয়ন্ত্রণ করতে দেয়৷"</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"সরাসরি ফোন নম্বরগুলিতে কল করে"</string>
-    <string name="permdesc_callPhone" msgid="3740797576113760827">"অ্যাপ্লিকেশানটিকে আপনার হস্তক্ষেপ ছাড়াই ফোন নম্বরগুলিতে কল করতে মঞ্জুর করে৷ এটি অপ্রত্যাশিত পরিমাণ খরচা বা কলের কারণ হতে পারে৷ মনে রাখবেন, এটি অ্যাপ্লিকেশানটির দ্বারা জরুরি নম্বরগুলিতে কল করাকে অনুমতি দেয় না৷ ক্ষতিকারক অ্যাপ্লিকেশানগুলি আপনার সম্মতি ছাড়াই কল করার ফলে আপনাকে অহেতুক অর্থ প্রদান করতে হতে পারে৷"</string>
+    <string name="permdesc_callPhone" msgid="3740797576113760827">"অ্যাপ্লিকেশানটিকে আপনার হস্তক্ষেপ ছাড়াই ফোন নম্বরগুলিতে কল করতে মঞ্জুর করে৷ এটি অপ্রত্যাশিত পরিমাণ খরচা বা কলের কারণ হতে পারে৷ মনে রাখবেন, এটি অ্যাপ্লিকেশানটির দ্বারা জরুরি নম্বরগুলিতে কল করাকে অনুমতি দেয় না৷ ক্ষতিকারক অ্যাপ্লিকেশানগুলি আপনার সম্মতি ছাড়াই কল করার ফলে আপনাকে অহেতুক পেমেন্ট করতে হতে পারে৷"</string>
     <string name="permlab_accessImsCallService" msgid="3574943847181793918">"IMS পরিষেবাতে অ্যাক্সেস"</string>
     <string name="permdesc_accessImsCallService" msgid="8992884015198298775">"আপনার হস্তক্ষেপ ছাড়াই কল করতে অ্যাপ্লিকেশানটিকে IMS পরিষেবা ব্যবহারের অনুমতি দিন৷"</string>
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"ফোনের স্থিতি এবং পরিচয় পড়ুন"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ইনপুট পদ্ধতি"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"পাঠ্য ক্রিয়াগুলি"</string>
     <string name="email" msgid="4560673117055050403">"ইমেল"</string>
-    <string name="dial" msgid="4204975095406423102">"ফোন করুন"</string>
-    <string name="map" msgid="6068210738233985748">"মানচিত্র"</string>
-    <string name="browse" msgid="6993590095938149861">"ব্রাউজার"</string>
+    <string name="dial" msgid="1253998302767701559">"কল"</string>
+    <string name="map" msgid="6521159124535543457">"অবস্থান নির্ণয় করুন"</string>
+    <string name="browse" msgid="1245903488306147205">"খুলুন"</string>
+    <string name="sms" msgid="4560537514610063430">"মেসেজ"</string>
+    <string name="add_contact" msgid="7867066569670597203">"যোগ করুন"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"স্টোরেজ পূর্ণ হতে চলেছে"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"কিছু কিছু সিস্টেম ক্রিয়াকলাপ কাজ নাও করতে পারে"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"সিস্টেমের জন্য যথেষ্ট স্টোরেজ নেই৷ আপনার কাছে ২৫০এমবি ফাঁকা স্থান রয়েছে কিনা সে বিষয়ে নিশ্চিত হন এবং সিস্টেম চালু করুন৷"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ট্যাবলেট"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"টিভি"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ফোন"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"হেডফোন"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ডক স্পিকার"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"হেডফোন"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"সিস্টেম"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ব্লুটুথ অডিও"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ওয়্যারলেস প্রদর্শন"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"দ্বিতীয় কার্যক্ষেত্র <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"তৃতীয় কার্যক্ষেত্র <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"এই স্ক্রিনটিকে আনপিন করতে ফিরে যাওয়া এবং এক নজরে বোতামদুটি ট্যাপ করে ধরে রাখুন"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"এই অ্যাপটি আনপিন করা যাবে না"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"স্ক্রিন পিন করা হয়েছে"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"পিন না করা স্ক্রীন"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"আনপিন করার আগে পিন চান"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"বিপদকালীন বার্তাগুলির পরীক্ষা"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"উত্তর দিন"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"সিম অনুমোদিত নয়"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"সিম প্রস্তুত নয়"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"সিম অনুমোদিত নয়"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ফোন অনুমোদিত নয়"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"এই সিম দিয়ে ভয়েস কল করা যাবে না"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"সিমটি ভয়েস কলের জন্য প্রস্তুত নয়"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"এই সিম দিয়ে ভয়েস কল করা যাবে না"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"এই ফোন দিয়ে ভয়েস কল করা যাবে না"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"পপ-আপ উইন্ডো"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>টি"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"এই শর্টকাটটির জন্য লেটেস্ট অ্যাপ প্রয়োজন"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"শর্টকাট ফিরিয়ে আনা যায়নি কারণ অ্যাপটিতে \'ব্যাক-আপ এবং রিস্টোর\' করার সুবিধা নেই"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"শর্টকাট ফিরিয়ে আনা যায়নি কারণ অ্যাপের সিগ্নেচারটি মিল হচ্ছে না"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"শর্টকাট ফিরিয়ে আনা যায়নি"</string>
 </resources>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 232a4e1..aa83d1d 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -128,7 +128,7 @@
     <item msgid="4397097370387921767">"Wi-Fi pozivanje preko operatera %s"</item>
   </string-array>
     <string name="wifi_calling_off_summary" msgid="8720659586041656098">"Isključeno"</string>
-    <string name="wfc_mode_wifi_preferred_summary" msgid="1994113411286935263">"Prednost ima Wi-Fi"</string>
+    <string name="wfc_mode_wifi_preferred_summary" msgid="1994113411286935263">"Preferira se Wi-Fi"</string>
     <string name="wfc_mode_cellular_preferred_summary" msgid="1988279625335345908">"Preferira se mobilna mreža"</string>
     <string name="wfc_mode_wifi_only_summary" msgid="2379919155237869320">"Samo Wi-Fi"</string>
     <string name="cfTemplateNotForwarded" msgid="1683685883841272560">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđen"</string>
@@ -174,6 +174,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Radni profil je izbrisan jer nedostaje aplikacija administratora"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Nedostaje aplikacija administratora za radni profil ili je neispravna. Zbog toga su vaš radni profil i povezani podaci izbrisani. Obratite administratoru za pomoć."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Radni profil više nije dostupan na ovom uređaju"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Previše puta ste pokušali otključati uređaj"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Uređajem se upravlja."</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Vaša organizacija upravlja ovim uređajem i može pratiti mrežni saobraćaj. Dodirnite za detalje."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Uređaj će biti izbrisan"</string>
@@ -251,6 +252,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Upozorenja"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Promotivna demonstracija u maloprodaji"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB veza"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Pokrenuta je aplikacija"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacije koje troše bateriju"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> troši bateriju"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Broj aplikacija koje troše bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
@@ -350,7 +352,7 @@
     <string name="permdesc_persistentActivity" product="tv" msgid="5086862529499103587">"Dozvoljava aplikaciji da jednim dijelom trajno ostaje u memoriji. Time se ostalim aplikacijama dostupna memorija može ograničiti te usporiti rad TV-a."</string>
     <string name="permdesc_persistentActivity" product="default" msgid="4384760047508278272">"Omogućava aplikaciji da neke svoje dijelove pohrani trajno u memoriji. Ovo može ograničiti veličinu raspoložive memorije za druge aplikacije i tako usporiti telefon."</string>
     <string name="permlab_getPackageSize" msgid="7472921768357981986">"mjerenje prostora kojeg aplikacije zauzimaju u pohrani"</string>
-    <string name="permdesc_getPackageSize" msgid="3921068154420738296">"Dozvoljava aplikaciji preuzimanje svog kôda, podataka i veličine keš memorije"</string>
+    <string name="permdesc_getPackageSize" msgid="3921068154420738296">"Dozvoljava aplikaciji preuzimanje svog koda, podataka i veličine keš memorije"</string>
     <string name="permlab_writeSettings" msgid="2226195290955224730">"izmjena postavki sistema"</string>
     <string name="permdesc_writeSettings" msgid="7775723441558907181">"Dozvoljava aplikaciji izmijenu postavki sistema. Zlonamjerne aplikacije mogu oštetiti konfiguraciju sistema."</string>
     <string name="permlab_receiveBootCompleted" msgid="5312965565987800025">"pokrenuti pri pokretanju"</string>
@@ -552,7 +554,7 @@
     <string name="permlab_access_notification_policy" msgid="4247510821662059671">"pristup opciji Ne ometaj"</string>
     <string name="permdesc_access_notification_policy" msgid="3296832375218749580">"Omogućava aplikaciji da čita i upisuje konfiguraciju opcije Ne ometaj."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Postavljanje pravila za lozinke"</string>
-    <string name="policydesc_limitPassword" msgid="2502021457917874968">"Kontrolira dužinu i znakove koji su dozvoljeni u lozinkama za zaključavanje ekrana i PIN kodovima."</string>
+    <string name="policydesc_limitPassword" msgid="2502021457917874968">"Kontrolira dužinu i znakove koji su dozvoljeni u lozinkama za zaključavanje ekrana i PIN-ovima."</string>
     <string name="policylab_watchLogin" msgid="5091404125971980158">"Prati pokušaje otključavanja ekrana"</string>
     <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"Prati broj pogrešno unijetih lozinki prilikom otključavanja ekrana i zaključava tablet ili briše sve podatke s njega ukoliko se previše puta unese pogrešna lozinka."</string>
     <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"Prati koliko puta je lozinka neispravno otkucana prilikom otključavanja ekrana i zaključaj TV ili izbriši sve podatke s TV-a ako se lozinka neispravno ukuca previše puta."</string>
@@ -980,7 +982,7 @@
     <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string>
     <string name="selectAll" msgid="6876518925844129331">"Odaberi sve"</string>
     <string name="cut" msgid="3092569408438626261">"Izreži"</string>
-    <string name="copy" msgid="2681946229533511987">"Kopirajte"</string>
+    <string name="copy" msgid="2681946229533511987">"Kopiraj"</string>
     <string name="failed_to_copy_to_clipboard" msgid="1833662432489814471">"Kopiranje u spremnik nije uspjelo"</string>
     <string name="paste" msgid="5629880836805036433">"Zalijepi"</string>
     <string name="paste_as_plain_text" msgid="5427792741908010675">"Zalijepi kao neformatiran tekst"</string>
@@ -997,9 +999,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Način unosa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Akcije za tekst"</string>
     <string name="email" msgid="4560673117055050403">"E-pošta"</string>
-    <string name="dial" msgid="4204975095406423102">"Pozovi"</string>
-    <string name="map" msgid="6068210738233985748">"Mape"</string>
-    <string name="browse" msgid="6993590095938149861">"Preglednik"</string>
+    <string name="dial" msgid="1253998302767701559">"Pozovite"</string>
+    <string name="map" msgid="6521159124535543457">"Odredite lokaciju"</string>
+    <string name="browse" msgid="1245903488306147205">"Otvorite"</string>
+    <string name="sms" msgid="4560537514610063430">"Poruka"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Dodajte"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Ponestaje prostora za pohranu"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Neke funkcije sistema možda neće raditi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Nema dovoljno prostora za sistem. Obezbijedite 250MB slobodnog prostora i ponovo pokrenite uređaj."</string>
@@ -1078,7 +1082,7 @@
     <string name="android_upgrading_notification_body" msgid="5761201379457064286">"Neke aplikacije možda neće raditi ispravno dok traje nadogradnja"</string>
     <string name="app_upgrading_toast" msgid="3008139776215597053">"Aplikacija <xliff:g id="APPLICATION">%1$s</xliff:g> se nadograđuje…"</string>
     <string name="android_upgrading_apk" msgid="7904042682111526169">"Optimiziranje aplikacije <xliff:g id="NUMBER_0">%1$d</xliff:g> od <xliff:g id="NUMBER_1">%2$d</xliff:g>."</string>
-    <string name="android_preparing_apk" msgid="8162599310274079154">"Priprema se <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
+    <string name="android_preparing_apk" msgid="8162599310274079154">"Pripremanje aplikacije <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="451464516346926713">"Pokretanje aplikacija."</string>
     <string name="android_upgrading_complete" msgid="1405954754112999229">"Pokretanje pri kraju."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"Pokrenuta je aplikacija <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -1445,9 +1449,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalice"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Zvučnici priključne stanice"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalice"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Bežični prikaz"</string>
@@ -1481,14 +1486,14 @@
     <string name="kg_pin_instructions" msgid="2377242233495111557">"Unesite PIN"</string>
     <string name="kg_password_instructions" msgid="5753646556186936819">"Unesite lozinku"</string>
     <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"SIM je sada onemogućen. Unesite PUK kôd da nastavite. Za više informacija obratite se operateru."</string>
-    <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"Unesite željeni PIN kôd"</string>
-    <string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"Potvrdi željeni PIN kôd"</string>
+    <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"Unesite željeni PIN"</string>
+    <string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"Potvrdi željeni PIN"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"Otključavanje SIM kartice…"</string>
-    <string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Pogrešan PIN kôd."</string>
+    <string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"Pogrešan PIN."</string>
     <string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"Unesite PIN koji sadrži od 4 do 8 brojeva."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6025069204539532000">"PUK kôd bi trebao imati 8 brojeva."</string>
     <string name="kg_invalid_puk" msgid="3638289409676051243">"Ponovo unesite ispravan PUK kôd. Ponovljeni pokušaji će trajno onemogućiti SIM."</string>
-    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN kodovi se ne poklapaju"</string>
+    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="7003469261464593516">"PIN-ovi se ne poklapaju"</string>
     <string name="kg_login_too_many_attempts" msgid="6486842094005698475">"Previše pokušaja otključavanja pomoću uzorka"</string>
     <string name="kg_login_instructions" msgid="1100551261265506448">"Da otključate, prijavite se sa svojim Google računom."</string>
     <string name="kg_login_username_hint" msgid="5718534272070920364">"Korisničko ime (adresa e-pošte)"</string>
@@ -1618,7 +1623,7 @@
     <string name="reason_service_unavailable" msgid="7824008732243903268">"Usluga štampanja nije omogućena."</string>
     <string name="print_service_installed_title" msgid="2246317169444081628">"Usluga <xliff:g id="NAME">%s</xliff:g> je instalirana"</string>
     <string name="print_service_installed_message" msgid="5897362931070459152">"Dodirnite da omogućite"</string>
-    <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Upišite PIN kôd administratora"</string>
+    <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Upišite PIN administratora"</string>
     <string name="restr_pin_enter_pin" msgid="3395953421368476103">"Unesite PIN"</string>
     <string name="restr_pin_incorrect" msgid="8571512003955077924">"Netačno"</string>
     <string name="restr_pin_enter_old_pin" msgid="1462206225512910757">"Trenutni PIN"</string>
@@ -1648,7 +1653,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3. poslovni <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Da otkačite ovaj ekran, dodirnite i držite dugmad Nazad i Pregled."</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Ova aplikacija se ne može otkačiti"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Ekran je zakačen"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran je otkačen"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Traži PIN prije nego se otkači"</string>
@@ -1814,9 +1818,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test poruka za hitne slučajeve"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odgovori"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM kartica nije dozvoljena"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM kartica nije dodijeljena"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM kartica nije dozvoljena"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon nije dozvoljen"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM kartica nije dozvoljena za govor"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM kartica nije dodijeljena za govor"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM kartica nije dozvoljena za govor"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefon nije dozvoljen za govor"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Iskočni prozor"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Za ovu prečicu potrebna je najnovija aplikacija"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Prečica nije uspješno vraćena jer aplikacija ne podržava izradu sigurnosne kopije i vraćanje"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Prečica nije uspješno vraćena zbog nepodudaranja potpisa aplikacije"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Prečica nije uspješno vraćena"</string>
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index d713c1b0..bb793a5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -127,7 +127,7 @@
     <item msgid="4397097370387921767">"Trucada de Wi-Fi de: %s"</item>
   </string-array>
     <string name="wifi_calling_off_summary" msgid="8720659586041656098">"Desactivat"</string>
-    <string name="wfc_mode_wifi_preferred_summary" msgid="1994113411286935263">"Preferència per la Wi-Fi"</string>
+    <string name="wfc_mode_wifi_preferred_summary" msgid="1994113411286935263">"Preferència per a la Wi-Fi"</string>
     <string name="wfc_mode_cellular_preferred_summary" msgid="1988279625335345908">"Preferència per a dades mòbils"</string>
     <string name="wfc_mode_wifi_only_summary" msgid="2379919155237869320">"Només Wi-Fi"</string>
     <string name="cfTemplateNotForwarded" msgid="1683685883841272560">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string>
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"S\'ha suprimit el perfil professional perquè falta l\'aplicació d\'administració"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Falta l\'aplicació d\'administració del perfil professional o està malmesa. Com a conseqüència, s\'han suprimit el teu perfil professional i les dades relacionades. Contacta amb l\'administrador per obtenir ajuda."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"El teu perfil professional ja no està disponible en aquest dispositiu"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Has intentat introduir la contrasenya massa vegades"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"El dispositiu està gestionat"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"La teva organització gestiona aquest dispositiu i és possible que supervisi el trànsit de xarxa. Toca per obtenir més informació."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"El contingut del dispositiu s\'esborrarà"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertes"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demostració comercial"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Connexió USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"S\'està executant una aplicació"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplicacions que consumeixen bateria"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> està consumint bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacions estan consumint bateria"</string>
@@ -273,7 +275,7 @@
     <string name="permgroupdesc_storage" msgid="637758554581589203">"accedir a fotos, contingut multimèdia i fitxers del dispositiu"</string>
     <string name="permgrouprequest_storage" msgid="7429669910547860218">"Permet que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; accedeixi a les fotos, al contingut multimèdia i als fitxers del dispositiu"</string>
     <string name="permgrouplab_microphone" msgid="171539900250043464">"Micròfon"</string>
-    <string name="permgroupdesc_microphone" msgid="4988812113943554584">"enregistrar àudio"</string>
+    <string name="permgroupdesc_microphone" msgid="4988812113943554584">"gravar àudio"</string>
     <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Permet que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; enregistri àudio"</string>
     <string name="permgrouplab_camera" msgid="4820372495894586615">"Càmera"</string>
     <string name="permgroupdesc_camera" msgid="3250611594678347720">"fer fotos i vídeos"</string>
@@ -392,12 +394,12 @@
     <string name="permdesc_accessCoarseLocation" product="default" msgid="7788009094906196995">"Aquesta aplicació pot obtenir la teva ubicació a partir de fonts de xarxa, com ara torres de telefonia mòbil i xarxes Wi-Fi. Aquests serveis d\'ubicació han d\'estar activats i disponibles al telèfon perquè l\'aplicació els pugui utilitzar."</string>
     <string name="permlab_modifyAudioSettings" msgid="6095859937069146086">"canviar la configuració d\'àudio"</string>
     <string name="permdesc_modifyAudioSettings" msgid="3522565366806248517">"Permet que l\'aplicació modifiqui la configuració d\'àudio general, com ara el volum i l\'altaveu de sortida que es fa servir."</string>
-    <string name="permlab_recordAudio" msgid="3876049771427466323">"enregistrar àudio"</string>
-    <string name="permdesc_recordAudio" msgid="4245930455135321433">"Aquesta aplicació pot enregistrar àudio amb el micròfon en qualsevol moment."</string>
+    <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar àudio"</string>
+    <string name="permdesc_recordAudio" msgid="4245930455135321433">"Aquesta aplicació pot gravar àudio amb el micròfon en qualsevol moment."</string>
     <string name="permlab_sim_communication" msgid="2935852302216852065">"enviar ordres a la SIM"</string>
     <string name="permdesc_sim_communication" msgid="5725159654279639498">"Permet que l\'aplicació enviï ordres a la SIM. Això és molt perillós."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"fer fotos i vídeos"</string>
-    <string name="permdesc_camera" msgid="5392231870049240670">"Aquesta aplicació pot fer fotos i enregistrar vídeos amb la càmera en qualsevol moment."</string>
+    <string name="permdesc_camera" msgid="5392231870049240670">"Aquesta aplicació pot fer fotos i gravar vídeos amb la càmera en qualsevol moment."</string>
     <string name="permlab_vibrate" msgid="7696427026057705834">"controlar la vibració"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Permet que l\'aplicació controli el vibrador."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"trucar directament a números de telèfon"</string>
@@ -448,7 +450,7 @@
     <string name="permdesc_changeWifiMulticastState" product="tablet" msgid="7969774021256336548">"Permet que l\'aplicació rebi paquets enviats a tots els dispositius d\'una xarxa Wi-Fi mitjançant les adreces multidifusió, no només a la teva tauleta. Fa servir més energia que el mode que no és multidifusió."</string>
     <string name="permdesc_changeWifiMulticastState" product="tv" msgid="9031975661145014160">"Permet que l\'aplicació rebi paquets enviats a tots els dispositius d\'una xarxa Wi-Fi mitjançant les adreces de multidifusió, no només al televisor. Fa servir més energia que el mode que no és de multidifusió."</string>
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="6851949706025349926">"Permet que l\'aplicació rebi paquets enviats a tots els dispositius d\'una xarxa Wi-Fi mitjançant les adreces multidifusió, no només al teu telèfon. Fa servir més energia que el mode que no és multidifusió."</string>
-    <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"accés a la configuració de Bluetooth"</string>
+    <string name="permlab_bluetoothAdmin" msgid="6006967373935926659">"accés a la configuració del Bluetooth"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="6921177471748882137">"Permet que l\'aplicació configuri la tauleta Bluetooth local i que detecti dispositius remots i s\'hi vinculi."</string>
     <string name="permdesc_bluetoothAdmin" product="tv" msgid="3373125682645601429">"Permet que l\'aplicació configuri el televisor Bluetooth local, cerqui dispositius remots i s\'hi vinculi."</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="8931682159331542137">"Permet que l\'aplicació configuri el telèfon Bluetooth local i que detecti dispositius remots i s\'hi vinculi."</string>
@@ -459,9 +461,9 @@
     <string name="permdesc_changeWimaxState" product="tv" msgid="6022307083934827718">"Permet que l\'aplicació connecti el televisor a xarxes WiMAX, o bé que el desconnecti."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="697025043004923798">"Permet que l\'aplicació connecti i desconnecti el telèfon de les xarxes WiMAX."</string>
     <string name="permlab_bluetooth" msgid="6127769336339276828">"vincula amb dispositius Bluetooth"</string>
-    <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Permet que l\'aplicació visualitzi la configuració de Bluetooth de la tauleta i que estableixi i accepti connexions amb dispositius sincronitzats."</string>
-    <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Permet que l\'aplicació consulti la configuració de Bluetooth del televisor i estableixi i accepti connexions amb dispositius vinculats ."</string>
-    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permet que una aplicació visualitzi la configuració de Bluetooth del telèfon i que estableixi i accepti connexions amb els dispositius sincronitzats."</string>
+    <string name="permdesc_bluetooth" product="tablet" msgid="3480722181852438628">"Permet que l\'aplicació visualitzi la configuració del Bluetooth de la tauleta i que estableixi i accepti connexions amb dispositius sincronitzats."</string>
+    <string name="permdesc_bluetooth" product="tv" msgid="3974124940101104206">"Permet que l\'aplicació consulti la configuració del Bluetooth del televisor i estableixi i accepti connexions amb dispositius vinculats ."</string>
+    <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"Permet que una aplicació visualitzi la configuració del Bluetooth del telèfon i que estableixi i accepti connexions amb els dispositius sincronitzats."</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"controlar Comunicació de camp proper (NFC)"</string>
     <string name="permdesc_nfc" msgid="7120611819401789907">"Permet que l\'aplicació es comuniqui amb les etiquetes, les targetes i els lectors de Comunicació de camp proper (NFC)."</string>
     <string name="permlab_disableKeyguard" msgid="3598496301486439258">"desactivació del bloqueig de pantalla"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Mètode d\'introducció de text"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Accions de text"</string>
     <string name="email" msgid="4560673117055050403">"Correu electrònic"</string>
-    <string name="dial" msgid="4204975095406423102">"Truca"</string>
-    <string name="map" msgid="6068210738233985748">"Mapes"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Truca"</string>
+    <string name="map" msgid="6521159124535543457">"Localitza"</string>
+    <string name="browse" msgid="1245903488306147205">"Obre"</string>
+    <string name="sms" msgid="4560537514610063430">"Missatge"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Afegeix"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"L\'espai d\'emmagatzematge s\'està esgotant"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"És possible que algunes funcions del sistema no funcionin"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"No hi ha prou espai d\'emmagatzematge per al sistema. Comprova que tinguis 250 MB d\'espai lliure i reinicia."</string>
@@ -1074,18 +1078,18 @@
     <string name="sendText" msgid="5209874571959469142">"Tria una acció per al text"</string>
     <string name="volume_ringtone" msgid="6885421406845734650">"Volum del timbre"</string>
     <string name="volume_music" msgid="5421651157138628171">"Volum de multimèdia"</string>
-    <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"S\'està reproduint a través de Bluetooth"</string>
+    <string name="volume_music_hint_playing_through_bluetooth" msgid="9165984379394601533">"S\'està reproduint per Bluetooth"</string>
     <string name="volume_music_hint_silent_ringtone_selected" msgid="8310739960973156272">"S\'ha establert el so de silenci"</string>
     <string name="volume_call" msgid="3941680041282788711">"Volum en trucada"</string>
-    <string name="volume_bluetooth_call" msgid="2002891926351151534">"Volum en trucada Bluetooth"</string>
+    <string name="volume_bluetooth_call" msgid="2002891926351151534">"Volum en trucada per Bluetooth"</string>
     <string name="volume_alarm" msgid="1985191616042689100">"Volum de l\'alarma"</string>
-    <string name="volume_notification" msgid="2422265656744276715">"Volum de notificació"</string>
+    <string name="volume_notification" msgid="2422265656744276715">"Volum de notificacions"</string>
     <string name="volume_unknown" msgid="1400219669770445902">"Volum"</string>
-    <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"Volum de Bluetooth"</string>
+    <string name="volume_icon_description_bluetooth" msgid="6538894177255964340">"Volum del Bluetooth"</string>
     <string name="volume_icon_description_ringer" msgid="3326003847006162496">"Volum del so"</string>
     <string name="volume_icon_description_incall" msgid="8890073218154543397">"Volum de trucada"</string>
     <string name="volume_icon_description_media" msgid="4217311719665194215">"Volum de multimèdia"</string>
-    <string name="volume_icon_description_notification" msgid="7044986546477282274">"Volum de notificació"</string>
+    <string name="volume_icon_description_notification" msgid="7044986546477282274">"Volum de notificacions"</string>
     <string name="ringtone_default" msgid="3789758980357696936">"So predeterminat"</string>
     <string name="ringtone_default_with_actual" msgid="1767304850491060581">"Predeterminat (<xliff:g id="ACTUAL_RINGTONE">%1$s</xliff:g>)"</string>
     <string name="ringtone_silent" msgid="7937634392408977062">"Cap"</string>
@@ -1285,7 +1289,7 @@
     <string name="wallpaper_binding_label" msgid="1240087844304687662">"Fons de pantalla"</string>
     <string name="chooser_wallpaper" msgid="7873476199295190279">"Canvia el fons de pantalla"</string>
     <string name="notification_listener_binding_label" msgid="2014162835481906429">"Oient de notificacions"</string>
-    <string name="vr_listener_binding_label" msgid="4316591939343607306">"Processador de RV"</string>
+    <string name="vr_listener_binding_label" msgid="4316591939343607306">"Processador d\'RV"</string>
     <string name="condition_provider_service_binding_label" msgid="1321343352906524564">"Proveïdor de condicions"</string>
     <string name="notification_ranker_binding_label" msgid="774540592299064747">"Servei de classificació de notificacions"</string>
     <string name="vpn_title" msgid="19615213552042827">"VPN activada"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tauleta"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televisor"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telèfon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculars"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altaveus de la base"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculars"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Àudio per Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Pantalla sense fil"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2n <xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3r <xliff:g id="LABEL">%1$s</xliff:g> de la feina"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Toca i mantén premuts els botons Enrere i Aplicacions recents per deixar de fixar aquesta pantalla"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"No es pot deixar de fixar aquesta aplicació"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Pantalla fixada"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Fixació de la pantalla anul·lada"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Sol·licita el codi PIN per deixar de fixar"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Prova de missatges d\'emergència"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Respon"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM no compatible"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM no proporcionada"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM no compatible"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telèfon no no compatible"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"La SIM no és compatible per a la veu"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"La SIM no està proporcionada per a la veu"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"La SIM no és compatible per a la veu"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"El telèfon no és compatible per a la veu"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Finestra emergent"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> més"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Per fer servir aquesta drecera has de tenir l\'última versió de l\'aplicació"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"No s\'ha pogut restaurar la drecera perquè l\'aplicació no permet la còpia de seguretat ni la restauració"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"No s\'ha pogut restaurar la drecera perquè la signatura de l\'aplicació no coincideix"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"No s\'ha pogut restaurar la drecera"</string>
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 31e7487..a7021e9 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Pracovní profil byl smazán, protože není k dispozici aplikace pro správu"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Aplikace pro správu pracovního profilu chybí nebo je poškozena. Váš pracovní profil a související data proto byla smazána. Požádejte o pomoc administrátora."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Váš pracovní profil v tomto zařízení již není k dispozici"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Příliš mnoho pokusů o zadání hesla"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Zařízení je spravováno"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Toto zařízení je spravováno vaší organizací, která může sledovat síťový provoz. Podrobnosti zobrazíte klepnutím."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Zařízení bude vymazáno"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Upozornění"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Prodejní ukázka"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Připojení USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikace je spuštěna"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikace spotřebovávají baterii"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> využívá baterii"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Aplikace (<xliff:g id="NUMBER">%1$d</xliff:g>) využívají baterii"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Operace s textem"</string>
     <string name="email" msgid="4560673117055050403">"Poslat e-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Mapy"</string>
-    <string name="browse" msgid="6993590095938149861">"Prohlížeč"</string>
+    <string name="dial" msgid="1253998302767701559">"Zavolat"</string>
+    <string name="map" msgid="6521159124535543457">"Najít"</string>
+    <string name="browse" msgid="1245903488306147205">"Otevřít"</string>
+    <string name="sms" msgid="4560537514610063430">"Zpráva"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Přidat"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"V úložišti je málo místa"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Některé systémové funkce nemusí fungovat"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Pro systém není dostatek místa v úložišti. Uvolněte alespoň 250 MB místa a restartujte zařízení."</string>
@@ -1105,7 +1109,7 @@
     <string name="heavy_weight_switcher_text" msgid="7022631924534406403">"Než spustíte novou aplikaci, je třeba zastavit jinou spuštěnou aplikaci."</string>
     <string name="old_app_action" msgid="493129172238566282">"Návrat do aplikace <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
     <string name="old_app_description" msgid="2082094275580358049">"Nespouštět novou aplikaci."</string>
-    <string name="new_app_action" msgid="5472756926945440706">"Spustit aplikaci <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
+    <string name="new_app_action" msgid="5472756926945440706">"Do aplikace <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
     <string name="new_app_description" msgid="1932143598371537340">"Zastavit starou aplikaci bez uložení."</string>
     <string name="dump_heap_notification" msgid="2618183274836056542">"Proces <xliff:g id="PROC">%1$s</xliff:g> překročil limit paměti"</string>
     <string name="dump_heap_notification_detail" msgid="6901391084243999274">"Byl shromážděn výpis haldy, klepnutím jej můžete sdílet"</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televize"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Sluchátka"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Reproduktory doku"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Sluchátka"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Systém"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth Audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Bezdrátový displej"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test nouzových zpráv"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odpovědět"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM karta není povolena"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM karta není poskytována"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM karta není povolena"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon není povolen"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Vyskakovací okno"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"a ještě <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Tato zkratka vyžaduje nejnovější verzi aplikace"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Zkratku nelze obnovit, protože aplikace nepodporuje zálohování a obnovu"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Zkratku nelze obnovit, protože se neshoduje podpis aplikace"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Zkratku nelze obnovit"</string>
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index f1ba174..5b0edbe 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Arbejdsprofilen blev slettet, fordi der mangler en administrationsapp"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Administrationsappen til arbejdsprofilen mangler eller er beskadiget. Derfor er din arbejdsprofil og dine relaterede data blevet slettet. Kontakt din administrator for at få hjælp."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Din arbejdsprofil er ikke længere tilgængelig på denne enhed"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"For mange mislykkede adgangskodeforsøg"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Dette er en administreret enhed"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Din organisation administrerer denne enhed og kan overvåge netværkstrafik. Tryk for at se oplysninger."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Enheden slettes"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Underretninger"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo til udstilling i butik"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-forbindelse"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Appen kører"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps, der bruger batteri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger batteri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps bruger batteri"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksthandlinger"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Opkald"</string>
-    <string name="map" msgid="6068210738233985748">"Kort"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Ring op"</string>
+    <string name="map" msgid="6521159124535543457">"Find"</string>
+    <string name="browse" msgid="1245903488306147205">"Åbn"</string>
+    <string name="sms" msgid="4560537514610063430">"Besked"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Tilføj"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Der er snart ikke mere lagerplads"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Nogle systemfunktioner virker måske ikke"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Der er ikke nok ledig lagerplads til systemet. Sørg for, at du har 250 MB ledig plads, og genstart."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Tv"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hovedtelefoner"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dockstationens højttalere"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hovedtelefoner"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-lyd"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Trådløs skærm"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test af nødbeskeder"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Svar"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-kortet har ikke adgangstilladelse"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-kortet er ikke aktiveret"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-kortet har ikke adgangstilladelse"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefonen har ikke adgangstilladelse"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop op-vindue"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> mere"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Denne genvej kræver den nyeste app"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Genvejen kunne ikke gendannes, da appen ikke understøtter sikkerhedskopiering og gendannelse"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Genvejen kunne ikke gendannes på grund af uoverensstemmelse i appsignatur"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Genvejen kunne ikke gendannes"</string>
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index cc510d7..0cff7dd 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Arbeitsprofil aufgrund fehlender Admin-App gelöscht"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Die Admin-App für das Arbeitsprofil fehlt oder ist beschädigt. Daher wurden dein Arbeitsprofil und alle zugehörigen Daten gelöscht. Bitte wende dich für weitere Hilfe an deinen Administrator."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Dein Arbeitsprofil ist auf diesem Gerät nicht mehr verfügbar"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Zu viele falsche Passworteingaben"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Dies ist ein verwaltetes Gerät"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Deine Organisation verwaltet dieses Gerät und überprüft unter Umständen den Netzwerkverkehr. Tippe hier, um weitere Informationen zu erhalten."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Die Daten auf deinem Gerät werden gelöscht."</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Warnmeldungen"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo für Einzelhandel"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-Verbindung"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App wird ausgeführt"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Strom verbrauchende Apps"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> verbraucht Strom"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> Apps verbrauchen Strom"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Textaktionen"</string>
     <string name="email" msgid="4560673117055050403">"E-Mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Karten"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Anrufen"</string>
+    <string name="map" msgid="6521159124535543457">"Suchen"</string>
+    <string name="browse" msgid="1245903488306147205">"Öffnen"</string>
+    <string name="sms" msgid="4560537514610063430">"SMS"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Hinzufügen"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Der Speicherplatz wird knapp"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Einige Systemfunktionen funktionieren möglicherweise nicht."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Der Speicherplatz reicht nicht für das System aus. Stelle sicher, dass 250 MB freier Speicherplatz vorhanden sind, und starte das Gerät dann neu."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kopfhörer"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock-Lautsprecher"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kopfhörer"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-Audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Kabellose Übertragung (WiDi)"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test der Notfallwarnungen"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Antworten"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-Karte nicht zulässig"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM nicht eingerichtet"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-Karte nicht zulässig"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Smartphone nicht zulässig"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-up-Fenster"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Für diese Verknüpfung ist die aktuelle App-Version erforderlich"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Verknüpfung konnte nicht wiederhergestellt werden, weil die App keine Sicherung und keine Wiederherstellung unterstützt"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Verknüpfung konnte nicht wiederhergestellt werden, weil die App-Signatur nicht übereinstimmt"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Verknüpfung konnte nicht wiederhergestellt werden"</string>
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ede305c..9619737 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Το προφίλ εργασίας διαγράφηκε λόγω απουσίας της εφαρμογής διαχείρισης"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Η εφαρμογή διαχείρισης προφίλ εργασίας είτε λείπει είτε είναι κατεστραμμένη. Ως αποτέλεσμα, διαγράφηκε το προφίλ εργασίας και τα σχετικά δεδομένα. Επικοινωνήστε με τον διαχειριστή σας για βοήθεια."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Το προφίλ εργασίας σας δεν είναι πια διαθέσιμο σε αυτήν τη συσκευή"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Πάρα πολλές προσπάθειες εισαγωγής κωδικού πρόσβασης"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Η συσκευή είναι διαχειριζόμενη"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ο οργανισμός σας διαχειρίζεται αυτήν τη συσκευή και ενδέχεται να παρακολουθεί την επισκεψιμότητα δικτύου. Πατήστε για λεπτομέρειες."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Η συσκευή σας θα διαγραφεί"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Ειδοποιήσεις"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Επίδειξη λιανικής"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Σύνδεση USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Η εφαρμογή εκτελείται"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Εφαρμογές που καταναλώνουν μπαταρία"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί μπαταρία"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> εφαρμογές χρησιμοποιούν μπαταρία"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Ενέργειες κειμένου"</string>
     <string name="email" msgid="4560673117055050403">"Ηλεκτρονικό ταχυδρομείο"</string>
-    <string name="dial" msgid="4204975095406423102">"Τηλέφωνο"</string>
-    <string name="map" msgid="6068210738233985748">"Χάρτες"</string>
-    <string name="browse" msgid="6993590095938149861">"Πρόγραμμα περιήγησης"</string>
+    <string name="dial" msgid="1253998302767701559">"Κλήση"</string>
+    <string name="map" msgid="6521159124535543457">"Εντοπισμός"</string>
+    <string name="browse" msgid="1245903488306147205">"Άνοιγμα"</string>
+    <string name="sms" msgid="4560537514610063430">"Μήνυμα"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Προσθήκη"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Ο αποθηκευτικός χώρος εξαντλείται"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Ορισμένες λειτουργίες συστήματος ενδέχεται να μην λειτουργούν"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Δεν υπάρχει αρκετός αποθηκευτικός χώρος για το σύστημα. Βεβαιωθείτε ότι διαθέτετε 250 MB ελεύθερου χώρου και κάντε επανεκκίνηση."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Τηλεόραση"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Τηλέφωνο"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ακουστικά"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Ηχεία βάσης σύνδεσης"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ακουστικά"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Σύστημα"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Ήχος Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Ασύρματη οθόνη"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Δοκιμαστικό μήνυμα έκτακτης ανάγκης"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Απάντηση"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Η κάρτα SIM δεν επιτρέπεται"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Δεν παρέχεται κάρτα SIM"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Η κάρτα SIM δεν επιτρέπεται"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Το τηλέφωνο δεν επιτρέπεται"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Αναδυόμενο παράθυρο"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Αυτή η συντόμευση απαιτεί την πιο πρόσφατη έκδοση της εφαρμογής"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Δεν ήταν δυνατή η επαναφορά της συντόμευσης, επειδή η εφαρμογή δεν υποστηρίζει τη δημιουργία αντιγράφων ασφαλείας και την επαναφορά"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Δεν ήταν δυνατή η επαναφορά της συντόμευσης, λόγω αναντιστοιχίας της υπογραφής εφαρμογής"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Δεν ήταν δυνατή η επαναφορά της συντόμευσης"</string>
 </resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index fa7f180..07450b2 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Work profile deleted due to missing admin app"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Your work profile is no longer available on this device"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Too many password attempts"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Device is managed"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Your device will be erased"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alerts"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Retail demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB connection"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App running"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps consuming battery"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
@@ -977,9 +979,16 @@
     <string name="inputMethod" msgid="1653630062304567879">"Input method"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Text actions"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Phone"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <!-- no translation found for dial (1253998302767701559) -->
+    <skip />
+    <!-- no translation found for map (6521159124535543457) -->
+    <skip />
+    <!-- no translation found for browse (1245903488306147205) -->
+    <skip />
+    <!-- no translation found for sms (4560537514610063430) -->
+    <skip />
+    <!-- no translation found for add_contact (7867066569670597203) -->
+    <skip />
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -989,6 +998,7 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Cancel"</string>
+    <string name="close" msgid="2318214661230355730">"CLOSE"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
     <string name="loading" msgid="7933681260296021180">"Loading…"</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
@@ -1045,6 +1055,8 @@
     <string name="screen_compat_mode_scale" msgid="3202955667675944499">"Scale"</string>
     <string name="screen_compat_mode_show" msgid="4013878876486655892">"Always show"</string>
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"Re-enable this in System settings &gt; Apps &gt; Downloaded."</string>
+    <string name="top_app_killed_title" msgid="6814231368167994497">"App isn\'t responding"</string>
+    <string name="top_app_killed_message" msgid="3487519022191609844">"<xliff:g id="APP_NAME">%1$s</xliff:g> may be using too much memory."</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g> does not support the current Display size setting and may behave unexpectedly."</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"Always show"</string>
     <string name="smv_application" msgid="3307209192155442829">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
@@ -1420,9 +1432,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Phone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock speakers"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Wireless display"</string>
@@ -1782,4 +1795,13 @@
     <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM not allowed"</string>
     <string name="mmcc_illegal_me" msgid="4438696681169345015">"Phone not allowed"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-Up Window"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <!-- no translation found for shortcut_restored_on_lower_version (5270675146351613828) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_not_supported (5028808567940014190) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_signature_mismatch (2406209324521327518) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_unknown_issue (8703738064603262597) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index fa7f180..07450b2 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Work profile deleted due to missing admin app"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Your work profile is no longer available on this device"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Too many password attempts"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Device is managed"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Your device will be erased"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alerts"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Retail demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB connection"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App running"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps consuming battery"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
@@ -977,9 +979,16 @@
     <string name="inputMethod" msgid="1653630062304567879">"Input method"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Text actions"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Phone"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <!-- no translation found for dial (1253998302767701559) -->
+    <skip />
+    <!-- no translation found for map (6521159124535543457) -->
+    <skip />
+    <!-- no translation found for browse (1245903488306147205) -->
+    <skip />
+    <!-- no translation found for sms (4560537514610063430) -->
+    <skip />
+    <!-- no translation found for add_contact (7867066569670597203) -->
+    <skip />
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -989,6 +998,7 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Cancel"</string>
+    <string name="close" msgid="2318214661230355730">"CLOSE"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
     <string name="loading" msgid="7933681260296021180">"Loading…"</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
@@ -1045,6 +1055,8 @@
     <string name="screen_compat_mode_scale" msgid="3202955667675944499">"Scale"</string>
     <string name="screen_compat_mode_show" msgid="4013878876486655892">"Always show"</string>
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"Re-enable this in System settings &gt; Apps &gt; Downloaded."</string>
+    <string name="top_app_killed_title" msgid="6814231368167994497">"App isn\'t responding"</string>
+    <string name="top_app_killed_message" msgid="3487519022191609844">"<xliff:g id="APP_NAME">%1$s</xliff:g> may be using too much memory."</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g> does not support the current Display size setting and may behave unexpectedly."</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"Always show"</string>
     <string name="smv_application" msgid="3307209192155442829">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
@@ -1420,9 +1432,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Phone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock speakers"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Wireless display"</string>
@@ -1782,4 +1795,13 @@
     <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM not allowed"</string>
     <string name="mmcc_illegal_me" msgid="4438696681169345015">"Phone not allowed"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-Up Window"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <!-- no translation found for shortcut_restored_on_lower_version (5270675146351613828) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_not_supported (5028808567940014190) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_signature_mismatch (2406209324521327518) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_unknown_issue (8703738064603262597) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index fa7f180..07450b2 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Work profile deleted due to missing admin app"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Your work profile is no longer available on this device"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Too many password attempts"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Device is managed"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Your device will be erased"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alerts"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Retail demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB connection"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App running"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps consuming battery"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
@@ -977,9 +979,16 @@
     <string name="inputMethod" msgid="1653630062304567879">"Input method"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Text actions"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Phone"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <!-- no translation found for dial (1253998302767701559) -->
+    <skip />
+    <!-- no translation found for map (6521159124535543457) -->
+    <skip />
+    <!-- no translation found for browse (1245903488306147205) -->
+    <skip />
+    <!-- no translation found for sms (4560537514610063430) -->
+    <skip />
+    <!-- no translation found for add_contact (7867066569670597203) -->
+    <skip />
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -989,6 +998,7 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Cancel"</string>
+    <string name="close" msgid="2318214661230355730">"CLOSE"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
     <string name="loading" msgid="7933681260296021180">"Loading…"</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
@@ -1045,6 +1055,8 @@
     <string name="screen_compat_mode_scale" msgid="3202955667675944499">"Scale"</string>
     <string name="screen_compat_mode_show" msgid="4013878876486655892">"Always show"</string>
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"Re-enable this in System settings &gt; Apps &gt; Downloaded."</string>
+    <string name="top_app_killed_title" msgid="6814231368167994497">"App isn\'t responding"</string>
+    <string name="top_app_killed_message" msgid="3487519022191609844">"<xliff:g id="APP_NAME">%1$s</xliff:g> may be using too much memory."</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g> does not support the current Display size setting and may behave unexpectedly."</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"Always show"</string>
     <string name="smv_application" msgid="3307209192155442829">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
@@ -1420,9 +1432,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Phone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock speakers"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Wireless display"</string>
@@ -1782,4 +1795,13 @@
     <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM not allowed"</string>
     <string name="mmcc_illegal_me" msgid="4438696681169345015">"Phone not allowed"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-Up Window"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <!-- no translation found for shortcut_restored_on_lower_version (5270675146351613828) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_not_supported (5028808567940014190) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_signature_mismatch (2406209324521327518) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_unknown_issue (8703738064603262597) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index fa7f180..07450b2 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Work profile deleted due to missing admin app"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Your work profile is no longer available on this device"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Too many password attempts"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Device is managed"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Your organisation manages this device and may monitor network traffic. Tap for details."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Your device will be erased"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alerts"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Retail demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB connection"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App running"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps consuming battery"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
@@ -977,9 +979,16 @@
     <string name="inputMethod" msgid="1653630062304567879">"Input method"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Text actions"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Phone"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <!-- no translation found for dial (1253998302767701559) -->
+    <skip />
+    <!-- no translation found for map (6521159124535543457) -->
+    <skip />
+    <!-- no translation found for browse (1245903488306147205) -->
+    <skip />
+    <!-- no translation found for sms (4560537514610063430) -->
+    <skip />
+    <!-- no translation found for add_contact (7867066569670597203) -->
+    <skip />
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Storage space running out"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Some system functions may not work"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Not enough storage for the system. Make sure that you have 250 MB of free space and restart."</string>
@@ -989,6 +998,7 @@
     <string name="cancel" msgid="6442560571259935130">"Cancel"</string>
     <string name="yes" msgid="5362982303337969312">"OK"</string>
     <string name="no" msgid="5141531044935541497">"Cancel"</string>
+    <string name="close" msgid="2318214661230355730">"CLOSE"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"Attention"</string>
     <string name="loading" msgid="7933681260296021180">"Loading…"</string>
     <string name="capital_on" msgid="1544682755514494298">"ON"</string>
@@ -1045,6 +1055,8 @@
     <string name="screen_compat_mode_scale" msgid="3202955667675944499">"Scale"</string>
     <string name="screen_compat_mode_show" msgid="4013878876486655892">"Always show"</string>
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"Re-enable this in System settings &gt; Apps &gt; Downloaded."</string>
+    <string name="top_app_killed_title" msgid="6814231368167994497">"App isn\'t responding"</string>
+    <string name="top_app_killed_message" msgid="3487519022191609844">"<xliff:g id="APP_NAME">%1$s</xliff:g> may be using too much memory."</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g> does not support the current Display size setting and may behave unexpectedly."</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"Always show"</string>
     <string name="smv_application" msgid="3307209192155442829">"The app <xliff:g id="APPLICATION">%1$s</xliff:g> (process <xliff:g id="PROCESS">%2$s</xliff:g>) has violated its self-enforced Strict Mode policy."</string>
@@ -1420,9 +1432,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Phone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dock speakers"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphones"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Wireless display"</string>
@@ -1782,4 +1795,13 @@
     <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM not allowed"</string>
     <string name="mmcc_illegal_me" msgid="4438696681169345015">"Phone not allowed"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-Up Window"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <!-- no translation found for shortcut_restored_on_lower_version (5270675146351613828) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_not_supported (5028808567940014190) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_signature_mismatch (2406209324521327518) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_unknown_issue (8703738064603262597) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 353b437..a131af5 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎Work profile deleted due to missing admin app‎‏‎‎‏‎"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎The work profile admin app is either missing or corrupted. As a result, your work profile and related data have been deleted. Contact your admin for assistance.‎‏‎‎‏‎"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎Your work profile is no longer available on this device‎‏‎‎‏‎"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎Too many password attempts‎‏‎‎‏‎"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‏‎Device is managed‎‏‎‎‏‎"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‎‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‏‎‏‎‎Your organization manages this device and may monitor network traffic. Tap for details.‎‏‎‎‏‎"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎Your device will be erased‎‏‎‎‏‎"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎Alerts‎‏‎‎‏‎"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎Retail demo‎‏‎‎‏‎"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎USB connection‎‏‎‎‏‎"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‎App running‎‏‎‎‏‎"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎Apps consuming battery‎‏‎‎‏‎"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is using battery‎‏‎‎‏‎"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎ apps are using battery‎‏‎‎‏‎"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎Input method‎‏‎‎‏‎"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎Text actions‎‏‎‎‏‎"</string>
     <string name="email" msgid="4560673117055050403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‎‏‏‎Email‎‏‎‎‏‎"</string>
-    <string name="dial" msgid="4204975095406423102">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎Phone‎‏‎‎‏‎"</string>
-    <string name="map" msgid="6068210738233985748">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‎Maps‎‏‎‎‏‎"</string>
-    <string name="browse" msgid="6993590095938149861">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎Browser‎‏‎‎‏‎"</string>
+    <string name="dial" msgid="1253998302767701559">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‎Call‎‏‎‎‏‎"</string>
+    <string name="map" msgid="6521159124535543457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‎‎‏‎Locate‎‏‎‎‏‎"</string>
+    <string name="browse" msgid="1245903488306147205">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎Open‎‏‎‎‏‎"</string>
+    <string name="sms" msgid="4560537514610063430">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎Message‎‏‎‎‏‎"</string>
+    <string name="add_contact" msgid="7867066569670597203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎Add‎‏‎‎‏‎"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎Storage space running out‎‏‎‎‏‎"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎Some system functions may not work‎‏‎‎‏‎"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎Not enough storage for the system. Make sure you have 250MB of free space and restart.‎‏‎‎‏‎"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‏‏‎Tablet‎‏‎‎‏‎"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎TV‎‏‎‎‏‎"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎Phone‎‏‎‎‏‎"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎Headphones‎‏‎‎‏‎"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎Dock speakers‎‏‎‎‏‎"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‎HDMI‎‏‎‎‏‎"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‏‏‎HDMI‎‏‎‎‏‎"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‏‎‎Headphones‎‏‎‎‏‎"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎USB‎‏‎‎‏‎"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎System‎‏‎‎‏‎"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‏‎Bluetooth audio‎‏‎‎‏‎"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎Wireless display‎‏‎‎‏‎"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎2nd Work ‎‏‎‎‏‏‎<xliff:g id="LABEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‎‏‎‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‎3rd Work ‎‏‎‎‏‏‎<xliff:g id="LABEL">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‎To unpin this screen, touch &amp; hold Back and Overview buttons‎‏‎‎‏‎"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‎This app can\'t be unpinned‎‏‎‎‏‎"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‎‏‏‎Screen pinned‎‏‎‎‏‎"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎Screen unpinned‎‏‎‎‏‎"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎Ask for PIN before unpinning‎‏‎‎‏‎"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎Emergency messages test‎‏‎‎‏‎"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‎‎‎‎Reply‎‏‎‎‏‎"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎SIM not allowed‎‏‎‎‏‎"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎SIM not provisioned‎‏‎‎‏‎"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎SIM not allowed‎‏‎‎‏‎"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‏‎Phone not allowed‎‏‎‎‏‎"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎SIM not allowed for voice‎‏‎‎‏‎"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‎‏‎‎‏‎SIM not provisioned for voice‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‏‏‎‎SIM not allowed for voice‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎Phone not allowed for voice‎‏‎‎‏‎"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎Popup Window‎‏‎‎‏‎"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎+ ‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‏‎‎‎This shortcut requires latest app‎‏‎‎‏‎"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‎Couldn’t restore shortcut because app doesn’t support backup and restore‎‏‎‎‏‎"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎Couldn’t restore shortcut because of app signature mismatch‎‏‎‎‏‎"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‎Couldn’t restore shortcut‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 4a7a191..67dac60 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Se borró el perfil de trabajo debido a la falta de una app de administración"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"La app de administración de perfil de trabajo no se encuentra o está dañada. Por lo tanto, se borraron tu perfil de trabajo y los datos relacionados. Para obtener asistencia, comunícate con el administrador."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Tu perfil de trabajo ya no está disponible en este dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Demasiados intentos para ingresar la contraseña"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Dispositivo administrado"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Tu organización administra este dispositivo y es posible que controle el tráfico de red. Presiona para obtener más información."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Se borrarán los datos del dispositivo"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertas"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo para punto de venta"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Conexión USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App en ejecución"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps que consumen batería"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> está consumiendo batería"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps están consumiendo batería"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Acciones de texto"</string>
     <string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
-    <string name="dial" msgid="4204975095406423102">"Teléfono"</string>
-    <string name="map" msgid="6068210738233985748">"Mapas"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Llamar"</string>
+    <string name="map" msgid="6521159124535543457">"Buscar"</string>
+    <string name="browse" msgid="1245903488306147205">"Abrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Mensaje"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Agregar"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Queda poco espacio de almacenamiento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Es posible que algunas funciones del sistema no estén disponibles."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"No hay espacio suficiente para el sistema. Asegúrate de que haya 250 MB libres y reinicia el dispositivo."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Dispositivo"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces del conector"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Pantalla inalámbrica"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Prueba de mensajes de emergencia"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM no permitida"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM no provista"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM no permitida"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Teléfono no permitido"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Ventana emergente"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> más"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Este acceso directo requiere la app más reciente"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Error al restablecer el acceso directo porque la app no admite la opción de copia de seguridad y restauración"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Error al restablecer el acceso directo por falta de coincidencia con la firma de apps"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Error al restablecer el acceso directo"</string>
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 425e988..dd26fe0 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -128,7 +128,7 @@
   </string-array>
     <string name="wifi_calling_off_summary" msgid="8720659586041656098">"Desactivado"</string>
     <string name="wfc_mode_wifi_preferred_summary" msgid="1994113411286935263">"Preferir Wi-Fi"</string>
-    <string name="wfc_mode_cellular_preferred_summary" msgid="1988279625335345908">"Preferencia a datos móviles"</string>
+    <string name="wfc_mode_cellular_preferred_summary" msgid="1988279625335345908">"Preferir datos móviles"</string>
     <string name="wfc_mode_wifi_only_summary" msgid="2379919155237869320">"Solo conexión Wi-Fi"</string>
     <string name="cfTemplateNotForwarded" msgid="1683685883841272560">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string>
     <string name="cfTemplateForwarded" msgid="1302922117498590521">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string>
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Se ha eliminado el perfil de trabajo porque falta la aplicación de administración"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Falta la aplicación de administración del perfil de trabajo o está dañada. Por ello, se han eliminado tu perfil de trabajo y los datos relacionados. Ponte en contacto con el administrador para obtener ayuda."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Tu perfil de trabajo ya no está disponible en este dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Has fallado demasiadas veces al introducir la contraseña"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"El dispositivo está administrado"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Tu organización administra este dispositivo y puede supervisar el tráfico de red. Toca la notificación para obtener más información."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Tu dispositivo se borrará"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertas"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo para tiendas"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Conexión USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplicación en ejecución"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplicaciones que consumen batería"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando la batería"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicaciones están usando la batería"</string>
@@ -524,12 +526,12 @@
     <string name="permdesc_modifyNetworkAccounting" msgid="5443412866746198123">"Permite que la aplicación modifique cómo se registra el uso de red en relación con las aplicaciones. Las aplicaciones normales no deben usar este permiso."</string>
     <string name="permlab_accessNotifications" msgid="7673416487873432268">"acceder a las notificaciones"</string>
     <string name="permdesc_accessNotifications" msgid="458457742683431387">"Permite que la aplicación recupere, examine y borre notificaciones, incluidas las que han publicado otras aplicaciones."</string>
-    <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"enlazar con un servicio de detector de notificaciones"</string>
-    <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Permite enlazar con la interfaz de nivel superior de un servicio de detector de notificaciones. No debe ser necesario para las aplicaciones normales."</string>
-    <string name="permlab_bindConditionProviderService" msgid="1180107672332704641">"enlazar con un servicio de proveedor de condiciones"</string>
-    <string name="permdesc_bindConditionProviderService" msgid="1680513931165058425">"Permite enlazar con la interfaz de nivel superior de un servicio de proveedor de condiciones. Las aplicaciones normales no deberían necesitar este permiso."</string>
-    <string name="permlab_bindDreamService" msgid="4153646965978563462">"enlazar con un servicio de salvapantallas"</string>
-    <string name="permdesc_bindDreamService" msgid="7325825272223347863">"Permite enlazar con la interfaz de nivel superior de un servicio de salvapantallas. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_bindNotificationListenerService" msgid="7057764742211656654">"vincular con un servicio de detector de notificaciones"</string>
+    <string name="permdesc_bindNotificationListenerService" msgid="985697918576902986">"Permite vincular con la interfaz de nivel superior de un servicio de detector de notificaciones. No debe ser necesario para las aplicaciones normales."</string>
+    <string name="permlab_bindConditionProviderService" msgid="1180107672332704641">"vincular con un servicio de proveedor de condiciones"</string>
+    <string name="permdesc_bindConditionProviderService" msgid="1680513931165058425">"Permite vincular con la interfaz de nivel superior de un servicio de proveedor de condiciones. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_bindDreamService" msgid="4153646965978563462">"vincular con un servicio de salvapantallas"</string>
+    <string name="permdesc_bindDreamService" msgid="7325825272223347863">"Permite vincular con la interfaz de nivel superior de un servicio de salvapantallas. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_invokeCarrierSetup" msgid="3699600833975117478">"ejecutar la aplicación de configuración proporcionada por el operador"</string>
     <string name="permdesc_invokeCarrierSetup" msgid="4159549152529111920">"Permite ejecutar la aplicación de configuración proporcionada por el operador. No debe ser necesario para aplicaciones normales."</string>
     <string name="permlab_accessNetworkConditions" msgid="8206077447838909516">"detectar cambios en el estado de la red"</string>
@@ -542,10 +544,10 @@
     <string name="permdesc_handoverStatus" msgid="4788144087245714948">"Permite que esta aplicación reciba información sobre las transferencias actuales de Android Beam"</string>
     <string name="permlab_removeDrmCertificates" msgid="7044888287209892751">"quitar certificados DRM"</string>
     <string name="permdesc_removeDrmCertificates" msgid="7272999075113400993">"Permite a una aplicación eliminar los certificados DRM. Las aplicaciones normales no deberí­an necesitar este permiso."</string>
-    <string name="permlab_bindCarrierMessagingService" msgid="1490229371796969158">"enlazar con el servicio de mensajería de un operador"</string>
-    <string name="permdesc_bindCarrierMessagingService" msgid="2762882888502113944">"Permite enlazar con la interfaz de nivel superior del servicio de mensajería de un operador. Las aplicaciones normales no deberían necesitar este permiso."</string>
-    <string name="permlab_bindCarrierServices" msgid="3233108656245526783">"enlazar con servicios de operador"</string>
-    <string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"Permite enlazar con servicios de operador. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_bindCarrierMessagingService" msgid="1490229371796969158">"vincular con el servicio de mensajería de un operador"</string>
+    <string name="permdesc_bindCarrierMessagingService" msgid="2762882888502113944">"Permite vincular con la interfaz de nivel superior del servicio de mensajería de un operador. Las aplicaciones normales no deberían necesitar este permiso."</string>
+    <string name="permlab_bindCarrierServices" msgid="3233108656245526783">"vincular con servicios de operador"</string>
+    <string name="permdesc_bindCarrierServices" msgid="1391552602551084192">"Permite vincular con servicios de operador. Las aplicaciones normales no deberían necesitar este permiso."</string>
     <string name="permlab_access_notification_policy" msgid="4247510821662059671">"acceso a No molestar"</string>
     <string name="permdesc_access_notification_policy" msgid="3296832375218749580">"Permite que la aplicación lea y modifique la configuración de No molestar."</string>
     <string name="policylab_limitPassword" msgid="4497420728857585791">"Establecimiento de reglas de contraseña"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Acciones de texto"</string>
     <string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
-    <string name="dial" msgid="4204975095406423102">"Teléfono"</string>
-    <string name="map" msgid="6068210738233985748">"Mapas"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Llamar"</string>
+    <string name="map" msgid="6521159124535543457">"Localizar"</string>
+    <string name="browse" msgid="1245903488306147205">"Abrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Mensaje"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Añadir"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Queda poco espacio"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Es posible que algunas funciones del sistema no funcionen."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"No hay espacio suficiente para el sistema. Comprueba que haya 250 MB libres y reinicia el dispositivo."</string>
@@ -1221,7 +1225,7 @@
     <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"El dispositivo no admite este medio externo (<xliff:g id="NAME">%s</xliff:g>). Toca para configurarlo con un formato admitido."</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"El dispositivo no admite esta <xliff:g id="NAME">%s</xliff:g>. Selecciónala para configurarla en un formato admitido."</string>
     <string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"Extracción inesperada de <xliff:g id="NAME">%s</xliff:g>"</string>
-    <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"Desactiva tu <xliff:g id="NAME">%s</xliff:g> antes de extraer la unidad para evitar pérdidas de datos"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"Desconecta tu <xliff:g id="NAME">%s</xliff:g> antes de extraer la unidad para evitar pérdidas de datos"</string>
     <string name="ext_media_nomedia_notification_title" msgid="1704840188641749091">"Tu <xliff:g id="NAME">%s</xliff:g> se ha extraído"</string>
     <string name="ext_media_nomedia_notification_message" msgid="6471542972147056586">"Tu <xliff:g id="NAME">%s</xliff:g> se ha extraído: inserta otra unidad"</string>
     <string name="ext_media_unmounting_notification_title" msgid="640674168454809372">"Expulsando <xliff:g id="NAME">%s</xliff:g>…"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Teléfono"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altavoces de la base"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Pantalla inalámbrica"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo 2"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"<xliff:g id="LABEL">%1$s</xliff:g> de trabajo 3"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Mantén pulsado el botón Atrás y el de aplicaciones recientes para dejar de fijar esta pantalla"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Esta aplicación no se puede dejar de fijar"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Pantalla fijada"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"La pantalla ya no está fija"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Solicitar PIN para desactivar"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Prueba de mensajes de emergencia"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM no compatible"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM no proporcionada"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM no compatible"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Teléfono no compatible"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM no permitida para voz"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM no proporcionada para voz"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM no permitida para voz"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Teléfono no permitido para voz"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Ventana emergente"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> más"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Para usar este acceso directo, necesitas la última versión de la aplicación"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"No se ha podido restaurar el acceso directo porque la aplicación no es compatible con la función de copia de seguridad y restauración"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"No se ha podido restaurar el acceso directo porque la firma de la aplicación no coincide"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"No se ha podido restaurar el acceso directo"</string>
 </resources>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 7c57daf..27ceef2 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Tööprofiil on kustutatud puuduva administraatori rakenduse tõttu"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Tööprofiili administraatori rakendus puudub või on rikutud. Seetõttu on teie tööprofiil ja seotud andmed kustutatud. Abi saamiseks võtke ühendust administraatoriga."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Teie tööprofiil pole selles seadmes enam saadaval"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Liiga palju paroolikatseid"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Seade on hallatud"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Teie organisatsioon haldab seda seadet ja võib jälgida võrguliiklust. Puudutage üksikasjade vaatamiseks."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Seade kustutatakse"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Teatised"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Poedemo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-ühendus"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Rakendus töötab"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Rakendused kasutavad akutoidet"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab akutoidet"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> rakendust kasutab akutoidet"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Sisestusmeetod"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstitoimingud"</string>
     <string name="email" msgid="4560673117055050403">"E-post"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"Brauser"</string>
+    <string name="dial" msgid="1253998302767701559">"Helista"</string>
+    <string name="map" msgid="6521159124535543457">"Leia"</string>
+    <string name="browse" msgid="1245903488306147205">"Ava"</string>
+    <string name="sms" msgid="4560537514610063430">"Saada sõnum"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Lisa"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Talletusruum saab täis"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Mõned süsteemifunktsioonid ei pruugi töötada"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Süsteemis pole piisavalt talletusruumi. Veenduge, et seadmes oleks 250 MB vaba ruumi, ja käivitage seade uuesti."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tahvelarvuti"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Teler"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kõrvaklapid"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Doki kõlarid"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kõrvaklapid"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Süsteem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-heli"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Juhtmeta ekraan"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Hädaabisõnumite test"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Vasta"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-kaart pole lubatud"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-kaart on ettevalmistamata"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-kaart pole lubatud"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon pole lubatud"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Hüpikaken"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"See otsetee nõuab rakenduse uusimat versiooni"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Otseteed ei õnnestunud taastada, kuna rakendus ei toeta varundamist ega taastamist"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Otseteed ei õnnestunud taastada, kuna rakenduse allkiri ei ühti"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Otseteed ei õnnestunud taastada"</string>
 </resources>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 5556a5d..ae29264 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Laneko profila ezabatu egin da hura administratzeko aplikazioa falta delako"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Laneko profila administratzeko aplikazioa falta da edo hondatuta dago. Ondorioz, ezabatu egin dira laneko profila bera eta harekin erlazionatutako datuak. Laguntza lortzeko, jarri administratzailearekin harremanetan."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Laneko profila ez dago erabilgarri gailu honetan"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Gehiegitan saiatu zara pasahitza idazten"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Jabeak kudeatzen du gailua"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Erakundeak kudeatzen du gailua eta baliteke sareko trafikoa gainbegiratzea. Sakatu hau xehetasunak ikusteko."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Gailuko datuak ezabatu egingo dira"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Abisuak"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Saltzaileentzako demoa"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB konexioa"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikazio bat abian da"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Bateria kontsumitzen ari diren aplikazioak"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ari da bateria erabiltzen"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikazio ari dira bateria erabiltzen"</string>
@@ -582,7 +584,7 @@
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"Etxekoa"</item>
     <item msgid="869923650527136615">"Mugikorra"</item>
-    <item msgid="7897544654242874543">"Lanekoa"</item>
+    <item msgid="7897544654242874543">"Lantokia"</item>
     <item msgid="1103601433382158155">"Laneko faxa"</item>
     <item msgid="1735177144948329370">"Etxeko faxa"</item>
     <item msgid="603878674477207394">"Bilagailua"</item>
@@ -591,24 +593,24 @@
   </string-array>
   <string-array name="emailAddressTypes">
     <item msgid="8073994352956129127">"Etxekoa"</item>
-    <item msgid="7084237356602625604">"Lanekoa"</item>
+    <item msgid="7084237356602625604">"Lantokia"</item>
     <item msgid="1112044410659011023">"Beste bat"</item>
     <item msgid="2374913952870110618">"Pertsonalizatua"</item>
   </string-array>
   <string-array name="postalAddressTypes">
     <item msgid="6880257626740047286">"Etxekoa"</item>
-    <item msgid="5629153956045109251">"Lanekoa"</item>
+    <item msgid="5629153956045109251">"Lantokia"</item>
     <item msgid="4966604264500343469">"Beste bat"</item>
     <item msgid="4932682847595299369">"Pertsonalizatua"</item>
   </string-array>
   <string-array name="imAddressTypes">
     <item msgid="1738585194601476694">"Etxekoa"</item>
-    <item msgid="1359644565647383708">"Lanekoa"</item>
+    <item msgid="1359644565647383708">"Lantokia"</item>
     <item msgid="7868549401053615677">"Beste bat"</item>
     <item msgid="3145118944639869809">"Pertsonalizatua"</item>
   </string-array>
   <string-array name="organizationTypes">
-    <item msgid="7546335612189115615">"Lanekoa"</item>
+    <item msgid="7546335612189115615">"Lantokia"</item>
     <item msgid="4378074129049520373">"Beste bat"</item>
     <item msgid="3455047468583965104">"Pertsonalizatua"</item>
   </string-array>
@@ -625,7 +627,7 @@
     <string name="phoneTypeCustom" msgid="1644738059053355820">"Pertsonalizatua"</string>
     <string name="phoneTypeHome" msgid="2570923463033985887">"Etxekoa"</string>
     <string name="phoneTypeMobile" msgid="6501463557754751037">"Mugikorra"</string>
-    <string name="phoneTypeWork" msgid="8863939667059911633">"Lanekoa"</string>
+    <string name="phoneTypeWork" msgid="8863939667059911633">"Lantokia"</string>
     <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Laneko faxa"</string>
     <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Etxeko faxa"</string>
     <string name="phoneTypePager" msgid="7582359955394921732">"Bilagailua"</string>
@@ -649,16 +651,16 @@
     <string name="eventTypeOther" msgid="7388178939010143077">"Beste bat"</string>
     <string name="emailTypeCustom" msgid="8525960257804213846">"Pertsonalizatua"</string>
     <string name="emailTypeHome" msgid="449227236140433919">"Etxekoa"</string>
-    <string name="emailTypeWork" msgid="3548058059601149973">"Lanekoa"</string>
+    <string name="emailTypeWork" msgid="3548058059601149973">"Lantokia"</string>
     <string name="emailTypeOther" msgid="2923008695272639549">"Beste bat"</string>
     <string name="emailTypeMobile" msgid="119919005321166205">"Mugikorra"</string>
     <string name="postalTypeCustom" msgid="8903206903060479902">"Pertsonalizatua"</string>
     <string name="postalTypeHome" msgid="8165756977184483097">"Etxekoa"</string>
-    <string name="postalTypeWork" msgid="5268172772387694495">"Lanekoa"</string>
+    <string name="postalTypeWork" msgid="5268172772387694495">"Lantokia"</string>
     <string name="postalTypeOther" msgid="2726111966623584341">"Beste bat"</string>
     <string name="imTypeCustom" msgid="2074028755527826046">"Pertsonalizatua"</string>
     <string name="imTypeHome" msgid="6241181032954263892">"Orri nagusia"</string>
-    <string name="imTypeWork" msgid="1371489290242433090">"Lanekoa"</string>
+    <string name="imTypeWork" msgid="1371489290242433090">"Lantokia"</string>
     <string name="imTypeOther" msgid="5377007495735915478">"Beste bat"</string>
     <string name="imProtocolCustom" msgid="6919453836618749992">"Pertsonalizatua"</string>
     <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string>
@@ -670,7 +672,7 @@
     <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string>
     <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string>
     <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string>
-    <string name="orgTypeWork" msgid="29268870505363872">"Lanekoa"</string>
+    <string name="orgTypeWork" msgid="29268870505363872">"Lantokia"</string>
     <string name="orgTypeOther" msgid="3951781131570124082">"Bestelakoa"</string>
     <string name="orgTypeCustom" msgid="225523415372088322">"Pertsonalizatua"</string>
     <string name="relationTypeCustom" msgid="3542403679827297300">"Pertsonalizatua"</string>
@@ -690,7 +692,7 @@
     <string name="relationTypeSpouse" msgid="394136939428698117">"Ezkonlaguna"</string>
     <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Pertsonalizatua"</string>
     <string name="sipAddressTypeHome" msgid="6093598181069359295">"Etxekoa"</string>
-    <string name="sipAddressTypeWork" msgid="6920725730797099047">"Lanekoa"</string>
+    <string name="sipAddressTypeWork" msgid="6920725730797099047">"Lantokia"</string>
     <string name="sipAddressTypeOther" msgid="4408436162950119849">"Beste bat"</string>
     <string name="quick_contacts_not_available" msgid="746098007828579688">"Ez da kontaktua ikusteko aplikaziorik aurkitu."</string>
     <string name="keyguard_password_enter_pin_code" msgid="3037685796058495017">"Idatzi PIN kodea"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Idazketa-metodoa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Testu-ekintzak"</string>
     <string name="email" msgid="4560673117055050403">"Posta"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefonoa"</string>
-    <string name="map" msgid="6068210738233985748">"Mapak"</string>
-    <string name="browse" msgid="6993590095938149861">"Arakatzailea"</string>
+    <string name="dial" msgid="1253998302767701559">"Deitu"</string>
+    <string name="map" msgid="6521159124535543457">"Aurkitu"</string>
+    <string name="browse" msgid="1245903488306147205">"Ireki"</string>
+    <string name="sms" msgid="4560537514610063430">"Bidali mezua"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Gehitu"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Memoria betetzen ari da"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Sistemaren funtzio batzuek ez dute agian funtzionatuko"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Sisteman ez dago behar adina memoria. Ziurtatu gutxienez 250 MB erabilgarri dituzula eta, ondoren, berrabiarazi gailua."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tableta"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Telebista"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefonoa"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Aurikularrak"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Konektatu bozgorailuak oinarrira"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Aurikularrak"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetootharen audioa"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Hari gabeko pantaila"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Larrialdi-mezuen proba"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Erantzun"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Ez da onartzen SIM txartela"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Ez dago SIM txartelik"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Ez da onartzen SIM txartela"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Ez da onartzen telefonoa"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Leiho gainerakorra"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"Beste <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Aplikazioaren bertsio berriena behar da lasterbideak funtziona dezan"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Ezin izan da leheneratu lasterbidea aplikazioak ez duelako onartzen babeskopiak egiteko eta leheneratzeko aukera"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Ezin izan da leheneratu lasterbidea aplikazioaren sinadurak ez datozelako bat"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Ezin izan da leheneratu lasterbidea"</string>
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index bb9635a..54b4011 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"به دلیل نداشتن برنامه سرپرست، نمایه کاری حذف شد"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"برنامه سرپرست نمایه کاری یا وجود ندارد یا خراب است. در نتیجه، نمایه کاری شما و داده‌های مرتبط با آن حذف شده است. برای دریافت راهنمایی با سرپرست سیستم تماس بگیرید."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"نمایه کاری شما دیگر در این دستگاه دردسترس نیست"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"تلاش‌های بسیار زیادی برای وارد کردن گذرواژه انجام شده است"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"دستگاه مدیریت می‌شود"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"سازمانتان این دستگاه را مدیریت می‌کند و ممکن است ترافیک شبکه را پایش کند. برای اطلاع از جزئیات، ضربه بزنید."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"دستگاهتان پاک خواهد شد"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"هشدارها"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"نمونه برای خرده‌فروشان"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"‏اتصال USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"برنامه درحال اجرا"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"برنامه‌های مصرف‌کننده باتری"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده کردن از باتری است"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> برنامه درحال استفاده کردن از باتری هستند"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"روش ورودی"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"عملکردهای متنی"</string>
     <string name="email" msgid="4560673117055050403">"رایانامه"</string>
-    <string name="dial" msgid="4204975095406423102">"تلفن"</string>
-    <string name="map" msgid="6068210738233985748">"نقشه‌ها"</string>
-    <string name="browse" msgid="6993590095938149861">"مرورگر"</string>
+    <string name="dial" msgid="1253998302767701559">"تماس"</string>
+    <string name="map" msgid="6521159124535543457">"مکان‌یابی"</string>
+    <string name="browse" msgid="1245903488306147205">"باز کردن"</string>
+    <string name="sms" msgid="4560537514610063430">"پیام"</string>
+    <string name="add_contact" msgid="7867066569670597203">"افزودن"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"حافظه درحال پر شدن است"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"برخی از عملکردهای سیستم ممکن است کار نکنند"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"فضای ذخیره‌سازی سیستم کافی نیست. اطمینان حاصل کنید که دارای ۲۵۰ مگابایت فضای خالی هستید و سیستم را راه‌اندازی مجدد کنید."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"رایانهٔ لوحی"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"تلویزیون"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"تلفن"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"هدفون‌ها"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"بلندگوهای جایگاه"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"هدفون‌ها"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"سیستم"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"بلوتوث‌های صوتی"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"صفحه نمایش بی‌سیم"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"آزمایش پیام‌های اضطراری"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"پاسخ"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"سیم‌کارت مجاز نیست"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"سیم‌کارت مجوز لازم را ندارد"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"سیم‌کارت مجاز نیست"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"تلفن مجاز نیست"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"پنجره بازشو"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"‎+ <xliff:g id="NUMBER">%1$d</xliff:g>‎"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"این میان‌بر به جدیدترین نسخه برنامه نیاز دارد"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"نمی‌توان میان‌بر را بازیابی کرد زیرا برنامه از پشتیبان‌گیری و بازیابی پشتیبانی نمی‌کند"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"به‌علت عدم تطابق امضای برنامه نمی‌توان میان‌بر را بازیابی کرد"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"نمی‌توان میان‌بر را بازیابی کرد"</string>
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index a32db2c..9ec963b 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Työprofiili poistettiin, koska laitteesta puuttuu hallintasovellus."</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Työprofiilin hallintasovellus puuttuu tai se on vioittunut. Tästä syystä työprofiilisi ja siihen liittyvät tiedot on poistettu. Pyydä ohjeita järjestelmänvalvojaltasi."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Työprofiilisi ei ole enää käytettävissä tällä laitteella."</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Liikaa salasanayrityksiä"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Hallinnoitu laite"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organisaatiosi hallinnoi tätä laitetta ja voi tarkkailla verkkoliikennettä. Katso lisätietoja napauttamalla."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Laitteen tiedot poistetaan"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Ilmoitukset"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Esittelytila"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-yhteys"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Sovellus käynnissä"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Akkua kuluttavat sovellukset"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää akkua."</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> sovellusta käyttää akkua."</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Syöttötapa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstitoiminnot"</string>
     <string name="email" msgid="4560673117055050403">"Sähköposti"</string>
-    <string name="dial" msgid="4204975095406423102">"Puhelin"</string>
-    <string name="map" msgid="6068210738233985748">"Kartat"</string>
-    <string name="browse" msgid="6993590095938149861">"Selain"</string>
+    <string name="dial" msgid="1253998302767701559">"Soita"</string>
+    <string name="map" msgid="6521159124535543457">"Paikanna"</string>
+    <string name="browse" msgid="1245903488306147205">"Avaa"</string>
+    <string name="sms" msgid="4560537514610063430">"Viesti"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Lisää"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Tallennustila loppumassa"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Kaikki järjestelmätoiminnot eivät välttämättä toimi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Tallennustila ei riitä. Varmista, että vapaata tilaa on 250 Mt, ja käynnistä uudelleen."</string>
@@ -1130,7 +1134,7 @@
     <string name="wifi_connect_alert_title" msgid="8455846016001810172">"Sallitaanko yhteys?"</string>
     <string name="wifi_connect_alert_message" msgid="6451273376815958922">"Sovellus %1$s yrittää yhdistää Wi-Fi-verkkoon %2$s."</string>
     <string name="wifi_connect_default_application" msgid="7143109390475484319">"Sovellus"</string>
-    <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Suora Wi-Fi-yhteys"</string>
+    <string name="wifi_p2p_dialog_title" msgid="97611782659324517">"Wi-Fi Direct"</string>
     <string name="wifi_p2p_turnon_message" msgid="2909250942299627244">"Käynnistä suora Wi-Fi-yhteys. Wi-Fi-asiakas/-hotspot poistetaan käytöstä."</string>
     <string name="wifi_p2p_failed_message" msgid="3763669677935623084">"Suoran Wi-Fi-yhteyden käynnistäminen epäonnistui."</string>
     <string name="wifi_p2p_enabled_notification_title" msgid="2068321881673734886">"Wi-Fi Direct on käytössä"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tabletti"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televisio"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Puhelin"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kuulokkeet"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Telineen kaiuttimet"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kuulokkeet"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Järjestelmä"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-ääni"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Langaton näyttö"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Hätäilmoitustesti"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Vastaa"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-kortti estetty"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-kortti ei käyttäjien hallinnassa"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-kortti estetty"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Puhelin estetty"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Ponnahdusikkuna"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Tämä pikakuvake edellyttää uusinta sovellusta."</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Pikakuvakkeen palautus epäonnistui, koska sovellus ei tue varmuuskopiointia eikä palauttamista."</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Pikakuvakkeen palautus epäonnistui sovelluksen allekirjoituksen yhteensopimattomuuden vuoksi."</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Pikakuvakkeen palautus epäonnistui."</string>
 </resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 8fe60e3..16283e7 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profil professionnel supprimé en raison de l\'application d\'administration manquante"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Le profil professionnel de l\'application d\'administration est manquant ou corrompu. Votre profil professionnel et ses données connexes ont donc été supprimés. Communiquez avec votre administrateur pour obtenir de l\'assistance."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Votre profil professionnel n\'est plus accessible sur cet appareil"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Trop de tentatives d\'entrée du mot de passe"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"L\'appareil est géré"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Votre organisation gère cet appareil et peut surveiller le trafic réseau. Touchez ici pour obtenir plus d\'information."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Le contenu de votre appareil sera effacé"</string>
@@ -248,12 +249,13 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertes"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Démo en magasin"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Connexion USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Application en cours d\'exécution"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Applications qui sollicitent la pile"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> sollicite la pile"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> applications sollicitent la pile"</string>
     <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Touchez pour afficher des détails sur l\'utilisation de la pile et des données"</string>
     <string name="foreground_service_multiple_separator" msgid="4021901567939866542">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
-    <string name="safeMode" msgid="2788228061547930246">"Mode sécurisé"</string>
+    <string name="safeMode" msgid="2788228061547930246">"Mode sans échec"</string>
     <string name="android_system_label" msgid="6577375335728551336">"Système Android"</string>
     <string name="user_owner_label" msgid="1119010402169916617">"Passer au profil personnel"</string>
     <string name="managed_profile_label" msgid="5289992269827577857">"Passer au profil professionnel"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Actions sur le texte"</string>
     <string name="email" msgid="4560673117055050403">"Courriel"</string>
-    <string name="dial" msgid="4204975095406423102">"Téléphone"</string>
-    <string name="map" msgid="6068210738233985748">"Cartes"</string>
-    <string name="browse" msgid="6993590095938149861">"Navigateur"</string>
+    <string name="dial" msgid="1253998302767701559">"Appel"</string>
+    <string name="map" msgid="6521159124535543457">"Localiser"</string>
+    <string name="browse" msgid="1245903488306147205">"Ouvert"</string>
+    <string name="sms" msgid="4560537514610063430">"Message"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Ajouter"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Espace de stockage bientôt saturé"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Il est possible que certaines fonctionnalités du système ne soient pas opérationnelles."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Espace de stockage insuffisant pour le système. Assurez-vous de disposer de 250 Mo d\'espace libre, puis redémarrez."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablette"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Télévision"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Téléphone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Oreillettes"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Haut-parleurs de la station d\'accueil"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Oreillettes"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Système"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Affichage sans fil"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test de messages d\'urgence"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Répondre"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Carte SIM non autorisée"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Carte SIM non configurée"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Carte SIM non autorisée"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Téléphone non autorisé"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Fenêtre contextuelle"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ce raccourci nécessite la dernière version de l\'application"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Impossible de restaurer le raccourci, car l\'application ne prend pas en charge la sauvegarde et la restauration"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Impossible de restaurer le raccourci en raison d\'une erreur de correspondance des signature d\'applications"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Impossible de restaurer le raccourci"</string>
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 52d4824..34e761b 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profil professionnel supprimé, car une application d\'administration est manquante"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"L\'application d\'administration du profil professionnel est manquante ou endommagée. Par conséquent, votre profil professionnel et toutes les données associées ont été supprimés. Pour obtenir de l\'aide, contactez l\'administrateur."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Votre profil professionnel n\'est plus disponible sur cet appareil"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Trop de tentatives de saisie du mot de passe"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"L\'appareil est géré"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Votre organisation gère cet appareil et peut surveiller le trafic réseau. Appuyez ici pour obtenir plus d\'informations."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Les données de votre appareil vont être effacées"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertes"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Démonstration en magasin"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Connexion USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Application en cours d\'exécution"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Applications utilisant la batterie"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise la batterie"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> applications utilisent la batterie"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Actions sur le texte"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Téléphone"</string>
-    <string name="map" msgid="6068210738233985748">"Cartes"</string>
-    <string name="browse" msgid="6993590095938149861">"Navigateur"</string>
+    <string name="dial" msgid="1253998302767701559">"Appeler"</string>
+    <string name="map" msgid="6521159124535543457">"Localiser"</string>
+    <string name="browse" msgid="1245903488306147205">"Ouvrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Envoyer un message"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Ajouter"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Espace de stockage bientôt saturé"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Il est possible que certaines fonctionnalités du système ne soient pas opérationnelles."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Espace de stockage insuffisant pour le système. Assurez-vous de disposer de 250 Mo d\'espace libre, puis redémarrez."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablette"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Téléviseur"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Téléphone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Écouteurs"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Haut-parleurs de la station d\'accueil"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Écouteurs"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Système"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Affichage sans fil"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test de messages d\'urgence"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Répondre"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Carte SIM non autorisée"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Carte SIM non configurée"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Carte SIM non autorisée"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Téléphone non autorisé"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Fenêtre pop-up"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> autres"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ce raccourci nécessite la dernière version de l\'application"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Impossible de restaurer le raccourci, car l\'application n\'accepte pas la sauvegarde et la restauration"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Impossible de restaurer le raccourci en raison de la non-correspondance de la signature de l\'application"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Impossible de restaurer le raccourci"</string>
 </resources>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 9a3a83f..e3b93cb 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -52,8 +52,8 @@
     </plurals>
     <string name="imei" msgid="2625429890869005782">"IMEI"</string>
     <string name="meid" msgid="4841221237681254195">"MEID"</string>
-    <string name="ClipMmi" msgid="6952821216480289285">"ID de chamada entrante"</string>
-    <string name="ClirMmi" msgid="7784673673446833091">"ID de chamada saínte"</string>
+    <string name="ClipMmi" msgid="6952821216480289285">"Identificador de chamada entrante"</string>
+    <string name="ClirMmi" msgid="7784673673446833091">"Identificador de chamada saínte"</string>
     <string name="ColpMmi" msgid="3065121483740183974">"ID de liña conectada"</string>
     <string name="ColrMmi" msgid="4996540314421889589">"Restrición de ID de liña conectada"</string>
     <string name="CfMmi" msgid="5123218989141573515">"Desvío de chamadas"</string>
@@ -67,24 +67,24 @@
     <string name="RuacMmi" msgid="7827887459138308886">"Rexeitamento de chamadas molestas non desexadas"</string>
     <string name="CndMmi" msgid="3116446237081575808">"Entrega de número de chamada entrante"</string>
     <string name="DndMmi" msgid="1265478932418334331">"Non molestar"</string>
-    <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"De forma predeterminada, restrínxese o ID de chamada. Próxima chamada: restrinxido."</string>
-    <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"De forma predeterminada, restrínxese o ID de chamada. Próxima chamada: non restrinxido."</string>
-    <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"De forma predeterminada, non se restrinxe o ID de chamada. Próxima chamada: restrinxido."</string>
-    <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"De forma predeterminada, non se restrinxe o ID de chamada. Próxima chamada: non restrinxido."</string>
+    <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: restrinxido"</string>
+    <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
+    <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"O valor predeterminado do identificador de chamada é non restrinxido. Próxima chamada: restrinxido"</string>
+    <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
     <string name="serviceNotProvisioned" msgid="8614830180508686666">"Servizo non ofrecido."</string>
-    <string name="CLIRPermanent" msgid="3377371145926835671">"Non podes cambiar a configuración do ID de chamada."</string>
+    <string name="CLIRPermanent" msgid="3377371145926835671">"Non podes cambiar a configuración do identificador de chamada."</string>
     <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"Non hai servizo de datos"</string>
-    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"Non se poden realizar chamadas de emerxencia"</string>
+    <string name="RestrictedOnEmergencyTitle" msgid="3646729271176394091">"Non se poden realizar chamadas de urxencia"</string>
     <string name="RestrictedOnNormalTitle" msgid="3179574012752700984">"Non hai servizo de chamadas de voz"</string>
-    <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"Non hai servizo de chamadas de emerxencia nin de voz"</string>
+    <string name="RestrictedOnAllVoiceTitle" msgid="158800171499150681">"Non hai servizo de chamadas de urxencia nin de voz"</string>
     <string name="RestrictedStateContent" msgid="4278821484643362350">"A rede de telefonía móbil non ofrece o servizo na túa localización temporalmente"</string>
     <string name="NetworkPreferenceSwitchTitle" msgid="4008877505368566980">"Non se pode conectar coa rede"</string>
     <string name="NetworkPreferenceSwitchSummary" msgid="1203771446683319957">"Para mellorar a recepción, proba a cambiar o tipo seleccionado en Configuración &gt; Rede e Internet &gt; Redes de telefonía móbil &gt; Tipo de rede preferido."</string>
     <string name="EmergencyCallWarningTitle" msgid="4790413876281901612">"As chamadas por wifi están activadas"</string>
-    <string name="EmergencyCallWarningSummary" msgid="8973232888021643293">"As chamadas de emerxencia precisan unha rede de telefonía móbil."</string>
+    <string name="EmergencyCallWarningSummary" msgid="8973232888021643293">"As chamadas de urxencia precisan unha rede de telefonía móbil."</string>
     <string name="notification_channel_network_alert" msgid="4427736684338074967">"Alertas"</string>
     <string name="notification_channel_call_forward" msgid="2419697808481833249">"Desvío de chamadas"</string>
-    <string name="notification_channel_emergency_callback" msgid="6686166232265733921">"Modo de devolución de chamadas de emerxencia"</string>
+    <string name="notification_channel_emergency_callback" msgid="6686166232265733921">"Modo de devolución de chamadas de urxencia"</string>
     <string name="notification_channel_mobile_data_status" msgid="4575131690860945836">"Estado dos datos móbiles"</string>
     <string name="notification_channel_sms" msgid="3441746047346135073">"Mensaxes SMS"</string>
     <string name="notification_channel_voice_mail" msgid="3954099424160511919">"Mensaxes de correo de voz"</string>
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Eliminouse o perfil de traballo porque falta a aplicación de administración"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Falta a aplicación de administración do perfil de traballo ou ben está danada. Como resultado, eliminouse o teu perfil de traballo e os datos relacionados. Para obter asistencia, contacta co administrador."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"O teu perfil de traballo xa non está dispoñible neste dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Demasiados intentos de introdución do contrasinal"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"O dispositivo está xestionado"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"A túa organización xestiona este dispositivo e pode controlar o tráfico de rede. Toca para obter máis detalles."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Borrarase o teu dispositivo"</string>
@@ -188,7 +189,7 @@
     <string name="silent_mode_silent" msgid="319298163018473078">"Timbre desactivado"</string>
     <string name="silent_mode_vibrate" msgid="7072043388581551395">"Timbre en vibración"</string>
     <string name="silent_mode_ring" msgid="8592241816194074353">"Timbre activado"</string>
-    <string name="reboot_to_update_title" msgid="6212636802536823850">"Actualización do sistema de Android"</string>
+    <string name="reboot_to_update_title" msgid="6212636802536823850">"Actualización do sistema Android"</string>
     <string name="reboot_to_update_prepare" msgid="6305853831955310890">"Preparando para actualizar…"</string>
     <string name="reboot_to_update_package" msgid="3871302324500927291">"Procesando paquete de actualización…"</string>
     <string name="reboot_to_update_reboot" msgid="6428441000951565185">"Reiniciando..."</string>
@@ -209,7 +210,7 @@
     <string name="global_actions" product="default" msgid="2406416831541615258">"Opcións de teléfono"</string>
     <string name="global_action_lock" msgid="2844945191792119712">"Bloqueo de pantalla"</string>
     <string name="global_action_power_off" msgid="4471879440839879722">"Apagar"</string>
-    <string name="global_action_emergency" msgid="7112311161137421166">"Emerxencias"</string>
+    <string name="global_action_emergency" msgid="7112311161137421166">"Urxencias"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"Informe de erros"</string>
     <string name="bugreport_title" msgid="2667494803742548533">"Crear informe de erros"</string>
     <string name="bugreport_message" msgid="398447048750350456">"Este informe recompilará información acerca do estado actual do teu dispositivo para enviala en forma de mensaxe de correo electrónico. O informe de erros tardará un pouco en completarse desde o seu inicio ata que estea preparado para enviarse, polo que che recomendamos que teñas paciencia."</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertas"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demostración comercial"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"conexión USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Estase executando a aplicación"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplicacións que consumen batería"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo batería"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacións están consumindo batería"</string>
@@ -315,7 +317,7 @@
     <string name="permlab_receiveMms" msgid="1821317344668257098">"recibir mensaxes de texto (MMS)"</string>
     <string name="permdesc_receiveMms" msgid="533019437263212260">"Permite á aplicación recibir e procesar mensaxes MMS. Isto significa que a aplicación pode supervisar ou eliminar mensaxes enviadas ao teu dispositivo sen mostrarchas."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"ler mensaxes de difusión móbil"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Permite á aplicación ler mensaxes de difusión móbil recibidas polo teu dispositivo. As alertas de difusión móbil envíanse nalgunhas localizacións para avisar de situacións de emerxencia. É posible que aplicacións maliciosas afecten ao rendemento ou funcionamento do teu dispositivo cando se recibe unha difusión móbil de emerxencia."</string>
+    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Permite á aplicación ler mensaxes de difusión móbil recibidas polo teu dispositivo. As alertas de difusión móbil envíanse nalgunhas localizacións para avisar de situacións de urxencia. É posible que aplicacións maliciosas afecten ao rendemento ou funcionamento do teu dispositivo cando se recibe unha difusión móbil de urxencia."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"ler feeds subscritos"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Permite á aplicación obter detalles acerca dos feeds sincronizados actualmente."</string>
     <string name="permlab_sendSms" msgid="7544599214260982981">"enviar e consultar mensaxes de SMS"</string>
@@ -401,7 +403,7 @@
     <string name="permlab_vibrate" msgid="7696427026057705834">"controlar a vibración"</string>
     <string name="permdesc_vibrate" msgid="6284989245902300945">"Permite á aplicación controlar o vibrador."</string>
     <string name="permlab_callPhone" msgid="3925836347681847954">"chamar directamente aos números de teléfono"</string>
-    <string name="permdesc_callPhone" msgid="3740797576113760827">"Permite á aplicación chamar a números de teléfono sen a túa intervención. Esta acción pode implicar chamadas ou custos inesperados. Ten en conta que isto non permite á aplicación chamar a números de emerxencia. É posible que aplicacións maliciosas che custen diñeiro debido á realización de chamadas sen a túa confirmación."</string>
+    <string name="permdesc_callPhone" msgid="3740797576113760827">"Permite á aplicación chamar a números de teléfono sen a túa intervención. Esta acción pode implicar chamadas ou custos inesperados. Ten en conta que isto non permite á aplicación chamar a números de urxencia. É posible que aplicacións maliciosas che custen diñeiro debido á realización de chamadas sen a túa confirmación."</string>
     <string name="permlab_accessImsCallService" msgid="3574943847181793918">"acceso ao servizo de chamadas de IMS"</string>
     <string name="permdesc_accessImsCallService" msgid="8992884015198298775">"Permite que a aplicación use o servizo de IMS para facer chamadas sen a túa intervención."</string>
     <string name="permlab_readPhoneState" msgid="9178228524507610486">"ler o estado e a identidade do teléfono"</string>
@@ -702,13 +704,13 @@
     <string name="keyguard_password_enter_pin_password_code" msgid="6391755146112503443">"Escribe o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="2422225591006134936">"Código PIN incorrecto"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"Para desbloquear, preme Menú e, a continuación, 0."</string>
-    <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de emerxencia"</string>
+    <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"Número de urxencia"</string>
     <string name="lockscreen_carrier_default" msgid="6169005837238288522">"Sen servizo"</string>
     <string name="lockscreen_screen_locked" msgid="7288443074806832904">"Pantalla bloqueada"</string>
-    <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Preme Menú para desbloquear ou realizar unha chamada de emerxencia."</string>
+    <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"Preme Menú para desbloquear ou realizar unha chamada de urxencia."</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"Preme Menú para desbloquear."</string>
     <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"Crea o padrón de desbloqueo"</string>
-    <string name="lockscreen_emergency_call" msgid="5298642613417801888">"Emerxencia"</string>
+    <string name="lockscreen_emergency_call" msgid="5298642613417801888">"Urxencia"</string>
     <string name="lockscreen_return_to_call" msgid="5244259785500040021">"Volver á chamada"</string>
     <string name="lockscreen_pattern_correct" msgid="9039008650362261237">"Correcto!"</string>
     <string name="lockscreen_pattern_wrong" msgid="4317955014948108794">"Téntao de novo"</string>
@@ -730,7 +732,7 @@
     <string name="lockscreen_transport_stop_description" msgid="5907083260651210034">"Deter"</string>
     <string name="lockscreen_transport_rew_description" msgid="6944412838651990410">"Rebobinar"</string>
     <string name="lockscreen_transport_ffw_description" msgid="42987149870928985">"Avance rápido"</string>
-    <string name="emergency_calls_only" msgid="6733978304386365407">"Só chamadas de emerxencia"</string>
+    <string name="emergency_calls_only" msgid="6733978304386365407">"Só chamadas de urxencia"</string>
     <string name="lockscreen_network_locked_message" msgid="143389224986028501">"Bloqueada pola rede"</string>
     <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"A tarxeta SIM está bloqueada con código PUK."</string>
     <string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"Consulta a guía do usuario ou ponte en contacto co servizo de asistencia ao cliente."</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Accións de texto"</string>
     <string name="email" msgid="4560673117055050403">"Correo electrónico"</string>
-    <string name="dial" msgid="4204975095406423102">"Teléfono"</string>
-    <string name="map" msgid="6068210738233985748">"Mapas"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Chamar"</string>
+    <string name="map" msgid="6521159124535543457">"Localizar"</string>
+    <string name="browse" msgid="1245903488306147205">"Abrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Mensaxe"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Engadir"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Estase esgotando o espazo de almacenamento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"É posible que algunhas funcións do sistema non funcionen"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Non hai almacenamento suficiente para o sistema. Asegúrate de ter un espazo libre de 250 MB e reinicia o dispositivo."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tableta"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televisión"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Teléfono"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Conectar altofalantes á base"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auriculares"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio por Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Visualización sen fíos"</string>
@@ -1774,12 +1779,21 @@
     <string name="etws_primary_default_message_earthquake" msgid="5541962250262769193">"Mantén a calma e busca refuxio cerca."</string>
     <string name="etws_primary_default_message_tsunami" msgid="1887685943498368548">"Abandona de inmediato rexións costeiras e situadas na beira de ríos para dirixirte a un lugar máis seguro, como un terreo elevado."</string>
     <string name="etws_primary_default_message_earthquake_and_tsunami" msgid="998797956848445862">"Mantén a calma e busca refuxio cerca."</string>
-    <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Proba de mensaxes de emerxencia"</string>
+    <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Proba de mensaxes de urxencia"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Non se admite a tarxeta SIM"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Non se introduciu ningunha tarxeta SIM"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Non se admite a tarxeta SIM"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Non se admite o teléfono"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Ventá emerxente"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> máis"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Para utilizar este atallo, necesítase a versión máis recente da aplicación"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Non se puido restaurar o atallo porque a aplicación non é compatible coa restauración e a copia de seguranza"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Non se puido restaurar o atallo porque a sinatura da aplicación non coincide"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Non se puido restaurar o atallo"</string>
 </resources>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index ec80236..d1c3700 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"ખૂટતી વ્યવસ્થાપક ઍપ્લિકેશનને કારણે કાર્યાલયની પ્રોફાઇલ કાઢી નાખી"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"કાર્ય પ્રોફાઇલ વ્યવસ્થાપક ઍપ્લિકેશન ખૂટે છે અથવા તો દૂષિત છે. પરિણામે, તમારી કાર્યાલયની પ્રોફાઇલ અને તે સંબંધિત ડેટા કાઢી નાખવામાં આવ્યો છે. સહાયતા માટે તમારા વ્યવસ્થાપકનો સંપર્ક કરો."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"આ ઉપકરણ પર તમારી કાર્યાલયની પ્રોફાઇલ હવે ઉપલબ્ધ નથી"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"પાસવર્ડના ઘણા વધુ પ્રયત્નો"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ઉપકરણ સંચાલિત છે"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"તમારી સંસ્થા આ ઉપકરણનું સંચાલન કરે છે અને નેટવર્ક ટ્રાફિફનું નિયમન કરી શકે છે. વિગતો માટે ટૅપ કરો."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"તમારું ઉપકરણ કાઢી નાખવામાં આવશે"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ચેતવણીઓ"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"રિટેલ ડેમો"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB કનેક્શન"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"ઍપ ચાલી રહ્યું છે"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ઍપ બૅટરીનો વપરાશ કરી રહ્યાં છે"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> બૅટરીનો ઉપયોગ કરી રહ્યું છે"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ઍપ બૅટરીનો ઉપયોગ કરી રહ્યાં છે"</string>
@@ -270,8 +272,8 @@
     <string name="permgroupdesc_sms" msgid="4656988620100940350">"SMS સંદેશા મોકલવાની અને જોવાની"</string>
     <string name="permgrouprequest_sms" msgid="605618939583628306">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને SMS સંદેશા મોકલવા અને જોવાની મંજૂરી આપો"</string>
     <string name="permgrouplab_storage" msgid="1971118770546336966">"સ્ટોરેજ"</string>
-    <string name="permgroupdesc_storage" msgid="637758554581589203">"તમારા ઉપકરણ પર ફોટા, મીડિયા અને ફાઇલો ઍક્સેસ કરવાની"</string>
-    <string name="permgrouprequest_storage" msgid="7429669910547860218">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને તમારા ઉપકરણ પર ફોટા, મીડિયા, અને ફાઇલોને ઍક્સેસ કરવાની મંજૂરી આપો"</string>
+    <string name="permgroupdesc_storage" msgid="637758554581589203">"તમારા ઉપકરણ પર ફોટો, મીડિયા અને ફાઇલો ઍક્સેસ કરવાની"</string>
+    <string name="permgrouprequest_storage" msgid="7429669910547860218">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને તમારા ઉપકરણ પર ફોટો, મીડિયા, અને ફાઇલોને ઍક્સેસ કરવાની મંજૂરી આપો"</string>
     <string name="permgrouplab_microphone" msgid="171539900250043464">"માઇક્રોફોન"</string>
     <string name="permgroupdesc_microphone" msgid="4988812113943554584">"ઑડિઓ રેકોર્ડ કરવાની"</string>
     <string name="permgrouprequest_microphone" msgid="8065941268709600606">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને ઑડિઓ રોકૉર્ડ કરવાની મંજૂરી આપો"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ઇનપુટ પદ્ધતિ"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"ટેક્સ્ટ ક્રિયાઓ"</string>
     <string name="email" msgid="4560673117055050403">"ઇમેઇલ"</string>
-    <string name="dial" msgid="4204975095406423102">"ફોન"</string>
-    <string name="map" msgid="6068210738233985748">"નકશા"</string>
-    <string name="browse" msgid="6993590095938149861">"બ્રાઉઝર"</string>
+    <string name="dial" msgid="1253998302767701559">"કૉલ કરો"</string>
+    <string name="map" msgid="6521159124535543457">"શોધો"</string>
+    <string name="browse" msgid="1245903488306147205">"ખોલો"</string>
+    <string name="sms" msgid="4560537514610063430">"સંદેશ મોકલો"</string>
+    <string name="add_contact" msgid="7867066569670597203">"ઉમેરો"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"સ્ટોરેજ સ્થાન સમાપ્ત થયું"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"કેટલાક સિસ્ટમ કાર્યો કામ કરી શકશે નહીં"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"સિસ્ટમ માટે પર્યાપ્ત સ્ટોરેજ નથી. ખાતરી કરો કે તમારી પાસે 250MB ખાલી સ્થાન છે અને ફરીથી પ્રારંભ કરો."</string>
@@ -1183,7 +1187,7 @@
     <string name="usb_charging_notification_title" msgid="6895185153353640787">"આ ઉપકરણને USB થી ચાર્જ કરે છે"</string>
     <string name="usb_supplying_notification_title" msgid="5310642257296510271">"જોડાયેલ ઉપકરણ માટે USB પાવર પૂરો પાડે છે"</string>
     <string name="usb_mtp_notification_title" msgid="8396264943589760855">"ફાઇલ ટ્રાન્સફર માટે USB"</string>
-    <string name="usb_ptp_notification_title" msgid="1347328437083192112">"ફોટા ટ્રાન્સફર માટે USB"</string>
+    <string name="usb_ptp_notification_title" msgid="1347328437083192112">"ફોટો ટ્રાન્સફર માટે USB"</string>
     <string name="usb_midi_notification_title" msgid="4850904915889144654">"MIDI માટે USB"</string>
     <string name="usb_accessory_notification_title" msgid="7848236974087653666">"USB ઍક્સેસરીથી કનેક્ટ થયાં"</string>
     <string name="usb_notification_message" msgid="3370903770828407960">"વધુ વિકલ્પો માટે ટૅપ કરો."</string>
@@ -1213,7 +1217,7 @@
     <string name="ext_media_checking_notification_title" msgid="5734005953288045806">"<xliff:g id="NAME">%s</xliff:g> ને તૈયાર કરી રહ્યું છે"</string>
     <string name="ext_media_checking_notification_message" msgid="4747432538578886744">"ભૂલો માટે તપાસી રહ્યું છે"</string>
     <string name="ext_media_new_notification_message" msgid="7589986898808506239">"નવું <xliff:g id="NAME">%s</xliff:g> મળ્યું"</string>
-    <string name="ext_media_ready_notification_message" msgid="4083398150380114462">"ફોટા અને મીડિયા ટ્રાન્સફર કરવા માટે"</string>
+    <string name="ext_media_ready_notification_message" msgid="4083398150380114462">"ફોટો અને મીડિયા ટ્રાન્સફર કરવા માટે"</string>
     <string name="ext_media_unmountable_notification_title" msgid="8295123366236989588">"દૂષિત <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> દૂષિત છે. ઠીક કરવા માટે ટૅપ કરો."</string>
     <string name="ext_media_unmountable_notification_message" product="tv" msgid="3941179940297874950">"<xliff:g id="NAME">%s</xliff:g> દૂષિત છે. સુધારવા માટે પસંદ કરો."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ટેબ્લેટ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ફોન"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"હેડફોન"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"સ્પીકર્સ ડૉક કરો"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"હેડફોન"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"સિસ્ટમ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"બ્લૂટૂથ ઑડિઓ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"વાયરલેસ ડિસ્પ્લે"</string>
@@ -1738,7 +1743,7 @@
     <string name="app_category_game" msgid="5431836943981492993">"રમતો"</string>
     <string name="app_category_audio" msgid="1659853108734301647">"સંગીત અને ઑડિઓ"</string>
     <string name="app_category_video" msgid="2728726078629384196">"મૂવી અને વીડિઓ"</string>
-    <string name="app_category_image" msgid="4867854544519846048">"ફોટા અને છબીઓ"</string>
+    <string name="app_category_image" msgid="4867854544519846048">"ફોટો અને છબીઓ"</string>
     <string name="app_category_social" msgid="5842783057834965912">"સામાજિક અને સંચાર"</string>
     <string name="app_category_news" msgid="7496506240743986873">"સમાચાર અને સામાયિકો"</string>
     <string name="app_category_maps" msgid="5878491404538024367">"નકશા અને નેવિગેશન"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"કટોકટી સંદેશાઓનું પરીક્ષણ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"જવાબ આપો"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"સિમ મંજૂર નથી"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIMની જોગવાઈ કરી નથી"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"સિમ મંજૂર નથી"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ફોન મંજૂર નથી"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"પૉપઅપ વિંડો"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"આ શૉર્ટકટ માટે નવી ઍપ જરૂરી છે"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"શૉર્ટકટ ફરી મેળવી શકાયો નથી કારણ કે ઍપ બૅકઅપ અને ફરી મેળવવાનું સમર્થન કરતી નથી"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"શૉર્ટકટ ફરી મેળવી શકાયો નથી કારણ કે ઍપમાં છે તે સહી મેળ ખાતી નથી"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"શૉર્ટકટ પાછો મેળવી શકાયો નથી"</string>
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 217d106..11a8820 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"अनुपलब्ध व्यवस्थापक ऐप्लिकेशन के कारण कार्य प्रोफ़ाइल हटा दी गई"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"कार्य प्रोफ़ाइल व्यवस्थापक ऐप्लिकेशन या तो मौजूद नहीं है या वह खराब हो गया है. परिणामस्वरूप, आपकी कार्य प्रोफ़ाइल और उससे जुड़े डेटा को हटा दिया गया है. सहायता के लिए अपने व्यवस्थापक से संपर्क करें."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"आपकी कार्य प्रोफ़ाइल अब इस डिवाइस पर उपलब्‍ध नहीं है"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"कई बार गलत पासवर्ड डाला गया"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"डिवाइस प्रबंधित है"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"आपका संगठन इस डिवाइस का प्रबंधन करता है और वह नेटवर्क ट्रैफ़िक की निगरानी भी कर सकता है. विवरण के लिए टैप करें."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"आपके डिवाइस को मिटा दिया जाएगा"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"सूचनाएं"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"खुदरा डेमो"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB कनेक्शन"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"ऐप अभी इस्तेमाल हो रहा है"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"बैटरी की खपत करने वाले ऐप"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> बैटरी का इस्तेमाल कर रहा है"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ऐप बैटरी का इस्तेमाल कर रहे हैं"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"इनपुट विधि"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"लेख क्रियाएं"</string>
     <string name="email" msgid="4560673117055050403">"ईमेल करें"</string>
-    <string name="dial" msgid="4204975095406423102">"फ़ोन"</string>
-    <string name="map" msgid="6068210738233985748">"मानचित्र"</string>
-    <string name="browse" msgid="6993590095938149861">"ब्राउज़र"</string>
+    <string name="dial" msgid="1253998302767701559">"कॉल करें"</string>
+    <string name="map" msgid="6521159124535543457">"पता लगाएं"</string>
+    <string name="browse" msgid="1245903488306147205">"खोलें"</string>
+    <string name="sms" msgid="4560537514610063430">"मैसेज"</string>
+    <string name="add_contact" msgid="7867066569670597203">"जोड़ें"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"मेमोरी में जगह नहीं बची है"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"हो सकता है कुछ सिस्टम फ़ंक्शन कार्य न करें"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"सिस्टम के लिए ज़रूरी मेमोरी नहीं है. पक्का करें कि आपके पास 250एमबी की खाली जगह है और फिर से शुरू करें."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"टैबलेट"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"टीवी"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"फ़ोन"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"हेडफ़ोन"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"डॉक स्‍पीकर"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"हेडफ़ोन"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"सिस्‍टम"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ब्लूटूथ ऑडियो"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"वायरलेस डिसप्ले"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"दूसरा कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"तीसरा कार्य <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"इस स्क्रीन को अनपिन करने के लिए, \'वापस जाएं\' और \'खास जानकारी\' के बटन को दबाकर रखें"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"इस ऐप्लिकेशन को अनपिन नहीं किया जा सकता"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"स्‍क्रीन पिन की गई"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"स्‍क्रीन अनपिन की गई"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"अनपिन करने से पहले पिन के लिए पूछें"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"आपातकालीन संदेश परीक्षण"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"जवाब दें"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM की अनुमति नहीं है"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM का प्रावधान नहीं किया गया है"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM की अनुमति नहीं है"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"फ़ोन की अनुमति नहीं है"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"सिम से कॉल करने की इजाज़त नहीं है"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"सिम से कॉल करने की इजाज़त नहीं है"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"सिम से कॉल करने की इजाज़त नहीं है"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"फ़ोन से कॉल करने की इजाज़त नहीं है"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"पॉपअप विंडो"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"इस शॉर्टकट वाला ऐप चलाने के लिए इसका नया वर्शन डाउनलोड करें"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"शॉर्टकट बहाल नहीं किया जा सका क्योंकि इस ऐप में बैकअप लेने और उसे बहाल करने की सुविधा नहीं है"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"ऐप का हस्ताक्षर अलग होने के कारण शॉर्टकट बहाल नहीं किया जा सका"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"शॉर्टकट बहाल नहीं किया जा सका"</string>
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 60a0cc2..1b92d58 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -174,6 +174,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Radni je profil izbrisan jer nedostaje administratorska aplikacija"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Administratorska aplikacija radnog profila nedostaje ili je oštećena. Zbog toga su radni profil i povezani podaci izbrisani. Za pomoć se obratite svom administratoru."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Vaš radni profil više nije dostupan na ovom uređaju"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Previše pokušaja unosa zaporke"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Uređaj je upravljan"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Vaša organizacija upravlja ovim uređajem i može nadzirati mrežni promet. Dodirnite za pojedinosti."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Uređaj će se izbrisati"</string>
@@ -251,6 +252,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Upozorenja"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Prodajni demo-način"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB veza"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Izvodi se aplikacija"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacije troše bateriju"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Broj aplikacija koje koriste bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
@@ -997,9 +999,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Način unosa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Radnje s tekstom"</string>
     <string name="email" msgid="4560673117055050403">"E-pošta"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Karte"</string>
-    <string name="browse" msgid="6993590095938149861">"Preglednik"</string>
+    <string name="dial" msgid="1253998302767701559">"Poziv"</string>
+    <string name="map" msgid="6521159124535543457">"Lociraj"</string>
+    <string name="browse" msgid="1245903488306147205">"Otvori"</string>
+    <string name="sms" msgid="4560537514610063430">"Poruka"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Dodaj"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Ponestaje prostora za pohranu"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Neke sistemske funkcije možda neće raditi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Nema dovoljno pohrane za sustav. Oslobodite 250 MB prostora i pokrenite uređaj ponovo."</string>
@@ -1443,9 +1447,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tabletno računalo"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televizor"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalice"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Zvučnici postolja"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalice"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sustav"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth zvuk"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Bežični prikaz"</string>
@@ -1812,9 +1817,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test hitnih poruka"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odgovori"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM nije dopušten"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Ne pruža se usluga za SIM"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM nije dopušten"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon nije dopušten"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Skočni prozor"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"još <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Za taj je prečac potrebna najnovija aplikacija"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Vraćanje prečaca nije uspjelo jer aplikacija ne podržava sigurnosno kopiranje i vraćanje"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Vraćanje prečaca nije uspjelo zbog nepodudaranja potpisa aplikacije"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Vraćanje prečaca nije uspjelo"</string>
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3c40d56..b1e5cb4 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"A munkaprofilt a rendszer hiányzó rendszergazdai alkalmazás miatt törölte"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"A munkaprofil rendszergazdai alkalmazása hiányzik vagy sérült. A rendszer ezért törölte a munkaprofilt, és az ahhoz kapcsolódó adatokat. Ha segítségre van szüksége, vegye fel a kapcsolatot rendszergazdájával."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Munkaprofilja már nem hozzáférhető ezen az eszközön."</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Túl sok jelszómegadási kísérlet"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Felügyelt eszköz"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ezt az eszközt szervezete kezeli, és lehetséges, hogy a hálózati forgalmat is figyelik. További részletekért koppintson."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"A rendszer törölni fogja eszközét"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Értesítések"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Kiskereskedelmi bemutató"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-kapcsolat"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Jelenleg futó alkalmazás"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Akkumulátort használó alkalmazások"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás használja az akkumulátort"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> alkalmazás használja az akkumulátort"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Beviteli mód"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Műveletek szöveggel"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Térkép"</string>
-    <string name="browse" msgid="6993590095938149861">"Böngésző"</string>
+    <string name="dial" msgid="1253998302767701559">"Hívás"</string>
+    <string name="map" msgid="6521159124535543457">"Helymeghatározás"</string>
+    <string name="browse" msgid="1245903488306147205">"Megnyitás"</string>
+    <string name="sms" msgid="4560537514610063430">"Üzenet"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Hozzáadás"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Kevés a szabad terület"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Előfordulhat, hogy néhány rendszerfunkció nem működik."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Nincs elegendő tárhely a rendszerhez. Győződjön meg arról, hogy rendelkezik 250 MB szabad területtel, majd kezdje elölről."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Táblagép"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fejhallgató"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokkolóegység hangszórója"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fejhallgató"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Rendszer"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth hang"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Vezeték nélküli kijelző"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Vészhelyzetben küldött üzenetek tesztelése"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Válasz"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"A SIM-kártya nem engedélyezett"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Nem engedélyezett SIM-kártya"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"A SIM-kártya nem engedélyezett"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"A telefon nem engedélyezett"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Előugró ablak"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"A parancsikon működéséhez az alkalmazás legfrissebb verziójára van szükség"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Nem sikerült visszaállítani a parancsikont, mert az alkalmazás nem támogatja a biztonsági mentést és visszaállítást"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Nem sikerült visszaállítani a parancsikont, mert az alkalmazás-aláírás nem egyezik"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Nem sikerült visszaállítani a parancsikont"</string>
 </resources>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 190b2d5..99ff527 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Աշխատանքային պրոֆիլը ջնջվել է ադմինիստրատորի հավելվածի բացակայության պատճառով"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Աշխատանքային պրոֆիլի ադմինիստրատորի հավելվածը բացակայում է կամ վնասված է: Արդյունքում ձեր աշխատանքային պրոֆիլը և առնչվող տվյալները ջնջվել են: Օգնության համար դիմեք ձեր ադմինիստրատորին:"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Ձեր աշխատանքային պրոֆիլն այս սարքում այլևս հասանելի չէ"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Գաղտնաբառը մուտքագրելու չափից շատ փորձեր են կատարվել"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Սարքը կառավարվում է"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ձեր կազմակերպությունը կառավարում է այս սարքը և կարող է վերահսկել ցանցի թրաֆիկը: Հպեք՝ մանրամասները դիտելու համար:"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Ձեր սարքը ջնջվելու է"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Ծանուցումներ"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Խանութի ցուցադրական ռեժիմ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB կապակցում"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Հավելվածն աշխատում է"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Մարտկոցի լիցքը ծախսող հավելվածներ"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"«<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածը ծախսում է մարտկոցի լիցքը"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> հավելված ծախսում է մարտկոցի լիցքը"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Մուտքագրման եղանակը"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Տեքստի գործողությունները"</string>
     <string name="email" msgid="4560673117055050403">"Էլփոստ"</string>
-    <string name="dial" msgid="4204975095406423102">"Հեռախոս"</string>
-    <string name="map" msgid="6068210738233985748">"Քարտեզներ"</string>
-    <string name="browse" msgid="6993590095938149861">"Դիտարկիչ"</string>
+    <string name="dial" msgid="1253998302767701559">"Զանգել"</string>
+    <string name="map" msgid="6521159124535543457">"Գտնել քարտեզում"</string>
+    <string name="browse" msgid="1245903488306147205">"Բացել"</string>
+    <string name="sms" msgid="4560537514610063430">"SMS գրել"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Ավելացնել"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Հիշողությունը սպառվում է"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Որոշ գործառույթներ կարող են չաշխատել"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Համակարգի համար բավարար հիշողություն չկա: Համոզվեք, որ ունեք 250ՄԲ ազատ տարածություն և վերագործարկեք:"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Գրասալիկ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Հեռուստացույց"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Հեռախոս"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ականջակալներ"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Համակցված բարձրախոսներ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ականջակալներ"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Համակարգ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-ի աուդիո ֆայլ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Անլար էկրան"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2-րդ աշխատանք <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3-րդ աշխատանք <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Այս էկրանն ապամրացնելու համար հպեք և պահեք Հետ և Համատեսք կոճակները"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Հնարավոր չէ ապամրացնել այս հավելվածը"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Էկրանն ամրացված է"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Էկրանն ապամրացված է"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Ապաամրացնելուց առաջ հարցնել PIN-կոդը"</string>
@@ -1760,10 +1764,10 @@
       <item quantity="one"><xliff:g id="COUNT">%1$s</xliff:g> ինքնալցման առաջարկ</item>
       <item quantity="other"><xliff:g id="COUNT">%1$s</xliff:g> ինքնալցման առաջարկ</item>
     </plurals>
-    <string name="autofill_save_title" msgid="3345527308992082601">"Պահե՞լ &lt;b&gt;<xliff:g id="LABEL">%1$s</xliff:g>&lt;/b&gt;-ում:"</string>
-    <string name="autofill_save_title_with_type" msgid="8637809388029313305">"Պահե՞լ <xliff:g id="TYPE">%1$s</xliff:g>-ը &lt;b&gt;<xliff:g id="LABEL">%2$s</xliff:g>&lt;/b&gt;-ում:"</string>
-    <string name="autofill_save_title_with_2types" msgid="5214035651838265325">"Պահե՞լ <xliff:g id="TYPE_0">%1$s</xliff:g>-ը և <xliff:g id="TYPE_1">%2$s</xliff:g>-ը &lt;b&gt;<xliff:g id="LABEL">%3$s</xliff:g>&lt;/b&gt;-ում:"</string>
-    <string name="autofill_save_title_with_3types" msgid="6943161834231458441">"Պահե՞լ <xliff:g id="TYPE_0">%1$s</xliff:g>-ը, <xliff:g id="TYPE_1">%2$s</xliff:g>-ը և <xliff:g id="TYPE_2">%3$s</xliff:g>-ը &lt;b&gt;<xliff:g id="LABEL">%4$s</xliff:g>&lt;/b&gt;-ում:"</string>
+    <string name="autofill_save_title" msgid="3345527308992082601">"Պահե՞լ &lt;b&gt;<xliff:g id="LABEL">%1$s</xliff:g>&lt;/b&gt; ծառայությունում"</string>
+    <string name="autofill_save_title_with_type" msgid="8637809388029313305">"Պահե՞լ <xliff:g id="TYPE">%1$s</xliff:g>ը &lt;b&gt;<xliff:g id="LABEL">%2$s</xliff:g>&lt;/b&gt; ծառայությունում"</string>
+    <string name="autofill_save_title_with_2types" msgid="5214035651838265325">"Պահե՞լ <xliff:g id="TYPE_0">%1$s</xliff:g>ն ու <xliff:g id="TYPE_1">%2$s</xliff:g>ը &lt;b&gt;<xliff:g id="LABEL">%3$s</xliff:g>&lt;/b&gt; ծառայությունում"</string>
+    <string name="autofill_save_title_with_3types" msgid="6943161834231458441">"Պահե՞լ <xliff:g id="TYPE_0">%1$s</xliff:g>ը, <xliff:g id="TYPE_1">%2$s</xliff:g>ն ու <xliff:g id="TYPE_2">%3$s</xliff:g>ը &lt;b&gt;<xliff:g id="LABEL">%4$s</xliff:g>&lt;/b&gt; ծառայությունում"</string>
     <string name="autofill_save_yes" msgid="6398026094049005921">"Պահել"</string>
     <string name="autofill_save_no" msgid="2625132258725581787">"Ոչ"</string>
     <string name="autofill_save_type_password" msgid="5288448918465971568">"գաղտնաբառ"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Արտակարգ իրավիճակների հաղորդագրությունների թեստ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Պատասխանել"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM քարտի օգտագործումն արգելված է"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM քարտը նախապատրաստված չէ"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM քարտի օգտագործումն արգելված է"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Հեռախոսի օգտագործումն արգելված է"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Այս SIM քարտով չեք կարող զանգել"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Այս SIM քարտը նախապատրաստված չէ զանգելու համար"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"Այս SIM քարտով չեք կարող զանգել"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Այս հեռախոսով չեք կարող զանգել"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Հայտնվող պատուհան"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Այս դյուրանցման համար անհրաժեշտ է հավելվածի վերջին տարբերակը"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Չհաջողվեց վերականգնել դյուրանցումը, քանի որ հավելվածում չի աջակցվում պահուստավորման և վերականգնման գործառույթը"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Չհաջողվեց վերականգնել դյուրանցումը, քանի որ հավելվածների ստորագրությունները տարբեր են"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Չհաջողվեց վերականգնել դյուրանցումը"</string>
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index b6a8aa2..57312e7 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -149,7 +149,7 @@
     <string name="httpErrorRedirectLoop" msgid="8679596090392779516">"Laman ini berisi terlalu banyak pengalihan server."</string>
     <string name="httpErrorUnsupportedScheme" msgid="5015730812906192208">"Protokol tidak didukung."</string>
     <string name="httpErrorFailedSslHandshake" msgid="96549606000658641">"Tidak dapat membuat sambungan aman."</string>
-    <string name="httpErrorBadUrl" msgid="3636929722728881972">"Tidak dapat membuka laman karena URL tidak valid."</string>
+    <string name="httpErrorBadUrl" msgid="3636929722728881972">"Tidak dapat membuka halaman karena URL tidak valid."</string>
     <string name="httpErrorFile" msgid="2170788515052558676">"Tidak dapat mengakses file."</string>
     <string name="httpErrorFileNotFound" msgid="6203856612042655084">"Tidak dapat menemukan file yang diminta."</string>
     <string name="httpErrorTooManyRequests" msgid="1235396927087188253">"Terlalu banyak permintaan yang diproses. Coba lagi nanti."</string>
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profil kerja dihapus karena tidak ada aplikasi admin"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Aplikasi admin profil kerja tidak ada atau rusak. Akibatnya, profil kerja dan data terkait telah dihapus. Hubungi admin untuk meminta bantuan."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Profil kerja tidak tersedia lagi di perangkat ini"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Terlalu banyak percobaan memasukkan sandi"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Perangkat ini ada yang mengelola"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organisasi mengelola perangkat ini dan mungkin memantau traffic jaringan. Tap untuk melihat detailnya."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Perangkat akan dihapus"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Notifikasi"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo promo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Sambungan USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikasi berjalan"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikasi yang menggunakan baterai"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan baterai"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikasi sedang meggunakan baterai"</string>
@@ -315,7 +317,7 @@
     <string name="permlab_receiveMms" msgid="1821317344668257098">"terima pesan teks (MMS)"</string>
     <string name="permdesc_receiveMms" msgid="533019437263212260">"Memungkinkan aplikasi menerima dan memproses pesan MMS. Ini artinya aplikasi dapat memantau atau menghapus pesan yang dikirim ke perangkat Anda tanpa menunjukkannya kepada Anda."</string>
     <string name="permlab_readCellBroadcasts" msgid="1598328843619646166">"membaca pesan siaran seluler"</string>
-    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Lansiran siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
+    <string name="permdesc_readCellBroadcasts" msgid="6361972776080458979">"Mengizinkan aplikasi membaca pesan siaran seluler yang diterima perangkat Anda. Notifikasi siaran seluler dikirimkan di beberapa lokasi untuk memperingatkan Anda tentang situasi darurat. Aplikasi berbahaya dapat mengganggu kinerja atau operasi perangkat Anda saat siaran seluler darurat diterima."</string>
     <string name="permlab_subscribedFeedsRead" msgid="4756609637053353318">"baca umpan langganan"</string>
     <string name="permdesc_subscribedFeedsRead" msgid="5557058907906144505">"Mengizinkan apl mendapatkan detail tentang umpan yang saat ini sedang disinkronkan."</string>
     <string name="permlab_sendSms" msgid="7544599214260982981">"mengirim dan melihat pesan SMS"</string>
@@ -805,7 +807,7 @@
     <string name="js_dialog_before_unload_title" msgid="2619376555525116593">"Konfirmasi Navigasi"</string>
     <string name="js_dialog_before_unload_positive_button" msgid="3112752010600484130">"Keluar dari Laman ini"</string>
     <string name="js_dialog_before_unload_negative_button" msgid="5614861293026099715">"Tetap di Laman ini"</string>
-    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nYakin ingin beranjak dari laman ini?"</string>
+    <string name="js_dialog_before_unload" msgid="3468816357095378590">"<xliff:g id="MESSAGE">%s</xliff:g>\n\nYakin ingin beranjak dari halaman ini?"</string>
     <string name="save_password_label" msgid="6860261758665825069">"Konfirmasi"</string>
     <string name="double_tap_toast" msgid="4595046515400268881">"Kiat: Ketuk dua kali untuk memperbesar dan memperkecil."</string>
     <string name="autofill_this_form" msgid="4616758841157816676">"IsiOtomatis"</string>
@@ -842,7 +844,7 @@
     <string name="save_password_notnow" msgid="6389675316706699758">"Tidak sekarang"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Ingat"</string>
     <string name="save_password_never" msgid="8274330296785855105">"Jangan"</string>
-    <string name="open_permission_deny" msgid="7374036708316629800">"Anda tidak memiliki izin untuk membuka laman ini."</string>
+    <string name="open_permission_deny" msgid="7374036708316629800">"Anda tidak memiliki izin untuk membuka halaman ini."</string>
     <string name="text_copied" msgid="4985729524670131385">"Teks disalin ke papan klip."</string>
     <string name="more_item_label" msgid="4650918923083320495">"Lainnya"</string>
     <string name="prepend_shortcut_label" msgid="2572214461676015642">"Menu+"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metode masukan"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tindakan teks"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Telepon"</string>
-    <string name="map" msgid="6068210738233985748">"Peta"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Panggil"</string>
+    <string name="map" msgid="6521159124535543457">"Temukan"</string>
+    <string name="browse" msgid="1245903488306147205">"Buka"</string>
+    <string name="sms" msgid="4560537514610063430">"Pesan"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Tambahkan"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Ruang penyimpanan hampir habis"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Beberapa fungsi sistem mungkin tidak dapat bekerja"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Penyimpanan tidak cukup untuk sistem. Pastikan Anda memiliki 250 MB ruang kosong, lalu mulai ulang."</string>
@@ -1311,7 +1315,7 @@
     <string name="next_button_label" msgid="1080555104677992408">"Selanjutnya"</string>
     <string name="skip_button_label" msgid="1275362299471631819">"Lewati"</string>
     <string name="no_matches" msgid="8129421908915840737">"Tidak ada kecocokan"</string>
-    <string name="find_on_page" msgid="1946799233822820384">"Temukan pada laman"</string>
+    <string name="find_on_page" msgid="1946799233822820384">"Temukan pada halaman"</string>
     <plurals name="matches_found" formatted="false" msgid="1210884353962081884">
       <item quantity="other"><xliff:g id="INDEX">%d</xliff:g> dari <xliff:g id="TOTAL">%d</xliff:g></item>
       <item quantity="one">1 kecocokan</item>
@@ -1380,7 +1384,7 @@
     <string name="storage_usb_drive_label" msgid="4501418548927759953">"Drive USB <xliff:g id="MANUFACTURER">%s</xliff:g>"</string>
     <string name="storage_usb" msgid="3017954059538517278">"Penyimpanan USB"</string>
     <string name="extract_edit_menu_button" msgid="8940478730496610137">"Edit"</string>
-    <string name="data_usage_warning_title" msgid="3620440638180218181">"Lansiran penggunaan data"</string>
+    <string name="data_usage_warning_title" msgid="3620440638180218181">"Notifikasi penggunaan data"</string>
     <string name="data_usage_warning_body" msgid="6660692274311972007">"Ketuk untuk lihat penggunaan &amp; setelan."</string>
     <string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Batas data 2G-3G terlampaui"</string>
     <string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Batas data 4G terlampaui"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Ponsel"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphone"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Pengeras suara dok"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Headphone"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Layar nirkabel"</string>
@@ -1437,7 +1442,7 @@
     <string name="media_route_status_available" msgid="6983258067194649391">"Tersedia"</string>
     <string name="media_route_status_not_available" msgid="6739899962681886401">"Tidak tersedia"</string>
     <string name="media_route_status_in_use" msgid="4533786031090198063">"Sedang digunakan"</string>
-    <string name="display_manager_built_in_display_name" msgid="2583134294292563941">"Layar Bawaan"</string>
+    <string name="display_manager_built_in_display_name" msgid="2583134294292563941">"Layar Built-In"</string>
     <string name="display_manager_hdmi_display_name" msgid="1555264559227470109">"Layar HDMI"</string>
     <string name="display_manager_overlay_display_name" msgid="5142365982271620716">"Hamparan #<xliff:g id="ID">%1$d</xliff:g>"</string>
     <string name="display_manager_overlay_display_title" msgid="652124517672257172">"<xliff:g id="NAME">%1$s</xliff:g>: <xliff:g id="WIDTH">%2$d</xliff:g>x<xliff:g id="HEIGHT">%3$d</xliff:g>, <xliff:g id="DPI">%4$d</xliff:g> dpi"</string>
@@ -1607,7 +1612,7 @@
     </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"Coba lagi nanti"</string>
     <string name="immersive_cling_title" msgid="8394201622932303336">"Melihat layar penuh"</string>
-    <string name="immersive_cling_description" msgid="3482371193207536040">"Untuk keluar, gesek ke bawah dari atas."</string>
+    <string name="immersive_cling_description" msgid="3482371193207536040">"Untuk keluar, geser layar ke bawah dari atas."</string>
     <string name="immersive_cling_positive" msgid="5016839404568297683">"Mengerti"</string>
     <string name="done_label" msgid="2093726099505892398">"Selesai"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"Penggeser putar jam"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"Upaya ke-2 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"Upaya ke-3 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Untuk melepas pin layar ini, sentuh &amp; tahan tombol Kembali dan Ringkasan"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Tidak dapat melepas pin aplikasi ini"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Layar disematkan"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Layar dicopot sematannya"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Meminta PIN sebelum melepas sematan"</string>
@@ -1631,8 +1635,8 @@
     <string name="package_updated_device_owner" msgid="1847154566357862089">"Diupdate oleh admin Anda"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"Dihapus oleh admin Anda"</string>
     <string name="battery_saver_description" msgid="1960431123816253034">"Untuk membantu meningkatkan masa pakai baterai, penghemat baterai mengurangi kinerja perangkat dan membatasi getaran, layanan lokasi, dan sebagian besar data latar belakang. Email, pesan, dan aplikasi lain yang mengandalkan sinkronisasi mungkin tidak diperbarui kecuali jika dibuka.\n\nPenghemat baterai otomatis nonaktif jika perangkat diisi dayanya."</string>
-    <string name="data_saver_description" msgid="6015391409098303235">"Untuk membantu mengurangi penggunaan data, Penghemat Data mencegah beberapa aplikasi mengirim atau menerima data di latar belakang. Aplikasi yang sedang digunakan dapat mengakses data, tetapi frekuensinya agak lebih jarang. Misalnya saja, gambar hanya akan ditampilkan setelah disentuh."</string>
-    <string name="data_saver_enable_title" msgid="4674073932722787417">"Aktifkan Penghemat Data?"</string>
+    <string name="data_saver_description" msgid="6015391409098303235">"Untuk membantu mengurangi penggunaan data, Penghemat Kuota Internet mencegah beberapa aplikasi mengirim atau menerima data di latar belakang. Aplikasi yang sedang digunakan dapat mengakses data, tetapi frekuensinya agak lebih jarang. Misalnya saja, gambar hanya akan ditampilkan setelah disentuh."</string>
+    <string name="data_saver_enable_title" msgid="4674073932722787417">"Aktifkan Penghemat Kuota Internet?"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"Aktifkan"</string>
     <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
       <item quantity="other">Selama %1$d menit (hingga <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Tes pesan darurat"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Balas"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM tidak diizinkan"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM tidak di-provisioning"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM tidak diizinkan"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Ponsel tidak diizinkan"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM tidak diizinkan untuk suara"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM tidak disediakan untuk suara"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM tidak diizinkan untuk suara"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Ponsel tidak diizinkan untuk suara"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Jendela Pop-up"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Pintasan ini memerlukan aplikasi terbaru"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Tidak dapat memulihkan pintasan karena aplikasi tidak mendukung backup dan pulihkan"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Tidak dapat memulihkan pintasan karena tanda tangan aplikasi tidak cocok"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Tidak dapat memulihkan pintasan."</string>
 </resources>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 8cc5f97..400228a 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Vinnusniði eytt vegna þess að stjórnunarforrit vantar"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Stjórnunarforrit vinnusniðsins vantar eða er skemmt. Vinnusniðinu og gögnum því tengdu hefur því verið eytt. Hafðu samband við kerfisstjórann til að fá frekari aðstoð."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Vinnusniðið þitt er ekki lengur í boði á þessu tæki"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Of margar tilraunir til að slá inn aðgangsorð"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Tækinu er stjórnað"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Fyrirtækið þitt stjórnar þessu tæki og kann að fylgjast með netnotkun. Ýttu hér til að fá upplýsingar."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Tækið verður hreinsað"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Tilkynningar"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Kynningarútgáfa fyrir verslanir"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-tenging"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Forrit er í gangi"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Forrit sem nota rafhlöðuorku"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> notar rafhlöðuorku"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> forrit nota rafhlöðuorku"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Innsláttaraðferð"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Textaaðgerðir"</string>
     <string name="email" msgid="4560673117055050403">"Tölvupóstur"</string>
-    <string name="dial" msgid="4204975095406423102">"Sími"</string>
-    <string name="map" msgid="6068210738233985748">"Kort"</string>
-    <string name="browse" msgid="6993590095938149861">"Vafri"</string>
+    <string name="dial" msgid="1253998302767701559">"Símtal"</string>
+    <string name="map" msgid="6521159124535543457">"Staðsetja"</string>
+    <string name="browse" msgid="1245903488306147205">"Opna"</string>
+    <string name="sms" msgid="4560537514610063430">"Skilaboð"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Bæta við"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Geymslurýmið er senn á þrotum"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Sumir kerfiseiginleikar kunna að vera óvirkir"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Ekki nægt geymslurými fyrir kerfið. Gakktu úr skugga um að 250 MB séu laus og endurræstu."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Spjaldtölva"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Sjónvarp"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Sími"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Heyrnartól"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokkuhátalarar"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Heyrnartól"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Kerfi"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-hljóð"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Þráðlaus skjábirting"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Prófun neyðarskilaboða"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Svara"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-kort er ekki leyft"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-korti ekki úthlutað"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-kort er ekki leyft"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Sími er ekki leyfður"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Sprettigluggi"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Nýjasta útgáfa forritsins þarf að vera til staðar til að þessi flýtileið virki"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Ekki var hægt að endurheimta flýtileið vegna þess að forritið styður ekki öryggisafritun og endurheimt"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Ekki var hægt að endurheimta flýtileið vegna þess að undirskriftir forrita passa ekki saman"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Ekki var hægt að endurheimta flýtileið"</string>
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 611a9a1..283d876 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profilo di lavoro eliminato per app di amministrazione mancante"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"L\'app di amministrazione dei profili di lavoro manca o è danneggiata. Di conseguenza, il tuo profilo di lavoro e i relativi dati sono stati eliminati. Contatta l\'amministratore per ricevere assistenza."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Il tuo profilo di lavoro non è più disponibile sul dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Troppi tentativi di inserimento della password"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Il dispositivo è gestito"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Questo dispositivo è gestito dalla tua organizzazione, che potrebbe monitorare il traffico di rete. Tocca per i dettagli."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Il dispositivo verrà resettato"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Avvisi"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo retail"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Connessione USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App in esecuzione"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"App che consumano la batteria"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> sta consumando la batteria"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> app stanno consumando la batteria"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Azioni testo"</string>
     <string name="email" msgid="4560673117055050403">"Invia una email"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefono"</string>
-    <string name="map" msgid="6068210738233985748">"Mappe"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Chiama"</string>
+    <string name="map" msgid="6521159124535543457">"Localizza"</string>
+    <string name="browse" msgid="1245903488306147205">"Apri"</string>
+    <string name="sms" msgid="4560537514610063430">"Invia messaggio"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Aggiungi"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Spazio di archiviazione in esaurimento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Alcune funzioni di sistema potrebbero non funzionare"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Memoria insufficiente per il sistema. Assicurati di avere 250 MB di spazio libero e riavvia."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefono"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Cuffie audio"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altoparlanti dock"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Cuffie audio"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Visualizzazione wireless"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Testo messaggi di emergenza"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Rispondi"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Scheda SIM non consentita"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Scheda SIM non predisposta"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Scheda SIM non consentita"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefono non consentito"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Finestra popup"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Per questa scorciatoia è necessaria l\'app più recente"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Impossibile ripristinare la scorciatoia perché l\'app non supporta il backup e il ripristino"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Impossibile ripristinare la scorciatoia perché la firma dell\'app non corrisponde"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Impossibile ripristinare la scorciatoia"</string>
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 40aeba4..0f146ba 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"פרופיל העבודה נמחק מפני שחסרה אפליקציית ניהול"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"אפליקציית הניהול של פרופיל העבודה חסרה או פגומה. כתוצאה מכך, פרופיל העבודה שלך נמחק, כולל כל הנתונים הקשורים אליו. לקבלת עזרה, פנה למנהל המערכת."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"פרופיל העבודה שלך אינו זמין עוד במכשיר הזה"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"בוצעו ניסיונות רבים מדי להזנת סיסמה"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"המכשיר מנוהל"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"הארגון שלך מנהל מכשיר זה ועשוי לנטר את התנועה ברשת. הקש לקבלת פרטים."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"תתבצע מחיקה של המכשיר"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"התראות"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"הדגמה לקמעונאים"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"‏חיבור USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"אפליקציה פועלת"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"אפליקציות שמרוקנות את הסוללה"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בסוללה"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> אפליקציות משתמשות בסוללה"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"שיטת קלט"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"פעולות טקסט"</string>
     <string name="email" msgid="4560673117055050403">"אימייל"</string>
-    <string name="dial" msgid="4204975095406423102">"טלפון"</string>
-    <string name="map" msgid="6068210738233985748">"מפות"</string>
-    <string name="browse" msgid="6993590095938149861">"דפדפן"</string>
+    <string name="dial" msgid="1253998302767701559">"שיחה"</string>
+    <string name="map" msgid="6521159124535543457">"איתור"</string>
+    <string name="browse" msgid="1245903488306147205">"פתיחה"</string>
+    <string name="sms" msgid="4560537514610063430">"הודעה"</string>
+    <string name="add_contact" msgid="7867066569670597203">"הוספה"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"שטח האחסון אוזל"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"ייתכן שפונקציות מערכת מסוימות לא יפעלו"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"‏אין מספיק שטח אחסון עבור המערכת. ודא שיש לך שטח פנוי בגודל 250MB התחל שוב."</string>
@@ -1341,7 +1345,7 @@
     <string name="vpn_lockdown_disconnected" msgid="735805531187559719">"‏אין חיבור ל-VPN שפועל כל הזמן"</string>
     <string name="vpn_lockdown_error" msgid="6009249814034708175">"‏שגיאת VPN שמופעל תמיד"</string>
     <string name="vpn_lockdown_config" msgid="8151951501116759194">"‏רוצה לשנות את הגדרות הרשת או הגדרות ה-VPN?"</string>
-    <string name="upload_file" msgid="2897957172366730416">"בחר קובץ"</string>
+    <string name="upload_file" msgid="2897957172366730416">"בחירת קובץ"</string>
     <string name="no_file_chosen" msgid="6363648562170759465">"לא נבחר קובץ"</string>
     <string name="reset" msgid="2448168080964209908">"איפוס"</string>
     <string name="submit" msgid="1602335572089911941">"שלח"</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"טאבלט"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"טלוויזיה"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"טלפון"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"אוזניות"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"רמקולים של מעגן"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"אוזניות"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"מערכת"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"‏אודיו Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"צג אלחוטי"</string>
@@ -1671,7 +1676,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"<xliff:g id="LABEL">%1$s</xliff:g> שני בעבודה"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"<xliff:g id="LABEL">%1$s</xliff:g> שלישי בעבודה"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"כדי לבטל את ההצמדה של מסך זה, גע בלחצנים \'הקודם\' ו\'סקירה\' והחזק אותם"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"לא ניתן לבטל את ההצמדה של האפליקציה הזו"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"המסך מוצמד"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"הצמדת המסך בוטלה"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"בקש קוד גישה לפני ביטול הצמדה"</string>
@@ -1847,9 +1851,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"בדיקה של הודעות חירום"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"השב"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"‏כרטיס ה-SIM לא מורשה"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"‏כרטיס ה-SIM לא מזוהה"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"‏כרטיס ה-SIM לא מורשה"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"הטלפון לא מורשה"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"‏כרטיס ה-SIM לא מורשה לזיהוי קולי"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"‏ניהול התצורה של כרטיס ה-SIM לא מתאים לזיהוי קולי"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"‏כרטיס ה-SIM לא מורשה לזיהוי קולי"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"הטלפון לא מורשה לזיהוי קולי"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"חלון קופץ"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"קיצור דרך זה דורש את האפליקציה העדכנית ביותר"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"לא ניתן היה לשחזר את קיצור הדרך מפני שהאפליקציה אינה תומכת בגיבוי ובשחזור"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"לא ניתן היה לשחזר את קיצור הדרך עקב חוסר התאמה בחתימה על האפליקציות"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"לא ניתן היה לשחזר את קיצור הדרך"</string>
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index aa86db3..baff85c 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"管理アプリがないため仕事用プロファイルが削除されました"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"仕事用プロファイルの管理アプリがないか、破損しています。そのため仕事用プロファイルと関連データが削除されました。管理者にサポートをご依頼ください。"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"お使いの仕事用プロファイルはこの端末で使用できなくなりました"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"パスワード入力回数が上限を超えました"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"管理対象の端末"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"この端末は組織によって管理され、ネットワーク トラフィックが監視される場合があります。詳しくはタップしてください。"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"端末のデータが消去されます"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"通知"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"販売店デモ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB 接続"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"アプリを実行しています"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"アプリが電池を消費しています"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」が電池を使用しています"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> 個のアプリが電池を使用しています"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"入力方法"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"テキスト操作"</string>
     <string name="email" msgid="4560673117055050403">"メール"</string>
-    <string name="dial" msgid="4204975095406423102">"電話"</string>
-    <string name="map" msgid="6068210738233985748">"マップ"</string>
-    <string name="browse" msgid="6993590095938149861">"ブラウザ"</string>
+    <string name="dial" msgid="1253998302767701559">"電話"</string>
+    <string name="map" msgid="6521159124535543457">"探す"</string>
+    <string name="browse" msgid="1245903488306147205">"開く"</string>
+    <string name="sms" msgid="4560537514610063430">"メッセージ"</string>
+    <string name="add_contact" msgid="7867066569670597203">"追加"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"空き容量わずか"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"一部のシステム機能が動作しない可能性があります"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"システムに十分な容量がありません。250MBの空き容量を確保して再起動してください。"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"タブレット"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"テレビ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"モバイル端末"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ヘッドホン"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ホルダーのスピーカー"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ヘッドホン"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"システム"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth音声"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ワイヤレスディスプレイ"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"テスト用緊急速報メール"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"返信"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM 使用不可"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM には対応していません"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM は許可されていません"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"電話は許可されていません"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"ポップアップ ウィンドウ"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"他 <xliff:g id="NUMBER">%1$d</xliff:g> 件"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"このショートカットを使用するには、最新のアプリが必要です"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"このアプリはバックアップと復元に対応していないため、ショートカットを復元できませんでした"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"アプリの署名が一致しないため、ショートカットを復元できませんでした"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"ショートカットを復元できませんでした"</string>
 </resources>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index ea99337..85234cb 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"სამსახურის პროფილი წაიშალა ადმინისტრატორის აპის არქონის გამო"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"სამსახურის პროფილის ადმინისტრატორის აპი მიუწვდომელია ან დაზიანებულია. ამის გამო, თქვენი სამსახურის პროფილი და დაკავშირებული მონაცემები წაიშალა. დახმარებისთვის დაუკავშირდით თქვენს ადმინისტრატორს."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"თქვენი სამსახურის პროფილი აღარ არის ხელმისაწვდომი ამ მოწყობილობაზე"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"დაფიქსირდა პაროლის შეყვანის ზედმეტად ბევრი მცდელობა"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"მოწყობილობა მართულია"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ამ მოწყობილობას თქვენი ორგანიზაცია მართავს და მას ქსელის ტრაფიკის მონიტორინგი შეუძლია. შეეხეთ დამატებითი დეტალებისთვის."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"თქვენი მოწყობილობა წაიშლება"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"გაფრთხილებები"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"დემო-რეჟიმი საცალო მოვაჭრეებისთვის"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB კავშირი"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"აპი გაშვებულია"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ბატარეის მხარჯავი აპები"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ბატარეას"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"ბატარეას <xliff:g id="NUMBER">%1$d</xliff:g> აპი იყენებს"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"შეყვანის მეთოდი"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"ქმედებები ტექსტზე"</string>
     <string name="email" msgid="4560673117055050403">"ელფოსტა"</string>
-    <string name="dial" msgid="4204975095406423102">"ტელეფონი"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"ბრაუზერი"</string>
+    <string name="dial" msgid="1253998302767701559">"ზარი"</string>
+    <string name="map" msgid="6521159124535543457">"მიკვლევა"</string>
+    <string name="browse" msgid="1245903488306147205">"გახსნა"</string>
+    <string name="sms" msgid="4560537514610063430">"შეტყობინება"</string>
+    <string name="add_contact" msgid="7867066569670597203">"დამატება"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"თავისუფალი ადგილი იწურება"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"სისტემის ზოგიერთმა ფუნქციამ შესაძლოა არ იმუშავოს"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"სისტემისათვის საკმარისი საცავი არ არის. დარწმუნდით, რომ იქონიოთ სულ მცირე 250 მბაიტი თავისუფალი სივრცე და დაიწყეთ ხელახლა."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ტაბლეტი"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ტელევიზია"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ტელეფონი"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ყურსასმენები"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"სპიკერების მიმაგრება"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ყურსასმენები"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"სისტემა"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth აუდიო"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"უსადენო ეკრანი"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"სატესტო საგანგებო შეტყობინება"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"პასუხი"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM ბარათი დაუშვებელია"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM ბარათი უზრუნველყოფილი არ არის"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM ბარათი დაუშვებელია"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ტელეფონი დაუშვებელია"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"ამომხტარი ფანჯარა"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ეს მალსახმობი საჭიროებს აპის უახლეს ვერსიას"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"მალსახმობის აღდგენა ვერ მოხერხდა, რადგან ამ აპის მიერ მხარდაუჭერელია სარეზერვო ასლით აღდგენა"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"მალსახმობის აღდგენა ვერ მოხერხდა აპის ხელმოწერის შეუსაბამობის გამო"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"მალსახმობის აღდგენა ვერ მოხერხდა"</string>
 </resources>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index d02dc0e..5616972 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Әкімші қолданбасы болмағандықтан жұмыс профилі жойылды"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Жұмыс профилінің әкімші қолданбасы жоқ немесе бүлінген. Нәтижесінде жұмыс профиліңіз және қатысты деректер жойылды. Көмек алу үшін әкімшіге хабарласыңыз."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Жұмыс профиліңіз осы құрылғыда енді қолжетімді емес"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Құпия сөз көп рет қате енгізілді"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Құрылғы басқарылады"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ұйымыңыз осы құрылғыны басқарады және желі трафигін бақылауы мүмкін. Мәліметтер алу үшін түртіңіз."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Құрылғыңыздағы деректер өшіріледі"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Дабылдар"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Бөлшек саудаға арналған демо нұсқасы"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB байланысы"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Қолданба қосулы"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Батареяны пайдаланып жатқан қолданбалар"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> батареяны пайдалануда"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> қолданба батареяны пайдалануда"</string>
@@ -968,7 +970,7 @@
     <string name="delete" msgid="6098684844021697789">"Жою"</string>
     <string name="copyUrl" msgid="2538211579596067402">"URL мекенжайын көшіру"</string>
     <string name="selectTextMode" msgid="1018691815143165326">"Мәтінді бөлектеу"</string>
-    <string name="undo" msgid="7905788502491742328">"Кері қайтару"</string>
+    <string name="undo" msgid="7905788502491742328">"Қайтару"</string>
     <string name="redo" msgid="7759464876566803888">"Қайтару"</string>
     <string name="autofill" msgid="3035779615680565188">"Aвтотолтыру"</string>
     <string name="textSelectionCABTitle" msgid="5236850394370820357">"Мәтін таңдау"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Енгізу әдісі"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Мәтін әрекеттері"</string>
     <string name="email" msgid="4560673117055050403">"Электрондық пошта"</string>
-    <string name="dial" msgid="4204975095406423102">"Телефон"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"Браузер"</string>
+    <string name="dial" msgid="1253998302767701559">"Қоңырау шалу"</string>
+    <string name="map" msgid="6521159124535543457">"Орынды анықтау"</string>
+    <string name="browse" msgid="1245903488306147205">"Ашу"</string>
+    <string name="sms" msgid="4560537514610063430">"Хабар"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Енгізу"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Жадта орын азайып барады"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Жүйенің кейбір функциялары жұмыс істемеуі мүмкін"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Жүйе үшін жад жеткіліксіз. 250 МБ бос орын бар екенін тексеріп, қайта іске қосыңыз."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Планшет"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ТД"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Құлақаспаптар"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Үндеткіштерді қондыру"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Құлақаспаптар"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Жүйе"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth aудио"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Сымсыз дисплей"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Төтенше хабарлар сынағы"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Жауап"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM картасына рұқсат етілмеген"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM картасы белсендірілмеген"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM картасына рұқсат етілмеген"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Телефонға рұқсат етілмеген"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Қалқымалы терезе"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Бұл таңбаша ең соңғы қолданбаны қажет етеді"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Қолданба сақтық көшірме жасау мен қалпына келтіруді қолдамайтындықтан, таңбаша қалпына келтірілмеді"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Қолтаңба сәйкес келмейтіндіктен, таңбаша қалпына келтірілмеді"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Таңбаша қалпына келтірілмеді"</string>
 </resources>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 38ee22f..cfc79fe 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"កម្រងព័ត៌មាន​ការងារ​ត្រូវបាន​លុប​ដោយសារ​បាត់​កម្មវិធី​អ្នកគ្រប់គ្រង"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"កម្មវិធី​អ្នកគ្រប់គ្រង​កម្រងព័ត៌មាន​ការងារនេះ​អាច​បាត់ ឬ​មាន​បញ្ហា។ ដូច្នេះហើយ​ទើប​កម្រងព័ត៌មាន​ការងារ​របស់អ្នក និង​ទិន្នន័យ​ដែល​ពាក់ព័ន្ធត្រូវ​បានលុប។ សូមទាក់ទង​ទៅអ្នក​គ្រប់គ្រង​របស់អ្នក ដើម្បី​ទទួល​បាន​ជំនួយ។"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"កម្រងព័ត៌មាន​ការងារ​របស់អ្នក​លែងមាន​នៅលើ​ឧបករណ៍​នេះទៀត​ហើយ"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"ការព្យាយាមបញ្ចូលពាក្យសម្ងាត់ច្រើនដងពេកហើយ"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ឧបករណ៍ស្ថិតក្រោមការគ្រប់គ្រង"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ស្ថាប័នរបស់អ្នកគ្រប់គ្រងឧបករណ៍នេះ ហើយអាចនឹងតាមដានចរាចរណ៍បណ្តាញ។ ចុចដើម្បីទទួលបានព័ត៌មានលម្អិត។"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"ឧបករណ៍របស់អ្នកនឹងត្រូវបានលុប"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ការ​ជូនដំណឹង"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"របៀបដាក់បង្ហាញក្នុងហាង"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"ការ​តភ្ជាប់ USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"កម្មវិធី​ដែល​កំពុង​ដំណើរការ"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"កម្មវិធីដែល​កំពុងប្រើថ្ម"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើថ្ម"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"កម្មវិធីចំនួន <xliff:g id="NUMBER">%1$d</xliff:g> កំពុងប្រើថ្ម"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"វិធីសាស្ត្រ​បញ្ចូល"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"សកម្មភាព​អត្ថបទ"</string>
     <string name="email" msgid="4560673117055050403">"អ៊ីមែល"</string>
-    <string name="dial" msgid="4204975095406423102">"ទូរសព្ទ"</string>
-    <string name="map" msgid="6068210738233985748">"ផែនទី"</string>
-    <string name="browse" msgid="6993590095938149861">"កម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string>
+    <string name="dial" msgid="1253998302767701559">"ហៅទូរសព្ទ"</string>
+    <string name="map" msgid="6521159124535543457">"កំណត់ទីតាំង"</string>
+    <string name="browse" msgid="1245903488306147205">"បើក"</string>
+    <string name="sms" msgid="4560537514610063430">"សារ"</string>
+    <string name="add_contact" msgid="7867066569670597203">"បញ្ចូល"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"អស់​ទំហំ​ផ្ទុក"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"មុខងារ​ប្រព័ន្ធ​មួយ​ចំនួន​អាច​មិន​ដំណើរការ​"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"មិន​មាន​ទំហំ​ផ្ទុក​​គ្រប់​គ្រាន់​សម្រាប់​ប្រព័ន្ធ​។ សូម​ប្រាកដ​ថា​អ្នក​មាន​ទំហំ​ទំនេរ​ 250MB ហើយ​ចាប់ផ្ដើម​ឡើង​វិញ។"</string>
@@ -1422,9 +1426,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"កុំព្យូទ័រ​បន្ទះ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ទូរទស្សន៍"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ទូរសព្ទ"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"កាស"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ភ្ជាប់​អូប៉ាល័រ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"កាស"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ប្រព័ន្ធ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"សំឡេង​ប៊្លូធូស"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"បង្ហាញ​បណ្ដាញ​ឥត​ខ្សែ"</string>
@@ -1779,9 +1784,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"សារសាកល្បងពេលមានអាសន្ន"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"ឆ្លើយតប"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"មិន​អនុញ្ញាត​សីុមកាត​ទេ"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"សីុម​មិន​ត្រូវបាន​ផ្តល់ជូន​ទេ"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"មិន​អនុញ្ញាត​ចំពោះសីុម​ទេ"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"មិន​អនុញ្ញាត​ចំពោះទូរសព្ទ​ទេ"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"វិនដូលេចឡើង"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ផ្លូវកាត់នេះត្រូវការកម្មវិធីថ្មីបំផុត"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"មិនអាចស្តារផ្លូវកាត់បានទេ ដោយសារកម្មវិធីមិនស្គាល់ការបម្រុងទុក និងការស្តារ"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"មិនអាចស្តារផ្លូវកាត់បានទេ ដោយសារការស៊ីញ៉េកម្មវិធីមិនត្រូវគ្នា"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"មិនអាចស្តារផ្លូវកាត់បានទេ"</string>
 </resources>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 5116c5d0..f02a7ba 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"ನಿರ್ವಾಹಕ ಅಪ್ಲಿಕೇಶನ್‌ ತಪ್ಪಿಹೋಗಿರುವುದರಿಂದ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಅನ್ನು ಅಳಿಸಲಾಗಿದೆ"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ನಿರ್ವಾಹಕ ಅಪ್ಲಿಕೇಶನ್ ಕಳೆದು ಹೋಗಿದೆ ಅಥವಾ ಹಾಳಾಗಿದೆ. ಇದರ ಪರಿಣಾಮವಾಗಿ ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಮತ್ತು ಅದಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಸಹಾಯಕ್ಕಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಈ ಸಾಧನದಲ್ಲಿ ಈಗ ಲಭ್ಯವಿಲ್ಲ"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"ಹಲವಾರು ಪಾಸ್‌ವರ್ಡ್ ಪ್ರಯತ್ನಗಳು"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ನಿಮ್ಮ ಸಂಸ್ಥೆಯು ಈ ಸಾಧನವನ್ನು ನಿರ್ವಹಿಸುತ್ತದೆ ಮತ್ತು ಅದು ನೆಟ್‌ವರ್ಕ್ ಟ್ರಾಫಿಕ್ ಮೇಲೆ ಗಮನವಿರಿಸಬಹುದು. ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ಎಚ್ಚರಿಕೆಗಳು"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"ರಿಟೇಲ್ ಡೆಮೋ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB ಸಂಪರ್ಕ"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App ರನ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಬ್ಯಾಟರಿಯನ್ನು ಉಪಯೋಗಿಸುತ್ತಿವೆ"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಬ್ಯಾಟರಿ ಬಳಸುತ್ತಿದೆ"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಬ್ಯಾಟರಿ ಬಳಸುತ್ತಿವೆ"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ಇನ್‌ಪುಟ್ ವಿಧಾನ"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"ಪಠ್ಯದ ಕ್ರಮಗಳು"</string>
     <string name="email" msgid="4560673117055050403">"ಇಮೇಲ್"</string>
-    <string name="dial" msgid="4204975095406423102">"ಫೋನ್"</string>
-    <string name="map" msgid="6068210738233985748">"ನಕ್ಷೆಗಳು"</string>
-    <string name="browse" msgid="6993590095938149861">"ಬ್ರೌಸರ್"</string>
+    <string name="dial" msgid="1253998302767701559">"ಕರೆ"</string>
+    <string name="map" msgid="6521159124535543457">"ಗುರುತಿಸಿ"</string>
+    <string name="browse" msgid="1245903488306147205">"ತೆರೆ"</string>
+    <string name="sms" msgid="4560537514610063430">"ಸಂದೇಶ"</string>
+    <string name="add_contact" msgid="7867066569670597203">"ಸೇರಿಸಿ"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"ಸಂಗ್ರಹಣೆ ಸ್ಥಳವು ತುಂಬಿದೆ"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"ಕೆಲವು ಸಿಸ್ಟಂ ಕಾರ್ಯವಿಧಾನಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"ಸಿಸ್ಟಂನಲ್ಲಿ ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆಯಿಲ್ಲ. ನೀವು 250MB ನಷ್ಟು ಖಾಲಿ ಸ್ಥಳವನ್ನು ಹೊಂದಿರುವಿರಾ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಹಾಗೂ ಮರುಪ್ರಾರಂಭಿಸಿ."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ಟ್ಯಾಬ್ಲೆಟ್‌‌"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ಟಿವಿ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ಫೋನ್"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ಹೆಡ್‌ಫೋನ್‌ಗಳು"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ಡಾಕ್ ಸ್ಪೀಕರ್‍‌ಗಳು"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ಹೆಡ್‌ಫೋನ್‌ಗಳು"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ಸಿಸ್ಟಂ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ವಯರ್‌ಲೆಸ್ ಪ್ರದರ್ಶನ"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"ತುರ್ತು ಸಂದೇಶಗಳ ಪರೀಕ್ಷೆ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"ಪ್ರತ್ಯುತ್ತರ"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"ಸಿಮ್‌ಗೆ ಅನುಮತಿಯಿಲ್ಲ"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"ಸಿಮ್ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"ಸಿಮ್‌ಗೆ ಅನುಮತಿಯಿಲ್ಲ"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ಫೋನ್‌ಗೆ ಅನುಮತಿಯಿಲ್ಲ"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"ಪಾಪ್‌ಅಪ್ ವಿಂಡೋ"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ಈ ಶಾರ್ಟ್‌ಕಟ್‌ಗೆ ಇತ್ತೀಚಿನ ಅಪ್ಲಿಕೇಶನ್‌ ಅಗತ್ಯವಿದೆ"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"ಅಪ್ಲಿಕೇಶನ್‌ ಬ್ಯಾಕಪ್ ಮತ್ತು ಪುನಃಸ್ಥಾಪನೆಯನ್ನು ಬೆಂಬಲಿಸದಿರುವುದರಿಂದ ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃಸ್ಥಾಪನೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"ಅಪ್ಲಿಕೇಶನ್‌ ಸಹಿ ಹೊಂದಿಕೆಯಾಗದ ಕಾರಣದಿಂದ ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃ ಸ್ಥಾಪನೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 722b251..1976bbe 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"관리 앱이 없어서 직장 프로필이 삭제되었습니다."</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"직장 프로필 관리 앱이 없거나 손상되어 직장 프로필 및 관련 데이터가 삭제되었습니다. 도움이 필요한 경우 관리자에게 문의하세요."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"직장 프로필을 이 기기에서 더 이상 사용할 수 없습니다."</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"비밀번호 입력을 너무 많이 시도함"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"관리되는 기기"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"조직에서 이 기기를 관리하며 네트워크 트래픽을 모니터링할 수도 있습니다. 자세한 내용을 보려면 탭하세요."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"기기가 삭제됩니다."</string>
@@ -192,7 +193,7 @@
     <string name="reboot_to_update_prepare" msgid="6305853831955310890">"업데이트 준비 중…"</string>
     <string name="reboot_to_update_package" msgid="3871302324500927291">"업데이트 패키지 처리 중…"</string>
     <string name="reboot_to_update_reboot" msgid="6428441000951565185">"다시 시작하는 중..."</string>
-    <string name="reboot_to_reset_title" msgid="4142355915340627490">"공장 초기화"</string>
+    <string name="reboot_to_reset_title" msgid="4142355915340627490">"초기화"</string>
     <string name="reboot_to_reset_message" msgid="2432077491101416345">"다시 시작하는 중..."</string>
     <string name="shutdown_progress" msgid="2281079257329981203">"종료 중..."</string>
     <string name="shutdown_confirm" product="tablet" msgid="3385745179555731470">"태블릿이 종료됩니다."</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"알림"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"소매 데모"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB 연결"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"실행 중인 앱"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"배터리를 소모하는 앱"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 배터리 사용 중"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"앱 <xliff:g id="NUMBER">%1$d</xliff:g>개에서 배터리 사용 중"</string>
@@ -358,11 +360,11 @@
     <string name="permdesc_broadcastSticky" product="tablet" msgid="7749760494399915651">"앱이 브로드캐스트가 끝난 후에 남은 브로드캐스트를 보낼 수 있도록 허용합니다. 앱을 지나치게 사용하면 태블릿에서 메모리를 너무 많이 사용하도록 하여 속도를 저하시키거나 불안정하게 만들 수 있습니다."</string>
     <string name="permdesc_broadcastSticky" product="tv" msgid="6839285697565389467">"앱이 브로드캐스트가 끝난 후에도 흥미로운 브로드캐스트를 보낼 수 있도록 허용합니다. 이 기능을 과도하게 사용하면 메모리 사용량이 많아져 TV 속도가 저하되거나 성능이 불안정해질 수 있습니다."</string>
     <string name="permdesc_broadcastSticky" product="default" msgid="2825803764232445091">"앱이 브로드캐스트가 끝난 후에 남은 브로드캐스트를 보낼 수 있도록 허용합니다. 앱을 지나치게 사용하면 휴대전화에서 메모리를 너무 많이 사용하도록 하여 속도를 저하시키거나 불안정하게 만들 수 있습니다."</string>
-    <string name="permlab_readContacts" msgid="8348481131899886131">"주소록 읽기"</string>
+    <string name="permlab_readContacts" msgid="8348481131899886131">"연락처 읽기"</string>
     <string name="permdesc_readContacts" product="tablet" msgid="5294866856941149639">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 태블릿에 저장된 연락처에 대한 데이터를 앱이 읽도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
     <string name="permdesc_readContacts" product="tv" msgid="1839238344654834087">"앱이 특정 연락처와 통화를 하거나 이메일을 주고받거나 다른 방법으로 연락한 횟수를 포함하여 TV에 저장된 연락처 관련 데이터를 읽을 수 있도록 허용합니다. 이 경우 앱이 연락처 데이터를 저장할 수 있으며 악성 앱이 사용자 몰래 연락처 데이터에 공유할 수도 있습니다."</string>
     <string name="permdesc_readContacts" product="default" msgid="8440654152457300662">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 휴대전화에 저장된 연락처에 대한 데이터를 앱이 읽도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 저장할 수 있으며, 악성 앱이 사용자 모르게 연락처 데이터를 공유할 수도 있습니다."</string>
-    <string name="permlab_writeContacts" msgid="5107492086416793544">"주소록 수정"</string>
+    <string name="permlab_writeContacts" msgid="5107492086416793544">"연락처 수정"</string>
     <string name="permdesc_writeContacts" product="tablet" msgid="897243932521953602">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 태블릿에 저장된 연락처에 대한 데이터를 앱이 수정할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 삭제할 수 있습니다."</string>
     <string name="permdesc_writeContacts" product="tv" msgid="5438230957000018959">"앱이 특정 연락처와 통화를 하거나 이메일을 주고받거나 다른 수단으로 연락한 횟수를 포함하여 TV에 저장된 연락처 관련 데이터를 수정할 수 있도록 허용합니다. 이 경우 앱이 연락처 데이터를 삭제할 수 있습니다."</string>
     <string name="permdesc_writeContacts" product="default" msgid="589869224625163558">"특정인과 전화, 이메일 또는 기타 수단으로 연락한 빈도를 포함하여 사용자 휴대전화에 저장된 연락처에 대한 데이터를 앱이 수정할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 연락처 데이터를 삭제할 수 있습니다."</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"텍스트 작업"</string>
     <string name="email" msgid="4560673117055050403">"이메일"</string>
-    <string name="dial" msgid="4204975095406423102">"전화"</string>
-    <string name="map" msgid="6068210738233985748">"지도"</string>
-    <string name="browse" msgid="6993590095938149861">"브라우저"</string>
+    <string name="dial" msgid="1253998302767701559">"전화"</string>
+    <string name="map" msgid="6521159124535543457">"위치 확인"</string>
+    <string name="browse" msgid="1245903488306147205">"열기"</string>
+    <string name="sms" msgid="4560537514610063430">"메시지"</string>
+    <string name="add_contact" msgid="7867066569670597203">"추가"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"저장 공간이 부족함"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"일부 시스템 기능이 작동하지 않을 수 있습니다."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"시스템의 저장 공간이 부족합니다. 250MB의 여유 공간이 확보한 후 다시 시작하세요."</string>
@@ -1227,7 +1231,7 @@
     <string name="ext_media_unmounting_notification_title" msgid="640674168454809372">"아직 <xliff:g id="NAME">%s</xliff:g> 꺼내는 중…"</string>
     <string name="ext_media_unmounting_notification_message" msgid="4182843895023357756">"삭제하지 마세요."</string>
     <string name="ext_media_init_action" msgid="7952885510091978278">"설정"</string>
-    <string name="ext_media_unmount_action" msgid="1121883233103278199">"꺼내기"</string>
+    <string name="ext_media_unmount_action" msgid="1121883233103278199">"마운트 해제"</string>
     <string name="ext_media_browse_action" msgid="8322172381028546087">"둘러보기"</string>
     <string name="ext_media_missing_title" msgid="620980315821543904">"<xliff:g id="NAME">%s</xliff:g> 없음"</string>
     <string name="ext_media_missing_message" msgid="5761133583368750174">"기기 다시 삽입"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"태블릿"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"휴대전화"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"헤드폰"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"도크 스피커"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"헤드폰"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"시스템"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"블루투스 오디오"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"무선 디스플레이"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"두 번째 업무용 <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"세 번째 업무용<xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"이 화면을 고정 해제하려면 \'뒤로\' 및 \'최근 사용\'을 길게 터치하세요."</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"이 앱은 고정 해제할 수 없습니다."</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"화면 고정됨"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"화면 고정 해제됨"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"고정 해제 이전에 PIN 요청"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"긴급 메시지 테스트"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"답장"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM이 허용되지 않음"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM이 프로비저닝되지 않음"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM이 허용되지 않음"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"전화가 허용되지 않음"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM에서 음성이 허용되지 않음"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM이 음성으로 프로비저닝되지 않음"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM에서 음성이 허용되지 않음"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"휴대전화에서 음성이 허용되지 않음"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"팝업 창"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g>개 더보기"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"바로가기를 사용하려면 최신 앱이 필요합니다"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"앱이 백업 및 복원을 지원하지 않으므로 바로가기를 복원할 수 없습니다"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"앱 서명이 일치하지 않아 바로가기를 복원할 수 없습니다"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"바로가기를 복원할 수 없습니다"</string>
 </resources>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 9cf927e..a1cc479 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Башкаруучу колдонмосу болбогондуктан, жумуш профили жок кылынды"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Жумуш профилинин башкаруучу колдонмосу жок же бузулгандыктан, жумуш профилиңиз жана ага байланыштуу дайындар жок кылынды. Жардам алуу үчүн администраторуңузга кайрылыңыз."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Жумуш профилиңиз бул түзмөктөн жок кылынды"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Өтө көп жолу сырсөздү киргизүү аракети жасалды"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Түзмөктү ишкана башкарат"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ишканаңыз бул түзмөктү башкарат жана тармак трафигин көзөмөлдөшү мүмкүн. Чоо-жайын көрүү үчүн таптап коюңуз."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Түзмөгүңүз тазаланат"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Эскертүүлөр"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Чекене соода дүкөнү үчүн демо режим"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB аркылуу туташуу"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Колдонмо иштеп жатат"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Колдонмолор батареяңызды коротууда"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу батареяны пайдаланып жатат"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> колдонмо батареяны пайдаланып жатат"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Киргизүү ыкмасы"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Текст боюнча иштер"</string>
     <string name="email" msgid="4560673117055050403">"Электрондук почта"</string>
-    <string name="dial" msgid="4204975095406423102">"Телефон"</string>
-    <string name="map" msgid="6068210738233985748">"Карталар"</string>
-    <string name="browse" msgid="6993590095938149861">"Серепчи"</string>
+    <string name="dial" msgid="1253998302767701559">"Чалуу"</string>
+    <string name="map" msgid="6521159124535543457">"Жайгашкан жер"</string>
+    <string name="browse" msgid="1245903488306147205">"Ачуу"</string>
+    <string name="sms" msgid="4560537514610063430">"Билдирүү"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Кошуу"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Сактагычта орун калбай баратат"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Системанын кээ бир функциялары иштебеши мүмкүн"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Тутумда сактагыч жетишсиз. 250МБ бош орун бар экенин текшерип туруп, өчүрүп күйгүзүңүз."</string>
@@ -1421,9 +1425,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Планшет"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Сыналгы"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Кулакчын"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Аудио док бекет"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Кулакчын"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Тутум"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth аудио"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Зымсыз дисплей"</string>
@@ -1778,9 +1783,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Өзгөчө кырдаалда жөнөтүлүүчү билдирүүлөрдү сыноо"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Жооп берүү"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM картаны колдонууга тыюу салынган"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM карта таанылган жок"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM картаны колдонууга тыюу салынган"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Телефонду колдонууга тыюу салынган"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Калкып чыкма терезе"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Бул кыска жолго колдонмонун эң акыркы версиясы талап кылынат"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Колдонмо камдык көчүрмөнү сактоо жана калыбына келтирүү функцияларын колдобогондуктан кыска жол калыбына келтирилбей койду"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Колдонмонун колтамгасы дал келбегендиктен кыска жол калыбына келтирилбей койду"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Кыска жол калыбына келтирилбей койду"</string>
 </resources>
diff --git a/core/res/res/values-large/strings.xml b/core/res/res/values-large/strings.xml
deleted file mode 100644
index e998b9a..0000000
--- a/core/res/res/values-large/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2011, 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.
-*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Do not translate.  WebView User Agent targeted content -->
-    <string name="web_user_agent_target_content" translatable="false"></string>
-
-</resources>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 27601af..79a41aa 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"ລຶບໂປຣໄຟລ໌ບ່ອນເຮັດວຽກແລ້ວເນື່ອງຈາກບໍ່ມີແອັບຜູ້ເບິ່ງແຍງລະບົບ"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"ບໍ່ມີແອັບຜູ້ເບິ່ງແຍງລະບົບໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ ຫຼື ເສຍຫາຍ. ຜົນກໍຄື, ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ ແລະ ຂໍ້ມູນທີ່ກ່ຽວຂ້ອງຂອງທ່ານຖືກລຶບອອກແລ້ວ. ໃຫ້ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບສຳລັບການຊ່ວຍເຫຼືອ."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກຂອງທ່ານບໍ່ສາມາດໃຊ້ໄດ້ໃນອຸປະກອນນີ້ອີກຕໍ່ໄປ"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"ລອງໃສ່ລະຫັດຜ່ານຫຼາຍເທື່ອເກີນໄປ"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ອຸປະກອນມີການຈັດການ"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ອົງກອນຂອງທ່ານຈັດການອຸປະກອນນີ້ ແລະ ອາດກວດສອບທຣາບຟິກເຄືອຂ່າຍນຳ. ແຕະເພື່ອເບິ່ງລາຍລະອຽດ."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"ອຸ​ປະ​ກອນ​ຂອງ​ທ່ານ​ຈະ​ຖືກ​ລຶບ"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ການເຕືອນ"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"ເດໂມສຳລັບຮ້ານຂາຍ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"ການເຊື່ອມຕໍ່ USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"ແອັບກຳລັງເຮັດວຽກ"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ແອັບທີ່ກຳລັງໃຊ້ແບັດເຕີຣີ"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ແອັບກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
@@ -977,9 +979,16 @@
     <string name="inputMethod" msgid="1653630062304567879">"ຮູບແບບການປ້ອນຂໍ້ມູນ"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"ການເຮັດວຽກຂອງຂໍ້ຄວາມ"</string>
     <string name="email" msgid="4560673117055050403">"ອີເມວ"</string>
-    <string name="dial" msgid="4204975095406423102">"ໂທລະສັບ"</string>
-    <string name="map" msgid="6068210738233985748">"ແຜນທີ່"</string>
-    <string name="browse" msgid="6993590095938149861">"ໂປຣແກຣມທ່ອງເວັບ"</string>
+    <!-- no translation found for dial (1253998302767701559) -->
+    <skip />
+    <!-- no translation found for map (6521159124535543457) -->
+    <skip />
+    <!-- no translation found for browse (1245903488306147205) -->
+    <skip />
+    <!-- no translation found for sms (4560537514610063430) -->
+    <skip />
+    <!-- no translation found for add_contact (7867066569670597203) -->
+    <skip />
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"ພື້ນທີ່ຈັດເກັບຂໍ້ມູນກຳລັງຈະເຕັມ"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"ການເຮັດວຽກບາງຢ່າງຂອງລະບົບບາງອາດຈະໃຊ້ບໍ່ໄດ້"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"​ບໍ່​ມີ​ບ່ອນ​ເກັບ​ຂໍ້​ມູນ​ພຽງ​ພໍ​ສຳ​ລັບ​ລະ​ບົບ. ກວດ​ສອບ​ໃຫ້​ແນ່​ໃຈ​ວ່າ​ທ່ານ​ມີ​ພື້ນ​ທີ່​ຫວ່າງ​ຢ່າງ​ໜ້ອຍ 250MB ​ແລ້ວລອງ​ໃໝ່."</string>
@@ -989,6 +998,7 @@
     <string name="cancel" msgid="6442560571259935130">"ຍົກເລີກ"</string>
     <string name="yes" msgid="5362982303337969312">"ຕົກລົງ"</string>
     <string name="no" msgid="5141531044935541497">"ຍົກເລີກ"</string>
+    <string name="close" msgid="2318214661230355730">"ປິດ"</string>
     <string name="dialog_alert_title" msgid="2049658708609043103">"ກະລຸນາຮັບຊາບ"</string>
     <string name="loading" msgid="7933681260296021180">"ກຳລັງໂຫລດ..."</string>
     <string name="capital_on" msgid="1544682755514494298">"ເປີດ"</string>
@@ -1045,6 +1055,8 @@
     <string name="screen_compat_mode_scale" msgid="3202955667675944499">"ຂະໜາດ"</string>
     <string name="screen_compat_mode_show" msgid="4013878876486655892">"ສະແດງຕະຫຼອດເວລາ"</string>
     <string name="screen_compat_mode_hint" msgid="1064524084543304459">"ເປີດການເຮັດວຽກນີ້ຄືນໄດ້ໃນ ການຕັ້ງຄ່າລະບົບ &gt; ແອັບຯ &gt; ດາວໂຫລດແລ້ວ"</string>
+    <string name="top_app_killed_title" msgid="6814231368167994497">"ແອັບບໍ່ຕອບສະໜອງ"</string>
+    <string name="top_app_killed_message" msgid="3487519022191609844">"<xliff:g id="APP_NAME">%1$s</xliff:g> ອາດກຳລັງໃຊ້ໜ່ວຍຄວາມຈຳຫຼາຍເກີນໄປ"</string>
     <string name="unsupported_display_size_message" msgid="6545327290756295232">"<xliff:g id="APP_NAME">%1$s</xliff:g> ບໍ່ຮອງຮັບການຕັ້ງຄ່າຂະໜາດສະແດງຜົນປັດຈຸບັນ ແລະ ອາດມີຄວາມຜິດພາດໄດ້."</string>
     <string name="unsupported_display_size_show" msgid="7969129195360353041">"ສະແດງທຸກເທື່ອ"</string>
     <string name="smv_application" msgid="3307209192155442829">"ແອັບຯ <xliff:g id="APPLICATION">%1$s</xliff:g> (ໂປຣເຊສ <xliff:g id="PROCESS">%2$s</xliff:g>) ໄດ້ລະເມີດນະໂຍບາຍ StrictMode ທີ່ບັງຄັບໃຊ້ດ້ວຍໂຕເອງ."</string>
@@ -1420,9 +1432,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ແທັບເລັດ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ໂທລະພາບ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ໂທລະສັບ"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ຫູຟັງ"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ບ່ອນຕັ້ງລຳໂພງ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ຫູຟັງ"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ລະບົບ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ສຽງ Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ການສະແດງຜົນໄຮ້ສາຍ"</string>
@@ -1782,4 +1795,13 @@
     <string name="mmcc_illegal_ms" msgid="2769452751852211112">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ SIM"</string>
     <string name="mmcc_illegal_me" msgid="4438696681169345015">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"ໜ້າຈໍປັອບອັບ"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <!-- no translation found for shortcut_restored_on_lower_version (5270675146351613828) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_not_supported (5028808567940014190) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_signature_mismatch (2406209324521327518) -->
+    <skip />
+    <!-- no translation found for shortcut_restore_unknown_issue (8703738064603262597) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 9e807b0..c836374 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Darbo profilis ištrintas dėl trūkstamos administratoriaus programos"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Trūksta darbo profilio administratoriaus programos arba ji sugadinta. Todėl darbo profilis ir susiję duomenys buvo ištrinti. Jei reikia pagalbos, susisiekite su administratoriumi."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Darbo profilis nebepasiekiamas šiame įrenginyje"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Per daug slaptažodžio bandymų"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Įrenginys yra tvarkomas"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Šį įrenginį tvarko organizacija ir gali stebėti tinklo srautą. Palieskite, kad gautumėte daugiau informacijos."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Įrenginys bus ištrintas"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Įspėjimai"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstracinė versija mažmenininkams"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB jungtis"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Programa paleista"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Programos, naudojančios akumuliatoriaus energiją"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja akumuliatoriaus energiją"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Programų, naudojančių akumuliatoriaus energiją: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Įvesties būdas"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksto veiksmai"</string>
     <string name="email" msgid="4560673117055050403">"Siųsti el. laišką"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefonas"</string>
-    <string name="map" msgid="6068210738233985748">"Žemėlapiai"</string>
-    <string name="browse" msgid="6993590095938149861">"Naršyklė"</string>
+    <string name="dial" msgid="1253998302767701559">"Skambinti"</string>
+    <string name="map" msgid="6521159124535543457">"Rasti"</string>
+    <string name="browse" msgid="1245903488306147205">"Atidaryti"</string>
+    <string name="sms" msgid="4560537514610063430">"Pranešimas"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Pridėti"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Mažėja laisvos saugyklos vietos"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Kai kurios sistemos funkcijos gali neveikti"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Sistemos saugykloje nepakanka vietos. Įsitikinkite, kad yra 250 MB laisvos vietos, ir paleiskite iš naujo."</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Planšetinis kompiuteris"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefonas"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ausinės"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Doko garsiakalbiai"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ausinės"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"„Bluetooth“ garsas"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Belaidis rodymas"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Kritinės padėties pranešimo bandymas"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Atsakyti"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM kortelė neleidžiama"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM kortelė neteikiama"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM kortelė neleidžiama"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefonas neleidžiamas"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Iššokantysis langas"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"Dar <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Norint naudoti šį spartųjį klavišą būtina naujausios versijos programa"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Nepavyko atkurti sparčiojo klavišo, nes programa nepalaiko atsarginės kopijos kūrimo ir atkūrimo funkcijų"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Nepavyko atkurti sparčiojo klavišo, nes programos parašas neatitinka"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Nepavyko atkurti sparčiojo klavišo"</string>
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 5058d43..9a067c6 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -174,6 +174,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Darba profils tika dzēsts, jo trūkst administratora lietotnes."</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Trūkst darba profila administratora lietotnes, vai šī lietotne ir bojāta. Šī iemesla dēļ jūsu darba profils un saistītie dati tika dzēsti. Lai saņemtu palīdzību, sazinieties ar administratoru."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Jūsu darba profils šai ierīcē vairs nav pieejams."</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Veikts pārāk daudz paroles ievadīšanas mēģinājumu."</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Ierīce tiek pārvaldīta"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Jūsu organizācija pārvalda šo ierīci un var uzraudzīt tīkla datplūsmu. Pieskarieties, lai saņemtu detalizētu informāciju."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Jūsu ierīces dati tiks dzēsti"</string>
@@ -251,6 +252,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Brīdinājumi"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstrācijas versija veikaliem"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB savienojums"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Lietotne darbojas"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Lietotnes, kas patērē akumulatora jaudu"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> izmanto akumulatoru"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> lietotne(-es) izmanto akumulatoru"</string>
@@ -997,9 +999,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Ievades metode"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksta darbības"</string>
     <string name="email" msgid="4560673117055050403">"E-pasts"</string>
-    <string name="dial" msgid="4204975095406423102">"Tālrunis"</string>
-    <string name="map" msgid="6068210738233985748">"Kartes"</string>
-    <string name="browse" msgid="6993590095938149861">"Pārlūkprogramma"</string>
+    <string name="dial" msgid="1253998302767701559">"Zvanīt"</string>
+    <string name="map" msgid="6521159124535543457">"Atrast"</string>
+    <string name="browse" msgid="1245903488306147205">"Atvērt"</string>
+    <string name="sms" msgid="4560537514610063430">"Īsziņa"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Pievienot"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Paliek maz brīvas vietas"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Dažas sistēmas funkcijas var nedarboties."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Sistēmai pietrūkst vietas. Atbrīvojiet vismaz 250 MB vietas un restartējiet ierīci."</string>
@@ -1443,9 +1447,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Planšetdators"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Tālrunis"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Austiņas"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Doka skaļruņi"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Austiņas"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistēma"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Bezvadu attēlošana"</string>
@@ -1812,9 +1817,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Ārkārtas ziņojuma pārbaude"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Atbildēt"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM karti nav atļauts izmantot"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM karte netiek nodrošināta"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM karti nav atļauts izmantot"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Tālruni nav atļauts izmantot"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Uznirstošais logs"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"Vēl <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Šai saīsnei ir nepieciešama jaunākā lietotne."</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Nevarēja atjaunot saīsni, jo lietotnē netiek atbalstīta dublēšana un atjaunošana."</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Saīsni nevarēja atjaunot lietotnes paraksta neatbilstības dēļ."</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Nevarēja atjaunot saīsni."</string>
 </resources>
diff --git a/core/res/res/values-mcc001-mnc01-af/strings.xml b/core/res/res/values-mcc001-mnc01-af/strings.xml
new file mode 100644
index 0000000..e251b61
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-af/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Foon nie toegelaat nie MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-am/strings.xml b/core/res/res/values-mcc001-mnc01-am/strings.xml
new file mode 100644
index 0000000..c5cc421
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-am/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ስልክ አይፈቀድም MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ar/strings.xml b/core/res/res/values-mcc001-mnc01-ar/strings.xml
new file mode 100644
index 0000000..ae68ed4
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ar/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"‏غير مسموح باستخدام الهاتف MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-az/strings.xml b/core/res/res/values-mcc001-mnc01-az/strings.xml
new file mode 100644
index 0000000..7ac0613
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-az/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"MM#6 telefonu dəstəklənmir"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml b/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..858fdcb
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-be/strings.xml b/core/res/res/values-mcc001-mnc01-be/strings.xml
new file mode 100644
index 0000000..a22b9c4
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-be/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Тэлефон не дапускаецца MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-bg/strings.xml b/core/res/res/values-mcc001-mnc01-bg/strings.xml
new file mode 100644
index 0000000..b311679
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-bg/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Телефонът не е разрешен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-bn/strings.xml b/core/res/res/values-mcc001-mnc01-bn/strings.xml
new file mode 100644
index 0000000..095f8c7
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-bn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ফোন অনুমোদিত নয় MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-bs/strings.xml b/core/res/res/values-mcc001-mnc01-bs/strings.xml
new file mode 100644
index 0000000..858fdcb
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-bs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ca/strings.xml b/core/res/res/values-mcc001-mnc01-ca/strings.xml
new file mode 100644
index 0000000..cfdaf3e
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ca/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telèfon no compatible MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-cs/strings.xml b/core/res/res/values-mcc001-mnc01-cs/strings.xml
new file mode 100644
index 0000000..4a7f221
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-cs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon není povolen (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-da/strings.xml b/core/res/res/values-mcc001-mnc01-da/strings.xml
new file mode 100644
index 0000000..6a7a5c8
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-da/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefonen har ikke adgangstilladelse MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-de/strings.xml b/core/res/res/values-mcc001-mnc01-de/strings.xml
new file mode 100644
index 0000000..25b6bd1
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-de/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Smartphone nicht zulässig MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-el/strings.xml b/core/res/res/values-mcc001-mnc01-el/strings.xml
new file mode 100644
index 0000000..ae6b17a
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-el/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-en-rAU/strings.xml b/core/res/res/values-mcc001-mnc01-en-rAU/strings.xml
new file mode 100644
index 0000000..231b858
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-en-rCA/strings.xml b/core/res/res/values-mcc001-mnc01-en-rCA/strings.xml
new file mode 100644
index 0000000..231b858
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-en-rGB/strings.xml b/core/res/res/values-mcc001-mnc01-en-rGB/strings.xml
new file mode 100644
index 0000000..231b858
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-en-rIN/strings.xml b/core/res/res/values-mcc001-mnc01-en-rIN/strings.xml
new file mode 100644
index 0000000..231b858
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-en-rXC/strings.xml b/core/res/res/values-mcc001-mnc01-en-rXC/strings.xml
new file mode 100644
index 0000000..00e7813
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‏‎‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-es-rUS/strings.xml b/core/res/res/values-mcc001-mnc01-es-rUS/strings.xml
new file mode 100644
index 0000000..059c64a
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-es/strings.xml b/core/res/res/values-mcc001-mnc01-es/strings.xml
new file mode 100644
index 0000000..059c64a
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-es/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-et/strings.xml b/core/res/res/values-mcc001-mnc01-et/strings.xml
new file mode 100644
index 0000000..62ff8ec
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-et/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon pole lubatud MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-eu/strings.xml b/core/res/res/values-mcc001-mnc01-eu/strings.xml
new file mode 100644
index 0000000..2140993
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-eu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefonoa ez da onartzen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-fa/strings.xml b/core/res/res/values-mcc001-mnc01-fa/strings.xml
new file mode 100644
index 0000000..3d1acdb
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-fa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"‏تلفن مجاز نیست MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-fi/strings.xml b/core/res/res/values-mcc001-mnc01-fi/strings.xml
new file mode 100644
index 0000000..1c75bb6
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-fi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Puhelin estetty MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-fr-rCA/strings.xml b/core/res/res/values-mcc001-mnc01-fr-rCA/strings.xml
new file mode 100644
index 0000000..dbb6052
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-fr/strings.xml b/core/res/res/values-mcc001-mnc01-fr/strings.xml
new file mode 100644
index 0000000..dbb6052
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-fr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-gl/strings.xml b/core/res/res/values-mcc001-mnc01-gl/strings.xml
new file mode 100644
index 0000000..a9cd85e
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-gl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Non se admite o teléfono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-gu/strings.xml b/core/res/res/values-mcc001-mnc01-gu/strings.xml
new file mode 100644
index 0000000..f7c3285
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-gu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"MM#6 ફોનની મંજૂરી નથી"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-hi/strings.xml b/core/res/res/values-mcc001-mnc01-hi/strings.xml
new file mode 100644
index 0000000..ff6fed8
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-hi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"फ़ोन की इजाज़त नहीं है MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-hr/strings.xml b/core/res/res/values-mcc001-mnc01-hr/strings.xml
new file mode 100644
index 0000000..a3b89c9
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-hr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon nije dopušten MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-hu/strings.xml b/core/res/res/values-mcc001-mnc01-hu/strings.xml
new file mode 100644
index 0000000..e591979
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-hu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"A telefon nem engedélyezett (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-hy/strings.xml b/core/res/res/values-mcc001-mnc01-hy/strings.xml
new file mode 100644
index 0000000..90a840c
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-hy/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-in/strings.xml b/core/res/res/values-mcc001-mnc01-in/strings.xml
new file mode 100644
index 0000000..1496178
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-in/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Ponsel tidak diizinkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-is/strings.xml b/core/res/res/values-mcc001-mnc01-is/strings.xml
new file mode 100644
index 0000000..cb33a8c
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-is/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Sími ekki leyfður MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-it/strings.xml b/core/res/res/values-mcc001-mnc01-it/strings.xml
new file mode 100644
index 0000000..ce902c7
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-it/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefono non consentito MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-iw/strings.xml b/core/res/res/values-mcc001-mnc01-iw/strings.xml
new file mode 100644
index 0000000..2f0eec8
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-iw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"‏הטלפון לא מורשה MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ja/strings.xml b/core/res/res/values-mcc001-mnc01-ja/strings.xml
new file mode 100644
index 0000000..6661e5f
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"電話は許可されていません(MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ka/strings.xml b/core/res/res/values-mcc001-mnc01-ka/strings.xml
new file mode 100644
index 0000000..3d8e1b2
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ka/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ტელეფონი დაუშვებელია MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-kk/strings.xml b/core/res/res/values-mcc001-mnc01-kk/strings.xml
new file mode 100644
index 0000000..ba210c2
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-kk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Телефон пайдалануға болмайды MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-km/strings.xml b/core/res/res/values-mcc001-mnc01-km/strings.xml
new file mode 100644
index 0000000..2ee5b75
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-km/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-kn/strings.xml b/core/res/res/values-mcc001-mnc01-kn/strings.xml
new file mode 100644
index 0000000..de459a2
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-kn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ko/strings.xml b/core/res/res/values-mcc001-mnc01-ko/strings.xml
new file mode 100644
index 0000000..39b839b
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ko/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"전화가 허용되지 않음 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ky/strings.xml b/core/res/res/values-mcc001-mnc01-ky/strings.xml
new file mode 100644
index 0000000..28a2fd0
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ky/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Телефонду колдонууга тыюу салынган MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-lo/strings.xml b/core/res/res/values-mcc001-mnc01-lo/strings.xml
new file mode 100644
index 0000000..ca560ce4
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-lo/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-lt/strings.xml b/core/res/res/values-mcc001-mnc01-lt/strings.xml
new file mode 100644
index 0000000..29f1433
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-lt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefonas neleidžiamas (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-lv/strings.xml b/core/res/res/values-mcc001-mnc01-lv/strings.xml
new file mode 100644
index 0000000..0e97385
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-lv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Tālruni nav atļauts izmantot: MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-mk/strings.xml b/core/res/res/values-mcc001-mnc01-mk/strings.xml
new file mode 100644
index 0000000..f488183
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-mk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Телефонот не е дозволен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ml/strings.xml b/core/res/res/values-mcc001-mnc01-ml/strings.xml
new file mode 100644
index 0000000..20392b6
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ml/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-mn/strings.xml b/core/res/res/values-mcc001-mnc01-mn/strings.xml
new file mode 100644
index 0000000..164462b
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-mn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Утсыг зөвшөөрөөгүй MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-mr/strings.xml b/core/res/res/values-mcc001-mnc01-mr/strings.xml
new file mode 100644
index 0000000..564573b
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-mr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"फोन MM#6 ला अनुमती देत नाही"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ms/strings.xml b/core/res/res/values-mcc001-mnc01-ms/strings.xml
new file mode 100644
index 0000000..1399187
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ms/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon tidak dibenarkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-my/strings.xml b/core/res/res/values-mcc001-mnc01-my/strings.xml
new file mode 100644
index 0000000..39fa0e3
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-my/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-nb/strings.xml b/core/res/res/values-mcc001-mnc01-nb/strings.xml
new file mode 100644
index 0000000..0d46cee
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-nb/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefonen er ikke tillatt, MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ne/strings.xml b/core/res/res/values-mcc001-mnc01-ne/strings.xml
new file mode 100644
index 0000000..469aaa8
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ne/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"फोनलाई अनुमति छैन MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-nl/strings.xml b/core/res/res/values-mcc001-mnc01-nl/strings.xml
new file mode 100644
index 0000000..adf5d3a
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-nl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefoon niet toegestaan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-pa/strings.xml b/core/res/res/values-mcc001-mnc01-pa/strings.xml
new file mode 100644
index 0000000..3531088
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-pa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-pl/strings.xml b/core/res/res/values-mcc001-mnc01-pl/strings.xml
new file mode 100644
index 0000000..1ee5497
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"MM#6 – telefon niedozwolony"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-pt-rBR/strings.xml b/core/res/res/values-mcc001-mnc01-pt-rBR/strings.xml
new file mode 100644
index 0000000..4eeb835
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-pt-rBR/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-pt-rPT/strings.xml b/core/res/res/values-mcc001-mnc01-pt-rPT/strings.xml
new file mode 100644
index 0000000..9de5a17
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telemóvel não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-pt/strings.xml b/core/res/res/values-mcc001-mnc01-pt/strings.xml
new file mode 100644
index 0000000..4eeb835
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-pt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ro/strings.xml b/core/res/res/values-mcc001-mnc01-ro/strings.xml
new file mode 100644
index 0000000..67f05da
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ro/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefonul nu este permis MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ru/strings.xml b/core/res/res/values-mcc001-mnc01-ru/strings.xml
new file mode 100644
index 0000000..59a0f40
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ru/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Звонки запрещены (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-si/strings.xml b/core/res/res/values-mcc001-mnc01-si/strings.xml
new file mode 100644
index 0000000..bf48fd0
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-si/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-sk/strings.xml b/core/res/res/values-mcc001-mnc01-sk/strings.xml
new file mode 100644
index 0000000..8c23a50
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-sk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefón nie je povolený (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-sl/strings.xml b/core/res/res/values-mcc001-mnc01-sl/strings.xml
new file mode 100644
index 0000000..ef0e4f7
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-sl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefon ni dovoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-sq/strings.xml b/core/res/res/values-mcc001-mnc01-sq/strings.xml
new file mode 100644
index 0000000..57cd6ab
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-sq/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefoni nuk lejohet MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-sr/strings.xml b/core/res/res/values-mcc001-mnc01-sr/strings.xml
new file mode 100644
index 0000000..a7ef974ac
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-sr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Телефон није дозвољен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-sv/strings.xml b/core/res/res/values-mcc001-mnc01-sv/strings.xml
new file mode 100644
index 0000000..dc903f6
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-sv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Mobil tillåts inte MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-sw/strings.xml b/core/res/res/values-mcc001-mnc01-sw/strings.xml
new file mode 100644
index 0000000..c09faee
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-sw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Simu hairuhusiwi MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ta/strings.xml b/core/res/res/values-mcc001-mnc01-ta/strings.xml
new file mode 100644
index 0000000..0621e7c
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ta/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-te/strings.xml b/core/res/res/values-mcc001-mnc01-te/strings.xml
new file mode 100644
index 0000000..9e0a1fc
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-te/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ఫోన్ అనుమతించబడదు MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-th/strings.xml b/core/res/res/values-mcc001-mnc01-th/strings.xml
new file mode 100644
index 0000000..f16f43f
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-th/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-tl/strings.xml b/core/res/res/values-mcc001-mnc01-tl/strings.xml
new file mode 100644
index 0000000..aa15f0e
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-tl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Hindi pinapahintulutan ang telepono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-tr/strings.xml b/core/res/res/values-mcc001-mnc01-tr/strings.xml
new file mode 100644
index 0000000..7d0c4c2
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-tr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Telefona izin verilmiyor MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-uk/strings.xml b/core/res/res/values-mcc001-mnc01-uk/strings.xml
new file mode 100644
index 0000000..d791af4
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-uk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Телефон заборонено (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-ur/strings.xml b/core/res/res/values-mcc001-mnc01-ur/strings.xml
new file mode 100644
index 0000000..3329702
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-ur/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"‏فون کی اجازت نہیں ہے MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-uz/strings.xml b/core/res/res/values-mcc001-mnc01-uz/strings.xml
new file mode 100644
index 0000000..73ac1c0
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-uz/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Chaqiruvlar taqiqlangan (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-vi/strings.xml b/core/res/res/values-mcc001-mnc01-vi/strings.xml
new file mode 100644
index 0000000..e9362de
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-vi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Không cho phép điện thoại MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-zh-rCN/strings.xml b/core/res/res/values-mcc001-mnc01-zh-rCN/strings.xml
new file mode 100644
index 0000000..c9abc9b
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"不受允许的手机 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-zh-rHK/strings.xml b/core/res/res/values-mcc001-mnc01-zh-rHK/strings.xml
new file mode 100644
index 0000000..375fe31
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"不允許手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-zh-rTW/strings.xml b/core/res/res/values-mcc001-mnc01-zh-rTW/strings.xml
new file mode 100644
index 0000000..5700f01
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"不支援的手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01-zu/strings.xml b/core/res/res/values-mcc001-mnc01-zu/strings.xml
new file mode 100644
index 0000000..b31303f
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01-zu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="2238090225563073546">"Ifoni ayivunyelwe MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc001-mnc01/strings.xml b/core/res/res/values-mcc001-mnc01/strings.xml
new file mode 100644
index 0000000..96af975
--- /dev/null
+++ b/core/res/res/values-mcc001-mnc01/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc030-af/strings.xml b/core/res/res/values-mcc310-mnc030-af/strings.xml
index 0b666c2..1b6eec8 100644
--- a/core/res/res/values-mcc310-mnc030-af/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-am/strings.xml b/core/res/res/values-mcc310-mnc030-am/strings.xml
index 08c5e32..9e10ee2 100644
--- a/core/res/res/values-mcc310-mnc030-am/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ar/strings.xml b/core/res/res/values-mcc310-mnc030-ar/strings.xml
index 5d6a53d..51db337 100644
--- a/core/res/res/values-mcc310-mnc030-ar/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-az/strings.xml b/core/res/res/values-mcc310-mnc030-az/strings.xml
index 194d189..3946a0f 100644
--- a/core/res/res/values-mcc310-mnc030-az/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml
index d306893..6dfa886 100644
--- a/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-be/strings.xml b/core/res/res/values-mcc310-mnc030-be/strings.xml
index 12fef7a..66992cb 100644
--- a/core/res/res/values-mcc310-mnc030-be/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-bg/strings.xml b/core/res/res/values-mcc310-mnc030-bg/strings.xml
index a7c014a..336a890 100644
--- a/core/res/res/values-mcc310-mnc030-bg/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-bn/strings.xml b/core/res/res/values-mcc310-mnc030-bn/strings.xml
index f07a3d6..f4ad84e 100644
--- a/core/res/res/values-mcc310-mnc030-bn/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-bs/strings.xml b/core/res/res/values-mcc310-mnc030-bs/strings.xml
index 1e6c7db..c17d685 100644
--- a/core/res/res/values-mcc310-mnc030-bs/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ca/strings.xml b/core/res/res/values-mcc310-mnc030-ca/strings.xml
index af25f9b..1e4a752 100644
--- a/core/res/res/values-mcc310-mnc030-ca/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-cs/strings.xml b/core/res/res/values-mcc310-mnc030-cs/strings.xml
index ee0f90c..e5c0cf2 100644
--- a/core/res/res/values-mcc310-mnc030-cs/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-da/strings.xml b/core/res/res/values-mcc310-mnc030-da/strings.xml
index 8539f7a..dab4912 100644
--- a/core/res/res/values-mcc310-mnc030-da/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-de/strings.xml b/core/res/res/values-mcc310-mnc030-de/strings.xml
index ad797b5..d3ff1164 100644
--- a/core/res/res/values-mcc310-mnc030-de/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-el/strings.xml b/core/res/res/values-mcc310-mnc030-el/strings.xml
index 62aa97f..22afb5f 100644
--- a/core/res/res/values-mcc310-mnc030-el/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc030-en-rAU/strings.xml
index 1a50ac6..c604346 100644
--- a/core/res/res/values-mcc310-mnc030-en-rAU/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc030-en-rCA/strings.xml
index 1a50ac6..c604346 100644
--- a/core/res/res/values-mcc310-mnc030-en-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc030-en-rGB/strings.xml
index 1a50ac6..c604346 100644
--- a/core/res/res/values-mcc310-mnc030-en-rGB/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc030-en-rIN/strings.xml
index 1a50ac6..c604346 100644
--- a/core/res/res/values-mcc310-mnc030-en-rIN/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc030-en-rXC/strings.xml
index 5eb9cba..6fbbcb7 100644
--- a/core/res/res/values-mcc310-mnc030-en-rXC/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‏‎‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‎‎‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‏‏‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc030-es-rUS/strings.xml
index 87226ac..42426cb 100644
--- a/core/res/res/values-mcc310-mnc030-es-rUS/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-es/strings.xml b/core/res/res/values-mcc310-mnc030-es/strings.xml
index c13f5f8..ea3224d 100644
--- a/core/res/res/values-mcc310-mnc030-es/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-et/strings.xml b/core/res/res/values-mcc310-mnc030-et/strings.xml
index 07229ab..fbcaa30 100644
--- a/core/res/res/values-mcc310-mnc030-et/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-eu/strings.xml b/core/res/res/values-mcc310-mnc030-eu/strings.xml
index 024fbab..4053e48 100644
--- a/core/res/res/values-mcc310-mnc030-eu/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-fa/strings.xml b/core/res/res/values-mcc310-mnc030-fa/strings.xml
index e754032..01b0ad3 100644
--- a/core/res/res/values-mcc310-mnc030-fa/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-fi/strings.xml b/core/res/res/values-mcc310-mnc030-fi/strings.xml
index 3b9c2ab..8e948c6 100644
--- a/core/res/res/values-mcc310-mnc030-fi/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc030-fr-rCA/strings.xml
index 31644b7..0e4f55d 100644
--- a/core/res/res/values-mcc310-mnc030-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-fr/strings.xml b/core/res/res/values-mcc310-mnc030-fr/strings.xml
index 9c690e7..2f001db 100644
--- a/core/res/res/values-mcc310-mnc030-fr/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-gl/strings.xml b/core/res/res/values-mcc310-mnc030-gl/strings.xml
index 59be216..fe18f6e 100644
--- a/core/res/res/values-mcc310-mnc030-gl/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-gu/strings.xml b/core/res/res/values-mcc310-mnc030-gu/strings.xml
index ac57a85..2cd3d2d 100644
--- a/core/res/res/values-mcc310-mnc030-gu/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-hi/strings.xml b/core/res/res/values-mcc310-mnc030-hi/strings.xml
index 244d175..a3315a8 100644
--- a/core/res/res/values-mcc310-mnc030-hi/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-hr/strings.xml b/core/res/res/values-mcc310-mnc030-hr/strings.xml
index a37043c..a938a55 100644
--- a/core/res/res/values-mcc310-mnc030-hr/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-hu/strings.xml b/core/res/res/values-mcc310-mnc030-hu/strings.xml
index b26b2b2..b28ce8e 100644
--- a/core/res/res/values-mcc310-mnc030-hu/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-hy/strings.xml b/core/res/res/values-mcc310-mnc030-hy/strings.xml
index 0d052f3..34cd04e 100644
--- a/core/res/res/values-mcc310-mnc030-hy/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-in/strings.xml b/core/res/res/values-mcc310-mnc030-in/strings.xml
index f8f6613..b2a94b9 100644
--- a/core/res/res/values-mcc310-mnc030-in/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-is/strings.xml b/core/res/res/values-mcc310-mnc030-is/strings.xml
index 1033965..008de9d 100644
--- a/core/res/res/values-mcc310-mnc030-is/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-it/strings.xml b/core/res/res/values-mcc310-mnc030-it/strings.xml
index fb74a97..1b17cff 100644
--- a/core/res/res/values-mcc310-mnc030-it/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-iw/strings.xml b/core/res/res/values-mcc310-mnc030-iw/strings.xml
index 50bd517..c5350b8 100644
--- a/core/res/res/values-mcc310-mnc030-iw/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ja/strings.xml b/core/res/res/values-mcc310-mnc030-ja/strings.xml
index 78cd78c..56fa5dd 100644
--- a/core/res/res/values-mcc310-mnc030-ja/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ka/strings.xml b/core/res/res/values-mcc310-mnc030-ka/strings.xml
index 04d6a7d..abcaa99 100644
--- a/core/res/res/values-mcc310-mnc030-ka/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-kk/strings.xml b/core/res/res/values-mcc310-mnc030-kk/strings.xml
index aad588c..b84e25f 100644
--- a/core/res/res/values-mcc310-mnc030-kk/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-km/strings.xml b/core/res/res/values-mcc310-mnc030-km/strings.xml
index bd99927..284310a 100644
--- a/core/res/res/values-mcc310-mnc030-km/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"ស៊ីមកាត​មិនត្រូវបាន​ផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាត​ទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-kn/strings.xml b/core/res/res/values-mcc310-mnc030-kn/strings.xml
index 39e9b070..402d9be 100644
--- a/core/res/res/values-mcc310-mnc030-kn/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ko/strings.xml b/core/res/res/values-mcc310-mnc030-ko/strings.xml
index 67e45b0..f9b2e5c 100644
--- a/core/res/res/values-mcc310-mnc030-ko/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ky/strings.xml b/core/res/res/values-mcc310-mnc030-ky/strings.xml
index 02ac153..a0c42fe 100644
--- a/core/res/res/values-mcc310-mnc030-ky/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-lo/strings.xml b/core/res/res/values-mcc310-mnc030-lo/strings.xml
index b41bf91..f8f57c4 100644
--- a/core/res/res/values-mcc310-mnc030-lo/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-lt/strings.xml b/core/res/res/values-mcc310-mnc030-lt/strings.xml
index 59c66be..2060253 100644
--- a/core/res/res/values-mcc310-mnc030-lt/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-lv/strings.xml b/core/res/res/values-mcc310-mnc030-lv/strings.xml
index 685c9b8..dd8e155 100644
--- a/core/res/res/values-mcc310-mnc030-lv/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-mk/strings.xml b/core/res/res/values-mcc310-mnc030-mk/strings.xml
index ce24e25..3fa9acb 100644
--- a/core/res/res/values-mcc310-mnc030-mk/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ml/strings.xml b/core/res/res/values-mcc310-mnc030-ml/strings.xml
index 9adfd9c..cd69a85 100644
--- a/core/res/res/values-mcc310-mnc030-ml/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-mn/strings.xml b/core/res/res/values-mcc310-mnc030-mn/strings.xml
index 6ff2d5e..5bbbe1a 100644
--- a/core/res/res/values-mcc310-mnc030-mn/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-mr/strings.xml b/core/res/res/values-mcc310-mnc030-mr/strings.xml
index afc40a1..56afb10 100644
--- a/core/res/res/values-mcc310-mnc030-mr/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ms/strings.xml b/core/res/res/values-mcc310-mnc030-ms/strings.xml
index 9a54b04..2bcfc77 100644
--- a/core/res/res/values-mcc310-mnc030-ms/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-my/strings.xml b/core/res/res/values-mcc310-mnc030-my/strings.xml
index 79a0791..7e8894e 100644
--- a/core/res/res/values-mcc310-mnc030-my/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-nb/strings.xml b/core/res/res/values-mcc310-mnc030-nb/strings.xml
index 7c06dba..267353e 100644
--- a/core/res/res/values-mcc310-mnc030-nb/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ne/strings.xml b/core/res/res/values-mcc310-mnc030-ne/strings.xml
index 3ef06ab..dd07fc9 100644
--- a/core/res/res/values-mcc310-mnc030-ne/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-nl/strings.xml b/core/res/res/values-mcc310-mnc030-nl/strings.xml
index 861385d..52b52d6 100644
--- a/core/res/res/values-mcc310-mnc030-nl/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-pa/strings.xml b/core/res/res/values-mcc310-mnc030-pa/strings.xml
index ba7b614..cefd738 100644
--- a/core/res/res/values-mcc310-mnc030-pa/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-pl/strings.xml b/core/res/res/values-mcc310-mnc030-pl/strings.xml
index 84ff351..fa5720a 100644
--- a/core/res/res/values-mcc310-mnc030-pl/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc030-pt-rBR/strings.xml
index 2679f93..663261c 100644
--- a/core/res/res/values-mcc310-mnc030-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc030-pt-rPT/strings.xml
index 2679f93..602b59e 100644
--- a/core/res/res/values-mcc310-mnc030-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-pt/strings.xml b/core/res/res/values-mcc310-mnc030-pt/strings.xml
index 2679f93..663261c 100644
--- a/core/res/res/values-mcc310-mnc030-pt/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ro/strings.xml b/core/res/res/values-mcc310-mnc030-ro/strings.xml
index 5bae0c0..77d374c 100644
--- a/core/res/res/values-mcc310-mnc030-ro/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ru/strings.xml b/core/res/res/values-mcc310-mnc030-ru/strings.xml
index 658badf..c2ca9d3 100644
--- a/core/res/res/values-mcc310-mnc030-ru/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-si/strings.xml b/core/res/res/values-mcc310-mnc030-si/strings.xml
index 635ffa4..9b9b1b7 100644
--- a/core/res/res/values-mcc310-mnc030-si/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-sk/strings.xml b/core/res/res/values-mcc310-mnc030-sk/strings.xml
index 2a046b6..968fd2d 100644
--- a/core/res/res/values-mcc310-mnc030-sk/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-sl/strings.xml b/core/res/res/values-mcc310-mnc030-sl/strings.xml
index 7321e4d..dcbf3e2 100644
--- a/core/res/res/values-mcc310-mnc030-sl/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-sq/strings.xml b/core/res/res/values-mcc310-mnc030-sq/strings.xml
index e553f01..4ea64fd 100644
--- a/core/res/res/values-mcc310-mnc030-sq/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-sr/strings.xml b/core/res/res/values-mcc310-mnc030-sr/strings.xml
index 945e2fc..2f36c77 100644
--- a/core/res/res/values-mcc310-mnc030-sr/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-sv/strings.xml b/core/res/res/values-mcc310-mnc030-sv/strings.xml
index 5f0cc46..bac0d61 100644
--- a/core/res/res/values-mcc310-mnc030-sv/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-sw/strings.xml b/core/res/res/values-mcc310-mnc030-sw/strings.xml
index fbd2076..c26e864 100644
--- a/core/res/res/values-mcc310-mnc030-sw/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ta/strings.xml b/core/res/res/values-mcc310-mnc030-ta/strings.xml
index 6fc3df6..661c813 100644
--- a/core/res/res/values-mcc310-mnc030-ta/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-te/strings.xml b/core/res/res/values-mcc310-mnc030-te/strings.xml
index 4c61791..12a0d70 100644
--- a/core/res/res/values-mcc310-mnc030-te/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-th/strings.xml b/core/res/res/values-mcc310-mnc030-th/strings.xml
index 9a8ee74..d4b2dc4 100644
--- a/core/res/res/values-mcc310-mnc030-th/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-tl/strings.xml b/core/res/res/values-mcc310-mnc030-tl/strings.xml
index 6408f4e..41d3924 100644
--- a/core/res/res/values-mcc310-mnc030-tl/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-tr/strings.xml b/core/res/res/values-mcc310-mnc030-tr/strings.xml
index 361ee9c..aab27e2 100644
--- a/core/res/res/values-mcc310-mnc030-tr/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-uk/strings.xml b/core/res/res/values-mcc310-mnc030-uk/strings.xml
index efee94e..9e0cd4c 100644
--- a/core/res/res/values-mcc310-mnc030-uk/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-ur/strings.xml b/core/res/res/values-mcc310-mnc030-ur/strings.xml
index a0e5fd6..e704516 100644
--- a/core/res/res/values-mcc310-mnc030-ur/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-uz/strings.xml b/core/res/res/values-mcc310-mnc030-uz/strings.xml
index 7eb641a..c7ae871 100644
--- a/core/res/res/values-mcc310-mnc030-uz/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-vi/strings.xml b/core/res/res/values-mcc310-mnc030-vi/strings.xml
index 362ee6a..bd87e0d 100644
--- a/core/res/res/values-mcc310-mnc030-vi/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc030-zh-rCN/strings.xml
index efa43f5..441eb3f 100644
--- a/core/res/res/values-mcc310-mnc030-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc030-zh-rHK/strings.xml
index c163544..7f3a94c4 100644
--- a/core/res/res/values-mcc310-mnc030-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc030-zh-rTW/strings.xml
index c163544..a4baf62 100644
--- a/core/res/res/values-mcc310-mnc030-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030-zu/strings.xml b/core/res/res/values-mcc310-mnc030-zu/strings.xml
index 720fa82..0142a35 100644
--- a/core/res/res/values-mcc310-mnc030-zu/strings.xml
+++ b/core/res/res/values-mcc310-mnc030-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6575159319460304530">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1930079814544869756">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="2995576894416087107">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc030/strings.xml b/core/res/res/values-mcc310-mnc030/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc310-mnc030/strings.xml
+++ b/core/res/res/values-mcc310-mnc030/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc150-af/strings.xml b/core/res/res/values-mcc310-mnc150-af/strings.xml
new file mode 100644
index 0000000..bfc24ab
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-af/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Foon nie toegelaat nie MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-am/strings.xml b/core/res/res/values-mcc310-mnc150-am/strings.xml
new file mode 100644
index 0000000..c75ae67
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-am/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ስልክ አይፈቀድም MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ar/strings.xml b/core/res/res/values-mcc310-mnc150-ar/strings.xml
new file mode 100644
index 0000000..a3a5d85
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ar/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"‏غير مسموح باستخدام الهاتف MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-az/strings.xml b/core/res/res/values-mcc310-mnc150-az/strings.xml
new file mode 100644
index 0000000..4c7a339
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-az/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"MM#6 telefonu dəstəklənmir"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..d40e0bf
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-be/strings.xml b/core/res/res/values-mcc310-mnc150-be/strings.xml
new file mode 100644
index 0000000..31caa9a
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-be/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Тэлефон не дапускаецца MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-bg/strings.xml b/core/res/res/values-mcc310-mnc150-bg/strings.xml
new file mode 100644
index 0000000..70445e1
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-bg/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Телефонът не е разрешен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-bn/strings.xml b/core/res/res/values-mcc310-mnc150-bn/strings.xml
new file mode 100644
index 0000000..25dc744
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-bn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ফোন অনুমোদিত নয় MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-bs/strings.xml b/core/res/res/values-mcc310-mnc150-bs/strings.xml
new file mode 100644
index 0000000..be60b92
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-bs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon nije dozvoljen  MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ca/strings.xml b/core/res/res/values-mcc310-mnc150-ca/strings.xml
new file mode 100644
index 0000000..836aab0
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ca/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telèfon no compatible MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-cs/strings.xml b/core/res/res/values-mcc310-mnc150-cs/strings.xml
new file mode 100644
index 0000000..f31b34e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-cs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon není povolen (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-da/strings.xml b/core/res/res/values-mcc310-mnc150-da/strings.xml
new file mode 100644
index 0000000..1213be0
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-da/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefonen har ikke adgangstilladelse MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-de/strings.xml b/core/res/res/values-mcc310-mnc150-de/strings.xml
new file mode 100644
index 0000000..7fdf306
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-de/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Smartphone nicht zulässig MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-el/strings.xml b/core/res/res/values-mcc310-mnc150-el/strings.xml
new file mode 100644
index 0000000..9dfeb6c
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-el/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc150-en-rAU/strings.xml
new file mode 100644
index 0000000..afeb95e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc150-en-rCA/strings.xml
new file mode 100644
index 0000000..afeb95e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc150-en-rGB/strings.xml
new file mode 100644
index 0000000..afeb95e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc150-en-rIN/strings.xml
new file mode 100644
index 0000000..afeb95e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc150-en-rXC/strings.xml
new file mode 100644
index 0000000..c3d43d9
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc150-es-rUS/strings.xml
new file mode 100644
index 0000000..4ed5253
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-es/strings.xml b/core/res/res/values-mcc310-mnc150-es/strings.xml
new file mode 100644
index 0000000..4ed5253
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-es/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-et/strings.xml b/core/res/res/values-mcc310-mnc150-et/strings.xml
new file mode 100644
index 0000000..6cb111e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-et/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon pole lubatud MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-eu/strings.xml b/core/res/res/values-mcc310-mnc150-eu/strings.xml
new file mode 100644
index 0000000..a38ed44d
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-eu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefonoa ez da onartzen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-fa/strings.xml b/core/res/res/values-mcc310-mnc150-fa/strings.xml
new file mode 100644
index 0000000..bb52d79
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-fa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"‏تلفن مجاز نیست MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-fi/strings.xml b/core/res/res/values-mcc310-mnc150-fi/strings.xml
new file mode 100644
index 0000000..d1eff31
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-fi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Puhelin estetty MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc150-fr-rCA/strings.xml
new file mode 100644
index 0000000..335a0e8
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-fr/strings.xml b/core/res/res/values-mcc310-mnc150-fr/strings.xml
new file mode 100644
index 0000000..335a0e8
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-fr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-gl/strings.xml b/core/res/res/values-mcc310-mnc150-gl/strings.xml
new file mode 100644
index 0000000..23faa06
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-gl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Non se admite o teléfono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-gu/strings.xml b/core/res/res/values-mcc310-mnc150-gu/strings.xml
new file mode 100644
index 0000000..95f89a0
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-gu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"MM#6 ફોનની મંજૂરી નથી"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-hi/strings.xml b/core/res/res/values-mcc310-mnc150-hi/strings.xml
new file mode 100644
index 0000000..1b88f5b
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-hi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"फ़ोन की इजाज़त नहीं है MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-hr/strings.xml b/core/res/res/values-mcc310-mnc150-hr/strings.xml
new file mode 100644
index 0000000..85bc29b
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-hr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon nije dopušten MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-hu/strings.xml b/core/res/res/values-mcc310-mnc150-hu/strings.xml
new file mode 100644
index 0000000..e741ab3
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-hu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"A telefon nem engedélyezett (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-hy/strings.xml b/core/res/res/values-mcc310-mnc150-hy/strings.xml
new file mode 100644
index 0000000..c6ec7d3
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-hy/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-in/strings.xml b/core/res/res/values-mcc310-mnc150-in/strings.xml
new file mode 100644
index 0000000..8a4fb2a
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-in/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Ponsel tidak diizinkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-is/strings.xml b/core/res/res/values-mcc310-mnc150-is/strings.xml
new file mode 100644
index 0000000..3c1a883
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-is/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Sími ekki leyfður MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-it/strings.xml b/core/res/res/values-mcc310-mnc150-it/strings.xml
new file mode 100644
index 0000000..1fed454
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-it/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefono non consentito MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-iw/strings.xml b/core/res/res/values-mcc310-mnc150-iw/strings.xml
new file mode 100644
index 0000000..f5e73d5
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-iw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"‏הטלפון לא מורשה MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ja/strings.xml b/core/res/res/values-mcc310-mnc150-ja/strings.xml
new file mode 100644
index 0000000..9b4a071
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"電話は許可されていません(MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ka/strings.xml b/core/res/res/values-mcc310-mnc150-ka/strings.xml
new file mode 100644
index 0000000..5387ec5
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ka/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ტელეფონი დაუშვებელია MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-kk/strings.xml b/core/res/res/values-mcc310-mnc150-kk/strings.xml
new file mode 100644
index 0000000..b8b20fd
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-kk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Телефон пайдалануға болмайды MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-km/strings.xml b/core/res/res/values-mcc310-mnc150-km/strings.xml
new file mode 100644
index 0000000..032f361
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-km/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-kn/strings.xml b/core/res/res/values-mcc310-mnc150-kn/strings.xml
new file mode 100644
index 0000000..629d8b8
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-kn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ko/strings.xml b/core/res/res/values-mcc310-mnc150-ko/strings.xml
new file mode 100644
index 0000000..94314c4
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ko/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"전화가 허용되지 않음 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ky/strings.xml b/core/res/res/values-mcc310-mnc150-ky/strings.xml
new file mode 100644
index 0000000..238bef7
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ky/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Телефонду колдонууга тыюу салынган MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-lo/strings.xml b/core/res/res/values-mcc310-mnc150-lo/strings.xml
new file mode 100644
index 0000000..fb7fcc2
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-lo/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-lt/strings.xml b/core/res/res/values-mcc310-mnc150-lt/strings.xml
new file mode 100644
index 0000000..59fa06a
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-lt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefonas neleidžiamas (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-lv/strings.xml b/core/res/res/values-mcc310-mnc150-lv/strings.xml
new file mode 100644
index 0000000..c1021d7
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-lv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Tālruni nav atļauts izmantot: MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-mk/strings.xml b/core/res/res/values-mcc310-mnc150-mk/strings.xml
new file mode 100644
index 0000000..934e03c
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-mk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Телефонот не е дозволен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ml/strings.xml b/core/res/res/values-mcc310-mnc150-ml/strings.xml
new file mode 100644
index 0000000..4efb94b
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ml/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-mn/strings.xml b/core/res/res/values-mcc310-mnc150-mn/strings.xml
new file mode 100644
index 0000000..4a56ecf
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-mn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Утсыг зөвшөөрөөгүй MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-mr/strings.xml b/core/res/res/values-mcc310-mnc150-mr/strings.xml
new file mode 100644
index 0000000..3797f53
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-mr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"फोन MM#6 ला अनुमती देत नाही"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ms/strings.xml b/core/res/res/values-mcc310-mnc150-ms/strings.xml
new file mode 100644
index 0000000..7fe8b74
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ms/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon tidak dibenarkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-my/strings.xml b/core/res/res/values-mcc310-mnc150-my/strings.xml
new file mode 100644
index 0000000..c1b0918
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-my/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-nb/strings.xml b/core/res/res/values-mcc310-mnc150-nb/strings.xml
new file mode 100644
index 0000000..a792c97
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-nb/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefonen er ikke tillatt, MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ne/strings.xml b/core/res/res/values-mcc310-mnc150-ne/strings.xml
new file mode 100644
index 0000000..bc94555
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ne/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"फोनलाई अनुमति छैन MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-nl/strings.xml b/core/res/res/values-mcc310-mnc150-nl/strings.xml
new file mode 100644
index 0000000..219c0c3
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-nl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefoon niet toegestaan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-pa/strings.xml b/core/res/res/values-mcc310-mnc150-pa/strings.xml
new file mode 100644
index 0000000..18216f3
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-pa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-pl/strings.xml b/core/res/res/values-mcc310-mnc150-pl/strings.xml
new file mode 100644
index 0000000..a696993
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"MM#6 – telefon niedozwolony"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc150-pt-rBR/strings.xml
new file mode 100644
index 0000000..1163c91
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-pt-rBR/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc150-pt-rPT/strings.xml
new file mode 100644
index 0000000..13bcac8
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telemóvel não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-pt/strings.xml b/core/res/res/values-mcc310-mnc150-pt/strings.xml
new file mode 100644
index 0000000..1163c91
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-pt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ro/strings.xml b/core/res/res/values-mcc310-mnc150-ro/strings.xml
new file mode 100644
index 0000000..200b59d
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ro/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefonul nu este permis MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ru/strings.xml b/core/res/res/values-mcc310-mnc150-ru/strings.xml
new file mode 100644
index 0000000..7118395
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ru/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Звонки запрещены (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-si/strings.xml b/core/res/res/values-mcc310-mnc150-si/strings.xml
new file mode 100644
index 0000000..c198a38
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-si/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-sk/strings.xml b/core/res/res/values-mcc310-mnc150-sk/strings.xml
new file mode 100644
index 0000000..0a0d119
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-sk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefón nie je povolený (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-sl/strings.xml b/core/res/res/values-mcc310-mnc150-sl/strings.xml
new file mode 100644
index 0000000..cebc04a
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-sl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefon ni dovoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-sq/strings.xml b/core/res/res/values-mcc310-mnc150-sq/strings.xml
new file mode 100644
index 0000000..2daf805
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-sq/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefoni nuk lejohet MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-sr/strings.xml b/core/res/res/values-mcc310-mnc150-sr/strings.xml
new file mode 100644
index 0000000..40ac0c2
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-sr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Телефон није дозвољен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-sv/strings.xml b/core/res/res/values-mcc310-mnc150-sv/strings.xml
new file mode 100644
index 0000000..5f1a340
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-sv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Mobil tillåts inte MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-sw/strings.xml b/core/res/res/values-mcc310-mnc150-sw/strings.xml
new file mode 100644
index 0000000..db3201d
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-sw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Simu hairuhusiwi MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ta/strings.xml b/core/res/res/values-mcc310-mnc150-ta/strings.xml
new file mode 100644
index 0000000..6177db2
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ta/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-te/strings.xml b/core/res/res/values-mcc310-mnc150-te/strings.xml
new file mode 100644
index 0000000..e8bb029
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-te/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ఫోన్ అనుమతించబడదు MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-th/strings.xml b/core/res/res/values-mcc310-mnc150-th/strings.xml
new file mode 100644
index 0000000..e047ebe
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-th/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-tl/strings.xml b/core/res/res/values-mcc310-mnc150-tl/strings.xml
new file mode 100644
index 0000000..550acde
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-tl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Hindi pinapahintulutan ang telepono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-tr/strings.xml b/core/res/res/values-mcc310-mnc150-tr/strings.xml
new file mode 100644
index 0000000..60615bb
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-tr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Telefona izin verilmiyor MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-uk/strings.xml b/core/res/res/values-mcc310-mnc150-uk/strings.xml
new file mode 100644
index 0000000..b709f6e
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-uk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Телефон заборонено (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-ur/strings.xml b/core/res/res/values-mcc310-mnc150-ur/strings.xml
new file mode 100644
index 0000000..fe29e15
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-ur/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"‏فون کی اجازت نہیں ہے MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-uz/strings.xml b/core/res/res/values-mcc310-mnc150-uz/strings.xml
new file mode 100644
index 0000000..e1372d1
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-uz/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Chaqiruvlar taqiqlangan (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-vi/strings.xml b/core/res/res/values-mcc310-mnc150-vi/strings.xml
new file mode 100644
index 0000000..85a7c62
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-vi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Không cho phép điện thoại MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc150-zh-rCN/strings.xml
new file mode 100644
index 0000000..9319941
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"不受允许的手机 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc150-zh-rHK/strings.xml
new file mode 100644
index 0000000..092d780
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"不允許手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc150-zh-rTW/strings.xml
new file mode 100644
index 0000000..f8d43ed
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"不支援的手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150-zu/strings.xml b/core/res/res/values-mcc310-mnc150-zu/strings.xml
new file mode 100644
index 0000000..4d0f31d
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150-zu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="5207603948149789531">"Ifoni ayivunyelwe MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc150/strings.xml b/core/res/res/values-mcc310-mnc150/strings.xml
new file mode 100644
index 0000000..96af975
--- /dev/null
+++ b/core/res/res/values-mcc310-mnc150/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
+</resources>
diff --git a/core/res/res/values-mcc310-mnc170-af/strings.xml b/core/res/res/values-mcc310-mnc170-af/strings.xml
index 4256d3a..00c2835 100644
--- a/core/res/res/values-mcc310-mnc170-af/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-am/strings.xml b/core/res/res/values-mcc310-mnc170-am/strings.xml
index 311d9e1..961fa69 100644
--- a/core/res/res/values-mcc310-mnc170-am/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ar/strings.xml b/core/res/res/values-mcc310-mnc170-ar/strings.xml
index a80ff2e..3ad58f5 100644
--- a/core/res/res/values-mcc310-mnc170-ar/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-az/strings.xml b/core/res/res/values-mcc310-mnc170-az/strings.xml
index a690668..43491fa 100644
--- a/core/res/res/values-mcc310-mnc170-az/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml
index b2da8a7..f47ef72 100644
--- a/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-be/strings.xml b/core/res/res/values-mcc310-mnc170-be/strings.xml
index fb7f556..f596195 100644
--- a/core/res/res/values-mcc310-mnc170-be/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-bg/strings.xml b/core/res/res/values-mcc310-mnc170-bg/strings.xml
index 1158b3b..b93c5c1 100644
--- a/core/res/res/values-mcc310-mnc170-bg/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-bn/strings.xml b/core/res/res/values-mcc310-mnc170-bn/strings.xml
index 4e6d41b..1c22bc1 100644
--- a/core/res/res/values-mcc310-mnc170-bn/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-bs/strings.xml b/core/res/res/values-mcc310-mnc170-bs/strings.xml
index d3f3e3b..4a2cb6f 100644
--- a/core/res/res/values-mcc310-mnc170-bs/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ca/strings.xml b/core/res/res/values-mcc310-mnc170-ca/strings.xml
index 9abeeb7..c82d5a5 100644
--- a/core/res/res/values-mcc310-mnc170-ca/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-cs/strings.xml b/core/res/res/values-mcc310-mnc170-cs/strings.xml
index 2d4d2fb..2443292 100644
--- a/core/res/res/values-mcc310-mnc170-cs/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-da/strings.xml b/core/res/res/values-mcc310-mnc170-da/strings.xml
index ae2155f..98ab336 100644
--- a/core/res/res/values-mcc310-mnc170-da/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-de/strings.xml b/core/res/res/values-mcc310-mnc170-de/strings.xml
index f7c5eec..f3c0b9a 100644
--- a/core/res/res/values-mcc310-mnc170-de/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-el/strings.xml b/core/res/res/values-mcc310-mnc170-el/strings.xml
index 68b7008..42000eb 100644
--- a/core/res/res/values-mcc310-mnc170-el/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc170-en-rAU/strings.xml
index fd16620..d389436 100644
--- a/core/res/res/values-mcc310-mnc170-en-rAU/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc170-en-rCA/strings.xml
index fd16620..d389436 100644
--- a/core/res/res/values-mcc310-mnc170-en-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc170-en-rGB/strings.xml
index fd16620..d389436 100644
--- a/core/res/res/values-mcc310-mnc170-en-rGB/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc170-en-rIN/strings.xml
index fd16620..d389436 100644
--- a/core/res/res/values-mcc310-mnc170-en-rIN/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc170-en-rXC/strings.xml
index 71d087e..054db20 100644
--- a/core/res/res/values-mcc310-mnc170-en-rXC/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‏‎‎‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‏‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc170-es-rUS/strings.xml
index 50d3c12..c794ad8 100644
--- a/core/res/res/values-mcc310-mnc170-es-rUS/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-es/strings.xml b/core/res/res/values-mcc310-mnc170-es/strings.xml
index 7191d04..5e0852a 100644
--- a/core/res/res/values-mcc310-mnc170-es/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-et/strings.xml b/core/res/res/values-mcc310-mnc170-et/strings.xml
index 7159bf9..24a004f 100644
--- a/core/res/res/values-mcc310-mnc170-et/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-eu/strings.xml b/core/res/res/values-mcc310-mnc170-eu/strings.xml
index 797f988..4f7b0d1 100644
--- a/core/res/res/values-mcc310-mnc170-eu/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-fa/strings.xml b/core/res/res/values-mcc310-mnc170-fa/strings.xml
index 968f952..1a516c4 100644
--- a/core/res/res/values-mcc310-mnc170-fa/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-fi/strings.xml b/core/res/res/values-mcc310-mnc170-fi/strings.xml
index c0523f4..e638085 100644
--- a/core/res/res/values-mcc310-mnc170-fi/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc170-fr-rCA/strings.xml
index 5600fa4..3b87213 100644
--- a/core/res/res/values-mcc310-mnc170-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-fr/strings.xml b/core/res/res/values-mcc310-mnc170-fr/strings.xml
index 73b4f0e..0f6adf0 100644
--- a/core/res/res/values-mcc310-mnc170-fr/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-gl/strings.xml b/core/res/res/values-mcc310-mnc170-gl/strings.xml
index fe13211..a2513d2 100644
--- a/core/res/res/values-mcc310-mnc170-gl/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-gu/strings.xml b/core/res/res/values-mcc310-mnc170-gu/strings.xml
index 60eba5b..adf23e4 100644
--- a/core/res/res/values-mcc310-mnc170-gu/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-hi/strings.xml b/core/res/res/values-mcc310-mnc170-hi/strings.xml
index eb350b0..134dbc1 100644
--- a/core/res/res/values-mcc310-mnc170-hi/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-hr/strings.xml b/core/res/res/values-mcc310-mnc170-hr/strings.xml
index d5cf025..e640ae5 100644
--- a/core/res/res/values-mcc310-mnc170-hr/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-hu/strings.xml b/core/res/res/values-mcc310-mnc170-hu/strings.xml
index e05e700..8d6dd9f 100644
--- a/core/res/res/values-mcc310-mnc170-hu/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-hy/strings.xml b/core/res/res/values-mcc310-mnc170-hy/strings.xml
index 90a5f6d..3c30292 100644
--- a/core/res/res/values-mcc310-mnc170-hy/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-in/strings.xml b/core/res/res/values-mcc310-mnc170-in/strings.xml
index fc1bd0a..51a82df 100644
--- a/core/res/res/values-mcc310-mnc170-in/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-is/strings.xml b/core/res/res/values-mcc310-mnc170-is/strings.xml
index eef786c..e9c6d48 100644
--- a/core/res/res/values-mcc310-mnc170-is/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-it/strings.xml b/core/res/res/values-mcc310-mnc170-it/strings.xml
index eaf0abc..8e97b1b 100644
--- a/core/res/res/values-mcc310-mnc170-it/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-iw/strings.xml b/core/res/res/values-mcc310-mnc170-iw/strings.xml
index edee703..cd818d6 100644
--- a/core/res/res/values-mcc310-mnc170-iw/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ja/strings.xml b/core/res/res/values-mcc310-mnc170-ja/strings.xml
index 6942641..b019dd1 100644
--- a/core/res/res/values-mcc310-mnc170-ja/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ka/strings.xml b/core/res/res/values-mcc310-mnc170-ka/strings.xml
index 6f6c4aa..fe5bc11 100644
--- a/core/res/res/values-mcc310-mnc170-ka/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-kk/strings.xml b/core/res/res/values-mcc310-mnc170-kk/strings.xml
index 210fb31..2f24d65 100644
--- a/core/res/res/values-mcc310-mnc170-kk/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-km/strings.xml b/core/res/res/values-mcc310-mnc170-km/strings.xml
index 79acf48..4a121e4 100644
--- a/core/res/res/values-mcc310-mnc170-km/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"ស៊ីមកាត​មិនត្រូវបានផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាតទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-kn/strings.xml b/core/res/res/values-mcc310-mnc170-kn/strings.xml
index e11523b..5bd7f17 100644
--- a/core/res/res/values-mcc310-mnc170-kn/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ko/strings.xml b/core/res/res/values-mcc310-mnc170-ko/strings.xml
index 22d7e35..783e7d5 100644
--- a/core/res/res/values-mcc310-mnc170-ko/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ky/strings.xml b/core/res/res/values-mcc310-mnc170-ky/strings.xml
index 1f07c68..4c09085 100644
--- a/core/res/res/values-mcc310-mnc170-ky/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-lo/strings.xml b/core/res/res/values-mcc310-mnc170-lo/strings.xml
index 3073000..e0a7379 100644
--- a/core/res/res/values-mcc310-mnc170-lo/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-lt/strings.xml b/core/res/res/values-mcc310-mnc170-lt/strings.xml
index 127e69f..25698a9 100644
--- a/core/res/res/values-mcc310-mnc170-lt/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-lv/strings.xml b/core/res/res/values-mcc310-mnc170-lv/strings.xml
index da2ff7c..6bb0838 100644
--- a/core/res/res/values-mcc310-mnc170-lv/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-mk/strings.xml b/core/res/res/values-mcc310-mnc170-mk/strings.xml
index 3bc8194..de6ac62 100644
--- a/core/res/res/values-mcc310-mnc170-mk/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ml/strings.xml b/core/res/res/values-mcc310-mnc170-ml/strings.xml
index 0479aef..4bfdffc 100644
--- a/core/res/res/values-mcc310-mnc170-ml/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-mn/strings.xml b/core/res/res/values-mcc310-mnc170-mn/strings.xml
index 59f24ec..6e5cfc3 100644
--- a/core/res/res/values-mcc310-mnc170-mn/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-mr/strings.xml b/core/res/res/values-mcc310-mnc170-mr/strings.xml
index 938207c..a2fe305 100644
--- a/core/res/res/values-mcc310-mnc170-mr/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ms/strings.xml b/core/res/res/values-mcc310-mnc170-ms/strings.xml
index 36be774..698c5d7 100644
--- a/core/res/res/values-mcc310-mnc170-ms/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-my/strings.xml b/core/res/res/values-mcc310-mnc170-my/strings.xml
index 61f1a67..6c2be32 100644
--- a/core/res/res/values-mcc310-mnc170-my/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-nb/strings.xml b/core/res/res/values-mcc310-mnc170-nb/strings.xml
index 3f213da..3a404b8 100644
--- a/core/res/res/values-mcc310-mnc170-nb/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ne/strings.xml b/core/res/res/values-mcc310-mnc170-ne/strings.xml
index d7febc4..65c01a1 100644
--- a/core/res/res/values-mcc310-mnc170-ne/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-nl/strings.xml b/core/res/res/values-mcc310-mnc170-nl/strings.xml
index b1f9ba8..bdfb53a 100644
--- a/core/res/res/values-mcc310-mnc170-nl/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-pa/strings.xml b/core/res/res/values-mcc310-mnc170-pa/strings.xml
index 9e993e3..6cdf639 100644
--- a/core/res/res/values-mcc310-mnc170-pa/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-pl/strings.xml b/core/res/res/values-mcc310-mnc170-pl/strings.xml
index d1ecd5d..41100a4 100644
--- a/core/res/res/values-mcc310-mnc170-pl/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc170-pt-rBR/strings.xml
index fc31e9e..fcffa16 100644
--- a/core/res/res/values-mcc310-mnc170-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc170-pt-rPT/strings.xml
index fc31e9e..7d43bf3 100644
--- a/core/res/res/values-mcc310-mnc170-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-pt/strings.xml b/core/res/res/values-mcc310-mnc170-pt/strings.xml
index fc31e9e..fcffa16 100644
--- a/core/res/res/values-mcc310-mnc170-pt/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ro/strings.xml b/core/res/res/values-mcc310-mnc170-ro/strings.xml
index 1ee5080..7c2c09e 100644
--- a/core/res/res/values-mcc310-mnc170-ro/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ru/strings.xml b/core/res/res/values-mcc310-mnc170-ru/strings.xml
index 0e00909..3b457da 100644
--- a/core/res/res/values-mcc310-mnc170-ru/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-si/strings.xml b/core/res/res/values-mcc310-mnc170-si/strings.xml
index bbd1e4c..61e0143 100644
--- a/core/res/res/values-mcc310-mnc170-si/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-sk/strings.xml b/core/res/res/values-mcc310-mnc170-sk/strings.xml
index e5f9376..cf71dd3 100644
--- a/core/res/res/values-mcc310-mnc170-sk/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-sl/strings.xml b/core/res/res/values-mcc310-mnc170-sl/strings.xml
index 2d4c7dd..2e600ac 100644
--- a/core/res/res/values-mcc310-mnc170-sl/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-sq/strings.xml b/core/res/res/values-mcc310-mnc170-sq/strings.xml
index df5b537..0b22ca2 100644
--- a/core/res/res/values-mcc310-mnc170-sq/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-sr/strings.xml b/core/res/res/values-mcc310-mnc170-sr/strings.xml
index 6bfbbb2..42057fe 100644
--- a/core/res/res/values-mcc310-mnc170-sr/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-sv/strings.xml b/core/res/res/values-mcc310-mnc170-sv/strings.xml
index 1a28db1..c609747 100644
--- a/core/res/res/values-mcc310-mnc170-sv/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-sw/strings.xml b/core/res/res/values-mcc310-mnc170-sw/strings.xml
index 0852115..7727944 100644
--- a/core/res/res/values-mcc310-mnc170-sw/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ta/strings.xml b/core/res/res/values-mcc310-mnc170-ta/strings.xml
index 0277cc2..cde140f 100644
--- a/core/res/res/values-mcc310-mnc170-ta/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-te/strings.xml b/core/res/res/values-mcc310-mnc170-te/strings.xml
index a208cd3..a088bb0 100644
--- a/core/res/res/values-mcc310-mnc170-te/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-th/strings.xml b/core/res/res/values-mcc310-mnc170-th/strings.xml
index e5d02c8..ad5f5dc 100644
--- a/core/res/res/values-mcc310-mnc170-th/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-tl/strings.xml b/core/res/res/values-mcc310-mnc170-tl/strings.xml
index e285759..187593a 100644
--- a/core/res/res/values-mcc310-mnc170-tl/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-tr/strings.xml b/core/res/res/values-mcc310-mnc170-tr/strings.xml
index b5102ef..e4a9255 100644
--- a/core/res/res/values-mcc310-mnc170-tr/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-uk/strings.xml b/core/res/res/values-mcc310-mnc170-uk/strings.xml
index 37e3118..89ad9b3 100644
--- a/core/res/res/values-mcc310-mnc170-uk/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-ur/strings.xml b/core/res/res/values-mcc310-mnc170-ur/strings.xml
index ea8b93e..50cd57e 100644
--- a/core/res/res/values-mcc310-mnc170-ur/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-uz/strings.xml b/core/res/res/values-mcc310-mnc170-uz/strings.xml
index 0bb3f52..9bfecfd 100644
--- a/core/res/res/values-mcc310-mnc170-uz/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-vi/strings.xml b/core/res/res/values-mcc310-mnc170-vi/strings.xml
index a37f48f..ad87648 100644
--- a/core/res/res/values-mcc310-mnc170-vi/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc170-zh-rCN/strings.xml
index f072b28..de68fe1 100644
--- a/core/res/res/values-mcc310-mnc170-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc170-zh-rHK/strings.xml
index db14b90..5fd10b3 100644
--- a/core/res/res/values-mcc310-mnc170-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc170-zh-rTW/strings.xml
index db14b90..cb19625 100644
--- a/core/res/res/values-mcc310-mnc170-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170-zu/strings.xml b/core/res/res/values-mcc310-mnc170-zu/strings.xml
index 282f403..26890e4e 100644
--- a/core/res/res/values-mcc310-mnc170-zu/strings.xml
+++ b/core/res/res/values-mcc310-mnc170-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="210168420192421012">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1130721094178658338">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="3173546391131606065">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc170/strings.xml b/core/res/res/values-mcc310-mnc170/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc310-mnc170/strings.xml
+++ b/core/res/res/values-mcc310-mnc170/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-af/strings.xml b/core/res/res/values-mcc310-mnc280-af/strings.xml
index a761881..b143077 100644
--- a/core/res/res/values-mcc310-mnc280-af/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-am/strings.xml b/core/res/res/values-mcc310-mnc280-am/strings.xml
index 6750a71..ffbb1cc 100644
--- a/core/res/res/values-mcc310-mnc280-am/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ar/strings.xml b/core/res/res/values-mcc310-mnc280-ar/strings.xml
index a77d78e..c7c03a5 100644
--- a/core/res/res/values-mcc310-mnc280-ar/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-az/strings.xml b/core/res/res/values-mcc310-mnc280-az/strings.xml
index b7ee114..7fb788a 100644
--- a/core/res/res/values-mcc310-mnc280-az/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml
index 0c78b5e..cd78afa 100644
--- a/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-be/strings.xml b/core/res/res/values-mcc310-mnc280-be/strings.xml
index 3ee9ad9..7da834b 100644
--- a/core/res/res/values-mcc310-mnc280-be/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-bg/strings.xml b/core/res/res/values-mcc310-mnc280-bg/strings.xml
index a320898..7b0fac1 100644
--- a/core/res/res/values-mcc310-mnc280-bg/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-bn/strings.xml b/core/res/res/values-mcc310-mnc280-bn/strings.xml
index dc950a7..ae0eaea 100644
--- a/core/res/res/values-mcc310-mnc280-bn/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-bs/strings.xml b/core/res/res/values-mcc310-mnc280-bs/strings.xml
index d61fad8..5259a9a 100644
--- a/core/res/res/values-mcc310-mnc280-bs/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ca/strings.xml b/core/res/res/values-mcc310-mnc280-ca/strings.xml
index 9a9e309..061c74c 100644
--- a/core/res/res/values-mcc310-mnc280-ca/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-cs/strings.xml b/core/res/res/values-mcc310-mnc280-cs/strings.xml
index 99e2bdb..422d205 100644
--- a/core/res/res/values-mcc310-mnc280-cs/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-da/strings.xml b/core/res/res/values-mcc310-mnc280-da/strings.xml
index 4f444a1..180c523 100644
--- a/core/res/res/values-mcc310-mnc280-da/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-de/strings.xml b/core/res/res/values-mcc310-mnc280-de/strings.xml
index 063c75b..0ca30c9 100644
--- a/core/res/res/values-mcc310-mnc280-de/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-el/strings.xml b/core/res/res/values-mcc310-mnc280-el/strings.xml
index 1161a7d..494e53b 100644
--- a/core/res/res/values-mcc310-mnc280-el/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc280-en-rAU/strings.xml
index e9c9eba..c7e6a34 100644
--- a/core/res/res/values-mcc310-mnc280-en-rAU/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc280-en-rCA/strings.xml
index e9c9eba..c7e6a34 100644
--- a/core/res/res/values-mcc310-mnc280-en-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc280-en-rGB/strings.xml
index e9c9eba..c7e6a34 100644
--- a/core/res/res/values-mcc310-mnc280-en-rGB/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc280-en-rIN/strings.xml
index e9c9eba..c7e6a34 100644
--- a/core/res/res/values-mcc310-mnc280-en-rIN/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc280-en-rXC/strings.xml
index 83640ae..7200c69 100644
--- a/core/res/res/values-mcc310-mnc280-en-rXC/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‎‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc280-es-rUS/strings.xml
index 86ad4fd..00feb92 100644
--- a/core/res/res/values-mcc310-mnc280-es-rUS/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-es/strings.xml b/core/res/res/values-mcc310-mnc280-es/strings.xml
index 2c7aa93..27945c8 100644
--- a/core/res/res/values-mcc310-mnc280-es/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-et/strings.xml b/core/res/res/values-mcc310-mnc280-et/strings.xml
index 7305d18..aa127fa 100644
--- a/core/res/res/values-mcc310-mnc280-et/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-eu/strings.xml b/core/res/res/values-mcc310-mnc280-eu/strings.xml
index 3c7296d..eec8d90 100644
--- a/core/res/res/values-mcc310-mnc280-eu/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-fa/strings.xml b/core/res/res/values-mcc310-mnc280-fa/strings.xml
index cd7ce85..219299c 100644
--- a/core/res/res/values-mcc310-mnc280-fa/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-fi/strings.xml b/core/res/res/values-mcc310-mnc280-fi/strings.xml
index b2ccc50..a21613f 100644
--- a/core/res/res/values-mcc310-mnc280-fi/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc280-fr-rCA/strings.xml
index 29bb9a0..c5f913f 100644
--- a/core/res/res/values-mcc310-mnc280-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-fr/strings.xml b/core/res/res/values-mcc310-mnc280-fr/strings.xml
index ce2f931..5b6ec9d 100644
--- a/core/res/res/values-mcc310-mnc280-fr/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-gl/strings.xml b/core/res/res/values-mcc310-mnc280-gl/strings.xml
index e941341..0a05c51 100644
--- a/core/res/res/values-mcc310-mnc280-gl/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-gu/strings.xml b/core/res/res/values-mcc310-mnc280-gu/strings.xml
index a3763be..382ce7e 100644
--- a/core/res/res/values-mcc310-mnc280-gu/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-hi/strings.xml b/core/res/res/values-mcc310-mnc280-hi/strings.xml
index ce866af..57218a3 100644
--- a/core/res/res/values-mcc310-mnc280-hi/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-hr/strings.xml b/core/res/res/values-mcc310-mnc280-hr/strings.xml
index 0021474..e6f9abd 100644
--- a/core/res/res/values-mcc310-mnc280-hr/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-hu/strings.xml b/core/res/res/values-mcc310-mnc280-hu/strings.xml
index 864faff..e673aea 100644
--- a/core/res/res/values-mcc310-mnc280-hu/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-hy/strings.xml b/core/res/res/values-mcc310-mnc280-hy/strings.xml
index 6d027c3..b9f59e0 100644
--- a/core/res/res/values-mcc310-mnc280-hy/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-in/strings.xml b/core/res/res/values-mcc310-mnc280-in/strings.xml
index a4f3486..23e60fa 100644
--- a/core/res/res/values-mcc310-mnc280-in/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-is/strings.xml b/core/res/res/values-mcc310-mnc280-is/strings.xml
index 30bbea4..56034d0 100644
--- a/core/res/res/values-mcc310-mnc280-is/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-it/strings.xml b/core/res/res/values-mcc310-mnc280-it/strings.xml
index f83921b..b3d34cf 100644
--- a/core/res/res/values-mcc310-mnc280-it/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-iw/strings.xml b/core/res/res/values-mcc310-mnc280-iw/strings.xml
index f3f87ff..4966a98 100644
--- a/core/res/res/values-mcc310-mnc280-iw/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ja/strings.xml b/core/res/res/values-mcc310-mnc280-ja/strings.xml
index a1cfd8b..463fc2e 100644
--- a/core/res/res/values-mcc310-mnc280-ja/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ka/strings.xml b/core/res/res/values-mcc310-mnc280-ka/strings.xml
index 91c434f..942944f 100644
--- a/core/res/res/values-mcc310-mnc280-ka/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-kk/strings.xml b/core/res/res/values-mcc310-mnc280-kk/strings.xml
index 44440d3..3559ee0 100644
--- a/core/res/res/values-mcc310-mnc280-kk/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-km/strings.xml b/core/res/res/values-mcc310-mnc280-km/strings.xml
index a016601..69777dc 100644
--- a/core/res/res/values-mcc310-mnc280-km/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"ស៊ីមកាត​មិនត្រូវបានផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាតទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-kn/strings.xml b/core/res/res/values-mcc310-mnc280-kn/strings.xml
index 1d9e353..3028b6f 100644
--- a/core/res/res/values-mcc310-mnc280-kn/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ko/strings.xml b/core/res/res/values-mcc310-mnc280-ko/strings.xml
index e7bb9bb..d6ff696 100644
--- a/core/res/res/values-mcc310-mnc280-ko/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ky/strings.xml b/core/res/res/values-mcc310-mnc280-ky/strings.xml
index 85483c7..9ecbcf2 100644
--- a/core/res/res/values-mcc310-mnc280-ky/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-lo/strings.xml b/core/res/res/values-mcc310-mnc280-lo/strings.xml
index 9415089..f72ece1 100644
--- a/core/res/res/values-mcc310-mnc280-lo/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-lt/strings.xml b/core/res/res/values-mcc310-mnc280-lt/strings.xml
index b5ff1b9..80257df 100644
--- a/core/res/res/values-mcc310-mnc280-lt/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-lv/strings.xml b/core/res/res/values-mcc310-mnc280-lv/strings.xml
index 4034bc1..f6bb072 100644
--- a/core/res/res/values-mcc310-mnc280-lv/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-mk/strings.xml b/core/res/res/values-mcc310-mnc280-mk/strings.xml
index a93cb79..f9a7d91 100644
--- a/core/res/res/values-mcc310-mnc280-mk/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ml/strings.xml b/core/res/res/values-mcc310-mnc280-ml/strings.xml
index 4aa7dec..9f8eee6 100644
--- a/core/res/res/values-mcc310-mnc280-ml/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-mn/strings.xml b/core/res/res/values-mcc310-mnc280-mn/strings.xml
index 54b8190..2b9d814 100644
--- a/core/res/res/values-mcc310-mnc280-mn/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-mr/strings.xml b/core/res/res/values-mcc310-mnc280-mr/strings.xml
index cb343cb..fbf98fb 100644
--- a/core/res/res/values-mcc310-mnc280-mr/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ms/strings.xml b/core/res/res/values-mcc310-mnc280-ms/strings.xml
index 27da747..d140049 100644
--- a/core/res/res/values-mcc310-mnc280-ms/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-my/strings.xml b/core/res/res/values-mcc310-mnc280-my/strings.xml
index 40cdc63..c4ba718 100644
--- a/core/res/res/values-mcc310-mnc280-my/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-nb/strings.xml b/core/res/res/values-mcc310-mnc280-nb/strings.xml
index 7666c3e..c9eece5 100644
--- a/core/res/res/values-mcc310-mnc280-nb/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ne/strings.xml b/core/res/res/values-mcc310-mnc280-ne/strings.xml
index 8735605..2108eda 100644
--- a/core/res/res/values-mcc310-mnc280-ne/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-nl/strings.xml b/core/res/res/values-mcc310-mnc280-nl/strings.xml
index 7d7bfa7..f6c0e0b 100644
--- a/core/res/res/values-mcc310-mnc280-nl/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-pa/strings.xml b/core/res/res/values-mcc310-mnc280-pa/strings.xml
index 3a65866..3ac0638 100644
--- a/core/res/res/values-mcc310-mnc280-pa/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-pl/strings.xml b/core/res/res/values-mcc310-mnc280-pl/strings.xml
index 52410f4..ee33cf6 100644
--- a/core/res/res/values-mcc310-mnc280-pl/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc280-pt-rBR/strings.xml
index 03d0efb..f7fb684 100644
--- a/core/res/res/values-mcc310-mnc280-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc280-pt-rPT/strings.xml
index 03d0efb..1a64f5c 100644
--- a/core/res/res/values-mcc310-mnc280-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-pt/strings.xml b/core/res/res/values-mcc310-mnc280-pt/strings.xml
index 03d0efb..f7fb684 100644
--- a/core/res/res/values-mcc310-mnc280-pt/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ro/strings.xml b/core/res/res/values-mcc310-mnc280-ro/strings.xml
index d60ea0c..5ed83b3 100644
--- a/core/res/res/values-mcc310-mnc280-ro/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ru/strings.xml b/core/res/res/values-mcc310-mnc280-ru/strings.xml
index 308c353..c9e22bb 100644
--- a/core/res/res/values-mcc310-mnc280-ru/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-si/strings.xml b/core/res/res/values-mcc310-mnc280-si/strings.xml
index 5bd6d1f..fa5b3c0 100644
--- a/core/res/res/values-mcc310-mnc280-si/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-sk/strings.xml b/core/res/res/values-mcc310-mnc280-sk/strings.xml
index 4098ac7..b116331 100644
--- a/core/res/res/values-mcc310-mnc280-sk/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-sl/strings.xml b/core/res/res/values-mcc310-mnc280-sl/strings.xml
index faa78ad..1a75b94 100644
--- a/core/res/res/values-mcc310-mnc280-sl/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-sq/strings.xml b/core/res/res/values-mcc310-mnc280-sq/strings.xml
index 48fba2d..54072e2 100644
--- a/core/res/res/values-mcc310-mnc280-sq/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-sr/strings.xml b/core/res/res/values-mcc310-mnc280-sr/strings.xml
index 30c9df3..f4591b6 100644
--- a/core/res/res/values-mcc310-mnc280-sr/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-sv/strings.xml b/core/res/res/values-mcc310-mnc280-sv/strings.xml
index cb5b9f32..294d762 100644
--- a/core/res/res/values-mcc310-mnc280-sv/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-sw/strings.xml b/core/res/res/values-mcc310-mnc280-sw/strings.xml
index 15a1a36..4912340 100644
--- a/core/res/res/values-mcc310-mnc280-sw/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ta/strings.xml b/core/res/res/values-mcc310-mnc280-ta/strings.xml
index c3911d4..35cc649 100644
--- a/core/res/res/values-mcc310-mnc280-ta/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-te/strings.xml b/core/res/res/values-mcc310-mnc280-te/strings.xml
index f5cabad..75d5b73 100644
--- a/core/res/res/values-mcc310-mnc280-te/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-th/strings.xml b/core/res/res/values-mcc310-mnc280-th/strings.xml
index 9810ba6..2312bb4 100644
--- a/core/res/res/values-mcc310-mnc280-th/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-tl/strings.xml b/core/res/res/values-mcc310-mnc280-tl/strings.xml
index 600ad05..8c05e82 100644
--- a/core/res/res/values-mcc310-mnc280-tl/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-tr/strings.xml b/core/res/res/values-mcc310-mnc280-tr/strings.xml
index ea90bdb..4e9cc2c 100644
--- a/core/res/res/values-mcc310-mnc280-tr/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-uk/strings.xml b/core/res/res/values-mcc310-mnc280-uk/strings.xml
index 68a34fd..aa500d6 100644
--- a/core/res/res/values-mcc310-mnc280-uk/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-ur/strings.xml b/core/res/res/values-mcc310-mnc280-ur/strings.xml
index d61a5dc..b0d842a 100644
--- a/core/res/res/values-mcc310-mnc280-ur/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-uz/strings.xml b/core/res/res/values-mcc310-mnc280-uz/strings.xml
index 324d364..9110cfc 100644
--- a/core/res/res/values-mcc310-mnc280-uz/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-vi/strings.xml b/core/res/res/values-mcc310-mnc280-vi/strings.xml
index 6806e39..444f882 100644
--- a/core/res/res/values-mcc310-mnc280-vi/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc280-zh-rCN/strings.xml
index add3f920..aa85594 100644
--- a/core/res/res/values-mcc310-mnc280-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc280-zh-rHK/strings.xml
index 856297c..c986d5d 100644
--- a/core/res/res/values-mcc310-mnc280-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc280-zh-rTW/strings.xml
index 856297c..720d097 100644
--- a/core/res/res/values-mcc310-mnc280-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280-zu/strings.xml b/core/res/res/values-mcc310-mnc280-zu/strings.xml
index 6c5147c..a198302 100644
--- a/core/res/res/values-mcc310-mnc280-zu/strings.xml
+++ b/core/res/res/values-mcc310-mnc280-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="6638755728961013003">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="5562215652599183258">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="822496463303720579">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc280/strings.xml b/core/res/res/values-mcc310-mnc280/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc310-mnc280/strings.xml
+++ b/core/res/res/values-mcc310-mnc280/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-af/strings.xml b/core/res/res/values-mcc310-mnc410-af/strings.xml
index 81688f1..7aea163 100644
--- a/core/res/res/values-mcc310-mnc410-af/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-am/strings.xml b/core/res/res/values-mcc310-mnc410-am/strings.xml
index 176a628..b5f5356 100644
--- a/core/res/res/values-mcc310-mnc410-am/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ar/strings.xml b/core/res/res/values-mcc310-mnc410-ar/strings.xml
index 884e18e..829ea43 100644
--- a/core/res/res/values-mcc310-mnc410-ar/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-az/strings.xml b/core/res/res/values-mcc310-mnc410-az/strings.xml
index 178451c..497f37a 100644
--- a/core/res/res/values-mcc310-mnc410-az/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml
index 8981a35..95b9e00 100644
--- a/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-be/strings.xml b/core/res/res/values-mcc310-mnc410-be/strings.xml
index 8ae8639..291eb12 100644
--- a/core/res/res/values-mcc310-mnc410-be/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-bg/strings.xml b/core/res/res/values-mcc310-mnc410-bg/strings.xml
index fc6f3e5..9ea8ddf 100644
--- a/core/res/res/values-mcc310-mnc410-bg/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-bn/strings.xml b/core/res/res/values-mcc310-mnc410-bn/strings.xml
index e42a375..8fe788b 100644
--- a/core/res/res/values-mcc310-mnc410-bn/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-bs/strings.xml b/core/res/res/values-mcc310-mnc410-bs/strings.xml
index 4d7da3a..550a5ca 100644
--- a/core/res/res/values-mcc310-mnc410-bs/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ca/strings.xml b/core/res/res/values-mcc310-mnc410-ca/strings.xml
index 19ab945b..2827e20 100644
--- a/core/res/res/values-mcc310-mnc410-ca/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-cs/strings.xml b/core/res/res/values-mcc310-mnc410-cs/strings.xml
index 3e36325..22610b0 100644
--- a/core/res/res/values-mcc310-mnc410-cs/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-da/strings.xml b/core/res/res/values-mcc310-mnc410-da/strings.xml
index e99ab57..38c47b3 100644
--- a/core/res/res/values-mcc310-mnc410-da/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-de/strings.xml b/core/res/res/values-mcc310-mnc410-de/strings.xml
index c5f64f4..ed8cd5d 100644
--- a/core/res/res/values-mcc310-mnc410-de/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-el/strings.xml b/core/res/res/values-mcc310-mnc410-el/strings.xml
index 128d1bf..9c9bafa 100644
--- a/core/res/res/values-mcc310-mnc410-el/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc410-en-rAU/strings.xml
index eb56351..5258201 100644
--- a/core/res/res/values-mcc310-mnc410-en-rAU/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc410-en-rCA/strings.xml
index eb56351..5258201 100644
--- a/core/res/res/values-mcc310-mnc410-en-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc410-en-rGB/strings.xml
index eb56351..5258201 100644
--- a/core/res/res/values-mcc310-mnc410-en-rGB/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc410-en-rIN/strings.xml
index eb56351..5258201 100644
--- a/core/res/res/values-mcc310-mnc410-en-rIN/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc410-en-rXC/strings.xml
index 4799e38..32a723f 100644
--- a/core/res/res/values-mcc310-mnc410-en-rXC/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎‎‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc410-es-rUS/strings.xml
index 85b0344..d9748af 100644
--- a/core/res/res/values-mcc310-mnc410-es-rUS/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-es/strings.xml b/core/res/res/values-mcc310-mnc410-es/strings.xml
index 248069e..0459bbb 100644
--- a/core/res/res/values-mcc310-mnc410-es/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-et/strings.xml b/core/res/res/values-mcc310-mnc410-et/strings.xml
index 6ef7802..02f60b3 100644
--- a/core/res/res/values-mcc310-mnc410-et/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-eu/strings.xml b/core/res/res/values-mcc310-mnc410-eu/strings.xml
index 966511b..ef42cb9 100644
--- a/core/res/res/values-mcc310-mnc410-eu/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-fa/strings.xml b/core/res/res/values-mcc310-mnc410-fa/strings.xml
index 82f6272..e9dcc07 100644
--- a/core/res/res/values-mcc310-mnc410-fa/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-fi/strings.xml b/core/res/res/values-mcc310-mnc410-fi/strings.xml
index c5a4ef6..38485c3 100644
--- a/core/res/res/values-mcc310-mnc410-fi/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc410-fr-rCA/strings.xml
index cec2491..fe8c960 100644
--- a/core/res/res/values-mcc310-mnc410-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-fr/strings.xml b/core/res/res/values-mcc310-mnc410-fr/strings.xml
index f715e71..c0fb381 100644
--- a/core/res/res/values-mcc310-mnc410-fr/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-gl/strings.xml b/core/res/res/values-mcc310-mnc410-gl/strings.xml
index c3aba8e..56ce287 100644
--- a/core/res/res/values-mcc310-mnc410-gl/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-gu/strings.xml b/core/res/res/values-mcc310-mnc410-gu/strings.xml
index 26898f4..0637f9e 100644
--- a/core/res/res/values-mcc310-mnc410-gu/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-hi/strings.xml b/core/res/res/values-mcc310-mnc410-hi/strings.xml
index a01845a..3b574c3 100644
--- a/core/res/res/values-mcc310-mnc410-hi/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-hr/strings.xml b/core/res/res/values-mcc310-mnc410-hr/strings.xml
index 47062c4..0ee4ae6 100644
--- a/core/res/res/values-mcc310-mnc410-hr/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-hu/strings.xml b/core/res/res/values-mcc310-mnc410-hu/strings.xml
index 194d865..8abc27d 100644
--- a/core/res/res/values-mcc310-mnc410-hu/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-hy/strings.xml b/core/res/res/values-mcc310-mnc410-hy/strings.xml
index 85129cc..79bc531 100644
--- a/core/res/res/values-mcc310-mnc410-hy/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-in/strings.xml b/core/res/res/values-mcc310-mnc410-in/strings.xml
index c065221..5750563 100644
--- a/core/res/res/values-mcc310-mnc410-in/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-is/strings.xml b/core/res/res/values-mcc310-mnc410-is/strings.xml
index a40a643..a786285 100644
--- a/core/res/res/values-mcc310-mnc410-is/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-it/strings.xml b/core/res/res/values-mcc310-mnc410-it/strings.xml
index ab5c731..135d39d 100644
--- a/core/res/res/values-mcc310-mnc410-it/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-iw/strings.xml b/core/res/res/values-mcc310-mnc410-iw/strings.xml
index b675f64..8e505a9 100644
--- a/core/res/res/values-mcc310-mnc410-iw/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ja/strings.xml b/core/res/res/values-mcc310-mnc410-ja/strings.xml
index 1961a75b..e0e98e3 100644
--- a/core/res/res/values-mcc310-mnc410-ja/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ka/strings.xml b/core/res/res/values-mcc310-mnc410-ka/strings.xml
index 5216e8f..63af51c 100644
--- a/core/res/res/values-mcc310-mnc410-ka/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-kk/strings.xml b/core/res/res/values-mcc310-mnc410-kk/strings.xml
index 137c73a..5a52be2 100644
--- a/core/res/res/values-mcc310-mnc410-kk/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-km/strings.xml b/core/res/res/values-mcc310-mnc410-km/strings.xml
index 94babe1..809ffd1 100644
--- a/core/res/res/values-mcc310-mnc410-km/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"ស៊ីមកាត​មិនត្រូវបានផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាតទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-kn/strings.xml b/core/res/res/values-mcc310-mnc410-kn/strings.xml
index b003dcc..40d05d4 100644
--- a/core/res/res/values-mcc310-mnc410-kn/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ko/strings.xml b/core/res/res/values-mcc310-mnc410-ko/strings.xml
index 7824d1e6..dc1a9a5 100644
--- a/core/res/res/values-mcc310-mnc410-ko/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ky/strings.xml b/core/res/res/values-mcc310-mnc410-ky/strings.xml
index 9e4f23e..05314ed 100644
--- a/core/res/res/values-mcc310-mnc410-ky/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-lo/strings.xml b/core/res/res/values-mcc310-mnc410-lo/strings.xml
index 9684e7a..9e095ba 100644
--- a/core/res/res/values-mcc310-mnc410-lo/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-lt/strings.xml b/core/res/res/values-mcc310-mnc410-lt/strings.xml
index c4a646a..72b9a08 100644
--- a/core/res/res/values-mcc310-mnc410-lt/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-lv/strings.xml b/core/res/res/values-mcc310-mnc410-lv/strings.xml
index e95d62b..e3c04f8 100644
--- a/core/res/res/values-mcc310-mnc410-lv/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-mk/strings.xml b/core/res/res/values-mcc310-mnc410-mk/strings.xml
index 76bba96..d34261d 100644
--- a/core/res/res/values-mcc310-mnc410-mk/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ml/strings.xml b/core/res/res/values-mcc310-mnc410-ml/strings.xml
index 94436bd..474814d 100644
--- a/core/res/res/values-mcc310-mnc410-ml/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-mn/strings.xml b/core/res/res/values-mcc310-mnc410-mn/strings.xml
index 2667aab..70cc206 100644
--- a/core/res/res/values-mcc310-mnc410-mn/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-mr/strings.xml b/core/res/res/values-mcc310-mnc410-mr/strings.xml
index e7b0644..db40711 100644
--- a/core/res/res/values-mcc310-mnc410-mr/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ms/strings.xml b/core/res/res/values-mcc310-mnc410-ms/strings.xml
index 85b8621..b896e67 100644
--- a/core/res/res/values-mcc310-mnc410-ms/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-my/strings.xml b/core/res/res/values-mcc310-mnc410-my/strings.xml
index faa80ec..07a2967 100644
--- a/core/res/res/values-mcc310-mnc410-my/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-nb/strings.xml b/core/res/res/values-mcc310-mnc410-nb/strings.xml
index 79be7af..4f6e125 100644
--- a/core/res/res/values-mcc310-mnc410-nb/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ne/strings.xml b/core/res/res/values-mcc310-mnc410-ne/strings.xml
index e270c7c..65adf0b 100644
--- a/core/res/res/values-mcc310-mnc410-ne/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-nl/strings.xml b/core/res/res/values-mcc310-mnc410-nl/strings.xml
index 2995beb..7da7fab 100644
--- a/core/res/res/values-mcc310-mnc410-nl/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-pa/strings.xml b/core/res/res/values-mcc310-mnc410-pa/strings.xml
index 70195f1..dd44bed 100644
--- a/core/res/res/values-mcc310-mnc410-pa/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-pl/strings.xml b/core/res/res/values-mcc310-mnc410-pl/strings.xml
index 8677ad6..f74650f 100644
--- a/core/res/res/values-mcc310-mnc410-pl/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc410-pt-rBR/strings.xml
index 4f41e5e..f6bfc67 100644
--- a/core/res/res/values-mcc310-mnc410-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc410-pt-rPT/strings.xml
index 4f41e5e..5457416 100644
--- a/core/res/res/values-mcc310-mnc410-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-pt/strings.xml b/core/res/res/values-mcc310-mnc410-pt/strings.xml
index 4f41e5e..f6bfc67 100644
--- a/core/res/res/values-mcc310-mnc410-pt/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ro/strings.xml b/core/res/res/values-mcc310-mnc410-ro/strings.xml
index fea0609..0fc9400 100644
--- a/core/res/res/values-mcc310-mnc410-ro/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ru/strings.xml b/core/res/res/values-mcc310-mnc410-ru/strings.xml
index a00e59c..8702e83 100644
--- a/core/res/res/values-mcc310-mnc410-ru/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-si/strings.xml b/core/res/res/values-mcc310-mnc410-si/strings.xml
index 8f66f3d..cddc168 100644
--- a/core/res/res/values-mcc310-mnc410-si/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-sk/strings.xml b/core/res/res/values-mcc310-mnc410-sk/strings.xml
index e4d4bc6..354b138 100644
--- a/core/res/res/values-mcc310-mnc410-sk/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-sl/strings.xml b/core/res/res/values-mcc310-mnc410-sl/strings.xml
index 2d03b04..d65d619 100644
--- a/core/res/res/values-mcc310-mnc410-sl/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-sq/strings.xml b/core/res/res/values-mcc310-mnc410-sq/strings.xml
index 51626d7..95ec705 100644
--- a/core/res/res/values-mcc310-mnc410-sq/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-sr/strings.xml b/core/res/res/values-mcc310-mnc410-sr/strings.xml
index b4a9006..66fe4e7 100644
--- a/core/res/res/values-mcc310-mnc410-sr/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-sv/strings.xml b/core/res/res/values-mcc310-mnc410-sv/strings.xml
index c270b04..77e0829 100644
--- a/core/res/res/values-mcc310-mnc410-sv/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-sw/strings.xml b/core/res/res/values-mcc310-mnc410-sw/strings.xml
index a6e6018..50c2e74 100644
--- a/core/res/res/values-mcc310-mnc410-sw/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ta/strings.xml b/core/res/res/values-mcc310-mnc410-ta/strings.xml
index 4ac46ce..61f922d 100644
--- a/core/res/res/values-mcc310-mnc410-ta/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-te/strings.xml b/core/res/res/values-mcc310-mnc410-te/strings.xml
index 5b56435..133b332 100644
--- a/core/res/res/values-mcc310-mnc410-te/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-th/strings.xml b/core/res/res/values-mcc310-mnc410-th/strings.xml
index cf51029..0b728bf 100644
--- a/core/res/res/values-mcc310-mnc410-th/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-tl/strings.xml b/core/res/res/values-mcc310-mnc410-tl/strings.xml
index b2aa68a..3bf972d 100644
--- a/core/res/res/values-mcc310-mnc410-tl/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-tr/strings.xml b/core/res/res/values-mcc310-mnc410-tr/strings.xml
index 9e5c38e..34ce123 100644
--- a/core/res/res/values-mcc310-mnc410-tr/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-uk/strings.xml b/core/res/res/values-mcc310-mnc410-uk/strings.xml
index 5d74f80..9d8ccb2 100644
--- a/core/res/res/values-mcc310-mnc410-uk/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-ur/strings.xml b/core/res/res/values-mcc310-mnc410-ur/strings.xml
index 8cf16e4..0a0032c 100644
--- a/core/res/res/values-mcc310-mnc410-ur/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-uz/strings.xml b/core/res/res/values-mcc310-mnc410-uz/strings.xml
index 4618525..fdfad9b 100644
--- a/core/res/res/values-mcc310-mnc410-uz/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-vi/strings.xml b/core/res/res/values-mcc310-mnc410-vi/strings.xml
index e6fc334..77cff43 100644
--- a/core/res/res/values-mcc310-mnc410-vi/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc410-zh-rCN/strings.xml
index a00316a..ec8d371 100644
--- a/core/res/res/values-mcc310-mnc410-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc410-zh-rHK/strings.xml
index 66a622e..7f54efb 100644
--- a/core/res/res/values-mcc310-mnc410-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc410-zh-rTW/strings.xml
index 66a622e..a0aaaa5 100644
--- a/core/res/res/values-mcc310-mnc410-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410-zu/strings.xml b/core/res/res/values-mcc310-mnc410-zu/strings.xml
index a52049f..67cc1ac 100644
--- a/core/res/res/values-mcc310-mnc410-zu/strings.xml
+++ b/core/res/res/values-mcc310-mnc410-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="376893116792604964">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="1593063035884873292">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="4477688981805467729">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc410/strings.xml b/core/res/res/values-mcc310-mnc410/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc310-mnc410/strings.xml
+++ b/core/res/res/values-mcc310-mnc410/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-af/strings.xml b/core/res/res/values-mcc310-mnc560-af/strings.xml
index ecbb954..87f698c 100644
--- a/core/res/res/values-mcc310-mnc560-af/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-am/strings.xml b/core/res/res/values-mcc310-mnc560-am/strings.xml
index 297c059..c1f8350 100644
--- a/core/res/res/values-mcc310-mnc560-am/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ar/strings.xml b/core/res/res/values-mcc310-mnc560-ar/strings.xml
index 12a06fa..d5c684f 100644
--- a/core/res/res/values-mcc310-mnc560-ar/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-az/strings.xml b/core/res/res/values-mcc310-mnc560-az/strings.xml
index 9acc2c1..1edff5e 100644
--- a/core/res/res/values-mcc310-mnc560-az/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml
index 616f871..d362bad 100644
--- a/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-be/strings.xml b/core/res/res/values-mcc310-mnc560-be/strings.xml
index 0e70cf2..3f62d3c 100644
--- a/core/res/res/values-mcc310-mnc560-be/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-bg/strings.xml b/core/res/res/values-mcc310-mnc560-bg/strings.xml
index 49c2d2f..98c362d 100644
--- a/core/res/res/values-mcc310-mnc560-bg/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-bn/strings.xml b/core/res/res/values-mcc310-mnc560-bn/strings.xml
index 438791d..989da8d 100644
--- a/core/res/res/values-mcc310-mnc560-bn/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-bs/strings.xml b/core/res/res/values-mcc310-mnc560-bs/strings.xml
index 82046a6..3dccc4be 100644
--- a/core/res/res/values-mcc310-mnc560-bs/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ca/strings.xml b/core/res/res/values-mcc310-mnc560-ca/strings.xml
index 1eac589..00fc4ed 100644
--- a/core/res/res/values-mcc310-mnc560-ca/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-cs/strings.xml b/core/res/res/values-mcc310-mnc560-cs/strings.xml
index 4701f99..9a68265 100644
--- a/core/res/res/values-mcc310-mnc560-cs/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-da/strings.xml b/core/res/res/values-mcc310-mnc560-da/strings.xml
index 2c63d87..857e040 100644
--- a/core/res/res/values-mcc310-mnc560-da/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-de/strings.xml b/core/res/res/values-mcc310-mnc560-de/strings.xml
index e8fec2c..a5d594b 100644
--- a/core/res/res/values-mcc310-mnc560-de/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-el/strings.xml b/core/res/res/values-mcc310-mnc560-el/strings.xml
index efd81f7..803ecdd 100644
--- a/core/res/res/values-mcc310-mnc560-el/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc560-en-rAU/strings.xml
index c218c64..d24a056 100644
--- a/core/res/res/values-mcc310-mnc560-en-rAU/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc560-en-rCA/strings.xml
index c218c64..d24a056 100644
--- a/core/res/res/values-mcc310-mnc560-en-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc560-en-rGB/strings.xml
index c218c64..d24a056 100644
--- a/core/res/res/values-mcc310-mnc560-en-rGB/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc560-en-rIN/strings.xml
index c218c64..d24a056 100644
--- a/core/res/res/values-mcc310-mnc560-en-rIN/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc560-en-rXC/strings.xml
index 67c05b2..694aa31 100644
--- a/core/res/res/values-mcc310-mnc560-en-rXC/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‏‏‎‎‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc560-es-rUS/strings.xml
index 62d61ba..52b2554 100644
--- a/core/res/res/values-mcc310-mnc560-es-rUS/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-es/strings.xml b/core/res/res/values-mcc310-mnc560-es/strings.xml
index dbd71d5..8fb818b 100644
--- a/core/res/res/values-mcc310-mnc560-es/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-et/strings.xml b/core/res/res/values-mcc310-mnc560-et/strings.xml
index b269ace..1bb5bd5 100644
--- a/core/res/res/values-mcc310-mnc560-et/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-eu/strings.xml b/core/res/res/values-mcc310-mnc560-eu/strings.xml
index cee0106..1fd51a9 100644
--- a/core/res/res/values-mcc310-mnc560-eu/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-fa/strings.xml b/core/res/res/values-mcc310-mnc560-fa/strings.xml
index b4683c7..d2c8c41 100644
--- a/core/res/res/values-mcc310-mnc560-fa/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-fi/strings.xml b/core/res/res/values-mcc310-mnc560-fi/strings.xml
index 54c80ed..fc7629d 100644
--- a/core/res/res/values-mcc310-mnc560-fi/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc560-fr-rCA/strings.xml
index 8b6c4ec..246b434 100644
--- a/core/res/res/values-mcc310-mnc560-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-fr/strings.xml b/core/res/res/values-mcc310-mnc560-fr/strings.xml
index b52eaf7..9601bc5 100644
--- a/core/res/res/values-mcc310-mnc560-fr/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-gl/strings.xml b/core/res/res/values-mcc310-mnc560-gl/strings.xml
index a8c04d2..1f78daa 100644
--- a/core/res/res/values-mcc310-mnc560-gl/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-gu/strings.xml b/core/res/res/values-mcc310-mnc560-gu/strings.xml
index c3892da..f14a72b 100644
--- a/core/res/res/values-mcc310-mnc560-gu/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-hi/strings.xml b/core/res/res/values-mcc310-mnc560-hi/strings.xml
index 4e07e4f..7c52266 100644
--- a/core/res/res/values-mcc310-mnc560-hi/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-hr/strings.xml b/core/res/res/values-mcc310-mnc560-hr/strings.xml
index dc6653e..f51599c 100644
--- a/core/res/res/values-mcc310-mnc560-hr/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-hu/strings.xml b/core/res/res/values-mcc310-mnc560-hu/strings.xml
index 1a7a6b3..f23dd04 100644
--- a/core/res/res/values-mcc310-mnc560-hu/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-hy/strings.xml b/core/res/res/values-mcc310-mnc560-hy/strings.xml
index c398877..072c0c5 100644
--- a/core/res/res/values-mcc310-mnc560-hy/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-in/strings.xml b/core/res/res/values-mcc310-mnc560-in/strings.xml
index c3c7df3..9b56660 100644
--- a/core/res/res/values-mcc310-mnc560-in/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-is/strings.xml b/core/res/res/values-mcc310-mnc560-is/strings.xml
index 4a59abd..3ae1361 100644
--- a/core/res/res/values-mcc310-mnc560-is/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-it/strings.xml b/core/res/res/values-mcc310-mnc560-it/strings.xml
index d2c966c..201e5b3 100644
--- a/core/res/res/values-mcc310-mnc560-it/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-iw/strings.xml b/core/res/res/values-mcc310-mnc560-iw/strings.xml
index 68f0aa9..168f5ea 100644
--- a/core/res/res/values-mcc310-mnc560-iw/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ja/strings.xml b/core/res/res/values-mcc310-mnc560-ja/strings.xml
index 0429996..244c93a 100644
--- a/core/res/res/values-mcc310-mnc560-ja/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ka/strings.xml b/core/res/res/values-mcc310-mnc560-ka/strings.xml
index b0371f0..c89674b 100644
--- a/core/res/res/values-mcc310-mnc560-ka/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-kk/strings.xml b/core/res/res/values-mcc310-mnc560-kk/strings.xml
index 2015f8c..33aa902 100644
--- a/core/res/res/values-mcc310-mnc560-kk/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-km/strings.xml b/core/res/res/values-mcc310-mnc560-km/strings.xml
index a13ca7d..b01e283 100644
--- a/core/res/res/values-mcc310-mnc560-km/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"ស៊ីមកាត​មិនត្រូវបានផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាតទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-kn/strings.xml b/core/res/res/values-mcc310-mnc560-kn/strings.xml
index da75904..0879d4d 100644
--- a/core/res/res/values-mcc310-mnc560-kn/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ko/strings.xml b/core/res/res/values-mcc310-mnc560-ko/strings.xml
index ee71a09c..74d6064 100644
--- a/core/res/res/values-mcc310-mnc560-ko/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ky/strings.xml b/core/res/res/values-mcc310-mnc560-ky/strings.xml
index 856bebc..75e4794 100644
--- a/core/res/res/values-mcc310-mnc560-ky/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-lo/strings.xml b/core/res/res/values-mcc310-mnc560-lo/strings.xml
index 702470e..9244031 100644
--- a/core/res/res/values-mcc310-mnc560-lo/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-lt/strings.xml b/core/res/res/values-mcc310-mnc560-lt/strings.xml
index c3c5a9e..8ced4c0 100644
--- a/core/res/res/values-mcc310-mnc560-lt/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-lv/strings.xml b/core/res/res/values-mcc310-mnc560-lv/strings.xml
index dcf7805..fc565d6 100644
--- a/core/res/res/values-mcc310-mnc560-lv/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-mk/strings.xml b/core/res/res/values-mcc310-mnc560-mk/strings.xml
index cf0ae7e..6d159f4 100644
--- a/core/res/res/values-mcc310-mnc560-mk/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ml/strings.xml b/core/res/res/values-mcc310-mnc560-ml/strings.xml
index fb506bf..859e887 100644
--- a/core/res/res/values-mcc310-mnc560-ml/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-mn/strings.xml b/core/res/res/values-mcc310-mnc560-mn/strings.xml
index 0bf1599..1d01e8c 100644
--- a/core/res/res/values-mcc310-mnc560-mn/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-mr/strings.xml b/core/res/res/values-mcc310-mnc560-mr/strings.xml
index 69e81ad..8a737e4 100644
--- a/core/res/res/values-mcc310-mnc560-mr/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ms/strings.xml b/core/res/res/values-mcc310-mnc560-ms/strings.xml
index cd0aed7..3c5c712 100644
--- a/core/res/res/values-mcc310-mnc560-ms/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-my/strings.xml b/core/res/res/values-mcc310-mnc560-my/strings.xml
index 58fba87..00600be 100644
--- a/core/res/res/values-mcc310-mnc560-my/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-nb/strings.xml b/core/res/res/values-mcc310-mnc560-nb/strings.xml
index bc90ae1..ce9dd874 100644
--- a/core/res/res/values-mcc310-mnc560-nb/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ne/strings.xml b/core/res/res/values-mcc310-mnc560-ne/strings.xml
index 75c493d..923db94 100644
--- a/core/res/res/values-mcc310-mnc560-ne/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-nl/strings.xml b/core/res/res/values-mcc310-mnc560-nl/strings.xml
index 7241627..eadbb84 100644
--- a/core/res/res/values-mcc310-mnc560-nl/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-pa/strings.xml b/core/res/res/values-mcc310-mnc560-pa/strings.xml
index a2b76be..3c48708 100644
--- a/core/res/res/values-mcc310-mnc560-pa/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-pl/strings.xml b/core/res/res/values-mcc310-mnc560-pl/strings.xml
index f844db6..0f4b474 100644
--- a/core/res/res/values-mcc310-mnc560-pl/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc560-pt-rBR/strings.xml
index 4c10ef9..fba400c 100644
--- a/core/res/res/values-mcc310-mnc560-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc560-pt-rPT/strings.xml
index 4c10ef9..991627c 100644
--- a/core/res/res/values-mcc310-mnc560-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-pt/strings.xml b/core/res/res/values-mcc310-mnc560-pt/strings.xml
index 4c10ef9..fba400c 100644
--- a/core/res/res/values-mcc310-mnc560-pt/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ro/strings.xml b/core/res/res/values-mcc310-mnc560-ro/strings.xml
index e24b74c..1e6c63a 100644
--- a/core/res/res/values-mcc310-mnc560-ro/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ru/strings.xml b/core/res/res/values-mcc310-mnc560-ru/strings.xml
index 35bf36f..948c8c8 100644
--- a/core/res/res/values-mcc310-mnc560-ru/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-si/strings.xml b/core/res/res/values-mcc310-mnc560-si/strings.xml
index 4c60ce4..b65d067 100644
--- a/core/res/res/values-mcc310-mnc560-si/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-sk/strings.xml b/core/res/res/values-mcc310-mnc560-sk/strings.xml
index ad4aba4..cae65ff 100644
--- a/core/res/res/values-mcc310-mnc560-sk/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-sl/strings.xml b/core/res/res/values-mcc310-mnc560-sl/strings.xml
index 7855987..15c495e 100644
--- a/core/res/res/values-mcc310-mnc560-sl/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-sq/strings.xml b/core/res/res/values-mcc310-mnc560-sq/strings.xml
index 527430e..a554e81 100644
--- a/core/res/res/values-mcc310-mnc560-sq/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-sr/strings.xml b/core/res/res/values-mcc310-mnc560-sr/strings.xml
index ec5a2b6..3a2d6c0 100644
--- a/core/res/res/values-mcc310-mnc560-sr/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-sv/strings.xml b/core/res/res/values-mcc310-mnc560-sv/strings.xml
index d113989..ef712b4 100644
--- a/core/res/res/values-mcc310-mnc560-sv/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-sw/strings.xml b/core/res/res/values-mcc310-mnc560-sw/strings.xml
index 4e3df8b..2f989a7 100644
--- a/core/res/res/values-mcc310-mnc560-sw/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ta/strings.xml b/core/res/res/values-mcc310-mnc560-ta/strings.xml
index 78f8243..dc98cca 100644
--- a/core/res/res/values-mcc310-mnc560-ta/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-te/strings.xml b/core/res/res/values-mcc310-mnc560-te/strings.xml
index aeda941..f5e09ba 100644
--- a/core/res/res/values-mcc310-mnc560-te/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-th/strings.xml b/core/res/res/values-mcc310-mnc560-th/strings.xml
index 7896973..881f797 100644
--- a/core/res/res/values-mcc310-mnc560-th/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-tl/strings.xml b/core/res/res/values-mcc310-mnc560-tl/strings.xml
index ac87cb5..2e32d53 100644
--- a/core/res/res/values-mcc310-mnc560-tl/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-tr/strings.xml b/core/res/res/values-mcc310-mnc560-tr/strings.xml
index 5ea1d80..2b41e2c 100644
--- a/core/res/res/values-mcc310-mnc560-tr/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-uk/strings.xml b/core/res/res/values-mcc310-mnc560-uk/strings.xml
index 7ee6b88..bd75240 100644
--- a/core/res/res/values-mcc310-mnc560-uk/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-ur/strings.xml b/core/res/res/values-mcc310-mnc560-ur/strings.xml
index 609d3e8..c8c20d6 100644
--- a/core/res/res/values-mcc310-mnc560-ur/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-uz/strings.xml b/core/res/res/values-mcc310-mnc560-uz/strings.xml
index 4157041..92b86e1 100644
--- a/core/res/res/values-mcc310-mnc560-uz/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-vi/strings.xml b/core/res/res/values-mcc310-mnc560-vi/strings.xml
index b2284e1..7023d51 100644
--- a/core/res/res/values-mcc310-mnc560-vi/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc560-zh-rCN/strings.xml
index 050ea01..bf168bd 100644
--- a/core/res/res/values-mcc310-mnc560-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc560-zh-rHK/strings.xml
index 43cfc01..ea8dcab 100644
--- a/core/res/res/values-mcc310-mnc560-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc560-zh-rTW/strings.xml
index 43cfc01..3674ee2 100644
--- a/core/res/res/values-mcc310-mnc560-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560-zu/strings.xml b/core/res/res/values-mcc310-mnc560-zu/strings.xml
index e1b7abb..7b17fed 100644
--- a/core/res/res/values-mcc310-mnc560-zu/strings.xml
+++ b/core/res/res/values-mcc310-mnc560-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="2976453378311251765">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2519618694918727742">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="7030488670186895244">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc560/strings.xml b/core/res/res/values-mcc310-mnc560/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc310-mnc560/strings.xml
+++ b/core/res/res/values-mcc310-mnc560/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-af/strings.xml b/core/res/res/values-mcc310-mnc950-af/strings.xml
index 19ae78d..95b73b9 100644
--- a/core/res/res/values-mcc310-mnc950-af/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-am/strings.xml b/core/res/res/values-mcc310-mnc950-am/strings.xml
index e81745d..ca5d603 100644
--- a/core/res/res/values-mcc310-mnc950-am/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ar/strings.xml b/core/res/res/values-mcc310-mnc950-ar/strings.xml
index 1aab01c..bc2248b 100644
--- a/core/res/res/values-mcc310-mnc950-ar/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-az/strings.xml b/core/res/res/values-mcc310-mnc950-az/strings.xml
index 26d91ef..ceb15ea 100644
--- a/core/res/res/values-mcc310-mnc950-az/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-b+sr+Latn/strings.xml b/core/res/res/values-mcc310-mnc950-b+sr+Latn/strings.xml
index 44c5d19..def86da 100644
--- a/core/res/res/values-mcc310-mnc950-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-be/strings.xml b/core/res/res/values-mcc310-mnc950-be/strings.xml
index 2bae8c4..4dd80ff 100644
--- a/core/res/res/values-mcc310-mnc950-be/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-bg/strings.xml b/core/res/res/values-mcc310-mnc950-bg/strings.xml
index 761b439..5342fa8 100644
--- a/core/res/res/values-mcc310-mnc950-bg/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-bn/strings.xml b/core/res/res/values-mcc310-mnc950-bn/strings.xml
index 32ba8b8..d12c75a 100644
--- a/core/res/res/values-mcc310-mnc950-bn/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-bs/strings.xml b/core/res/res/values-mcc310-mnc950-bs/strings.xml
index b06b586..4fbaa3c 100644
--- a/core/res/res/values-mcc310-mnc950-bs/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ca/strings.xml b/core/res/res/values-mcc310-mnc950-ca/strings.xml
index 9b77ce7..adb12e8 100644
--- a/core/res/res/values-mcc310-mnc950-ca/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-cs/strings.xml b/core/res/res/values-mcc310-mnc950-cs/strings.xml
index 3f8b8aa..49cc25f 100644
--- a/core/res/res/values-mcc310-mnc950-cs/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-da/strings.xml b/core/res/res/values-mcc310-mnc950-da/strings.xml
index 6cd8306..e63a586 100644
--- a/core/res/res/values-mcc310-mnc950-da/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-de/strings.xml b/core/res/res/values-mcc310-mnc950-de/strings.xml
index 70e3068..60d81e6 100644
--- a/core/res/res/values-mcc310-mnc950-de/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-el/strings.xml b/core/res/res/values-mcc310-mnc950-el/strings.xml
index 1a2542c..bffaa00 100644
--- a/core/res/res/values-mcc310-mnc950-el/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-en-rAU/strings.xml b/core/res/res/values-mcc310-mnc950-en-rAU/strings.xml
index 7ef6605..d2d137c 100644
--- a/core/res/res/values-mcc310-mnc950-en-rAU/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-en-rCA/strings.xml b/core/res/res/values-mcc310-mnc950-en-rCA/strings.xml
index 7ef6605..d2d137c 100644
--- a/core/res/res/values-mcc310-mnc950-en-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-en-rGB/strings.xml b/core/res/res/values-mcc310-mnc950-en-rGB/strings.xml
index 7ef6605..d2d137c 100644
--- a/core/res/res/values-mcc310-mnc950-en-rGB/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-en-rIN/strings.xml b/core/res/res/values-mcc310-mnc950-en-rIN/strings.xml
index 7ef6605..d2d137c 100644
--- a/core/res/res/values-mcc310-mnc950-en-rIN/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-en-rXC/strings.xml b/core/res/res/values-mcc310-mnc950-en-rXC/strings.xml
index 243d731..89619c4 100644
--- a/core/res/res/values-mcc310-mnc950-en-rXC/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-es-rUS/strings.xml b/core/res/res/values-mcc310-mnc950-es-rUS/strings.xml
index 31a863c..d194180 100644
--- a/core/res/res/values-mcc310-mnc950-es-rUS/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-es/strings.xml b/core/res/res/values-mcc310-mnc950-es/strings.xml
index ba8c78b..bbb0acf 100644
--- a/core/res/res/values-mcc310-mnc950-es/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-et/strings.xml b/core/res/res/values-mcc310-mnc950-et/strings.xml
index 013243a..14b4d45 100644
--- a/core/res/res/values-mcc310-mnc950-et/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-eu/strings.xml b/core/res/res/values-mcc310-mnc950-eu/strings.xml
index 097e423..2e33d02 100644
--- a/core/res/res/values-mcc310-mnc950-eu/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-fa/strings.xml b/core/res/res/values-mcc310-mnc950-fa/strings.xml
index 3281bb5..f889d5e 100644
--- a/core/res/res/values-mcc310-mnc950-fa/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-fi/strings.xml b/core/res/res/values-mcc310-mnc950-fi/strings.xml
index 01e04f7..846ee78 100644
--- a/core/res/res/values-mcc310-mnc950-fi/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-fr-rCA/strings.xml b/core/res/res/values-mcc310-mnc950-fr-rCA/strings.xml
index a310f82..08a3e55 100644
--- a/core/res/res/values-mcc310-mnc950-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-fr/strings.xml b/core/res/res/values-mcc310-mnc950-fr/strings.xml
index 7b0fb2c..a0f6016 100644
--- a/core/res/res/values-mcc310-mnc950-fr/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-gl/strings.xml b/core/res/res/values-mcc310-mnc950-gl/strings.xml
index 55ff461..e771683 100644
--- a/core/res/res/values-mcc310-mnc950-gl/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-gu/strings.xml b/core/res/res/values-mcc310-mnc950-gu/strings.xml
index a6e1a83..9f4596b 100644
--- a/core/res/res/values-mcc310-mnc950-gu/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-hi/strings.xml b/core/res/res/values-mcc310-mnc950-hi/strings.xml
index 41b9bc9..e71aa25 100644
--- a/core/res/res/values-mcc310-mnc950-hi/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-hr/strings.xml b/core/res/res/values-mcc310-mnc950-hr/strings.xml
index 1c39a09..c7f6b22 100644
--- a/core/res/res/values-mcc310-mnc950-hr/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-hu/strings.xml b/core/res/res/values-mcc310-mnc950-hu/strings.xml
index 6b8ee76..22b5e9b 100644
--- a/core/res/res/values-mcc310-mnc950-hu/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-hy/strings.xml b/core/res/res/values-mcc310-mnc950-hy/strings.xml
index 63dfa46..5136640 100644
--- a/core/res/res/values-mcc310-mnc950-hy/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-in/strings.xml b/core/res/res/values-mcc310-mnc950-in/strings.xml
index a7c0232..bb9e380 100644
--- a/core/res/res/values-mcc310-mnc950-in/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-is/strings.xml b/core/res/res/values-mcc310-mnc950-is/strings.xml
index ceee15a..d8b847f 100644
--- a/core/res/res/values-mcc310-mnc950-is/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-it/strings.xml b/core/res/res/values-mcc310-mnc950-it/strings.xml
index 21e2a4a..bf6d8bb 100644
--- a/core/res/res/values-mcc310-mnc950-it/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-iw/strings.xml b/core/res/res/values-mcc310-mnc950-iw/strings.xml
index 041408f..fd0ac8f 100644
--- a/core/res/res/values-mcc310-mnc950-iw/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ja/strings.xml b/core/res/res/values-mcc310-mnc950-ja/strings.xml
index 97f697e..4224a8a 100644
--- a/core/res/res/values-mcc310-mnc950-ja/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ka/strings.xml b/core/res/res/values-mcc310-mnc950-ka/strings.xml
index 4d8c8a8..0cbd72c 100644
--- a/core/res/res/values-mcc310-mnc950-ka/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-kk/strings.xml b/core/res/res/values-mcc310-mnc950-kk/strings.xml
index 899a9a2..271083a 100644
--- a/core/res/res/values-mcc310-mnc950-kk/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-km/strings.xml b/core/res/res/values-mcc310-mnc950-km/strings.xml
index 3c80d9f..d5a98d5 100644
--- a/core/res/res/values-mcc310-mnc950-km/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"ស៊ីមកាត​មិនត្រូវបានផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាតទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-kn/strings.xml b/core/res/res/values-mcc310-mnc950-kn/strings.xml
index 1c7ab20..f15aec8 100644
--- a/core/res/res/values-mcc310-mnc950-kn/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ko/strings.xml b/core/res/res/values-mcc310-mnc950-ko/strings.xml
index 2419c2a..96c9b62 100644
--- a/core/res/res/values-mcc310-mnc950-ko/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ky/strings.xml b/core/res/res/values-mcc310-mnc950-ky/strings.xml
index 2583338..f8c7313 100644
--- a/core/res/res/values-mcc310-mnc950-ky/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-lo/strings.xml b/core/res/res/values-mcc310-mnc950-lo/strings.xml
index b69536f..38f82b4 100644
--- a/core/res/res/values-mcc310-mnc950-lo/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-lt/strings.xml b/core/res/res/values-mcc310-mnc950-lt/strings.xml
index 8ef0869..4c51da7 100644
--- a/core/res/res/values-mcc310-mnc950-lt/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-lv/strings.xml b/core/res/res/values-mcc310-mnc950-lv/strings.xml
index 2a491c0..0ed0ae7 100644
--- a/core/res/res/values-mcc310-mnc950-lv/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-mk/strings.xml b/core/res/res/values-mcc310-mnc950-mk/strings.xml
index a1da44b..375b09a 100644
--- a/core/res/res/values-mcc310-mnc950-mk/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ml/strings.xml b/core/res/res/values-mcc310-mnc950-ml/strings.xml
index 0079c08..b1defa6 100644
--- a/core/res/res/values-mcc310-mnc950-ml/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-mn/strings.xml b/core/res/res/values-mcc310-mnc950-mn/strings.xml
index 2d4b5f5..e8ef11c 100644
--- a/core/res/res/values-mcc310-mnc950-mn/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-mr/strings.xml b/core/res/res/values-mcc310-mnc950-mr/strings.xml
index eca7dca..088d800 100644
--- a/core/res/res/values-mcc310-mnc950-mr/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ms/strings.xml b/core/res/res/values-mcc310-mnc950-ms/strings.xml
index 4f5c781..bb4600d 100644
--- a/core/res/res/values-mcc310-mnc950-ms/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-my/strings.xml b/core/res/res/values-mcc310-mnc950-my/strings.xml
index ecf9f61..ed581ef 100644
--- a/core/res/res/values-mcc310-mnc950-my/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-nb/strings.xml b/core/res/res/values-mcc310-mnc950-nb/strings.xml
index 907482e..5134e7d 100644
--- a/core/res/res/values-mcc310-mnc950-nb/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ne/strings.xml b/core/res/res/values-mcc310-mnc950-ne/strings.xml
index 380f01c..0b3c815 100644
--- a/core/res/res/values-mcc310-mnc950-ne/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-nl/strings.xml b/core/res/res/values-mcc310-mnc950-nl/strings.xml
index a01896c..d1e39df 100644
--- a/core/res/res/values-mcc310-mnc950-nl/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-pa/strings.xml b/core/res/res/values-mcc310-mnc950-pa/strings.xml
index a67b0fb..05ef594 100644
--- a/core/res/res/values-mcc310-mnc950-pa/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-pl/strings.xml b/core/res/res/values-mcc310-mnc950-pl/strings.xml
index ce40c22..ab6e8fb 100644
--- a/core/res/res/values-mcc310-mnc950-pl/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-pt-rBR/strings.xml b/core/res/res/values-mcc310-mnc950-pt-rBR/strings.xml
index 1f89e47..83c84ce 100644
--- a/core/res/res/values-mcc310-mnc950-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-pt-rPT/strings.xml b/core/res/res/values-mcc310-mnc950-pt-rPT/strings.xml
index 1f89e47..7b657fd 100644
--- a/core/res/res/values-mcc310-mnc950-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-pt/strings.xml b/core/res/res/values-mcc310-mnc950-pt/strings.xml
index 1f89e47..83c84ce 100644
--- a/core/res/res/values-mcc310-mnc950-pt/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ro/strings.xml b/core/res/res/values-mcc310-mnc950-ro/strings.xml
index 7fb3f2e..e51800b 100644
--- a/core/res/res/values-mcc310-mnc950-ro/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ru/strings.xml b/core/res/res/values-mcc310-mnc950-ru/strings.xml
index 16deeed..1dbdd7b 100644
--- a/core/res/res/values-mcc310-mnc950-ru/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-si/strings.xml b/core/res/res/values-mcc310-mnc950-si/strings.xml
index dd512d9..bb0f22f 100644
--- a/core/res/res/values-mcc310-mnc950-si/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-sk/strings.xml b/core/res/res/values-mcc310-mnc950-sk/strings.xml
index 6a8e6f0..7bba3ae 100644
--- a/core/res/res/values-mcc310-mnc950-sk/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-sl/strings.xml b/core/res/res/values-mcc310-mnc950-sl/strings.xml
index 391ee78..cd08650 100644
--- a/core/res/res/values-mcc310-mnc950-sl/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-sq/strings.xml b/core/res/res/values-mcc310-mnc950-sq/strings.xml
index ebc978c..8230755 100644
--- a/core/res/res/values-mcc310-mnc950-sq/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-sr/strings.xml b/core/res/res/values-mcc310-mnc950-sr/strings.xml
index 4f3f86f..cac39e2 100644
--- a/core/res/res/values-mcc310-mnc950-sr/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-sv/strings.xml b/core/res/res/values-mcc310-mnc950-sv/strings.xml
index 4739f06..7ac957d 100644
--- a/core/res/res/values-mcc310-mnc950-sv/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-sw/strings.xml b/core/res/res/values-mcc310-mnc950-sw/strings.xml
index 903b4a5..8967e18 100644
--- a/core/res/res/values-mcc310-mnc950-sw/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ta/strings.xml b/core/res/res/values-mcc310-mnc950-ta/strings.xml
index 8838aed..35a7202 100644
--- a/core/res/res/values-mcc310-mnc950-ta/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-te/strings.xml b/core/res/res/values-mcc310-mnc950-te/strings.xml
index dc9625b..4611a72 100644
--- a/core/res/res/values-mcc310-mnc950-te/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-th/strings.xml b/core/res/res/values-mcc310-mnc950-th/strings.xml
index 3feb1f6..d8c61dd 100644
--- a/core/res/res/values-mcc310-mnc950-th/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-tl/strings.xml b/core/res/res/values-mcc310-mnc950-tl/strings.xml
index 55e0bfd..66f686c 100644
--- a/core/res/res/values-mcc310-mnc950-tl/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-tr/strings.xml b/core/res/res/values-mcc310-mnc950-tr/strings.xml
index 7a274a4..54e8e7d 100644
--- a/core/res/res/values-mcc310-mnc950-tr/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-uk/strings.xml b/core/res/res/values-mcc310-mnc950-uk/strings.xml
index bf5e6411..be0cd2b 100644
--- a/core/res/res/values-mcc310-mnc950-uk/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-ur/strings.xml b/core/res/res/values-mcc310-mnc950-ur/strings.xml
index 35857cd..7b925c4 100644
--- a/core/res/res/values-mcc310-mnc950-ur/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-uz/strings.xml b/core/res/res/values-mcc310-mnc950-uz/strings.xml
index e31c7b6..99ac738 100644
--- a/core/res/res/values-mcc310-mnc950-uz/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-vi/strings.xml b/core/res/res/values-mcc310-mnc950-vi/strings.xml
index b9360b5..829a4dc 100644
--- a/core/res/res/values-mcc310-mnc950-vi/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-zh-rCN/strings.xml b/core/res/res/values-mcc310-mnc950-zh-rCN/strings.xml
index 3b4ba0a..240dd0b 100644
--- a/core/res/res/values-mcc310-mnc950-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-zh-rHK/strings.xml b/core/res/res/values-mcc310-mnc950-zh-rHK/strings.xml
index 763e498..97a13bc 100644
--- a/core/res/res/values-mcc310-mnc950-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-zh-rTW/strings.xml b/core/res/res/values-mcc310-mnc950-zh-rTW/strings.xml
index 763e498..934aea0 100644
--- a/core/res/res/values-mcc310-mnc950-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950-zu/strings.xml b/core/res/res/values-mcc310-mnc950-zu/strings.xml
index 1c29754..9dc4403 100644
--- a/core/res/res/values-mcc310-mnc950-zu/strings.xml
+++ b/core/res/res/values-mcc310-mnc950-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="94675382531896663">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="2418195136279399212">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="8920048244573695129">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc310-mnc950/strings.xml b/core/res/res/values-mcc310-mnc950/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc310-mnc950/strings.xml
+++ b/core/res/res/values-mcc310-mnc950/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-af/strings.xml b/core/res/res/values-mcc311-mnc180-af/strings.xml
index ead8992..73c9a6c 100644
--- a/core/res/res/values-mcc311-mnc180-af/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-af/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM is nie opgestel nie MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM word nie toegelaat nie MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Foon nie toegelaat nie MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-am/strings.xml b/core/res/res/values-mcc311-mnc180-am/strings.xml
index a7d0d1d..935312e 100644
--- a/core/res/res/values-mcc311-mnc180-am/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-am/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"ሲም አልቀረበም MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"ሲም አይፈቀድም MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ስልክ አይፈቀድም MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ar/strings.xml b/core/res/res/values-mcc311-mnc180-ar/strings.xml
index f5412bd..cdb14c5 100644
--- a/core/res/res/values-mcc311-mnc180-ar/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ar/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"‏لم يتم توفير SIM ‏MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"‏غير مسموح باستخدام SIM ‏MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"‏غير مسموح باستخدام الهاتف MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-az/strings.xml b/core/res/res/values-mcc311-mnc180-az/strings.xml
index c2c06be..7471fca 100644
--- a/core/res/res/values-mcc311-mnc180-az/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-az/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM MM#2 təmin etmir"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM MM#3 dəstəkləmir"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"MM#6 telefonu dəstəklənmir"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml b/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml
index e8b35c3..2d0af9e 100644
--- a/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-b+sr+Latn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM kartica nije podešena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-be/strings.xml b/core/res/res/values-mcc311-mnc180-be/strings.xml
index f2c1069..0b2f7cf 100644
--- a/core/res/res/values-mcc311-mnc180-be/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-be/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-карты няма MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-карта не дапускаецца MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Тэлефон не дапускаецца MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-bg/strings.xml b/core/res/res/values-mcc311-mnc180-bg/strings.xml
index faf5a42..542557e 100644
--- a/core/res/res/values-mcc311-mnc180-bg/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-bg/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM картата не е обезпечена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM картата не е разрешена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Телефонът не е разрешен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-bn/strings.xml b/core/res/res/values-mcc311-mnc180-bn/strings.xml
index 80efd0f..59d1624 100644
--- a/core/res/res/values-mcc311-mnc180-bn/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-bn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"সিমের জন্য প্রস্তুত নয় MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"সিমের অনুমতি নেই MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ফোন অনুমোদিত নয় MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-bs/strings.xml b/core/res/res/values-mcc311-mnc180-bs/strings.xml
index 7c3e5b3..409df22 100644
--- a/core/res/res/values-mcc311-mnc180-bs/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-bs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM kartica nije dodijeljena MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM kartica nije dozvoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon nije dozvoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ca/strings.xml b/core/res/res/values-mcc311-mnc180-ca/strings.xml
index e33a901..505f396 100644
--- a/core/res/res/values-mcc311-mnc180-ca/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ca/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"La SIM no està proporcionada a MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"La SIM no és compatible a MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telèfon no compatible MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-cs/strings.xml b/core/res/res/values-mcc311-mnc180-cs/strings.xml
index 653a55a..29c8fdf 100644
--- a/core/res/res/values-mcc311-mnc180-cs/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-cs/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM karta není poskytována (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM karta není povolena (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon není povolen (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-da/strings.xml b/core/res/res/values-mcc311-mnc180-da/strings.xml
index 04c89a5..6fadac1 100644
--- a/core/res/res/values-mcc311-mnc180-da/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-da/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-kort leveres ikke MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-kort er ikke tilladt MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefonen har ikke adgangstilladelse MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-de/strings.xml b/core/res/res/values-mcc311-mnc180-de/strings.xml
index 8fef081..714eac9 100644
--- a/core/res/res/values-mcc311-mnc180-de/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-de/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-Karte nicht eingerichtet MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-Karte nicht zulässig MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Smartphone nicht zulässig MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-el/strings.xml b/core/res/res/values-mcc311-mnc180-el/strings.xml
index 795f48f..2e29b12 100644
--- a/core/res/res/values-mcc311-mnc180-el/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-el/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Δεν παρέχεται κάρτα SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Η κάρτα SIM δεν επιτρέπεται MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-en-rAU/strings.xml b/core/res/res/values-mcc311-mnc180-en-rAU/strings.xml
index 9c972bc..b284371 100644
--- a/core/res/res/values-mcc311-mnc180-en-rAU/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-en-rAU/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-en-rCA/strings.xml b/core/res/res/values-mcc311-mnc180-en-rCA/strings.xml
index 9c972bc..b284371 100644
--- a/core/res/res/values-mcc311-mnc180-en-rCA/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-en-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-en-rGB/strings.xml b/core/res/res/values-mcc311-mnc180-en-rGB/strings.xml
index 9c972bc..b284371 100644
--- a/core/res/res/values-mcc311-mnc180-en-rGB/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-en-rGB/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-en-rIN/strings.xml b/core/res/res/values-mcc311-mnc180-en-rIN/strings.xml
index 9c972bc..b284371 100644
--- a/core/res/res/values-mcc311-mnc180-en-rIN/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-en-rIN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM not provisioned MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM not allowed MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Phone not allowed MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-en-rXC/strings.xml b/core/res/res/values-mcc311-mnc180-en-rXC/strings.xml
index f1a6cc7..53832df 100644
--- a/core/res/res/values-mcc311-mnc180-en-rXC/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-en-rXC/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‎‎‎SIM not provisioned MM#2‎‏‎‎‏‎"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‎‏‎SIM not allowed MM#3‎‏‎‎‏‎"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎‎‎‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-es-rUS/strings.xml b/core/res/res/values-mcc311-mnc180-es-rUS/strings.xml
index 694cd76..cb06a6e 100644
--- a/core/res/res/values-mcc311-mnc180-es-rUS/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-es-rUS/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM no provista MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM no permitida MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-es/strings.xml b/core/res/res/values-mcc311-mnc180-es/strings.xml
index 605e314..6368483 100644
--- a/core/res/res/values-mcc311-mnc180-es/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-es/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM no proporcionada (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM no admitida (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Teléfono no admitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-et/strings.xml b/core/res/res/values-mcc311-mnc180-et/strings.xml
index 39dfa1b..142824f 100644
--- a/core/res/res/values-mcc311-mnc180-et/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-et/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-kaart on ette valmistamata MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-kaart pole lubatud MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon pole lubatud MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-eu/strings.xml b/core/res/res/values-mcc311-mnc180-eu/strings.xml
index 84732c9..4bb3cd3 100644
--- a/core/res/res/values-mcc311-mnc180-eu/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-eu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Ez dago SIM txartelik MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Ez da onartzen SIM txartela MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefonoa ez da onartzen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-fa/strings.xml b/core/res/res/values-mcc311-mnc180-fa/strings.xml
index 902d821..3354859 100644
--- a/core/res/res/values-mcc311-mnc180-fa/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-fa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"‏سیم‌کارت مجوز لازم را ندارد MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"‏سیم‌کارت مجاز نیست MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"‏تلفن مجاز نیست MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-fi/strings.xml b/core/res/res/values-mcc311-mnc180-fi/strings.xml
index 004e645..c66d593 100644
--- a/core/res/res/values-mcc311-mnc180-fi/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-fi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-kortti ei käyttäjien hallinnassa MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-kortti estetty MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Puhelin estetty MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-fr-rCA/strings.xml b/core/res/res/values-mcc311-mnc180-fr-rCA/strings.xml
index c0aaf99..b17c342 100644
--- a/core/res/res/values-mcc311-mnc180-fr-rCA/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-fr-rCA/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Carte SIM non configurée, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Carte SIM non autorisée, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-fr/strings.xml b/core/res/res/values-mcc311-mnc180-fr/strings.xml
index b9adf79..0cc7264 100644
--- a/core/res/res/values-mcc311-mnc180-fr/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-fr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Carte SIM non provisionnée MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Carte SIM non autorisée MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Téléphone non autorisé MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-gl/strings.xml b/core/res/res/values-mcc311-mnc180-gl/strings.xml
index cb59c97..8acb5c1 100644
--- a/core/res/res/values-mcc311-mnc180-gl/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-gl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Non se introduciu ningunha tarxeta SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Non se admite a tarxeta SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Non se admite o teléfono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-gu/strings.xml b/core/res/res/values-mcc311-mnc180-gu/strings.xml
index 556416e..529491e 100644
--- a/core/res/res/values-mcc311-mnc180-gu/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-gu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIMને MM#2ની જોગવાઈ નથી"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIMને MM#3 કરવાની મંજૂરી નથી"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"MM#6 ફોનની મંજૂરી નથી"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-hi/strings.xml b/core/res/res/values-mcc311-mnc180-hi/strings.xml
index da1f9e1..69303e7 100644
--- a/core/res/res/values-mcc311-mnc180-hi/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-hi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM काम नहीं कर रहा है MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM की अनुमति नहीं है MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"फ़ोन की इजाज़त नहीं है MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-hr/strings.xml b/core/res/res/values-mcc311-mnc180-hr/strings.xml
index a1f0b0e..84d36b7 100644
--- a/core/res/res/values-mcc311-mnc180-hr/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-hr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Ne pruža se usluga za SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM nije dopušten MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon nije dopušten MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-hu/strings.xml b/core/res/res/values-mcc311-mnc180-hu/strings.xml
index 917c2ee9..5d3507d 100644
--- a/core/res/res/values-mcc311-mnc180-hu/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-hu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Nem engedélyezett SIM-kártya (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"A SIM-kártya nem engedélyezett (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"A telefon nem engedélyezett (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-hy/strings.xml b/core/res/res/values-mcc311-mnc180-hy/strings.xml
index a832d02..aeaaeb9 100644
--- a/core/res/res/values-mcc311-mnc180-hy/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-hy/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM քարտը նախապատրաստված չէ (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM քարտի օգտագործումն արգելված է (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-in/strings.xml b/core/res/res/values-mcc311-mnc180-in/strings.xml
index bdc8d20..8a96e4c 100644
--- a/core/res/res/values-mcc311-mnc180-in/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-in/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM tidak di-provisioning MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM tidak diizinkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Ponsel tidak diizinkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-is/strings.xml b/core/res/res/values-mcc311-mnc180-is/strings.xml
index 35966f7..bd6223b 100644
--- a/core/res/res/values-mcc311-mnc180-is/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-is/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-korti ekki úthlutað MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-kort ekki leyft MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Sími ekki leyfður MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-it/strings.xml b/core/res/res/values-mcc311-mnc180-it/strings.xml
index 25f301b..7757a25 100644
--- a/core/res/res/values-mcc311-mnc180-it/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-it/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Scheda SIM non predisposta MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Scheda SIM non consentita MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefono non consentito MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-iw/strings.xml b/core/res/res/values-mcc311-mnc180-iw/strings.xml
index c6bdc14..dbb180b 100644
--- a/core/res/res/values-mcc311-mnc180-iw/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-iw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"‏כרטיס ה-SIM לא הופעל MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"‏כרטיס ה-SIM לא מורשה לשימוש ברשת הסלולרית MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"‏הטלפון לא מורשה MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ja/strings.xml b/core/res/res/values-mcc311-mnc180-ja/strings.xml
index 6d30808..3defd2e 100644
--- a/core/res/res/values-mcc311-mnc180-ja/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ja/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM には対応していません(MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM は許可されていません(MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"電話は許可されていません(MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ka/strings.xml b/core/res/res/values-mcc311-mnc180-ka/strings.xml
index b86ce07..2fa76c4 100644
--- a/core/res/res/values-mcc311-mnc180-ka/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ka/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM ბარათი უზრუნველყოფილი არ არის (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM ბარათი დაუშვებელია (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ტელეფონი დაუშვებელია MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-kk/strings.xml b/core/res/res/values-mcc311-mnc180-kk/strings.xml
index 539fbe4..91fd767 100644
--- a/core/res/res/values-mcc311-mnc180-kk/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-kk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM картасы қарастырылмаған MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM картасына рұқсат етілмеген MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Телефон пайдалануға болмайды MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-km/strings.xml b/core/res/res/values-mcc311-mnc180-km/strings.xml
index 970532e..c2f29a5 100644
--- a/core/res/res/values-mcc311-mnc180-km/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-km/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"ស៊ីមកាត​មិនត្រូវបានផ្ដល់ជូនទេ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"មិនអនុញ្ញាត​ចំពោះស៊ីមកាតទេ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-kn/strings.xml b/core/res/res/values-mcc311-mnc180-kn/strings.xml
index 53a0419..08568fa 100644
--- a/core/res/res/values-mcc311-mnc180-kn/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-kn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"MM#2 ಗೆ ಸಿಮ್‌ ಸಿದ್ಧವಾಗಿಲ್ಲ"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"ಸಿಮ್‌ MM#3 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ko/strings.xml b/core/res/res/values-mcc311-mnc180-ko/strings.xml
index 01d6fec..b9bd3c8 100644
--- a/core/res/res/values-mcc311-mnc180-ko/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ko/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM이 프로비저닝되지 않음 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM이 허용되지 않음 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"전화가 허용되지 않음 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ky/strings.xml b/core/res/res/values-mcc311-mnc180-ky/strings.xml
index 7339e40..305d81e 100644
--- a/core/res/res/values-mcc311-mnc180-ky/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ky/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM карта таанылган жок (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM картаны колдонууга тыюу салынган (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Телефонду колдонууга тыюу салынган MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-lo/strings.xml b/core/res/res/values-mcc311-mnc180-lo/strings.xml
index 5cbd1c8..f662b44 100644
--- a/core/res/res/values-mcc311-mnc180-lo/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-lo/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM ບໍ່ໄດ້ເປີດໃຊ້ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM ບໍ່ອະນຸຍາດ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-lt/strings.xml b/core/res/res/values-mcc311-mnc180-lt/strings.xml
index 40a7e4a..fbffbae 100644
--- a/core/res/res/values-mcc311-mnc180-lt/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-lt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM kortelė neteikiama (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM kortelė neleidžiama (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefonas neleidžiamas (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-lv/strings.xml b/core/res/res/values-mcc311-mnc180-lv/strings.xml
index 74b6818..747b5ea 100644
--- a/core/res/res/values-mcc311-mnc180-lv/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-lv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM karte netiek nodrošināta: MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM karti nav atļauts izmantot: MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Tālruni nav atļauts izmantot: MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-mk/strings.xml b/core/res/res/values-mcc311-mnc180-mk/strings.xml
index 7db80fa..9077dc2 100644
--- a/core/res/res/values-mcc311-mnc180-mk/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-mk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Не е обезбедена SIM-картичка, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Не е дозволена SIM-картичка, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Телефонот не е дозволен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ml/strings.xml b/core/res/res/values-mcc311-mnc180-ml/strings.xml
index 7cdd126..fc23728 100644
--- a/core/res/res/values-mcc311-mnc180-ml/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ml/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"സിം MM#2 പ്രൊവിഷൻ ചെയ്‌തിട്ടില്ല"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"സിം MM#3 അനുവദിച്ചിട്ടില്ല"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-mn/strings.xml b/core/res/res/values-mcc311-mnc180-mn/strings.xml
index 1be055c..4960154 100644
--- a/core/res/res/values-mcc311-mnc180-mn/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-mn/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-г идэвхжүүлээгүй байна MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-г зөвшөөрөөгүй байна MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Утсыг зөвшөөрөөгүй MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-mr/strings.xml b/core/res/res/values-mcc311-mnc180-mr/strings.xml
index f8e08ae..f6886fe 100644
--- a/core/res/res/values-mcc311-mnc180-mr/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-mr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM ने MM#2 ची तरतूद केलेली नाही"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM ने MM#3 ला परवानगी दिली नाही"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"फोन MM#6 ला अनुमती देत नाही"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ms/strings.xml b/core/res/res/values-mcc311-mnc180-ms/strings.xml
index 305a10d..222d346 100644
--- a/core/res/res/values-mcc311-mnc180-ms/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ms/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM tidak diperuntukkan MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM tidak dibenarkan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon tidak dibenarkan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-my/strings.xml b/core/res/res/values-mcc311-mnc180-my/strings.xml
index 306c0dd..d68c8ba 100644
--- a/core/res/res/values-mcc311-mnc180-my/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-my/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"ဆင်းမ်ကို ခွင့်မပြုပါ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-nb/strings.xml b/core/res/res/values-mcc311-mnc180-nb/strings.xml
index e1296f4..d2c4758 100644
--- a/core/res/res/values-mcc311-mnc180-nb/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-nb/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-kortet er ikke klargjort, MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-kortet er ikke tillatt, MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefonen er ikke tillatt, MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ne/strings.xml b/core/res/res/values-mcc311-mnc180-ne/strings.xml
index 6177a83..f98b5b5 100644
--- a/core/res/res/values-mcc311-mnc180-ne/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ne/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM को प्रावधान छैन MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM लाई अनुमति छैन MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"फोनलाई अनुमति छैन MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-nl/strings.xml b/core/res/res/values-mcc311-mnc180-nl/strings.xml
index 3f12e64..fbfd787 100644
--- a/core/res/res/values-mcc311-mnc180-nl/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-nl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Simkaart niet geregistreerd MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Simkaart niet toegestaan MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefoon niet toegestaan MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-pa/strings.xml b/core/res/res/values-mcc311-mnc180-pa/strings.xml
index 32f6a7e..3e69e8b 100644
--- a/core/res/res/values-mcc311-mnc180-pa/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-pa/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"ਸਿਮ ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"ਸਿਮ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-pl/strings.xml b/core/res/res/values-mcc311-mnc180-pl/strings.xml
index 0744491..296000e 100644
--- a/core/res/res/values-mcc311-mnc180-pl/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-pl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"MM#2 – karta SIM nieobsługiwana"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"MM#3 – niedozwolona karta SIM"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"MM#6 – telefon niedozwolony"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-pt-rBR/strings.xml b/core/res/res/values-mcc311-mnc180-pt-rBR/strings.xml
index c4d2240..637a91c 100644
--- a/core/res/res/values-mcc311-mnc180-pt-rBR/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-pt-rBR/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-pt-rPT/strings.xml b/core/res/res/values-mcc311-mnc180-pt-rPT/strings.xml
index c4d2240..7938a5f 100644
--- a/core/res/res/values-mcc311-mnc180-pt-rPT/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-pt-rPT/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telemóvel não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-pt/strings.xml b/core/res/res/values-mcc311-mnc180-pt/strings.xml
index c4d2240..637a91c 100644
--- a/core/res/res/values-mcc311-mnc180-pt/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-pt/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM não aprovisionado MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM não permitido MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Smartphone não permitido MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ro/strings.xml b/core/res/res/values-mcc311-mnc180-ro/strings.xml
index 68cab29..c5126e7 100644
--- a/core/res/res/values-mcc311-mnc180-ro/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ro/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Cardul SIM nu este activat MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Cardul SIM nu este permis MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefonul nu este permis MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ru/strings.xml b/core/res/res/values-mcc311-mnc180-ru/strings.xml
index 90a3a09..a6f6785 100644
--- a/core/res/res/values-mcc311-mnc180-ru/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ru/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-карта не активирована (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Использование SIM-карты запрещено (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Звонки запрещены (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-si/strings.xml b/core/res/res/values-mcc311-mnc180-si/strings.xml
index 1907d3e..b9ef425 100644
--- a/core/res/res/values-mcc311-mnc180-si/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-si/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM MM#2 ප්‍රතිපාදනය නොකරයි"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM MM#3 ඉඩ නොදේ"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-sk/strings.xml b/core/res/res/values-mcc311-mnc180-sk/strings.xml
index a456431..ecd9222 100644
--- a/core/res/res/values-mcc311-mnc180-sk/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-sk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM karta nie je k dispozícii – MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM karta je zakázaná – MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefón nie je povolený (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-sl/strings.xml b/core/res/res/values-mcc311-mnc180-sl/strings.xml
index 454a1cd..33d9621 100644
--- a/core/res/res/values-mcc311-mnc180-sl/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-sl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Kartica SIM ni omogočena za uporabo MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Kartica SIM ni dovoljena MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefon ni dovoljen MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-sq/strings.xml b/core/res/res/values-mcc311-mnc180-sq/strings.xml
index 23da1a3..b5b28fa 100644
--- a/core/res/res/values-mcc311-mnc180-sq/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-sq/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Karta SIM nuk është dhënë MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Karta SIM nuk lejohet MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefoni nuk lejohet MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-sr/strings.xml b/core/res/res/values-mcc311-mnc180-sr/strings.xml
index 182a7bc..e8b6017 100644
--- a/core/res/res/values-mcc311-mnc180-sr/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-sr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM картица није подешена MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM картица није дозвољена MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Телефон није дозвољен MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-sv/strings.xml b/core/res/res/values-mcc311-mnc180-sv/strings.xml
index fee7a1f..9ca48e0 100644
--- a/core/res/res/values-mcc311-mnc180-sv/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-sv/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-kort tillhandahålls inte MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-kort tillåts inte MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Mobil tillåts inte MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-sw/strings.xml b/core/res/res/values-mcc311-mnc180-sw/strings.xml
index e49b3d1..5b76485 100644
--- a/core/res/res/values-mcc311-mnc180-sw/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-sw/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM haitumiki MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM hairuhusiwi MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Simu hairuhusiwi MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ta/strings.xml b/core/res/res/values-mcc311-mnc180-ta/strings.xml
index 6c11c4f..9242b4d 100644
--- a/core/res/res/values-mcc311-mnc180-ta/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ta/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"சிம் அமைக்கப்படவில்லை MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"சிம் அனுமதிக்கப்படவில்லை MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-te/strings.xml b/core/res/res/values-mcc311-mnc180-te/strings.xml
index 5bb9f97..90fe4ca 100644
--- a/core/res/res/values-mcc311-mnc180-te/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-te/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM MM#2ని సక్రియం చేయలేదు"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM MM#3ని అనుమతించలేదు"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ఫోన్ అనుమతించబడదు MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-th/strings.xml b/core/res/res/values-mcc311-mnc180-th/strings.xml
index be4b08e..d587bcd 100644
--- a/core/res/res/values-mcc311-mnc180-th/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-th/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"ไม่มีการจัดสรรซิม MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"ไม่อนุญาตให้ใช้ซิม MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-tl/strings.xml b/core/res/res/values-mcc311-mnc180-tl/strings.xml
index 6aacf16..963db42 100644
--- a/core/res/res/values-mcc311-mnc180-tl/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-tl/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"Hindi na-provision ang SIM MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"Hindi pinapahintulutan ang SIM MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Hindi pinapahintulutan ang telepono MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-tr/strings.xml b/core/res/res/values-mcc311-mnc180-tr/strings.xml
index cf4fd77..28280f0 100644
--- a/core/res/res/values-mcc311-mnc180-tr/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-tr/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM, MM#2\'nin temel hazırlığını yapamadı"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM MM#3\'e izin vermiyor"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Telefona izin verilmiyor MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-uk/strings.xml b/core/res/res/values-mcc311-mnc180-uk/strings.xml
index fae40ac..00b22d9 100644
--- a/core/res/res/values-mcc311-mnc180-uk/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-uk/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM-карту не надано (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM-карта заборонена (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Телефон заборонено (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-ur/strings.xml b/core/res/res/values-mcc311-mnc180-ur/strings.xml
index ebceb5e..a9685c4 100644
--- a/core/res/res/values-mcc311-mnc180-ur/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-ur/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"‏SIM فراہم کردہ نہیں ہے MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"‏SIM کی اجازت نہیں ہے MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"‏فون کی اجازت نہیں ہے MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-uz/strings.xml b/core/res/res/values-mcc311-mnc180-uz/strings.xml
index ebcdd3b..c6f9b37 100644
--- a/core/res/res/values-mcc311-mnc180-uz/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-uz/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM karta ishlatish taqiqlangan (MM#2)"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM karta ishlatish taqiqlangan (MM#3)"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Chaqiruvlar taqiqlangan (MM#6)"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-vi/strings.xml b/core/res/res/values-mcc311-mnc180-vi/strings.xml
index 0d7ff96..b5a6567 100644
--- a/core/res/res/values-mcc311-mnc180-vi/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-vi/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"SIM không được cấp phép MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM không được phép MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Không cho phép điện thoại MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-zh-rCN/strings.xml b/core/res/res/values-mcc311-mnc180-zh-rCN/strings.xml
index d8ff182..cd27edf 100644
--- a/core/res/res/values-mcc311-mnc180-zh-rCN/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-zh-rCN/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"未配置的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"不被允许的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"不受允许的手机 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-zh-rHK/strings.xml b/core/res/res/values-mcc311-mnc180-zh-rHK/strings.xml
index 14332bf..9f2f8b9 100644
--- a/core/res/res/values-mcc311-mnc180-zh-rHK/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-zh-rHK/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"SIM 卡不允許 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"不允許手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-zh-rTW/strings.xml b/core/res/res/values-mcc311-mnc180-zh-rTW/strings.xml
index 775a70b..9336296 100644
--- a/core/res/res/values-mcc311-mnc180-zh-rTW/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-zh-rTW/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"未佈建的 SIM 卡 MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"不支援的 SIM 卡 MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"不支援的手機 MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180-zu/strings.xml b/core/res/res/values-mcc311-mnc180-zu/strings.xml
index a66760f..5708a22 100644
--- a/core/res/res/values-mcc311-mnc180-zu/strings.xml
+++ b/core/res/res/values-mcc311-mnc180-zu/strings.xml
@@ -22,4 +22,5 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr" msgid="531930017979728896">"I-SIM ayinikezelwe MM#2"</string>
     <string name="mmcc_illegal_ms" msgid="97745044956236881">"I-SIM ayivunyelwe MM#3"</string>
+    <string name="mmcc_illegal_me" msgid="5766888847785331904">"Ifoni ayivunyelwe MM#6"</string>
 </resources>
diff --git a/core/res/res/values-mcc311-mnc180/strings.xml b/core/res/res/values-mcc311-mnc180/strings.xml
index a3fea29..6a404d5 100644
--- a/core/res/res/values-mcc311-mnc180/strings.xml
+++ b/core/res/res/values-mcc311-mnc180/strings.xml
@@ -20,4 +20,5 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned MM#2</string>
     <string name="mmcc_illegal_ms">SIM not allowed MM#3</string>
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
 </resources>
diff --git a/core/res/res/values-mcc312-mnc670-af/strings.xml b/core/res/res/values-mcc312-mnc670-af/strings.xml
new file mode 100644
index 0000000..8884326
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-af/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Foon nie toegelaat nie MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-am/strings.xml b/core/res/res/values-mcc312-mnc670-am/strings.xml
new file mode 100644
index 0000000..26c082b
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-am/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ስልክ አይፈቀድም MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ar/strings.xml b/core/res/res/values-mcc312-mnc670-ar/strings.xml
new file mode 100644
index 0000000..133e2b0
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ar/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"‏غير مسموح باستخدام الهاتف MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-az/strings.xml b/core/res/res/values-mcc312-mnc670-az/strings.xml
new file mode 100644
index 0000000..80a1926
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-az/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"MM#6 telefonu dəstəklənmir"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml b/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..47b219e
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-be/strings.xml b/core/res/res/values-mcc312-mnc670-be/strings.xml
new file mode 100644
index 0000000..ad5d190
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-be/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Тэлефон не дапускаецца MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-bg/strings.xml b/core/res/res/values-mcc312-mnc670-bg/strings.xml
new file mode 100644
index 0000000..98a60c5
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-bg/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Телефонът не е разрешен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-bn/strings.xml b/core/res/res/values-mcc312-mnc670-bn/strings.xml
new file mode 100644
index 0000000..6b5a1d0
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-bn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ফোন অনুমোদিত নয় MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-bs/strings.xml b/core/res/res/values-mcc312-mnc670-bs/strings.xml
new file mode 100644
index 0000000..47b219e
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-bs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ca/strings.xml b/core/res/res/values-mcc312-mnc670-ca/strings.xml
new file mode 100644
index 0000000..adaa2ba
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ca/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telèfon no compatible MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-cs/strings.xml b/core/res/res/values-mcc312-mnc670-cs/strings.xml
new file mode 100644
index 0000000..b88b619
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-cs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon není povolen (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-da/strings.xml b/core/res/res/values-mcc312-mnc670-da/strings.xml
new file mode 100644
index 0000000..50fdbd4
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-da/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefonen har ikke adgangstilladelse MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-de/strings.xml b/core/res/res/values-mcc312-mnc670-de/strings.xml
new file mode 100644
index 0000000..18b33e5
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-de/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Smartphone nicht zulässig MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-el/strings.xml b/core/res/res/values-mcc312-mnc670-el/strings.xml
new file mode 100644
index 0000000..22ea1db
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-el/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-en-rAU/strings.xml b/core/res/res/values-mcc312-mnc670-en-rAU/strings.xml
new file mode 100644
index 0000000..3eb880a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-en-rCA/strings.xml b/core/res/res/values-mcc312-mnc670-en-rCA/strings.xml
new file mode 100644
index 0000000..3eb880a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-en-rGB/strings.xml b/core/res/res/values-mcc312-mnc670-en-rGB/strings.xml
new file mode 100644
index 0000000..3eb880a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-en-rIN/strings.xml b/core/res/res/values-mcc312-mnc670-en-rIN/strings.xml
new file mode 100644
index 0000000..3eb880a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-en-rXC/strings.xml b/core/res/res/values-mcc312-mnc670-en-rXC/strings.xml
new file mode 100644
index 0000000..be68f41
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‏‎‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-es-rUS/strings.xml b/core/res/res/values-mcc312-mnc670-es-rUS/strings.xml
new file mode 100644
index 0000000..89b28f5
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-es/strings.xml b/core/res/res/values-mcc312-mnc670-es/strings.xml
new file mode 100644
index 0000000..89b28f5
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-es/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-et/strings.xml b/core/res/res/values-mcc312-mnc670-et/strings.xml
new file mode 100644
index 0000000..1ed4b41
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-et/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon pole lubatud MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-eu/strings.xml b/core/res/res/values-mcc312-mnc670-eu/strings.xml
new file mode 100644
index 0000000..8f0daba
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-eu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefonoa ez da onartzen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-fa/strings.xml b/core/res/res/values-mcc312-mnc670-fa/strings.xml
new file mode 100644
index 0000000..bd87bdf
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-fa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"‏تلفن مجاز نیست MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-fi/strings.xml b/core/res/res/values-mcc312-mnc670-fi/strings.xml
new file mode 100644
index 0000000..5ec33a3
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-fi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Puhelin estetty MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-fr-rCA/strings.xml b/core/res/res/values-mcc312-mnc670-fr-rCA/strings.xml
new file mode 100644
index 0000000..2110dda
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-fr/strings.xml b/core/res/res/values-mcc312-mnc670-fr/strings.xml
new file mode 100644
index 0000000..2110dda
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-fr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-gl/strings.xml b/core/res/res/values-mcc312-mnc670-gl/strings.xml
new file mode 100644
index 0000000..dc66c1d
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-gl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Non se admite o teléfono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-gu/strings.xml b/core/res/res/values-mcc312-mnc670-gu/strings.xml
new file mode 100644
index 0000000..bc0db0b
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-gu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"MM#6 ફોનની મંજૂરી નથી"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-hi/strings.xml b/core/res/res/values-mcc312-mnc670-hi/strings.xml
new file mode 100644
index 0000000..1db4db2
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-hi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"फ़ोन की इजाज़त नहीं है MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-hr/strings.xml b/core/res/res/values-mcc312-mnc670-hr/strings.xml
new file mode 100644
index 0000000..e3b49c9
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-hr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon nije dopušten MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-hu/strings.xml b/core/res/res/values-mcc312-mnc670-hu/strings.xml
new file mode 100644
index 0000000..85b947c
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-hu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"A telefon nem engedélyezett (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-hy/strings.xml b/core/res/res/values-mcc312-mnc670-hy/strings.xml
new file mode 100644
index 0000000..bc39d50
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-hy/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-in/strings.xml b/core/res/res/values-mcc312-mnc670-in/strings.xml
new file mode 100644
index 0000000..0f526dd
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-in/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Ponsel tidak diizinkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-is/strings.xml b/core/res/res/values-mcc312-mnc670-is/strings.xml
new file mode 100644
index 0000000..cad2282
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-is/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Sími ekki leyfður MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-it/strings.xml b/core/res/res/values-mcc312-mnc670-it/strings.xml
new file mode 100644
index 0000000..1763bdc
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-it/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefono non consentito MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-iw/strings.xml b/core/res/res/values-mcc312-mnc670-iw/strings.xml
new file mode 100644
index 0000000..a042e10
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-iw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"‏הטלפון לא מורשה MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ja/strings.xml b/core/res/res/values-mcc312-mnc670-ja/strings.xml
new file mode 100644
index 0000000..c5de3af
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"電話は許可されていません(MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ka/strings.xml b/core/res/res/values-mcc312-mnc670-ka/strings.xml
new file mode 100644
index 0000000..a2c7b8a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ka/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ტელეფონი დაუშვებელია MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-kk/strings.xml b/core/res/res/values-mcc312-mnc670-kk/strings.xml
new file mode 100644
index 0000000..1ac2314
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-kk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Телефон пайдалануға болмайды MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-km/strings.xml b/core/res/res/values-mcc312-mnc670-km/strings.xml
new file mode 100644
index 0000000..8786411
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-km/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-kn/strings.xml b/core/res/res/values-mcc312-mnc670-kn/strings.xml
new file mode 100644
index 0000000..581fe17
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-kn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ko/strings.xml b/core/res/res/values-mcc312-mnc670-ko/strings.xml
new file mode 100644
index 0000000..fcd98f9
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ko/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"전화가 허용되지 않음 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ky/strings.xml b/core/res/res/values-mcc312-mnc670-ky/strings.xml
new file mode 100644
index 0000000..ed830f6
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ky/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Телефонду колдонууга тыюу салынган MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-lo/strings.xml b/core/res/res/values-mcc312-mnc670-lo/strings.xml
new file mode 100644
index 0000000..04bb1c9
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-lo/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-lt/strings.xml b/core/res/res/values-mcc312-mnc670-lt/strings.xml
new file mode 100644
index 0000000..c416a9a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-lt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefonas neleidžiamas (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-lv/strings.xml b/core/res/res/values-mcc312-mnc670-lv/strings.xml
new file mode 100644
index 0000000..dfed609
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-lv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Tālruni nav atļauts izmantot: MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-mk/strings.xml b/core/res/res/values-mcc312-mnc670-mk/strings.xml
new file mode 100644
index 0000000..0bf51cc
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-mk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Телефонот не е дозволен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ml/strings.xml b/core/res/res/values-mcc312-mnc670-ml/strings.xml
new file mode 100644
index 0000000..78dc1ec
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ml/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-mn/strings.xml b/core/res/res/values-mcc312-mnc670-mn/strings.xml
new file mode 100644
index 0000000..682cf86
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-mn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Утсыг зөвшөөрөөгүй MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-mr/strings.xml b/core/res/res/values-mcc312-mnc670-mr/strings.xml
new file mode 100644
index 0000000..b9dd523
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-mr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"फोन MM#6 ला अनुमती देत नाही"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ms/strings.xml b/core/res/res/values-mcc312-mnc670-ms/strings.xml
new file mode 100644
index 0000000..3e807a0
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ms/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon tidak dibenarkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-my/strings.xml b/core/res/res/values-mcc312-mnc670-my/strings.xml
new file mode 100644
index 0000000..292e8db
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-my/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-nb/strings.xml b/core/res/res/values-mcc312-mnc670-nb/strings.xml
new file mode 100644
index 0000000..431fda6
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-nb/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefonen er ikke tillatt, MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ne/strings.xml b/core/res/res/values-mcc312-mnc670-ne/strings.xml
new file mode 100644
index 0000000..ce2ce77
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ne/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"फोनलाई अनुमति छैन MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-nl/strings.xml b/core/res/res/values-mcc312-mnc670-nl/strings.xml
new file mode 100644
index 0000000..d7eb032
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-nl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefoon niet toegestaan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-pa/strings.xml b/core/res/res/values-mcc312-mnc670-pa/strings.xml
new file mode 100644
index 0000000..6b98d49
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-pa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-pl/strings.xml b/core/res/res/values-mcc312-mnc670-pl/strings.xml
new file mode 100644
index 0000000..dd39c79
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"MM#6 – telefon niedozwolony"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-pt-rBR/strings.xml b/core/res/res/values-mcc312-mnc670-pt-rBR/strings.xml
new file mode 100644
index 0000000..bb58d18
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-pt-rBR/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-pt-rPT/strings.xml b/core/res/res/values-mcc312-mnc670-pt-rPT/strings.xml
new file mode 100644
index 0000000..f04d740
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telemóvel não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-pt/strings.xml b/core/res/res/values-mcc312-mnc670-pt/strings.xml
new file mode 100644
index 0000000..bb58d18
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-pt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ro/strings.xml b/core/res/res/values-mcc312-mnc670-ro/strings.xml
new file mode 100644
index 0000000..3129943
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ro/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefonul nu este permis MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ru/strings.xml b/core/res/res/values-mcc312-mnc670-ru/strings.xml
new file mode 100644
index 0000000..46080e0
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ru/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Звонки запрещены (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-si/strings.xml b/core/res/res/values-mcc312-mnc670-si/strings.xml
new file mode 100644
index 0000000..6fdac6b
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-si/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-sk/strings.xml b/core/res/res/values-mcc312-mnc670-sk/strings.xml
new file mode 100644
index 0000000..c717019
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-sk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefón nie je povolený (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-sl/strings.xml b/core/res/res/values-mcc312-mnc670-sl/strings.xml
new file mode 100644
index 0000000..15c7670
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-sl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefon ni dovoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-sq/strings.xml b/core/res/res/values-mcc312-mnc670-sq/strings.xml
new file mode 100644
index 0000000..5c97026
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-sq/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefoni nuk lejohet MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-sr/strings.xml b/core/res/res/values-mcc312-mnc670-sr/strings.xml
new file mode 100644
index 0000000..9fdf70d
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-sr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Телефон није дозвољен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-sv/strings.xml b/core/res/res/values-mcc312-mnc670-sv/strings.xml
new file mode 100644
index 0000000..0f9d454
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-sv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Mobil tillåts inte MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-sw/strings.xml b/core/res/res/values-mcc312-mnc670-sw/strings.xml
new file mode 100644
index 0000000..e2c461e
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-sw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Simu hairuhusiwi MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ta/strings.xml b/core/res/res/values-mcc312-mnc670-ta/strings.xml
new file mode 100644
index 0000000..2c56a7e
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ta/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-te/strings.xml b/core/res/res/values-mcc312-mnc670-te/strings.xml
new file mode 100644
index 0000000..f9bd60a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-te/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ఫోన్ అనుమతించబడదు MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-th/strings.xml b/core/res/res/values-mcc312-mnc670-th/strings.xml
new file mode 100644
index 0000000..b144ca3
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-th/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-tl/strings.xml b/core/res/res/values-mcc312-mnc670-tl/strings.xml
new file mode 100644
index 0000000..79b88ec
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-tl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Hindi pinapahintulutan ang telepono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-tr/strings.xml b/core/res/res/values-mcc312-mnc670-tr/strings.xml
new file mode 100644
index 0000000..1e3dcea
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-tr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Telefona izin verilmiyor MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-uk/strings.xml b/core/res/res/values-mcc312-mnc670-uk/strings.xml
new file mode 100644
index 0000000..d2dd817
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-uk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Телефон заборонено (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-ur/strings.xml b/core/res/res/values-mcc312-mnc670-ur/strings.xml
new file mode 100644
index 0000000..f7ef38c
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-ur/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"‏فون کی اجازت نہیں ہے MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-uz/strings.xml b/core/res/res/values-mcc312-mnc670-uz/strings.xml
new file mode 100644
index 0000000..4bf0f74
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-uz/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Chaqiruvlar taqiqlangan (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-vi/strings.xml b/core/res/res/values-mcc312-mnc670-vi/strings.xml
new file mode 100644
index 0000000..4bae4af
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-vi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Không cho phép điện thoại MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-zh-rCN/strings.xml b/core/res/res/values-mcc312-mnc670-zh-rCN/strings.xml
new file mode 100644
index 0000000..317531a
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"不受允许的手机 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-zh-rHK/strings.xml b/core/res/res/values-mcc312-mnc670-zh-rHK/strings.xml
new file mode 100644
index 0000000..bef6362
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"不允許手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-zh-rTW/strings.xml b/core/res/res/values-mcc312-mnc670-zh-rTW/strings.xml
new file mode 100644
index 0000000..1fa82ed
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"不支援的手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670-zu/strings.xml b/core/res/res/values-mcc312-mnc670-zu/strings.xml
new file mode 100644
index 0000000..35c2cbf
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670-zu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="3649306773478362802">"Ifoni ayivunyelwe MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc312-mnc670/strings.xml b/core/res/res/values-mcc312-mnc670/strings.xml
new file mode 100644
index 0000000..96af975
--- /dev/null
+++ b/core/res/res/values-mcc312-mnc670/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-af/strings.xml b/core/res/res/values-mcc313-mnc100-af/strings.xml
new file mode 100644
index 0000000..7645fc8
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-af/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Foon nie toegelaat nie MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-am/strings.xml b/core/res/res/values-mcc313-mnc100-am/strings.xml
new file mode 100644
index 0000000..b76ed04
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-am/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ስልክ አይፈቀድም MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ar/strings.xml b/core/res/res/values-mcc313-mnc100-ar/strings.xml
new file mode 100644
index 0000000..640fb8a
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ar/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"‏غير مسموح باستخدام الهاتف MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-az/strings.xml b/core/res/res/values-mcc313-mnc100-az/strings.xml
new file mode 100644
index 0000000..44796df
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-az/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"MM#6 telefonu dəstəklənmir"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-b+sr+Latn/strings.xml b/core/res/res/values-mcc313-mnc100-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..d5bf39e
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-b+sr+Latn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-be/strings.xml b/core/res/res/values-mcc313-mnc100-be/strings.xml
new file mode 100644
index 0000000..c9f4633
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-be/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Тэлефон не дапускаецца MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-bg/strings.xml b/core/res/res/values-mcc313-mnc100-bg/strings.xml
new file mode 100644
index 0000000..8c946ed
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-bg/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Телефонът не е разрешен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-bn/strings.xml b/core/res/res/values-mcc313-mnc100-bn/strings.xml
new file mode 100644
index 0000000..5292241
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-bn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ফোন অনুমোদিত নয় MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-bs/strings.xml b/core/res/res/values-mcc313-mnc100-bs/strings.xml
new file mode 100644
index 0000000..d5bf39e
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-bs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon nije dozvoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ca/strings.xml b/core/res/res/values-mcc313-mnc100-ca/strings.xml
new file mode 100644
index 0000000..f6846cb
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ca/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telèfon no compatible MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-cs/strings.xml b/core/res/res/values-mcc313-mnc100-cs/strings.xml
new file mode 100644
index 0000000..4e57d15
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-cs/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon není povolen (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-da/strings.xml b/core/res/res/values-mcc313-mnc100-da/strings.xml
new file mode 100644
index 0000000..c00d95c
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-da/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefonen har ikke adgangstilladelse MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-de/strings.xml b/core/res/res/values-mcc313-mnc100-de/strings.xml
new file mode 100644
index 0000000..df08b13
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-de/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Smartphone nicht zulässig MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-el/strings.xml b/core/res/res/values-mcc313-mnc100-el/strings.xml
new file mode 100644
index 0000000..0fcb42e
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-el/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Το τηλέφωνο δεν επιτρέπεται MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-en-rAU/strings.xml b/core/res/res/values-mcc313-mnc100-en-rAU/strings.xml
new file mode 100644
index 0000000..f1a3611
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-en-rAU/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-en-rCA/strings.xml b/core/res/res/values-mcc313-mnc100-en-rCA/strings.xml
new file mode 100644
index 0000000..f1a3611
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-en-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-en-rGB/strings.xml b/core/res/res/values-mcc313-mnc100-en-rGB/strings.xml
new file mode 100644
index 0000000..f1a3611
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-en-rGB/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-en-rIN/strings.xml b/core/res/res/values-mcc313-mnc100-en-rIN/strings.xml
new file mode 100644
index 0000000..f1a3611
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-en-rIN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Phone not allowed MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-en-rXC/strings.xml b/core/res/res/values-mcc313-mnc100-en-rXC/strings.xml
new file mode 100644
index 0000000..8a8bf7e
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-en-rXC/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‎Phone not allowed MM#6‎‏‎‎‏‎"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-es-rUS/strings.xml b/core/res/res/values-mcc313-mnc100-es-rUS/strings.xml
new file mode 100644
index 0000000..122d4b9
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-es-rUS/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-es/strings.xml b/core/res/res/values-mcc313-mnc100-es/strings.xml
new file mode 100644
index 0000000..122d4b9
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-es/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Teléfono no admitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-et/strings.xml b/core/res/res/values-mcc313-mnc100-et/strings.xml
new file mode 100644
index 0000000..83cfbaf
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-et/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon pole lubatud MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-eu/strings.xml b/core/res/res/values-mcc313-mnc100-eu/strings.xml
new file mode 100644
index 0000000..028ca37
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-eu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefonoa ez da onartzen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-fa/strings.xml b/core/res/res/values-mcc313-mnc100-fa/strings.xml
new file mode 100644
index 0000000..f29da6b
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-fa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"‏تلفن مجاز نیست MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-fi/strings.xml b/core/res/res/values-mcc313-mnc100-fi/strings.xml
new file mode 100644
index 0000000..f64a38a
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-fi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Puhelin estetty MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-fr-rCA/strings.xml b/core/res/res/values-mcc313-mnc100-fr-rCA/strings.xml
new file mode 100644
index 0000000..89c50ea
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-fr-rCA/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-fr/strings.xml b/core/res/res/values-mcc313-mnc100-fr/strings.xml
new file mode 100644
index 0000000..89c50ea
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-fr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Téléphone non autorisé MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-gl/strings.xml b/core/res/res/values-mcc313-mnc100-gl/strings.xml
new file mode 100644
index 0000000..04390a0
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-gl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Non se admite o teléfono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-gu/strings.xml b/core/res/res/values-mcc313-mnc100-gu/strings.xml
new file mode 100644
index 0000000..6291d57
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-gu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"MM#6 ફોનની મંજૂરી નથી"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-hi/strings.xml b/core/res/res/values-mcc313-mnc100-hi/strings.xml
new file mode 100644
index 0000000..f31e6bf
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-hi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"फ़ोन की इजाज़त नहीं है MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-hr/strings.xml b/core/res/res/values-mcc313-mnc100-hr/strings.xml
new file mode 100644
index 0000000..290e92b
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-hr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon nije dopušten MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-hu/strings.xml b/core/res/res/values-mcc313-mnc100-hu/strings.xml
new file mode 100644
index 0000000..31605dd
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-hu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"A telefon nem engedélyezett (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-hy/strings.xml b/core/res/res/values-mcc313-mnc100-hy/strings.xml
new file mode 100644
index 0000000..62acde3
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-hy/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Հեռախոսի օգտագործումն արգելված է (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-in/strings.xml b/core/res/res/values-mcc313-mnc100-in/strings.xml
new file mode 100644
index 0000000..d95657f
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-in/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Ponsel tidak diizinkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-is/strings.xml b/core/res/res/values-mcc313-mnc100-is/strings.xml
new file mode 100644
index 0000000..3ad7b3c
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-is/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Sími ekki leyfður MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-it/strings.xml b/core/res/res/values-mcc313-mnc100-it/strings.xml
new file mode 100644
index 0000000..1d3deeb
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-it/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefono non consentito MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-iw/strings.xml b/core/res/res/values-mcc313-mnc100-iw/strings.xml
new file mode 100644
index 0000000..383e3d1
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-iw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"‏הטלפון לא מורשה MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ja/strings.xml b/core/res/res/values-mcc313-mnc100-ja/strings.xml
new file mode 100644
index 0000000..6a89e3d
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ja/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"電話は許可されていません(MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ka/strings.xml b/core/res/res/values-mcc313-mnc100-ka/strings.xml
new file mode 100644
index 0000000..a063fc4
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ka/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ტელეფონი დაუშვებელია MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-kk/strings.xml b/core/res/res/values-mcc313-mnc100-kk/strings.xml
new file mode 100644
index 0000000..0562a2f
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-kk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Телефон пайдалануға болмайды MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-km/strings.xml b/core/res/res/values-mcc313-mnc100-km/strings.xml
new file mode 100644
index 0000000..74e607b
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-km/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"មិន​អនុញ្ញាត​ចំពោះ​ទូរសព្ទ​ទេ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-kn/strings.xml b/core/res/res/values-mcc313-mnc100-kn/strings.xml
new file mode 100644
index 0000000..e287270
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-kn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ಫೋನ್ MM#6 ಅನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ko/strings.xml b/core/res/res/values-mcc313-mnc100-ko/strings.xml
new file mode 100644
index 0000000..fbe222b
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ko/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"전화가 허용되지 않음 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ky/strings.xml b/core/res/res/values-mcc313-mnc100-ky/strings.xml
new file mode 100644
index 0000000..8c08c4f
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ky/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Телефонду колдонууга тыюу салынган MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-lo/strings.xml b/core/res/res/values-mcc313-mnc100-lo/strings.xml
new file mode 100644
index 0000000..793b87b
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-lo/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ບໍ່ອະນຸຍາດໃຫ້ໃຊ້ໂທລະສັບ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-lt/strings.xml b/core/res/res/values-mcc313-mnc100-lt/strings.xml
new file mode 100644
index 0000000..5edc6bf
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-lt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefonas neleidžiamas (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-lv/strings.xml b/core/res/res/values-mcc313-mnc100-lv/strings.xml
new file mode 100644
index 0000000..de1ad9c
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-lv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Tālruni nav atļauts izmantot: MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-mk/strings.xml b/core/res/res/values-mcc313-mnc100-mk/strings.xml
new file mode 100644
index 0000000..0b403e9
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-mk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Телефонот не е дозволен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ml/strings.xml b/core/res/res/values-mcc313-mnc100-ml/strings.xml
new file mode 100644
index 0000000..1adc455
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ml/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ഫോൺ അനുവദനീയമല്ല MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-mn/strings.xml b/core/res/res/values-mcc313-mnc100-mn/strings.xml
new file mode 100644
index 0000000..5d5fbff
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-mn/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Утсыг зөвшөөрөөгүй MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-mr/strings.xml b/core/res/res/values-mcc313-mnc100-mr/strings.xml
new file mode 100644
index 0000000..32c6946
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-mr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"फोन MM#6 ला अनुमती देत नाही"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ms/strings.xml b/core/res/res/values-mcc313-mnc100-ms/strings.xml
new file mode 100644
index 0000000..ebd1724
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ms/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon tidak dibenarkan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-my/strings.xml b/core/res/res/values-mcc313-mnc100-my/strings.xml
new file mode 100644
index 0000000..7de66f7
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-my/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ဖုန်းကို ခွင့်မပြုပါ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-nb/strings.xml b/core/res/res/values-mcc313-mnc100-nb/strings.xml
new file mode 100644
index 0000000..84a7582
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-nb/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefonen er ikke tillatt, MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ne/strings.xml b/core/res/res/values-mcc313-mnc100-ne/strings.xml
new file mode 100644
index 0000000..0fb9d64
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ne/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"फोनलाई अनुमति छैन MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-nl/strings.xml b/core/res/res/values-mcc313-mnc100-nl/strings.xml
new file mode 100644
index 0000000..14e940d
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-nl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefoon niet toegestaan MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-pa/strings.xml b/core/res/res/values-mcc313-mnc100-pa/strings.xml
new file mode 100644
index 0000000..87b2e47
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-pa/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ਫ਼ੋਨ ਨੂੰ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-pl/strings.xml b/core/res/res/values-mcc313-mnc100-pl/strings.xml
new file mode 100644
index 0000000..7df915c
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-pl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"MM#6 – telefon niedozwolony"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-pt-rBR/strings.xml b/core/res/res/values-mcc313-mnc100-pt-rBR/strings.xml
new file mode 100644
index 0000000..f80f618
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-pt-rBR/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-pt-rPT/strings.xml b/core/res/res/values-mcc313-mnc100-pt-rPT/strings.xml
new file mode 100644
index 0000000..35d4f58
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-pt-rPT/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telemóvel não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-pt/strings.xml b/core/res/res/values-mcc313-mnc100-pt/strings.xml
new file mode 100644
index 0000000..f80f618
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-pt/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Smartphone não permitido MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ro/strings.xml b/core/res/res/values-mcc313-mnc100-ro/strings.xml
new file mode 100644
index 0000000..57a455d
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ro/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefonul nu este permis MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ru/strings.xml b/core/res/res/values-mcc313-mnc100-ru/strings.xml
new file mode 100644
index 0000000..8edec35
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ru/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Звонки запрещены (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-si/strings.xml b/core/res/res/values-mcc313-mnc100-si/strings.xml
new file mode 100644
index 0000000..9493af0b
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-si/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"දුරකථනය MM#6 ඉඩ නොදේ"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-sk/strings.xml b/core/res/res/values-mcc313-mnc100-sk/strings.xml
new file mode 100644
index 0000000..04a1a08
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-sk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefón nie je povolený (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-sl/strings.xml b/core/res/res/values-mcc313-mnc100-sl/strings.xml
new file mode 100644
index 0000000..e59c833
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-sl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefon ni dovoljen MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-sq/strings.xml b/core/res/res/values-mcc313-mnc100-sq/strings.xml
new file mode 100644
index 0000000..237a4a4
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-sq/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefoni nuk lejohet MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-sr/strings.xml b/core/res/res/values-mcc313-mnc100-sr/strings.xml
new file mode 100644
index 0000000..6d6c310
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-sr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Телефон није дозвољен MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-sv/strings.xml b/core/res/res/values-mcc313-mnc100-sv/strings.xml
new file mode 100644
index 0000000..145a960
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-sv/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Mobil tillåts inte MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-sw/strings.xml b/core/res/res/values-mcc313-mnc100-sw/strings.xml
new file mode 100644
index 0000000..a7574fb
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-sw/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Simu hairuhusiwi MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ta/strings.xml b/core/res/res/values-mcc313-mnc100-ta/strings.xml
new file mode 100644
index 0000000..7ef5ea9
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ta/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ஃபோன் அனுமதிக்கப்படவில்லை MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-te/strings.xml b/core/res/res/values-mcc313-mnc100-te/strings.xml
new file mode 100644
index 0000000..8908fb7
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-te/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ఫోన్ అనుమతించబడదు MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-th/strings.xml b/core/res/res/values-mcc313-mnc100-th/strings.xml
new file mode 100644
index 0000000..e562744
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-th/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"ไม่อนุญาตให้ใช้โทรศัพท์ MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-tl/strings.xml b/core/res/res/values-mcc313-mnc100-tl/strings.xml
new file mode 100644
index 0000000..6da1dbd
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-tl/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Hindi pinapahintulutan ang telepono MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-tr/strings.xml b/core/res/res/values-mcc313-mnc100-tr/strings.xml
new file mode 100644
index 0000000..7200666
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-tr/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Telefona izin verilmiyor MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-uk/strings.xml b/core/res/res/values-mcc313-mnc100-uk/strings.xml
new file mode 100644
index 0000000..833f9b1
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-uk/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Телефон заборонено (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-ur/strings.xml b/core/res/res/values-mcc313-mnc100-ur/strings.xml
new file mode 100644
index 0000000..d670d0e
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-ur/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"‏فون کی اجازت نہیں ہے MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-uz/strings.xml b/core/res/res/values-mcc313-mnc100-uz/strings.xml
new file mode 100644
index 0000000..202a30c
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-uz/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Chaqiruvlar taqiqlangan (MM#6)"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-vi/strings.xml b/core/res/res/values-mcc313-mnc100-vi/strings.xml
new file mode 100644
index 0000000..6a8c752
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-vi/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Không cho phép điện thoại MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-zh-rCN/strings.xml b/core/res/res/values-mcc313-mnc100-zh-rCN/strings.xml
new file mode 100644
index 0000000..056a75a
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-zh-rCN/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"不受允许的手机 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-zh-rHK/strings.xml b/core/res/res/values-mcc313-mnc100-zh-rHK/strings.xml
new file mode 100644
index 0000000..db85730
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-zh-rHK/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"不允許手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-zh-rTW/strings.xml b/core/res/res/values-mcc313-mnc100-zh-rTW/strings.xml
new file mode 100644
index 0000000..c907e39
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-zh-rTW/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"不支援的手機 MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100-zu/strings.xml b/core/res/res/values-mcc313-mnc100-zu/strings.xml
new file mode 100644
index 0000000..1794f82
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100-zu/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me" msgid="7320955531336937252">"Ifoni ayivunyelwe MM#6"</string>
+</resources>
diff --git a/core/res/res/values-mcc313-mnc100/strings.xml b/core/res/res/values-mcc313-mnc100/strings.xml
new file mode 100644
index 0000000..96af975
--- /dev/null
+++ b/core/res/res/values-mcc313-mnc100/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2006, 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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mmcc_illegal_me">Phone not allowed MM#6</string>
+</resources>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index ef5430c..1deac5d 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Работниот профил е избришан поради исчезнувањето на апликацијата на администратор"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Апликацијата на администраторот за работниот профил или исчезна или е оштетена. Како резултат на тоа, вашиот работен профил и поврзаните податоци ќе се избришат. За помош, контактирајте со администраторот."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Вашиот работен профил веќе не е достапен на уредов"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Премногу обиди за внесување лозинка"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Некој управува со уредот"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Вашата организација управува со уредов и можно е да го следи сообраќајот на мрежата. Допрете за детали."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Уредот ќе се избрише"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Предупредувања"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Демонстрација за малопродажба"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-врска"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Апликацијата работи"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Апликации што ја трошат батеријата"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерија"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> апликации користат батерија"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Метод на внес"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Дејства со текст"</string>
     <string name="email" msgid="4560673117055050403">"E-пошта"</string>
-    <string name="dial" msgid="4204975095406423102">"Телефон"</string>
-    <string name="map" msgid="6068210738233985748">"„Карти“"</string>
-    <string name="browse" msgid="6993590095938149861">"Прелистувач"</string>
+    <string name="dial" msgid="1253998302767701559">"Повикај"</string>
+    <string name="map" msgid="6521159124535543457">"Лоцирај"</string>
+    <string name="browse" msgid="1245903488306147205">"Отвори"</string>
+    <string name="sms" msgid="4560537514610063430">"Порака"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Додај"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Меморијата е речиси полна"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Некои системски функции може да не работат"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Нема доволно меморија во системот. Проверете дали има слободен простор од 250 МБ и рестартирајте."</string>
@@ -1422,9 +1426,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Таблет"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Телевизор"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Слушалки"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Приклучи звучници"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"ХДМИ"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Слушалки"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Систем"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Аудио на Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Безжичен приказ"</string>
@@ -1779,9 +1784,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Тестирање пораки за итни случаи"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Одговори"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Не е дозволена SIM-картичка"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Не е обезбедена SIM-картичка"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Не е дозволена SIM-картичка"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Не е дозволен телефон"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Појавен прозорец"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"За кратенкава е потребна најновата апликација"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Не можеше да се врати кратенката бидејќи апликацијата не поддржува бекап и враќање"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Не можеше да се врати кратенката бидејќи потписот на апликацијата не се совпаѓа"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Не можеше да се врати кратенката"</string>
 </resources>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 6850119..ae897b3 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"അഡ്‌മിൻ ആപ്പ് വിട്ടുപോയിരിക്കുന്നതിനാൽ ഔദ്യോഗിക പ്രൊഫൈൽ ഇല്ലാതാക്കി"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"ഔദ്യോഗിക പ്രൊഫൈൽ അഡ്‌മിൻ ആപ്പ് വിട്ടുപോയിരിക്കുന്നു അല്ലെങ്കിൽ കേടായിരിക്കുന്നു. ഫലമായി, നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈലും ബന്ധപ്പെട്ട വിവരങ്ങളും ഇല്ലാതാക്കിയിരിക്കുന്നു. സഹായത്തിന് അഡ്‌മിനെ ബന്ധപ്പെടുക."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ഈ ഉപകരണത്തിൽ തുടർന്നങ്ങോട്ട് നിങ്ങളുടെ ഔദ്യോഗിക പ്രൊഫൈൽ ലഭ്യമല്ല"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"വളരെയധികം പാസ്‌വേഡ് ശ്രമങ്ങൾ"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ഉപകരണം മാനേജുചെയ്യുന്നുണ്ട്"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"നിങ്ങളുടെ സ്ഥാപനമാണ് ഈ ഉപകരണം മാനേജുചെയ്യുന്നത്, നെറ്റ്‌വർക്ക് ട്രാഫിക്ക് നിരീക്ഷിക്കുകയും ചെയ്തേക്കാം, വിശദാംശങ്ങൾ അറിയാൻ ടാപ്പുചെയ്യുക."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"നിങ്ങളുടെ ഉപകരണം മായ്‌ക്കും"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"അലേർട്ടുകൾ"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"റീട്ടെയിൽ ഡെമോ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB കണക്ഷൻ"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"ആപ്പ് പ്രവർത്തിക്കുന്നു"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ടൈപ്പുചെയ്യൽ രീതി"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"ടെക്‌സ്‌റ്റ് പ്രവർത്തനങ്ങൾ"</string>
     <string name="email" msgid="4560673117055050403">"ഇമെയിൽ"</string>
-    <string name="dial" msgid="4204975095406423102">"ഫോണ്‍"</string>
-    <string name="map" msgid="6068210738233985748">"മാപ്‌സ്"</string>
-    <string name="browse" msgid="6993590095938149861">"ബ്രൗസർ"</string>
+    <string name="dial" msgid="1253998302767701559">"വിളിക്കുക"</string>
+    <string name="map" msgid="6521159124535543457">"കണ്ടെത്തുക"</string>
+    <string name="browse" msgid="1245903488306147205">"തുറക്കുക"</string>
+    <string name="sms" msgid="4560537514610063430">"സന്ദേശം"</string>
+    <string name="add_contact" msgid="7867066569670597203">"ചേർക്കുക"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"സംഭരണയിടം കഴിഞ്ഞു"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"ചില സിസ്റ്റം പ്രവർത്തനങ്ങൾ പ്രവർത്തിക്കണമെന്നില്ല."</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"സിസ്‌റ്റത്തിനായി മതിയായ സംഭരണമില്ല. 250MB സൗജന്യ സംഭരണമുണ്ടെന്ന് ഉറപ്പുവരുത്തി പുനരാരംഭിക്കുക."</string>
@@ -1026,7 +1030,7 @@
     <string name="aerr_process_repeated" msgid="6235302956890402259">"<xliff:g id="PROCESS">%1$s</xliff:g> നിലയ്ക്കുന്നത് തുടരുന്നു"</string>
     <string name="aerr_restart" msgid="7581308074153624475">"ആപ്പ് വീണ്ടും തുറക്കുക"</string>
     <string name="aerr_report" msgid="5371800241488400617">"ഫീഡ്‌ബാക്ക് അയയ്‌ക്കുക"</string>
-    <string name="aerr_close" msgid="2991640326563991340">"അടയ്‌ക്കുക"</string>
+    <string name="aerr_close" msgid="2991640326563991340">"അവസാനിപ്പിക്കുക"</string>
     <string name="aerr_mute" msgid="1974781923723235953">"ഉപകരണം പുനഃരാരംഭിക്കുന്നത് വരെ മ്യൂട്ടുചെയ്യുക"</string>
     <string name="aerr_wait" msgid="3199956902437040261">"കാത്തിരിക്കുക"</string>
     <string name="aerr_close_app" msgid="3269334853724920302">"ആപ്പ് അടയ്‌ക്കുക"</string>
@@ -1189,7 +1193,7 @@
     <string name="usb_notification_message" msgid="3370903770828407960">"കൂടുതൽ ഓപ്ഷനുകൾക്ക് ടാപ്പുചെയ്യുക."</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"അനലോഗ് ഓഡിയോ ആക്‌സസറി കണ്ടെത്തി"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"അറ്റാച്ചുചെയ്‌ത ഉപകരണം ഈ ഫോണിന് അനുയോജ്യമല്ല. കൂടുതലറിയാൻ ടാപ്പുചെയ്യുക."</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB ഡീബഗ്ഗിംഗ് കണക്‌റ്റുചെയ്‌തു"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB ഡീബഗ്ഗിംഗ് കണക്റ്റ് ചെയ്തു"</string>
     <string name="adb_active_notification_message" msgid="4948470599328424059">"USB ഡീബഗ്ഗിംഗ് പ്രവർത്തനരഹിതമാക്കാൻ ടാപ്പുചെയ്യുക."</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB ഡീബഗ്ഗുചെയ്യൽ പ്രവർത്തനരഹിതമാക്കാൻ തിരഞ്ഞെടുക്കുക."</string>
     <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"ബഗ് റിപ്പോർട്ട് എടുക്കുന്നു…"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ടാബ്‌ലെറ്റ്"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ടിവി"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ഫോണ്‍"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ഹെഡ്‌ഫോണുകൾ"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ഡോക്ക് സ്‌പീക്കറുകൾ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ഹെഡ്‌ഫോണുകൾ"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"സിസ്റ്റം"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ബ്ലൂടൂത്ത് ഓഡിയോ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"വയർലെസ് ഡിസ്‌പ്ലേ"</string>
@@ -1696,7 +1701,7 @@
     <string name="floating_toolbar_open_overflow_description" msgid="4797287862999444631">"കൂടുതൽ‍ ഓപ്ഷനുകള്‍"</string>
     <string name="floating_toolbar_close_overflow_description" msgid="559796923090723804">"ഓവർഫ്ലോ അടയ്‌ക്കുക"</string>
     <string name="maximize_button_text" msgid="7543285286182446254">"വലുതാക്കുക"</string>
-    <string name="close_button_text" msgid="3937902162644062866">"അടയ്‌ക്കുക"</string>
+    <string name="close_button_text" msgid="3937902162644062866">"അവസാനിപ്പിക്കുക"</string>
     <string name="notification_messaging_title_template" msgid="3452480118762691020">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
     <plurals name="selected_count" formatted="false" msgid="7187339492915744615">
       <item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> തിരഞ്ഞെടുത്തു</item>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"അടിയന്തര സന്ദേശ ടെസ്റ്റ്"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"മറുപടി നൽകുക"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM അനുവദനീയമല്ല"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM പ്രൊവിഷൻ ചെയ്തിട്ടില്ല"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM അനുവദനീയമല്ല"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ഫോൺ അനുവദനീയമല്ല"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"പോപ്പ് അപ്പ് വിൻഡോ"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ഈ കുറുക്കുവഴിക്ക് ഏറ്റവും പുതിയ ആപ്പ് ആവശ്യമാണ്"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"ആപ്പ് \'ബാക്കപ്പും പുനഃസ്ഥാപിക്കലും\' പിന്തുണയ്ക്കാത്തതിനാൽ കുറുക്കുവഴി പുനഃസ്ഥാപിക്കാനായില്ല"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"ആപ്പ് സിഗ്നേച്ചർ പൊരുത്തപ്പെടാത്തതിനാൽ കുറുക്കുവഴി പുനഃസ്ഥാപിക്കാനായില്ല"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"കുറുക്കുവഴി പുനഃസ്ഥാപിക്കാനായില്ല"</string>
 </resources>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 319ea01..3e1992b 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Админ апп байхгүй байгаа тул ажлын профайлыг устгасан байна"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Ажлын профайлын админ апп байхгүй эсвэл эвдэрсэн байна. Үүний улмаас таны ажлын профайл болон холбогдох мэдээллийг устгасан болно. Тусламж хэрэгтэй бол админтай холбогдоно уу."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Таны ажлын профайл энэ төхөөрөмжид боломжгүй байна"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Нууц үгийг хэт олон удаа буруу оруулсан байна"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Төхөөрөмжийг удирдсан"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Таны байгууллага энэ төхөөрөмжийг удирдаж, сүлжээний ачааллыг хянадаг. Дэлгэрэнгүй мэдээлэл авах бол товшино уу."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Таны төхөөрөмж устах болно."</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Сануулга"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Жижиглэнгийн жишээ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB холболт"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Апп ажиллаж байна"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Апп батерей ашиглаж байна"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> батерей ашиглаж байна"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> апп батерей ашиглаж байна"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Оруулах арга"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Текст үйлдэл"</string>
     <string name="email" msgid="4560673117055050403">"Имэйл"</string>
-    <string name="dial" msgid="4204975095406423102">"Утас"</string>
-    <string name="map" msgid="6068210738233985748">"Газрын зураг"</string>
-    <string name="browse" msgid="6993590095938149861">"Хөтөч"</string>
+    <string name="dial" msgid="1253998302767701559">"Залгах"</string>
+    <string name="map" msgid="6521159124535543457">"Байрших"</string>
+    <string name="browse" msgid="1245903488306147205">"Нээх"</string>
+    <string name="sms" msgid="4560537514610063430">"Зурвас"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Нэмэх"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Сангийн хэмжээ дутагдаж байна"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Зарим систем функц ажиллахгүй байна"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Системд хангалттай сан байхгүй байна. 250MБ чөлөөтэй зай байгаа эсэхийг шалгаад дахин эхлүүлнэ үү."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Таблет"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Tелевиз"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Утас"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Чихэвч"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Чанга яригчийг суулгах"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Чихэвч"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Систем"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth аудио"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Утасгүй дэлгэц"</string>
@@ -1775,9 +1780,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Онцгой байдлын зурвасын тест"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Хариу бичих"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM боломжгүй"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-г хийгээгүй"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM боломжгүй"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Утас боломжгүй"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"гэнэт гарч ирэх цонх"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Энэ товчлолд саяхны апп шаардлагатай"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Апп нөөцлөлт, сэргээлтийг дэмждэггүй тул товчлолыг сэргээж чадсангүй"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Аппын гарын үсэг таарахгүй байгаа тул товчлолыг сэргээж чадсангүй"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Товчлолыг сэргээж чадсангүй"</string>
 </resources>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 8436eb4..96a9eda 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"प्रशासक अॅप गहाळ असल्यामुळे कार्य प्रोफाइल हटवले गेले"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"कार्य प्रोफाइल प्रशासक अॅप गहाळ आहे किंवा करप्ट आहे. परिणामी, आपले कार्य प्रोफाइल आणि संबंधित डेटा हटवले गेले आहेत. सहाय्यासाठी आपल्या प्रशासकाशी संपर्क साधा."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"आपले कार्य प्रोफाइल आता या डिव्हाइसवर उपलब्‍ध नाही"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"बर्‍याचदा पासवर्ड टाकण्‍याचा प्रयत्‍न केला"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"डिव्हाइस व्यवस्थापित केले आहे"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"आपली संस्था हे डिव्हाइस व्यवस्थापित करते आणि नेटवर्क रहदारीचे निरीक्षण करू शकते. तपशीलांसाठी टॅप करा."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"तुमचे डिव्हाइस मिटविले जाईल"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"सूचना"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"रीटेल डेमो"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB कनेक्‍शन"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"APP चालत आहे"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"अॅप्‍समुळे बॅटरी संपत आहे"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> बॅटरी वापरत आहे"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> अॅप्‍स बॅटरी वापरत आहेत"</string>
@@ -641,7 +643,7 @@
     <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string>
     <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"कार्य मोबाईल"</string>
     <string name="phoneTypeWorkPager" msgid="649938731231157056">"कार्य पेजर"</string>
-    <string name="phoneTypeAssistant" msgid="5596772636128562884">"सहाय्यक"</string>
+    <string name="phoneTypeAssistant" msgid="5596772636128562884">"साहाय्यक"</string>
     <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string>
     <string name="eventTypeCustom" msgid="7837586198458073404">"सानुकूल"</string>
     <string name="eventTypeBirthday" msgid="2813379844211390740">"वाढदिवस"</string>
@@ -674,7 +676,7 @@
     <string name="orgTypeOther" msgid="3951781131570124082">"अन्य"</string>
     <string name="orgTypeCustom" msgid="225523415372088322">"सानुकूल"</string>
     <string name="relationTypeCustom" msgid="3542403679827297300">"सानुकूल"</string>
-    <string name="relationTypeAssistant" msgid="6274334825195379076">"सहाय्यक"</string>
+    <string name="relationTypeAssistant" msgid="6274334825195379076">"साहाय्यक"</string>
     <string name="relationTypeBrother" msgid="8757913506784067713">"भाऊ"</string>
     <string name="relationTypeChild" msgid="1890746277276881626">"मूल"</string>
     <string name="relationTypeDomesticPartner" msgid="6904807112121122133">"घरातील जोडीदार"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"इनपुट पद्धत"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"मजकूर क्रिया"</string>
     <string name="email" msgid="4560673117055050403">"ईमेल"</string>
-    <string name="dial" msgid="4204975095406423102">"फोन"</string>
-    <string name="map" msgid="6068210738233985748">"नकाशे"</string>
-    <string name="browse" msgid="6993590095938149861">"ब्राउझर"</string>
+    <string name="dial" msgid="1253998302767701559">"कॉल करा"</string>
+    <string name="map" msgid="6521159124535543457">"शोधा"</string>
+    <string name="browse" msgid="1245903488306147205">"उघडा"</string>
+    <string name="sms" msgid="4560537514610063430">"संदेश"</string>
+    <string name="add_contact" msgid="7867066569670597203">"जोडा"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"संचयन स्थान संपत आहे"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"काही सिस्टम कार्ये कार्य करू शकत नाहीत"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"सिस्टीमसाठी पुरेसे संचयन नाही. आपल्याकडे 250MB मोकळे स्थान असल्याचे सुनिश्चित करा आणि रीस्टार्ट करा."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"टॅबलेट"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"टीव्ही"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"फोन"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"हेडफोन"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"स्पीकर डॉक करा"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"हेडफोन"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"सिस्टम"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ब्लूटूथ ऑडिओ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"वायरलेस डिस्प्ले"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"आणीबाणी संदेश चाचणी"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"प्रत्युत्तर द्या"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"सिमला अनुमती नाही"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"सिमसाठी तरतूद नाही"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"सिमला अनुमती नाही"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"फोनला अनुमती नाही"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"पॉपअप विंडो"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"या शॉर्टकटला नवीनतम अॅपची आवश्यकता आहे"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"अॅप बॅकअप आणि रिस्‍टोअर करण्यास सपोर्ट देत नसल्यामुळे शॉर्टकट रिस्‍टोअर करू शकलो नाही"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"अॅप स्वाक्षरी न जुळल्यामुळे शॉर्टकट रिस्‍टोअर करू शकलो नाही"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"शॉर्टकट रिस्‍टोअर करू शकलो नाही"</string>
 </resources>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 67d81d4..3753fb2 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profil kerja dipadamkan kerana ketiadaan apl pentadbir"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Apl pentadbir profil kerja tiada atau rosak. Akibatnya, profil kerja anda dan data yang berkaitan telah dipadamkan. Hubungi pentadbir anda untuk mendapatkan bantuan."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Profil kerja anda tidak lagi tersedia pada peranti ini"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Terlalu banyak percubaan kata laluan"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Peranti ini diurus"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organisasi anda mengurus peranti ini dan mungkin memantau trafik rangkaian. Ketik untuk mendapatkan butiran."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Peranti anda akan dipadam"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Makluman"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Tunjuk cara runcit"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Sambungan USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Apl berjalan"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apl yang menggunakan bateri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan bateri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apl sedang menggunakan bateri"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Kaedah input"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tindakan teks"</string>
     <string name="email" msgid="4560673117055050403">"E-mel"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Peta"</string>
-    <string name="browse" msgid="6993590095938149861">"Penyemak imbas"</string>
+    <string name="dial" msgid="1253998302767701559">"Panggil"</string>
+    <string name="map" msgid="6521159124535543457">"Cari"</string>
+    <string name="browse" msgid="1245903488306147205">"Buka"</string>
+    <string name="sms" msgid="4560537514610063430">"Mesej"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Tambah"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Ruang storan semakin berkurangan"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Beberapa fungsi sistem mungkin tidak berfungsi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Tidak cukup storan untuk sistem. Pastikan anda mempunyai 250MB ruang kosong dan mulakan semula."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fon kepala"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Pembesar suara dok"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fon kepala"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Paparan wayarles"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Ujian mesej kecemasan"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Balas"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM tidak dibenarkan"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM tidak diperuntukkan"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM tidak dibenarkan"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon tidak dibenarkan"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Tetingkap Timbul"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Pintasan ini memerlukan apl yang terbaharu"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Tidak dapat memulihkan pintasan kerana apl tidak menyokong sandaran dan segerakan"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Tidak dapat memulihkan pintasan kerana ketakpadanan tandatangan apl"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Tidak dapat memulihkan pintasan"</string>
 </resources>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index d432505..0f0ae23 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"စီမံခန့်ခွဲရန် အက်ပ်မရှိသောကြောင့် အလုပ်ပရိုဖိုင်ကို ဖျက်လိုက်ခြင်းဖြစ်သည်"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"အလုပ်ပရိုဖိုင် စီမံခန့်ခွဲရန်အက်ပ် မရှိပါ သို့မဟုတ် ပျက်စီးနေပါသည်။ ထို့ကြောင့် သင်၏ အလုပ်ပရိုဖိုင်နှင့် ဆက်စပ်နေသော ဒေတာများကို ဖျက်လိုက်ပါပြီ။ အကူအညီရယူရန် သင်၏စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ။"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ဤစက်ပစ္စည်းတွင် သင်၏ အလုပ်ပရိုဖိုင်မရှိတော့ပါ"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"စကားဝှက်ထည့်သွင်းရန် ကြိုးစားသည့် အကြိမ်အရေအတွက် အလွန်များသွား၍ ဖြစ်ပါသည်"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"စက်ပစ္စည်းကို စီမံခန့်ခွဲထားပါသည်"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ဤစက်ပစ္စည်းကို သင်၏ အဖွဲ့အစည်းက စီမံပြီး ကွန်ရက်အသွားအလာကို စောင့်ကြည့်နိုင်ပါသည်။ ထပ်မံလေ့လာရန် တို့ပါ။"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"သင့်ကိရိယာအား ပယ်ဖျက်လိမ့်မည်"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"သတိပေးချက်များ"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"လက်လီအရောင်းဆိုင် သရုပ်ပြမှု"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB ချိတ်ဆက်မှု"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"APP လုပ်ဆောင်နေသည်"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"အက်ပ်များက ဘက်ထရီကုန်စေသည်"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> က ဘက်ထရီကို အသုံးပြုနေသည်"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"အက်ပ် <xliff:g id="NUMBER">%1$d</xliff:g> ခုက ဘက်ထရီကို အသုံးပြုနေသည်"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ထည့်သွင်းရန်နည်းလမ်း"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"စာတို လုပ်ဆောင်ချက်"</string>
     <string name="email" msgid="4560673117055050403">"အီးမေးလ်"</string>
-    <string name="dial" msgid="4204975095406423102">"ဖုန်း"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"ဘရောင်ဇာ"</string>
+    <string name="dial" msgid="1253998302767701559">"ခေါ်ဆိုရန်"</string>
+    <string name="map" msgid="6521159124535543457">"တည်နေရာ"</string>
+    <string name="browse" msgid="1245903488306147205">"ဖွင့်ရန်"</string>
+    <string name="sms" msgid="4560537514610063430">"စာပို့ရန်"</string>
+    <string name="add_contact" msgid="7867066569670597203">"ထည့်ရန်"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"သိမ်းဆည်သော နေရာ နည်းနေပါသည်"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"တချို့ စနစ်လုပ်ငန်းများ အလုပ် မလုပ်ခြင်း ဖြစ်နိုင်ပါသည်"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"စနစ်အတွက် သိုလှောင်ခန်း မလုံလောက်ပါ။ သင့်ဆီမှာ နေရာလွတ် ၂၅၀ MB ရှိတာ စစ်ကြည့်ပြီး စတင်ပါ။"</string>
@@ -1180,7 +1184,7 @@
     <string name="no_permissions" msgid="7283357728219338112">"ခွင့်ပြုချက်မလိုအပ်ပါ"</string>
     <string name="perm_costs_money" msgid="4902470324142151116">"သင့်အတွက် ပိုက်ဆံကုန်ကျနိုင်ပါသည်"</string>
     <string name="dlg_ok" msgid="7376953167039865701">"အိုကေ"</string>
-    <string name="usb_charging_notification_title" msgid="6895185153353640787">"USB ဖြင့်ဤစက်ပစ္စည်းကို အားသွင်းနေသည်"</string>
+    <string name="usb_charging_notification_title" msgid="6895185153353640787">"ဤစက်ကို USB ဖြင့် အားသွင်းနေသည်"</string>
     <string name="usb_supplying_notification_title" msgid="5310642257296510271">"ချိတ်ဆက်ထားသည့် စက်ပစ္စည်းကို USB မှတစ်ဆင့် အားသွင်းနေသည်"</string>
     <string name="usb_mtp_notification_title" msgid="8396264943589760855">"ဖိုင်လွှဲပြောင်းရန်အတွက် USB"</string>
     <string name="usb_ptp_notification_title" msgid="1347328437083192112">"ဓာတ်ပုံလွှဲပြောင်းရန်အတွက် USB"</string>
@@ -1189,8 +1193,8 @@
     <string name="usb_notification_message" msgid="3370903770828407960">"နောက်ထပ်ရွေးချယ်စရာများအတွက် တို့ပါ။"</string>
     <string name="usb_unsupported_audio_accessory_title" msgid="3529881374464628084">"အန်နာလော့ အသံကိရိယာကို တွေ့ထားပါသည်"</string>
     <string name="usb_unsupported_audio_accessory_message" msgid="6309553946441565215">"တပ်ဆင်ထားသော ကိရိယာကို ဤဖုန်းနှင့် တွဲသုံး၍မရပါ။ ပိုမိုလေ့လာရန် တို့ပါ။"</string>
-    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB အမှားစစ်ခြင်းအား ချိတ်ဆက်ထားသည်"</string>
-    <string name="adb_active_notification_message" msgid="4948470599328424059">"USB ဆက်သွယ်ရေးစနစ်ကို ပိတ်ရန် တို့ပါ။"</string>
+    <string name="adb_active_notification_title" msgid="6729044778949189918">"USB အမှားရှာပြင်စနစ် ချိတ်ဆက်ထားသည်"</string>
+    <string name="adb_active_notification_message" msgid="4948470599328424059">"USB အမှားရှာပြင်စနစ် ပိတ်ရန် တို့ပါ။"</string>
     <string name="adb_active_notification_message" product="tv" msgid="8470296818270110396">"USB ဖြင့် အမှားရှာပြင်ခြင်းကို ပိတ်ရန် ရွေးပါ။"</string>
     <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"ချွတ်ယွင်းချက် အစီရင်ခံစာပြုစုနေသည်..."</string>
     <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"ချွတ်ယွင်းချက် အစီရင်ခံစာကို မျှဝေမလား။"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"တက်ဘလက်"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"တီဗွီ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ဖုန်း"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"နားကြပ်"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"အထိုင်ရှိသော စပီကာများ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"နားကြပ်"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"စနစ်"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ဘလူးတုသ် အသံ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ကြိုးမဲ့ပြသခြင်း"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"အရေးပေါ် မက်ဆေ့ဂျ် စမ်းသပ်မှု"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"စာပြန်ရန်"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"ဆင်းမ်ကို ခွင့်မပြုပါ"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"ဆင်းမ်ကို ထောက်ပံ့မထားပါ"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"ဆင်းမ်ကို ခွင့်မပြုပါ"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ဖုန်းကို ခွင့်မပြုပါ"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"ပေါ့ပ်အပ် ဝင်းဒိုး"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ဤဖြတ်လမ်းလင့်ခ်ကို အသုံးပြုနိုင်ရန် နောက်ဆုံးထွက်အက်ပ် လိုအပ်ပါသည်"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"အက်ပ်သည် မိတ္တူကူးခြင်းနှင့် ပြန်ယူခြင်းကို ပံ့ပိုးခြင်းမရှိသည့်အတွက် ဖြတ်လမ်းလင့်ခ်ကို ပြန်ယူ၍မရပါ"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"အက်ပ်လက်မှတ် မတူညီသည့်အတွက် ဖြတ်လမ်းလင့်ခ်များကို ပြန်ယူ၍မရပါ"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"ဖြတ်လမ်းလင့်ခ်ကို ပြန်ယူ၍မရပါ"</string>
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 07a622c..93e978c 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Jobbprofilen er slettet på grunn av manglende administratorapp"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Administratorappen for jobbprofilen mangler eller er skadet. Dette har ført til at jobbprofilen og alle data knyttet til den, har blitt slettet. Ta kontakt med administratoren for å få hjelp."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Jobbprofilen din er ikke lenger tilgjengelig på denne enheten"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"For mange passordforsøk"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Enheten administreres"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organisasjonen din kontrollerer denne enheten og kan overvåke nettverkstrafikk. Trykk for å få mer informasjon."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Enheten blir slettet"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Varsler"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Butikkdemo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-tilkobling"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App kjører"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apper bruker batteri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker batteri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apper bruker batteri"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Teksthandlinger"</string>
     <string name="email" msgid="4560673117055050403">"E-post"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Kart"</string>
-    <string name="browse" msgid="6993590095938149861">"Nettleser"</string>
+    <string name="dial" msgid="1253998302767701559">"Ring"</string>
+    <string name="map" msgid="6521159124535543457">"Finn"</string>
+    <string name="browse" msgid="1245903488306147205">"Åpne"</string>
+    <string name="sms" msgid="4560537514610063430">"Melding"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Legg til"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Lite ledig lagringsplass"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Enkelte systemfunksjoner fungerer muligens ikke slik de skal"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Det er ikke nok lagringsplass for systemet. Kontrollér at du har 250 MB ledig plass, og start på nytt."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Nettbrett"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Google TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hodetelefoner"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dokkhøyttalere"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hodetelefoner"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-lyd"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Trådløs skjerm"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test av nødmeldinger"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Svar"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-kortet er ikke tillatt"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-kortet er ikke klargjort"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-kortet er ikke tillatt"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefonen er ikke tillatt"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Forgrunnsvindu"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Denne snarveien krever den nyeste appen"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Kunne ikke gjenopprette snarveien fordi appen ikke støtter sikkerhetskopiering og gjenoppretting"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Kunne ikke gjenopprette snarveien på grunn av manglende samsvar for appsignaturen"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Kunne ikke gjenopprette snarveien"</string>
 </resources>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 581f344..7de97b3 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"प्रशासकीय अनुप्रयोग नभएकाले कार्य प्रोफाइल मेटाइयो"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"उक्त कार्य प्रोफाइलको प्रशासकीय अनुप्रयोग छैन वा बिग्रेको छ। त्यसले गर्दा, तपाईंको कार्य प्रोफाइल र सम्बन्धित डेटालाई मेटिएको छ। सहायताका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्।"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"तपाईंको कार्य प्रोफाइल अब उप्रान्त यस यन्त्रमा उपलब्ध छैन"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"पासवर्ड प्रविष्ट गर्ने अत्यधिक गलत प्रयासहरू भए"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"यन्त्र व्यवस्थित गरिएको छ"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"तपाईंको संगठनले यस यन्त्रको व्यवस्थापन गर्दछ र नेटवर्क ट्राफिकको अनुगमन गर्न सक्छ। विवरणहरूका लागि ट्याप गर्नुहोस्।"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"तपाईंको यन्त्र मेटिनेछ"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"अलर्टहरू"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"खुद्रा बिक्री सम्बन्धी डेमो"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB जडान"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"अनुप्रयोग चलिरहेको छ"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"अनुप्रयोगहरूले ब्याट्री खपत गर्दै छन्"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले ब्याट्री प्रयोग गर्दै छ"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> अनुप्रयोगहरूले ब्याट्री प्रयोग गर्दै छन्"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"निवेश विधि"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"पाठ कार्यहरू"</string>
     <string name="email" msgid="4560673117055050403">"इमेल"</string>
-    <string name="dial" msgid="4204975095406423102">"फोन गर्नुहोस्"</string>
-    <string name="map" msgid="6068210738233985748">"नक्सा"</string>
-    <string name="browse" msgid="6993590095938149861">"ब्राउजर"</string>
+    <string name="dial" msgid="1253998302767701559">"कल"</string>
+    <string name="map" msgid="6521159124535543457">"पत्ता लगाउनुहोस्"</string>
+    <string name="browse" msgid="1245903488306147205">"खोल्नुहोस्"</string>
+    <string name="sms" msgid="4560537514610063430">"सन्देश"</string>
+    <string name="add_contact" msgid="7867066569670597203">"थप्नुहोस्"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"भण्डारण ठाउँ सकिँदै छ"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"सायद केही प्रणाली कार्यक्रमहरूले काम गर्दैनन्"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"प्रणालीको लागि पर्याप्त भण्डारण छैन। तपाईँसँग २५० मेगा बाइट ठाउँ खाली भएको निश्चित गर्नुहोस् र फेरि सुरु गर्नुहोस्।"</string>
@@ -1426,9 +1430,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ट्याब्लेट"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"फोन"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"हेडफोनहरू"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"डक स्पिकरहरू"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"हेडफोनहरू"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"प्रणाली"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"ब्लुटुथ अडियो"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ताररहित प्रदर्शन"</string>
@@ -1627,7 +1632,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"कार्यालयको दोस्रो <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"कार्यालयको तेस्रो <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"यस स्क्रिनलाई अनपनि गर्न पछाडि जाने र परिदृश्य बटनहरूलाई छोएर थिची राख्नुहोस्"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"यस अनुप्रयोगलाई अनपिन गर्न मिल्दैन"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"स्क्रिन पिन गरियो"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"स्क्रिन अनपिन गरियो"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"पिन निकाल्नुअघि PIN सोध्नुहोस्"</string>
@@ -1783,9 +1787,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"आपतकालीन सन्देशहरूको परीक्षण"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"जवाफ दिनुहोस्"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM लाई अनुमति छैन"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM को प्रावधान छैन"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM लाई अनुमति छैन"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"फोनलाई अनुमति छैन"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM मार्फत भ्वाइस कल गर्न मिल्दैन"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM मार्फत भ्वाइस कल गर्ने प्रावधान छैन"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM मार्फत भ्वाइस कल गर्न मिल्दैन"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"फोनमार्फत भ्वाइस कल गर्न मिल्दैन"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"पपअप विन्डो"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"यो सर्टकटलाई पछिल्लो अनुप्रयोग आवश्यक हुन्छ"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"अनुप्रयोगले ब्याकअप तथा पुनर्स्थापनालाई समर्थन नगर्ने हुँदा सर्टकट पुनर्स्थापित गर्न सकिएन"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"अनुप्रयोगमा प्रयोग गरिने हस्ताक्षर नमिल्ने हुँदा सर्टकट पुनर्स्थापित गर्न सकिएन"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"सर्टकट पुनर्स्थापित गर्न सकिएन"</string>
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 45dd701..f99d531 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Werkprofiel verwijderd vanwege ontbrekende beheer-app"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"De beheer-app van het werkprofiel ontbreekt of is beschadigd. Als gevolg hiervan zijn je werkprofiel en alle gerelateerde gegevens verwijderd. Neem contact op met je beheerder voor hulp."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Je werkprofiel is niet meer beschikbaar op dit apparaat"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Te veel wachtwoordpogingen"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Apparaat wordt beheerd"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Dit apparaat wordt beheerd door je organisatie. Het netwerkverkeer kan worden bijgehouden. Tik voor meer informatie."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Je apparaat wordt gewist"</string>
@@ -210,9 +211,9 @@
     <string name="global_action_lock" msgid="2844945191792119712">"Schermvergrendeling"</string>
     <string name="global_action_power_off" msgid="4471879440839879722">"Uitschakelen"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"Noodgeval"</string>
-    <string name="global_action_bug_report" msgid="7934010578922304799">"Foutenrapport"</string>
-    <string name="bugreport_title" msgid="2667494803742548533">"Foutenrapport genereren"</string>
-    <string name="bugreport_message" msgid="398447048750350456">"Hiermee worden gegevens over de huidige status van je apparaat verzameld en als e-mail verzonden. Wanneer u een foutenrapport start, duurt het even voordat het kan worden verzonden. Even geduld alstublieft."</string>
+    <string name="global_action_bug_report" msgid="7934010578922304799">"Bugrapport"</string>
+    <string name="bugreport_title" msgid="2667494803742548533">"Bugrapport genereren"</string>
+    <string name="bugreport_message" msgid="398447048750350456">"Hiermee worden gegevens over de huidige status van je apparaat verzameld en als e-mail verzonden. Wanneer u een bugrapport start, duurt het even voordat het kan worden verzonden. Even geduld alstublieft."</string>
     <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Interactief rapport"</string>
     <string name="bugreport_option_interactive_summary" msgid="229299488536107968">"Gebruik deze optie in de meeste situaties. Hiermee kun je de voortgang van het rapport bijhouden, meer gegevens over het probleem opgeven en screenshots maken. Mogelijk worden bepaalde minder vaak gebruikte gedeelten weggelaten (waarvoor het lang zou duren om een rapport te genereren)."</string>
     <string name="bugreport_option_full_title" msgid="6354382025840076439">"Volledig rapport"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Meldingen"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo voor de detailhandel"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-verbinding"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App actief"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps die de batterij gebruiken"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt de batterij"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps gebruiken de batterij"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tekstacties"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefoon"</string>
-    <string name="map" msgid="6068210738233985748">"Kaarten"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Bellen"</string>
+    <string name="map" msgid="6521159124535543457">"Zoeken"</string>
+    <string name="browse" msgid="1245903488306147205">"Openen"</string>
+    <string name="sms" msgid="4560537514610063430">"Bericht"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Toevoegen"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Opslagruimte is bijna vol"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Bepaalde systeemfuncties werken mogelijk niet"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Onvoldoende opslagruimte voor het systeem. Zorg ervoor dat je 250 MB vrije ruimte hebt en start opnieuw."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Tv"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefoon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hoofdtelefoon"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dockluidsprekers"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hoofdtelefoon"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Systeem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Draadloze weergave"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2e <xliff:g id="LABEL">%1$s</xliff:g>, werk"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3e <xliff:g id="LABEL">%1$s</xliff:g>, werk"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Tik op Terug en Overzicht en houd deze knoppen vast om dit scherm los te maken"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Deze app kan niet worden losgemaakt"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Scherm vastgezet"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Scherm losgemaakt"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Vraag pin voor losmaken"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test voor noodberichten"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Beantwoorden"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Simkaart niet toegestaan"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Simkaart niet geregistreerd"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Simkaart niet toegestaan"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefoon niet toegestaan"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Simkaart niet toegestaan voor spraak"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Sim niet geregistreerd voor spraak"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"Simkaart niet toegestaan voor spraak"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefoon niet toegestaan voor spraak"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-upvenster"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Voor deze snelkoppeling is de nieuwste app vereist"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Kan snelkoppeling niet herstellen omdat de app geen ondersteuning biedt voor \'Back-up maken en terugzetten\'"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Kan snelkoppeling niet herstellen vanwege een niet-overeenkomende app-ondertekening"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Kan snelkoppeling niet herstellen"</string>
 </resources>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index bc09332..d6233fb 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -45,10 +45,10 @@
     <string name="invalidPuk" msgid="8761456210898036513">"ਇੱਕ PUK ਕੋਡ ਟਾਈਪ ਕਰੋ ਜੋ 8 ਜਾਂ ਵੱਧ ਸੰਖਿਆਵਾਂ ਦਾ ਹੋਵੇ।"</string>
     <string name="needPuk" msgid="919668385956251611">"ਤੁਹਾਡਾ ਸਿਮ ਕਾਰਡ PUK-ਲੌਕਡ ਹੈ। ਇਸਨੂੰ ਅਣਲਾਕ ਕਰਨ ਲਈ PUK ਕੋਡ ਟਾਈਪ ਕਰੋ।"</string>
     <string name="needPuk2" msgid="4526033371987193070">"SIM ਕਾਰਡ ਅਨਬਲੌਕ ਕਰਨ ਲਈ PUK2 ਟਾਈਪ ਕਰੋ।"</string>
-    <string name="enablePin" msgid="209412020907207950">"ਅਸਫਲ, SIM/RUIM ਲੌਕ ਨੂੰ ਚਾਲੂ ਕਰੋ।"</string>
+    <string name="enablePin" msgid="209412020907207950">"ਅਸਫਲ, SIM/RUIM  ਲਾਕ  ਨੂੰ ਚਾਲੂ ਕਰੋ।"</string>
     <plurals name="pinpuk_attempts" formatted="false" msgid="1251012001539225582">
-      <item quantity="one">ਇਸਤੋਂ ਪਹਿਲਾਂ ਕਿ SIM ਲੌਕ ਹੋਵੇ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item>
-      <item quantity="other">ਇਸਤੋਂ ਪਹਿਲਾਂ ਕਿ SIM ਲੌਕ ਹੋਵੇ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item>
+      <item quantity="one">ਇਸਤੋਂ ਪਹਿਲਾਂ ਕਿ SIM  ਲਾਕ  ਹੋਵੇ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item>
+      <item quantity="other">ਇਸਤੋਂ ਪਹਿਲਾਂ ਕਿ SIM  ਲਾਕ  ਹੋਵੇ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item>
     </plurals>
     <string name="imei" msgid="2625429890869005782">"IMEI"</string>
     <string name="meid" msgid="4841221237681254195">"MEID"</string>
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"ਗੁੰਮਸ਼ੁਦਾ ਪ੍ਰਸ਼ਾਸਕ ਐਪ ਦੇ ਕਾਰਨ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਮਿਟਾਇਆ ਗਿਆ"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਪ੍ਰਸ਼ਾਸਕ ਐਪ ਜਾਂ ਤਾਂ ਗੁੰਮਸ਼ੁਦਾ ਹੈ ਜਾਂ ਖਰਾਬ ਹੈ। ਨਤੀਜੇ ਵਜੋਂ, ਤੁਹਾਡੀ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਅਤੇ ਸਬੰਧਿਤ ਡਾਟਾ ਮਿਟਾਇਆ ਗਿਆ ਹੈ। ਸਹਾਇਤਾ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ਤੁਹਾਡਾ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਹੁਣ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"ਕਈ ਵਾਰ ਗਲਤ ਪਾਸਵਰਡ ਦਾਖਲ ਕੀਤਾ ਗਿਆ"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਨ ਅਧੀਨ ਹੈ"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ਤੁਹਾਡਾ ਸੰਗਠਨ ਇਸ ਡੀਵਾਈਸ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਦਾ ਹੈ ਅਤੇ ਨੈੱਟਵਰਕ ਟਰੈਫਿਕ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦਾ ਹੈ। ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"ਤੁਹਾਡਾ ਡੀਵਾਈਸ ਮਿਟਾਇਆ ਜਾਏਗਾ"</string>
@@ -183,7 +184,7 @@
     <string name="silent_mode" msgid="7167703389802618663">"ਸਾਈਲੈਂਟ ਮੋਡ"</string>
     <string name="turn_on_radio" msgid="3912793092339962371">"ਵਾਇਰਲੈਸ ਚਾਲੂ ਕਰੋ"</string>
     <string name="turn_off_radio" msgid="8198784949987062346">"ਵਾਇਰਲੈਸ ਬੰਦ ਕਰੋ"</string>
-    <string name="screen_lock" msgid="799094655496098153">"ਸਕ੍ਰੀਨ ਲੌਕ"</string>
+    <string name="screen_lock" msgid="799094655496098153">"ਸਕ੍ਰੀਨ  ਲਾਕ"</string>
     <string name="power_off" msgid="4266614107412865048">"ਪਾਵਰ ਬੰਦ"</string>
     <string name="silent_mode_silent" msgid="319298163018473078">"ਰਿੰਗਰ ਬੰਦ"</string>
     <string name="silent_mode_vibrate" msgid="7072043388581551395">"ਰਿੰਗਰ ਥਰਥਰਾਹਟ"</string>
@@ -207,7 +208,7 @@
     <string name="global_actions" product="tablet" msgid="408477140088053665">"ਟੈਬਲੈੱਟ ਵਿਕਲਪ"</string>
     <string name="global_actions" product="tv" msgid="7240386462508182976">"TV ਚੋਣਾਂ"</string>
     <string name="global_actions" product="default" msgid="2406416831541615258">"ਫ਼ੋਨ ਚੋਣਾਂ"</string>
-    <string name="global_action_lock" msgid="2844945191792119712">"ਸਕ੍ਰੀਨ ਲੌਕ"</string>
+    <string name="global_action_lock" msgid="2844945191792119712">"ਸਕ੍ਰੀਨ  ਲਾਕ"</string>
     <string name="global_action_power_off" msgid="4471879440839879722">"ਪਾਵਰ ਬੰਦ"</string>
     <string name="global_action_emergency" msgid="7112311161137421166">"ਸੰਕਟਕਾਲ"</string>
     <string name="global_action_bug_report" msgid="7934010578922304799">"ਬਗ ਰਿਪੋਰਟ"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ਸੁਚੇਤਨਾਵਾਂ"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"ਪ੍ਰਚੂਨ ਸਟੋਰਾਂ ਲਈ ਡੈਮੋ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB ਕਨੈਕਸ਼ਨ"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"ਐਪ ਚੱਲ ਰਹੀ ਹੈ"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ਬੈਟਰੀ ਦੀ ਖਪਤ ਕਰਨ ਵਾਲੀਆਂ ਐਪਾਂ"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ਐਪਾਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀਆਂ ਹਨ"</string>
@@ -340,7 +342,7 @@
     <string name="permdesc_systemAlertWindow" msgid="2393776099672266188">"ਇਹ ਐਪ ਹੋਰ ਐਪਾਂ ਦੇ ਸਿਖਰ \'ਤੇ ਜਾਂ ਸਕ੍ਰੀਨ ਦੇ ਹੋਰ ਭਾਗਾਂ \'ਤੇ ਵਿਖਾਈ ਦੇ ਸਕਦੀ ਹੈ। ਇਹ ਸਧਾਰਨ ਐਪ ਵਰਤੋਂ ਵਿੱਚ ਵਿਘਨ ਪਾ ਸਕਦੀ ਹੈ ਅਤੇ ਹੋਰ ਐਪਾਂ ਦੇ ਵਿਖਾਈ ਦੇਣ ਦੇ ਤਰੀਕੇ ਨੂੰ ਬਦਲ ਸਕਦੀ ਹੈ।"</string>
     <string name="permlab_runInBackground" msgid="7365290743781858803">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚਲਾਓ"</string>
     <string name="permdesc_runInBackground" msgid="7370142232209999824">"ਇਹ ਐਪ ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਚੱਲ ਸਕਦੀ ਹੈ। ਇਸ ਨਾਲ ਬੈਟਰੀ ਵਧੇਰੇ ਤੇਜ਼ੀ ਨਾਲ ਖਤਮ ਹੋ ਸਕਦੀ ਹੈ।"</string>
-    <string name="permlab_useDataInBackground" msgid="8694951340794341809">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਡੈਟੇ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
+    <string name="permlab_useDataInBackground" msgid="8694951340794341809">"ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ  ਡਾਟੇ  ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="permdesc_useDataInBackground" msgid="6049514223791806027">"ਇਹ ਐਪ ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਡਾਟੇ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦੀ ਹੈ। ਇਸ ਨਾਲ ਡਾਟਾ ਵਰਤੋਂ ਵਧ ਸਕਦੀ ਹੈ।"</string>
     <string name="permlab_persistentActivity" msgid="8841113627955563938">"ਐਪ ਨੂੰ ਹਮੇਸ਼ਾਂ ਚਲਾਏ ਰੱਖੋ"</string>
     <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"ਐਪ ਨੂੰ ਮੈਮਰੀ ਵਿੱਚ ਖੁਦ ਦੇ ਭਾਗਾਂ ਨੂੰ ਸਥਾਈ ਬਣਾਉਣ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਟੈਬਲੈੱਟ ਨੂੰ ਹੌਲੀ ਕਰਦੇ ਹੋਏ ਹੋਰਾਂ ਐਪਾਂ ਲਈ ਉਪਲਬਧ ਮੈਮਰੀ ਨੂੰ ਸੀਮਿਤ ਕਰ ਸਕਦਾ ਹੈ।"</string>
@@ -464,7 +466,7 @@
     <string name="permdesc_bluetooth" product="default" msgid="3207106324452312739">"ਐਪ ਨੂੰ ਬਲੂਟੁੱਥ ਤੇ ਬਲੂਟੁੱਥ ਦਾ ਸੰਰੂਪਣ ਦੇਖਣ, ਜੋੜਾਬੱਧ ਕੀਤੇ ਡੀਵਾਈਸਾਂ ਨਾਲ ਕਨੈਕਸ਼ਨ ਬਣਾਉਣ ਅਤੇ ਸਵੀਕਾਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
     <string name="permlab_nfc" msgid="4423351274757876953">"ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ ਤੇ ਨਿਯੰਤਰਣ ਪਾਓ"</string>
     <string name="permdesc_nfc" msgid="7120611819401789907">"ਐਪ ਨੂੰ ਨਜ਼ਦੀਕੀ ਖੇਤਰ ਸੰਚਾਰ (NFC) ਟੈਗਾਂ, ਕਾਰਡਾਂ ਅਤੇ ਰੀਡਰਾਂ ਨਾਲ ਸੰਚਾਰ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
-    <string name="permlab_disableKeyguard" msgid="3598496301486439258">"ਆਪਣਾ ਸਕ੍ਰੀਨ ਲੌਕ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
+    <string name="permlab_disableKeyguard" msgid="3598496301486439258">"ਆਪਣਾ ਸਕ੍ਰੀਨ  ਲਾਕ  ਅਸਮਰੱਥ ਬਣਾਓ"</string>
     <string name="permdesc_disableKeyguard" msgid="6034203065077122992">"ਐਪ ਨੂੰ ਕੀਲਾਕ ਅਤੇ ਕਿਸੇ ਵੀ ਸੰਬੰਧਿਤ ਪਾਸਵਰਡ ਸੁਰੱਖਿਆ ਨੂੰ ਬੰਦ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਉਦਾਹਰਨ ਲਈ, ਫ਼ੋਨ ਇੱਕ ਇਨਕਮਿੰਗ ਫ਼ੋਨ ਕਾਲ ਪ੍ਰਾਪਤ ਕਰਨ ਵੇਲੇ ਬੰਦ ਕਰਦਾ ਹੈ, ਫਿਰ ਜਦੋਂ ਕਾਲ ਖਤਮ ਹੁੰਦੀ ਹੈ ਤਾਂ ਕੀਲਾਕ ਨੂੰ ਮੁੜ-ਚਾਲੂ ਕਰਦਾ ਹੈ।"</string>
     <string name="permlab_manageFingerprint" msgid="5640858826254575638">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਹਾਰਡਵੇਅਰ ਵਿਵਸਥਿਤ ਕਰੋ"</string>
     <string name="permdesc_manageFingerprint" msgid="178208705828055464">"ਐਪ ਨੂੰ ਵਰਤੋਂ ਲਈ ਫਿੰਗਰਪ੍ਰਿੰਟ ਸ਼ਾਮਲ ਕਰਨ ਅਤੇ ਮਿਟਾਉਣ ਦੀਆਂ ਵਿਧੀਆਂ ਦੀ ਬੇਨਤੀ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ।"</string>
@@ -552,15 +554,15 @@
     <string name="policydesc_limitPassword" msgid="2502021457917874968">"ਸਕ੍ਰੀਨ ਲਾਕ ਪਾਸਵਰਡਾਂ ਅਤੇ ਪਿੰਨ ਵਿੱਚ ਆਗਿਆ ਦਿੱਤੀ ਲੰਮਾਈ ਅਤੇ ਅੱਖਰਾਂ ਤੇ ਨਿਯੰਤਰਣ ਪਾਓ।"</string>
     <string name="policylab_watchLogin" msgid="5091404125971980158">"ਸਕ੍ਰੀਨ ਅਣਲਾਕ ਕਰਨ ਦੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ \'ਤੇ ਨਿਗਰਾਨੀ ਰੱਖੋ"</string>
     <string name="policydesc_watchLogin" product="tablet" msgid="3215729294215070072">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਟੈਬਲੈੱਟ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਟੈਬਲੈੱਟ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
-    <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਨਲੌਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗ਼ਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ TV ਨੂੰ ਲੌਕ ਕਰੋ ਜਾਂ TV ਦਾ ਸਾਰਾ  ਡਾਟਾ  ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗ਼ਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
+    <string name="policydesc_watchLogin" product="TV" msgid="2707817988309890256">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗ਼ਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ TV ਨੂੰ  ਲਾਕ  ਕਰੋ ਜਾਂ TV ਦਾ ਸਾਰਾ  ਡਾਟਾ  ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗ਼ਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
     <string name="policydesc_watchLogin" product="default" msgid="5712323091846761073">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਫ਼ੋਨ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਫ਼ੋਨ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
     <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਟੈਬਲੈੱਟ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਟੈਬਲੈੱਟ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
-    <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਨਲੌਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗ਼ਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ TV ਨੂੰ ਲੌਕ ਕਰੋ ਜਾਂ TV ਦਾ ਸਾਰਾ  ਡਾਟਾ  ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗ਼ਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
+    <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗ਼ਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ TV ਨੂੰ  ਲਾਕ  ਕਰੋ ਜਾਂ TV ਦਾ ਸਾਰਾ  ਡਾਟਾ  ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗ਼ਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
     <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਫ਼ੋਨ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਫ਼ੋਨ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string>
-    <string name="policylab_resetPassword" msgid="4934707632423915395">"ਸਕ੍ਰੀਨ ਲੌਕ ਬਦਲੋ"</string>
-    <string name="policydesc_resetPassword" msgid="1278323891710619128">"ਸਕ੍ਰੀਨ ਲੌਕ ਬਦਲੋ।"</string>
-    <string name="policylab_forceLock" msgid="2274085384704248431">"ਸਕ੍ਰੀਨ ਲੌਕ ਕਰੋ"</string>
-    <string name="policydesc_forceLock" msgid="1141797588403827138">"ਇਸਤੇ ਨਿਯੰਤਰਣ ਪਾਓ ਕਿ ਸਕ੍ਰਿਨ ਕਿਵੇਂ ਅਤੇ ਕਦੋਂ ਲੌਕ ਹੁੰਦੀ ਹੈ।"</string>
+    <string name="policylab_resetPassword" msgid="4934707632423915395">"ਸਕ੍ਰੀਨ  ਲਾਕ  ਬਦਲੋ"</string>
+    <string name="policydesc_resetPassword" msgid="1278323891710619128">"ਸਕ੍ਰੀਨ  ਲਾਕ  ਬਦਲੋ।"</string>
+    <string name="policylab_forceLock" msgid="2274085384704248431">"ਸਕ੍ਰੀਨ  ਲਾਕ  ਕਰੋ"</string>
+    <string name="policydesc_forceLock" msgid="1141797588403827138">"ਇਸਤੇ ਨਿਯੰਤਰਣ ਪਾਓ ਕਿ ਸਕ੍ਰਿਨ ਕਿਵੇਂ ਅਤੇ ਕਦੋਂ  ਲਾਕ  ਹੁੰਦੀ ਹੈ।"</string>
     <string name="policylab_wipeData" msgid="3910545446758639713">"ਸਾਰਾ  ਡਾਟਾ  ਮਿਟਾਓ"</string>
     <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"ਇੱਕ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਕੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਟੈਬਲੈੱਟ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string>
     <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"ਇੱਕ ਫੈਕਟਰੀ ਡਾਟਾ ਰੀਸੈੱਟ ਕਰਕੇ ਚਿਤਾਵਨੀ ਤੋਂ ਬਿਨਾਂ ਟੀਵੀ ਦਾ ਡਾਟਾ ਮਿਟਾਓ।"</string>
@@ -577,8 +579,8 @@
     <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"ਲੋੜ ਹੈ ਕਿ ਸਟੋਰ ਕੀਤਾ ਐਪ  ਡਾਟਾ  ਇਨਕ੍ਰਿਪਟ ਕੀਤਾ ਜਾਏ।"</string>
     <string name="policylab_disableCamera" msgid="6395301023152297826">"ਕੈਮਰੇ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
     <string name="policydesc_disableCamera" msgid="2306349042834754597">"ਸਾਰੇ ਡੀਵਾਈਸ ਕੈਮਰਿਆਂ ਦੀ ਵਰਤੋਂ ਰੋਕੋ।"</string>
-    <string name="policylab_disableKeyguardFeatures" msgid="8552277871075367771">"ਸਕ੍ਰੀਨ ਲੌਕ ਦੀਆਂ ਕੁਝ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
-    <string name="policydesc_disableKeyguardFeatures" msgid="2044755691354158439">"ਸਕ੍ਰੀਨ ਲੌਕ ਦੀਆਂ ਕੁਝ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਦੀ ਵਰਤੋਂ ਰੋਕੋ।"</string>
+    <string name="policylab_disableKeyguardFeatures" msgid="8552277871075367771">"ਸਕ੍ਰੀਨ  ਲਾਕ  ਦੀਆਂ ਕੁਝ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
+    <string name="policydesc_disableKeyguardFeatures" msgid="2044755691354158439">"ਸਕ੍ਰੀਨ  ਲਾਕ  ਦੀਆਂ ਕੁਝ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਦੀ ਵਰਤੋਂ ਰੋਕੋ।"</string>
   <string-array name="phoneTypes">
     <item msgid="8901098336658710359">"ਘਰ"</item>
     <item msgid="869923650527136615">"ਮੋਬਾਈਲ"</item>
@@ -704,7 +706,7 @@
     <string name="keyguard_label_text" msgid="861796461028298424">"ਅਣਲਾਕ ਕਰਨ ਲਈ, ਪਹਿਲਾਂ ਮੀਨੂ ਫਿਰ 0 ਦਬਾਓ।"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"ਐਮਰਜੈਂਸੀ ਨੰਬਰ"</string>
     <string name="lockscreen_carrier_default" msgid="6169005837238288522">"ਕੋਈ ਸੇਵਾ ਨਹੀਂ"</string>
-    <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ਸਕ੍ਰੀਨ ਲੌਕ ਕੀਤੀ।"</string>
+    <string name="lockscreen_screen_locked" msgid="7288443074806832904">"ਸਕ੍ਰੀਨ  ਲਾਕ  ਕੀਤੀ।"</string>
     <string name="lockscreen_instructions_when_pattern_enabled" msgid="46154051614126049">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਮੀਨੂ ਦਬਾਓ ਜਾਂ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਕਰੋ।"</string>
     <string name="lockscreen_instructions_when_pattern_disabled" msgid="686260028797158364">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਮੀਨੂ ਦਬਾਓ।"</string>
     <string name="lockscreen_pattern_instructions" msgid="7478703254964810302">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪੈਟਰਨ ਡ੍ਰਾ ਕਰੋ"</string>
@@ -731,11 +733,11 @@
     <string name="lockscreen_transport_rew_description" msgid="6944412838651990410">"ਰੀਵਾਈਂਡ ਕਰੋ"</string>
     <string name="lockscreen_transport_ffw_description" msgid="42987149870928985">"ਤੇਜ਼ੀ ਨਾਲ ਅੱਗੇ ਭੇਜੋ"</string>
     <string name="emergency_calls_only" msgid="6733978304386365407">"ਕੇਵਲ ਐਮਰਜੈਂਸੀ ਕਾਲਾਂ"</string>
-    <string name="lockscreen_network_locked_message" msgid="143389224986028501">"ਨੈੱਟਵਰਕ ਲੌਕ ਕੀਤਾ"</string>
+    <string name="lockscreen_network_locked_message" msgid="143389224986028501">"ਨੈੱਟਵਰਕ  ਲਾਕ  ਕੀਤਾ"</string>
     <string name="lockscreen_sim_puk_locked_message" msgid="7441797339976230">"SIM ਕਾਰਡ PUK-ਲੌਕਡ ਹੈ।"</string>
     <string name="lockscreen_sim_puk_locked_instructions" msgid="8127916255245181063">"ਵਰਤੋਂਕਾਰ ਗਾਈਡ ਦੇਖੋ ਜਾਂ ਗਾਹਕ ਸੇਵਾ ਨੂੰ ਫ਼ੋਨ ਕਰੋ।"</string>
-    <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM ਕਾਰਡ ਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
-    <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM ਕਾਰਡ ਅਨਲੌਕ ਕਰ ਰਿਹਾ ਹੈ…"</string>
+    <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM ਕਾਰਡ  ਲਾਕ  ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
+    <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"SIM ਕਾਰਡ ਅਣਲਾਕ ਕਰ ਰਿਹਾ ਹੈ…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="6481623830344107222">"ਤੁਸੀਂ <xliff:g id="NUMBER_0">%1$d</xliff:g> ਵਾਰ ਆਪਣਾ ਅਣਲਾਕ ਪੈਟਰਨ ਗਲਤ ਢੰਗ ਨਾਲ ਉਲੀਕਿਆ ਹੈ। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
     <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="2725973286239344555">"ਤੁਸੀਂ <xliff:g id="NUMBER_0">%1$d</xliff:g> ਵਾਰ ਆਪਣਾ ਪਾਸਵਰਡ ਗਲਤ ਢੰਗ ਨਾਲ ਟਾਈਪ ਕੀਤਾ ਹੈ।\n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6216672706545696955">"ਤੁਸੀਂ ਆਪਣਾ ਪਿੰਨ <xliff:g id="NUMBER_0">%1$d</xliff:g> ਵਾਰ ਗਲਤ ਢੰਗ ਨਾਲ ਟਾਈਪ ਕੀਤਾ ਹੈ। \n\n<xliff:g id="NUMBER_1">%2$d</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ਇਨਪੁੱਟ ਵਿਧੀ"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"ਟੈਕਸਟ ਕਿਰਿਆਵਾਂ"</string>
     <string name="email" msgid="4560673117055050403">"ਈਮੇਲ ਕਰੋ"</string>
-    <string name="dial" msgid="4204975095406423102">"ਫ਼ੋਨ ਕਰੋ"</string>
-    <string name="map" msgid="6068210738233985748">"ਨਕਸ਼ੇ"</string>
-    <string name="browse" msgid="6993590095938149861">"ਬ੍ਰਾਊਜ਼ਰ"</string>
+    <string name="dial" msgid="1253998302767701559">"ਕਾਲ ਕਰੋ"</string>
+    <string name="map" msgid="6521159124535543457">"ਟਿਕਾਣਾ ਦੇਖੋ"</string>
+    <string name="browse" msgid="1245903488306147205">"ਖੋਲ੍ਹੋ"</string>
+    <string name="sms" msgid="4560537514610063430">"ਸੁਨੇਹਾ ਭੇਜੋ"</string>
+    <string name="add_contact" msgid="7867066569670597203">"ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"ਸਟੋਰੇਜ ਦੀ ਜਗ੍ਹਾ ਖਤਮ ਹੋ ਰਹੀ ਹੈ"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"ਕੁਝ ਸਿਸਟਮ ਫੰਕਸ਼ਨ ਕੰਮ ਨਹੀਂ ਵੀ ਕਰ ਸਕਦੇ"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"ਸਿਸਟਮ ਲਈ ਲੋੜੀਂਦੀ ਸਟੋਰੇਜ ਨਹੀਂ ਹੈ। ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡੇ ਕੋਲ 250MB ਖਾਲੀ ਜਗ੍ਹਾ ਹੈ ਅਤੇ ਮੁੜ-ਚਾਲੂ ਕਰੋ।"</string>
@@ -1221,7 +1225,7 @@
     <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"ਇਹ ਡੀਵਾਈਸ ਇਸ <xliff:g id="NAME">%s</xliff:g> ਨੂੰ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਹੈ। ਕਿਸੇ ਸਮਰਥਿਤ ਫਾਰਮੈਟ ਵਿੱਚ ਸਥਾਪਤ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
     <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"ਇਹ ਡੀਵਾਈਸ ਇਸ <xliff:g id="NAME">%s</xliff:g> ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਹੈ। ਕਿਸੇ ਸਮਰਥਿਤ ਫਾਰਮੈਟ ਵਿੱਚ ਸਥਾਪਤ ਕਰਨ ਲਈ ਚੁਣੋ।"</string>
     <string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"<xliff:g id="NAME">%s</xliff:g> ਨੂੰ ਅਚਨਚੇਤ ਹਟਾਇਆ ਗਿਆ"</string>
-    <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"ਡੇਟਾ ਦੇ ਨੁਕਸਾਨ ਤੋਂ ਬੱਚਣ ਲਈ ਹਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ <xliff:g id="NAME">%s</xliff:g> ਅਨਮਾਊਂਟ ਕਰੋ"</string>
+    <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">" ਡਾਟਾ  ਦੇ ਨੁਕਸਾਨ ਤੋਂ ਬੱਚਣ ਲਈ ਹਟਾਉਣ ਤੋਂ ਪਹਿਲਾਂ <xliff:g id="NAME">%s</xliff:g> ਅਨਮਾਊਂਟ ਕਰੋ"</string>
     <string name="ext_media_nomedia_notification_title" msgid="1704840188641749091">"ਹਟਾਇਆ <xliff:g id="NAME">%s</xliff:g>"</string>
     <string name="ext_media_nomedia_notification_message" msgid="6471542972147056586">"<xliff:g id="NAME">%s</xliff:g> ਨੂੰ ਹਟਾਇਆ ਗਿਆ, ਕੋਈ ਨਵਾਂ ਸੰਮਿਲਿਤ ਕਰੋ"</string>
     <string name="ext_media_unmounting_notification_title" msgid="640674168454809372">"ਅਜੇ ਵੀ <xliff:g id="NAME">%s</xliff:g> ਨੂੰ ਕੱਢ ਰਿਹਾ ਹੈ..."</string>
@@ -1232,11 +1236,11 @@
     <string name="ext_media_missing_title" msgid="620980315821543904">"<xliff:g id="NAME">%s</xliff:g> ਲਾਪਤਾ"</string>
     <string name="ext_media_missing_message" msgid="5761133583368750174">"ਇਸ ਡੀਵਾਈਸ ਨੂੰ ਮੁੜ ਸੰਮਿਲਿਤ ਕਰੋ"</string>
     <string name="ext_media_move_specific_title" msgid="1471100343872375842">"<xliff:g id="NAME">%s</xliff:g> ਮੂਵ ਕਰ ਰਿਹਾ ਹੈ"</string>
-    <string name="ext_media_move_title" msgid="1022809140035962662">"ਡੇਟਾ ਮੂਵ ਕਰ ਰਿਹਾ ਹੈ"</string>
+    <string name="ext_media_move_title" msgid="1022809140035962662">" ਡਾਟਾ  ਮੂਵ ਕਰ ਰਿਹਾ ਹੈ"</string>
     <string name="ext_media_move_success_title" msgid="8575300932957954671">"ਮੂਵ ਸੰਪੂਰਣ"</string>
-    <string name="ext_media_move_success_message" msgid="4199002148206265426">"ਡੇਟਾ ਨੂੰ <xliff:g id="NAME">%s</xliff:g> ਵਿੱਚ ਮੂਵ ਕੀਤਾ ਗਿਆ"</string>
-    <string name="ext_media_move_failure_title" msgid="7613189040358789908">"ਡੇਟਾ ਨੂੰ ਮੂਵ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
-    <string name="ext_media_move_failure_message" msgid="1978096440816403360">"ਡੇਟਾ ਮੂਲ ਸਥਾਨ \'ਤੇ ਛੱਡਿਆ ਗਿਆ"</string>
+    <string name="ext_media_move_success_message" msgid="4199002148206265426">" ਡਾਟਾ  ਨੂੰ <xliff:g id="NAME">%s</xliff:g> ਵਿੱਚ ਮੂਵ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="ext_media_move_failure_title" msgid="7613189040358789908">" ਡਾਟਾ  ਨੂੰ ਮੂਵ ਨਹੀਂ ਕਰ ਸਕਿਆ"</string>
+    <string name="ext_media_move_failure_message" msgid="1978096440816403360">" ਡਾਟਾ  ਮੂਲ ਸਥਾਨ \'ਤੇ ਛੱਡਿਆ ਗਿਆ"</string>
     <string name="ext_media_status_removed" msgid="6576172423185918739">"ਹਟਾਏ ਗਏ"</string>
     <string name="ext_media_status_unmounted" msgid="2551560878416417752">"ਹਟਾਇਆ ਗਿਆ"</string>
     <string name="ext_media_status_checking" msgid="6193921557423194949">"ਜਾਂਚ ਕਰ ਰਿਹਾ ਹੈ..."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ਟੈਬਲੈੱਟ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ਫ਼ੋਨ ਕਰੋ"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ਹੈੱਡਫ਼ੋਨ"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ਡੌਕ ਸਪੀਕਰਸ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ਹੈੱਡਫ਼ੋਨ"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ਸਿਸਟਮ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth  ਆਡੀਓ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"ਵਾਇਰਲੈੱਸ ਡਿਸਪਲੇ"</string>
@@ -1457,7 +1462,7 @@
     <string name="kg_puk_enter_puk_hint" msgid="453227143861735537">"ਸਿਮ ਹੁਣ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="kg_puk_enter_pin_hint" msgid="7871604527429602024">"ਇੱਛਤ ਪਿੰਨ ਕੋਡ ਦਾਖਲ ਕਰੋ"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="325676184762529976">"ਇੱਛਤ ਪਿੰਨ ਕੋਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
-    <string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM ਕਾਰਡ ਅਨਲੌਕ ਕਰ ਰਿਹਾ ਹੈ…"</string>
+    <string name="kg_sim_unlock_progress_dialog_message" msgid="8950398016976865762">"SIM ਕਾਰਡ ਅਣਲਾਕ ਕਰ ਰਿਹਾ ਹੈ…"</string>
     <string name="kg_password_wrong_pin_code" msgid="1139324887413846912">"ਗਲਤ ਪਿੰਨ ਕੋਡ।"</string>
     <string name="kg_invalid_sim_pin_hint" msgid="8795159358110620001">"ਕੋਈ ਪਿੰਨ ਟਾਈਪ ਕਰੋ ਜੋ 4 ਤੋਂ 8 ਨੰਬਰਾਂ ਦਾ ਹੋਵੇ।"</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6025069204539532000">"PUK ਕੋਡ 8 ਸੰਖਿਆਵਾਂ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।"</string>
@@ -1721,7 +1726,7 @@
     <string name="new_sms_notification_content" msgid="7002938807812083463">"ਦੇਖਣ ਲਈ SMS ਐਪ ਖੋਲ੍ਹੋ"</string>
     <string name="user_encrypted_title" msgid="9054897468831672082">"ਕੁਝ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਸੀਮਿਤ ਹੋ ਸਕਦੀ ਹੈ"</string>
     <string name="user_encrypted_message" msgid="4923292604515744267">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
-    <string name="user_encrypted_detail" msgid="5708447464349420392">"ਵਰਤੋਂਕਾਰ  ਡਾਟਾ  ਲੌਕ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="user_encrypted_detail" msgid="5708447464349420392">"ਵਰਤੋਂਕਾਰ  ਡਾਟਾ   ਲਾਕ  ਕੀਤਾ ਗਿਆ"</string>
     <string name="profile_encrypted_detail" msgid="3700965619978314974">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਲਾਕ ਕੀਤੀ ਗਈ"</string>
     <string name="profile_encrypted_message" msgid="6964994232310195874">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਅਣਲਾਕ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="usb_mtp_launch_notification_title" msgid="8359219638312208932">"<xliff:g id="PRODUCT_NAME">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋਈ"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"ਸੰਕਟਕਾਲੀਨ ਸੰਦੇਸ਼ ਟੈਸਟ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"ਜਵਾਬ ਦਿਓ"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM ਦੀ ਵਿਵਸਥਾ ਨਹੀਂ ਹੈ"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ਫ਼ੋਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"ਪੌਪਅੱਪ ਵਿੰਡੋ"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ਇਸ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਨਵੀਨਤਮ ਐਪ ਦੀ ਲੋੜ ਹੈ"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"ਸ਼ਾਰਟਕੱਟ ਮੁੜ-ਬਹਾਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ ਕਿਉਂਕਿ ਐਪ \'ਬੈਕਅੱਪ ਅਤੇ ਮੁੜ-ਬਹਾਲੀ\' ਨਾਲ ਕੰਮ ਨਹੀਂ ਕਰਦੀ"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"ਐਪ ਹਸਤਾਖਰ ਦਾ ਮੇਲ ਨਾ ਹੋਣ ਕਾਰਨ ਸ਼ਾਰਟਕੱਟ ਮੁੜ-ਬਹਾਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"ਸ਼ਾਰਟਕੱਟ ਮੁੜ-ਬਹਾਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 5bca5a9..d7c10ec 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profil do pracy został usunięty z powodu braku aplikacji administratora"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Brakuje aplikacji administratora profilu do pracy lub jest ona uszkodzona. Dlatego Twój profil do pracy i związane z nim dane zostały usunięte. Skontaktuj się ze swoim administratorem, by uzyskać pomoc."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Twój profil do pracy nie jest już dostępny na tym urządzeniu"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Zbyt wiele prób podania hasła"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Urządzenie jest zarządzane"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Twoja organizacja zarządza tym urządzeniem i może monitorować ruch w sieci. Kliknij, by dowiedzieć się więcej."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Twoje urządzenie zostanie wyczyszczone"</string>
@@ -254,10 +255,11 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alerty"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Tryb demo dla sklepów"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Połączenie USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Działa aplikacja"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacje zużywające baterię"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> zużywa baterię"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Liczba aplikacji zużywających baterię: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
-    <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i transmisji danych"</string>
+    <string name="foreground_service_tap_for_details" msgid="372046743534354644">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i użycia danych"</string>
     <string name="foreground_service_multiple_separator" msgid="4021901567939866542">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
     <string name="safeMode" msgid="2788228061547930246">"Tryb awaryjny"</string>
     <string name="android_system_label" msgid="6577375335728551336">"System Android"</string>
@@ -347,7 +349,7 @@
     <string name="permlab_runInBackground" msgid="7365290743781858803">"działanie w tle"</string>
     <string name="permdesc_runInBackground" msgid="7370142232209999824">"Ta aplikacja może działać w tle. Bateria może się szybciej rozładowywać."</string>
     <string name="permlab_useDataInBackground" msgid="8694951340794341809">"transmisja danych w tle"</string>
-    <string name="permdesc_useDataInBackground" msgid="6049514223791806027">"Ta aplikacja może przesyłać i odbierać dane w tle. Transmisja danych może się zwiększyć."</string>
+    <string name="permdesc_useDataInBackground" msgid="6049514223791806027">"Ta aplikacja może przesyłać i odbierać dane w tle. Użycie danych może się zwiększyć."</string>
     <string name="permlab_persistentActivity" msgid="8841113627955563938">"sprawianie, że aplikacja jest cały czas uruchomiona"</string>
     <string name="permdesc_persistentActivity" product="tablet" msgid="8525189272329086137">"Pozwala aplikacji na trwałe zapisywanie swoich fragmentów w pamięci. Może to zmniejszyć ilość pamięci dostępnej dla innych aplikacji i spowolnić działanie tabletu."</string>
     <string name="permdesc_persistentActivity" product="tv" msgid="5086862529499103587">"Pozwala aplikacji zapewnić nieusuwalność swoich fragmentów z pamięci. Może to ograniczyć ilość pamięci dostępną dla innych aplikacji i spowalniać działanie telewizora."</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Sposób wprowadzania tekstu"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Działania na tekście"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Mapy"</string>
-    <string name="browse" msgid="6993590095938149861">"Internet"</string>
+    <string name="dial" msgid="1253998302767701559">"Zadzwoń"</string>
+    <string name="map" msgid="6521159124535543457">"Zlokalizuj"</string>
+    <string name="browse" msgid="1245903488306147205">"Otwórz"</string>
+    <string name="sms" msgid="4560537514610063430">"Wyślij SMS-a"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Dodaj"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Kończy się miejsce"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Niektóre funkcje systemu mogą nie działać"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Za mało pamięci w systemie. Upewnij się, że masz 250 MB wolnego miejsca i uruchom urządzenie ponownie."</string>
@@ -1426,7 +1430,7 @@
     <string name="storage_usb_drive_label" msgid="4501418548927759953">"Dysk USB (<xliff:g id="MANUFACTURER">%s</xliff:g>)"</string>
     <string name="storage_usb" msgid="3017954059538517278">"Nośnik USB"</string>
     <string name="extract_edit_menu_button" msgid="8940478730496610137">"Edytuj"</string>
-    <string name="data_usage_warning_title" msgid="3620440638180218181">"Alert transmisji danych"</string>
+    <string name="data_usage_warning_title" msgid="3620440638180218181">"Alert użycia danych"</string>
     <string name="data_usage_warning_body" msgid="6660692274311972007">"Kliknij, by wyświetlić użycie i ustawienia."</string>
     <string name="data_usage_3g_limit_title" msgid="4361523876818447683">"Osiągnięto limit danych 2G/3G"</string>
     <string name="data_usage_4g_limit_title" msgid="4609566827219442376">"Osiągnięto limit danych 4G"</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Telewizor"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Słuchawki"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Głośniki stacji dokującej"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Słuchawki"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Dźwięk Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Wyświetlacz bezprzewodowy"</string>
@@ -1671,7 +1676,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"<xliff:g id="LABEL">%1$s</xliff:g> – praca 2"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"<xliff:g id="LABEL">%1$s</xliff:g> – praca 3"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Aby odpiąć ten ekran, naciśnij i przytrzymaj Wstecz oraz Przegląd"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Tej aplikacji nie można odpiąć"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Ekran przypięty"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran odpięty"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Podaj PIN, aby odpiąć"</string>
@@ -1847,9 +1851,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test komunikatów alarmowych"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odpowiedz"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Niedozwolona karta SIM"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Nieobsługiwana karta SIM"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Niedozwolona karta SIM"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Niedozwolony telefon"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Karta SIM jest niedozwolona w przypadku usług głosowych"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Karta SIM nie jest obsługiwana w przypadku usług głosowych"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"Karta SIM jest niedozwolona w przypadku usług głosowych"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telefon jest niedozwolony w przypadku usług głosowych"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Wyskakujące okienko"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ten skrót wymaga zainstalowania najnowszej wersji aplikacji"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Nie można przywrócić skrótu, bo aplikacja nie obsługuje tworzenia i przywracania kopii zapasowej"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Nie można przywrócić skrótu z powodu niezgodności podpisu aplikacji"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Nie można przywrócić skrótu"</string>
 </resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 43c0bb1..d088cdd 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Perfil de trabalho excluído devido à ausência de um app para administrador"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"O app para administrador do perfil de trabalho não foi encontrado ou está corrompido. Consequentemente, seu perfil de trabalho e os dados relacionados foram excluídos. Entre em contato com seu administrador para receber assistência."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Seu perfil de trabalho não está mais disponível neste dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Muitas tentativas de senha"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"O dispositivo é gerenciado"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Sua organização gerencia este dispositivo e pode monitorar o tráfego de rede. Toque para ver detalhes."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Seu dispositivo será limpo"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertas"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstração na loja"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Conexão USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App em execução"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps que estão consumindo a bateria"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Ações de texto"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefone"</string>
-    <string name="map" msgid="6068210738233985748">"Mapas"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Ligar"</string>
+    <string name="map" msgid="6521159124535543457">"Localizar"</string>
+    <string name="browse" msgid="1245903488306147205">"Abrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Mensagem"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Adicionar"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Pouco espaço de armazenamento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Algumas funções do sistema podem não funcionar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Não há armazenamento suficiente para o sistema. Certifique-se de ter 250 MB de espaço livre e reinicie."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fones de ouvido"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Alto-falantes da dock"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fones de ouvido"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Áudio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Display sem fio"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Teste de mensagens de emergência"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM não permitido"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM não aprovisionado"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM não permitido"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Smartphone não permitido"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Janela pop-up"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"Mais <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Esse atalho requer o app mais recente"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Não foi possível restaurar o atalho porque o app não é compatível com backup e restauração"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Não foi possível restaurar o atalho devido à incompatibilidade de assinatura de apps"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Não foi possível restaurar o atalho"</string>
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index d130f97..2de83c6 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -166,16 +166,17 @@
       <item quantity="other">Autoridades de certificação instaladas</item>
     </plurals>
     <string name="ssl_ca_cert_noti_by_unknown" msgid="4475437862189850602">"Por um terceiro desconhecido"</string>
-    <string name="ssl_ca_cert_noti_by_administrator" msgid="3541729986326153557">"Pelo administrador do seu perfil de trabalho"</string>
+    <string name="ssl_ca_cert_noti_by_administrator" msgid="3541729986326153557">"Pelo gestor do seu perfil de trabalho"</string>
     <string name="ssl_ca_cert_noti_managed" msgid="4030263497686867141">"Por <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
     <string name="work_profile_deleted" msgid="5005572078641980632">"Perfil de trabalho eliminado"</string>
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Perfil de trabalho eliminado devido a aplicação de administração em falta"</string>
-    <string name="work_profile_deleted_details" msgid="6307630639269092360">"A aplicação de administração do perfil de trabalho está em falta ou danificada. Consequentemente, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o administrador para obter assistência."</string>
+    <string name="work_profile_deleted_details" msgid="6307630639269092360">"A aplicação de administração do perfil de trabalho está em falta ou danificada. Consequentemente, o seu perfil de trabalho e os dados relacionados foram eliminados. Contacte o gestor para obter assistência."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"O seu perfil de trabalho já não está disponível neste dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Demasiadas tentativas de introdução da palavra-passe"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"O dispositivo é gerido"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"A sua entidade gere este dispositivo e pode monitorizar o tráfego de rede. Toque para obter mais detalhes."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"O seu dispositivo será apagado"</string>
-    <string name="factory_reset_message" msgid="7972496262232832457">"Não é possível utilizar a aplicação de administração. O seu dispositivo será agora apagado.\n\nSe tiver questões, contacte o administrador da entidade."</string>
+    <string name="factory_reset_message" msgid="7972496262232832457">"Não é possível utilizar a aplicação de administração. O seu dispositivo será agora apagado.\n\nSe tiver questões, contacte o gestor da entidade."</string>
     <string name="me" msgid="6545696007631404292">"Eu"</string>
     <string name="power_dialog" product="tablet" msgid="8545351420865202853">"Opções do tablet"</string>
     <string name="power_dialog" product="tv" msgid="6153888706430556356">"Opções de TV"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertas"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstração para retalho"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Ligação USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplicação em execução"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplicações que estão a consumir bateria"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"A aplicação <xliff:g id="APP_NAME">%1$s</xliff:g> está a consumir bateria."</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicações estão a consumir bateria."</string>
@@ -608,7 +610,7 @@
     <item msgid="3145118944639869809">"Personalizado"</item>
   </string-array>
   <string-array name="organizationTypes">
-    <item msgid="7546335612189115615">"Emprego"</item>
+    <item msgid="7546335612189115615">"Trabalho"</item>
     <item msgid="4378074129049520373">"Outro"</item>
     <item msgid="3455047468583965104">"Personalizado"</item>
   </string-array>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Acções de texto"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Telemóvel"</string>
-    <string name="map" msgid="6068210738233985748">"Mapas"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Telefonar"</string>
+    <string name="map" msgid="6521159124535543457">"Localizar"</string>
+    <string name="browse" msgid="1245903488306147205">"Abrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Mensagem"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Adicionar"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Está quase sem espaço de armazenamento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Algumas funções do sistema poderão não funcionar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Não existe armazenamento suficiente para o sistema. Certifique-se de que tem 250 MB de espaço livre e reinicie."</string>
@@ -1195,7 +1199,7 @@
     <string name="taking_remote_bugreport_notification_title" msgid="6742483073875060934">"A criar relatório de erro…"</string>
     <string name="share_remote_bugreport_notification_title" msgid="4987095013583691873">"Pretende partilhar o relatório de erro?"</string>
     <string name="sharing_remote_bugreport_notification_title" msgid="7572089031496651372">"A partilhar relatório de erro…"</string>
-    <string name="share_remote_bugreport_notification_message_finished" msgid="6029609949340992866">"O seu administrador solicitou um relatório de erro para ajudar na resolução de problemas deste dispositivo. As aplicações e os dados podem ser partilhados."</string>
+    <string name="share_remote_bugreport_notification_message_finished" msgid="6029609949340992866">"O seu gestor solicitou um relatório de erro para ajudar na resolução de problemas deste dispositivo. As aplicações e os dados podem ser partilhados."</string>
     <string name="share_remote_bugreport_action" msgid="6249476773913384948">"PARTILHAR"</string>
     <string name="decline_remote_bugreport_action" msgid="6230987241608770062">"RECUSAR"</string>
     <string name="select_input_method" msgid="8547250819326693584">"Alterar teclado"</string>
@@ -1306,7 +1310,7 @@
     <string name="tethered_notification_title" msgid="3146694234398202601">"Ligação ponto a ponto ou hotspot activos"</string>
     <string name="tethered_notification_message" msgid="2113628520792055377">"Toque para configurar."</string>
     <string name="disable_tether_notification_title" msgid="7526977944111313195">"A ligação (à Internet) via telemóvel está desativada."</string>
-    <string name="disable_tether_notification_message" msgid="2913366428516852495">"Contacte o administrador para obter detalhes."</string>
+    <string name="disable_tether_notification_message" msgid="2913366428516852495">"Contacte o gestor para obter detalhes."</string>
     <string name="back_button_label" msgid="2300470004503343439">"Anterior"</string>
     <string name="next_button_label" msgid="1080555104677992408">"Seguinte"</string>
     <string name="skip_button_label" msgid="1275362299471631819">"Ignorar"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telemóvel"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auscultadores"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altif. estação ancoragem"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Auscultadores"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Áudio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Visualização sem fios"</string>
@@ -1500,7 +1505,7 @@
     <string name="user_logging_out_message" msgid="8939524935808875155">"A terminar a sessão de <xliff:g id="NAME">%1$s</xliff:g>…"</string>
     <string name="owner_name" msgid="2716755460376028154">"Proprietário"</string>
     <string name="error_message_title" msgid="4510373083082500195">"Erro"</string>
-    <string name="error_message_change_not_allowed" msgid="1238035947357923497">"O administrador não permite esta alteração"</string>
+    <string name="error_message_change_not_allowed" msgid="1238035947357923497">"O gestor não permite esta alteração"</string>
     <string name="app_not_found" msgid="3429141853498927379">"Não foram encontradas aplicações para executar esta ação"</string>
     <string name="revoke" msgid="5404479185228271586">"Revogar"</string>
     <string name="mediasize_iso_a0" msgid="1994474252931294172">"ISO A0"</string>
@@ -1592,7 +1597,7 @@
     <string name="reason_service_unavailable" msgid="7824008732243903268">"Serviço de impressão não ativado"</string>
     <string name="print_service_installed_title" msgid="2246317169444081628">"Serviço <xliff:g id="NAME">%s</xliff:g> instalado"</string>
     <string name="print_service_installed_message" msgid="5897362931070459152">"Toque para ativar"</string>
-    <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Introduzir o PIN do administrador"</string>
+    <string name="restr_pin_enter_admin_pin" msgid="8641662909467236832">"Introduzir o PIN do gestor"</string>
     <string name="restr_pin_enter_pin" msgid="3395953421368476103">"Introduzir PIN"</string>
     <string name="restr_pin_incorrect" msgid="8571512003955077924">"Incorreto"</string>
     <string name="restr_pin_enter_old_pin" msgid="1462206225512910757">"PIN Atual"</string>
@@ -1621,15 +1626,14 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2.º <xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3.º <xliff:g id="LABEL">%1$s</xliff:g> de trabalho"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Para soltar este ecrã, toque sem soltar nos botões Anterior e Vista geral"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Não é possível soltar esta aplicação"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Ecrã fixo"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Ecrã solto"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Pedir PIN antes de soltar"</string>
     <string name="lock_to_app_unlock_pattern" msgid="4182192144797225137">"Pedir sequência de desbloqueio antes de soltar"</string>
     <string name="lock_to_app_unlock_password" msgid="6380979775916974414">"Pedir palavra-passe antes de soltar"</string>
-    <string name="package_installed_device_owner" msgid="6875717669960212648">"Instalado pelo seu administrador"</string>
-    <string name="package_updated_device_owner" msgid="1847154566357862089">"Atualizado pelo seu administrador"</string>
-    <string name="package_deleted_device_owner" msgid="2307122077550236438">"Eliminado pelo seu administrador"</string>
+    <string name="package_installed_device_owner" msgid="6875717669960212648">"Instalado pelo seu gestor"</string>
+    <string name="package_updated_device_owner" msgid="1847154566357862089">"Atualizado pelo seu gestor"</string>
+    <string name="package_deleted_device_owner" msgid="2307122077550236438">"Eliminado pelo seu gestor"</string>
     <string name="battery_saver_description" msgid="1960431123816253034">"Para ajudar a melhorar a autonomia da bateria, a poupança de bateria reduz o desempenho do seu dispositivo e limita a vibração, os serviços de localização e a maioria dos dados em segundo plano. O email, as mensagens e outras aplicações que dependem da sincronização não podem ser atualizados exceto se os abrir.\n\nA poupança de bateria desliga-se automaticamente quando o dispositivo está a carregar."</string>
     <string name="data_saver_description" msgid="6015391409098303235">"Para ajudar a reduzir a utilização de dados, a Poupança de dados impede que algumas aplicações enviem ou recebam dados em segundo plano. Uma determinada aplicação que esteja a utilizar atualmente pode aceder aos dados, mas é possível que o faça com menos frequência. Isto pode significar, por exemplo, que as imagens não são apresentadas até que toque nas mesmas."</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"Ativar a Poupança de dados?"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Teste de mensagens de emergência"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM não permitido"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM não ativado"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM não permitido"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telemóvel não permitido"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM não permitido para voz"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM não aprovisionado para voz"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM não permitido para voz"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Telemóvel não permitido para voz"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Janela pop-up"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Este atalho requer a aplicação mais recente."</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Não foi possível restaurar o atalho porque a aplicação não é compatível com a funcionalidade de cópia de segurança e restauro."</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Não foi possível restaurar o atalho devido a uma falha de correspondência entre as assinaturas das aplicações."</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Não foi possível restaurar o atalho."</string>
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 43c0bb1..d088cdd 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Perfil de trabalho excluído devido à ausência de um app para administrador"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"O app para administrador do perfil de trabalho não foi encontrado ou está corrompido. Consequentemente, seu perfil de trabalho e os dados relacionados foram excluídos. Entre em contato com seu administrador para receber assistência."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Seu perfil de trabalho não está mais disponível neste dispositivo"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Muitas tentativas de senha"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"O dispositivo é gerenciado"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Sua organização gerencia este dispositivo e pode monitorar o tráfego de rede. Toque para ver detalhes."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Seu dispositivo será limpo"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alertas"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstração na loja"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Conexão USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App em execução"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Apps que estão consumindo a bateria"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Ações de texto"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefone"</string>
-    <string name="map" msgid="6068210738233985748">"Mapas"</string>
-    <string name="browse" msgid="6993590095938149861">"Navegador"</string>
+    <string name="dial" msgid="1253998302767701559">"Ligar"</string>
+    <string name="map" msgid="6521159124535543457">"Localizar"</string>
+    <string name="browse" msgid="1245903488306147205">"Abrir"</string>
+    <string name="sms" msgid="4560537514610063430">"Mensagem"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Adicionar"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Pouco espaço de armazenamento"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Algumas funções do sistema podem não funcionar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Não há armazenamento suficiente para o sistema. Certifique-se de ter 250 MB de espaço livre e reinicie."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefone"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fones de ouvido"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Alto-falantes da dock"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Fones de ouvido"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistema"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Áudio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Display sem fio"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Teste de mensagens de emergência"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Responder"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM não permitido"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM não aprovisionado"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM não permitido"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Smartphone não permitido"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Janela pop-up"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"Mais <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Esse atalho requer o app mais recente"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Não foi possível restaurar o atalho porque o app não é compatível com backup e restauração"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Não foi possível restaurar o atalho devido à incompatibilidade de assinatura de apps"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Não foi possível restaurar o atalho"</string>
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index d496941..0afa413 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -174,6 +174,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profilul de serviciu a fost șters, deoarece aplicația de administrare lipsește"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Aplicația de administrare a profilului de serviciu lipsește sau este deteriorată. Prin urmare, profilul de serviciu și datele asociate au fost șterse. Pentru asistență, contactați administratorul."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Profilul de serviciu nu mai este disponibil pe acest dispozitiv"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Prea multe încercări de introducere a parolei"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Dispozitivul este gestionat"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organizația dvs. gestionează acest dispozitiv și poate monitoriza traficul în rețea. Atingeți pentru mai multe detalii."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Datele de pe dispozitiv vor fi șterse"</string>
@@ -251,6 +252,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Alerte"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstrație comercială"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Conexiune USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplicația rulează"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplicațiile consumă bateria"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește bateria"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicații folosesc bateria"</string>
@@ -997,9 +999,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metodă de intrare"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Acțiuni pentru text"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Hărți"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Apelați"</string>
+    <string name="map" msgid="6521159124535543457">"Localizați"</string>
+    <string name="browse" msgid="1245903488306147205">"Deschideți"</string>
+    <string name="sms" msgid="4560537514610063430">"Mesaj"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Adăugați"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Spațiul de stocare aproape ocupat"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Este posibil ca unele funcții de sistem să nu funcționeze"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Spațiu de stocare insuficient pentru sistem. Asigurați-vă că aveți 250 MB de spațiu liber și reporniți."</string>
@@ -1443,9 +1447,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tabletă"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Căști"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Difuz. dispozit. andocare"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Căști"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Ecran wireless"</string>
@@ -1812,9 +1817,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Testarea mesajelor de urgență"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Răspundeți"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Cardul SIM nu este permis"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Cardul SIM nu este activat"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Cardul SIM nu este permis"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefonul nu este permis"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Fereastră pop-up"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Această comandă rapidă necesită cea mai recentă aplicație"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Nu s-a putut restabili comanda rapidă deoarece aplicația nu acceptă backupul și restabilirea"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Nu s-a putut restabili comanda rapidă din cauza nepotrivirii semnăturii aplicației"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Nu s-a putut restabili comanda rapidă"</string>
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 564dbbb..fc9298c 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Рабочий профиль удален, поскольку отсутствует приложение для администрирования"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Приложение для администрирования рабочего профиля отсутствует или повреждено. Из-за этого рабочий профиль и связанные с ним данные были удалены. Если у вас возникли вопросы, обратитесь к администратору."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Ваш рабочий профиль больше не доступен на этом устройстве"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Слишком много попыток ввести пароль."</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Это управляемое устройство"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Ваша организация управляет этим устройством и может отслеживать сетевой трафик. Подробнее…"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Все данные с устройства будут удалены"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Уведомления"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Деморежим для магазина"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-подключение"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Приложение активно"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Приложения, расходующие заряд"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" расходует заряд"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Несколько приложений (<xliff:g id="NUMBER">%1$d</xliff:g>) расходуют заряд"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Операции с текстом"</string>
     <string name="email" msgid="4560673117055050403">"Письмо"</string>
-    <string name="dial" msgid="4204975095406423102">"Телефон"</string>
-    <string name="map" msgid="6068210738233985748">"Карты"</string>
-    <string name="browse" msgid="6993590095938149861">"Браузер"</string>
+    <string name="dial" msgid="1253998302767701559">"Позвонить"</string>
+    <string name="map" msgid="6521159124535543457">"Найти на карте"</string>
+    <string name="browse" msgid="1245903488306147205">"Открыть"</string>
+    <string name="sms" msgid="4560537514610063430">"Написать SMS"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Добавить"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Недостаточно памяти"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Некоторые функции могут не работать"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Недостаточно свободного места для системы. Освободите не менее 250 МБ дискового пространства и перезапустите устройство."</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Планшетный ПК"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Телевизор"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Наушники"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Динамики док-станции"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Наушники"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Система"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Воспроизведение звука через Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Беспроводной монитор"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Тестовое экстренное сообщение"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Ответить"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Использование SIM-карты запрещено"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-карта не активирована"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Использование SIM-карты запрещено"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Звонки запрещены"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Всплывающее окно"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Требуется последняя версия приложения"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Не удалось восстановить ярлык: приложение не поддерживает резервное копирование"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Не удалось восстановить ярлык: некорректная подпись приложения"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Не удалось восстановить ярлык"</string>
 </resources>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 39caed3..9eff25e 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"පරිපාලක යෙදුමක් නොමැති වීමෙන් කාර්යාල පැතිකඩ මකා දමන ලදි"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"කාර්යාල පැතිකඩ පාලක යෙදුම නොමැති හෝ දූෂණය වී ඇත. ප්‍රතිඵලයක් ලෙස ඔබගේ කාර්යාල පැතිකඩ සහ අදාළ දත්ත මකා දමා ඇත. සහය සඳහා ඔබගේ පරිපාලකයා සම්බන්ධ කර ගන්න."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ඔබේ කාර්යාල පැතිකඩ මෙම උපාංගය මත තවදුරටත් ලබා ගැනීමට නොහැකිය"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"මුරපද උත්සාහ කිරීම් ඉතා වැඩි ගණනකි"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"උපාංගය කළමනාකරණය කෙරේ"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"ඔබගේ ආයතනය මෙම උපාංගය කළමනාකරණය කරන අතර එය ජාල තදබදය නිරීක්ෂණය කළ හැක. විස්තර සඳහා තට්ටු කරන්න."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"ඔබගේ උපාංගය මකා දැමෙනු ඇත"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"ඇඟවීම්"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"සිල්ලර ආදර්ශනය"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB සම්බන්ධතාවය"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"යෙදුම ධාවනය කරමින්"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"බැටරිය භාවිත කරන යෙදුම්"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> බැටරිය භාවිත කරයි"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"යෙදුම් <xliff:g id="NUMBER">%1$d</xliff:g>ක් බැටරිය භාවිත කරයි"</string>
@@ -979,9 +981,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ආදාන ක්‍රමය"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"පෙළ ක්‍රියාවන්"</string>
     <string name="email" msgid="4560673117055050403">"ඊ-තැපෑල"</string>
-    <string name="dial" msgid="4204975095406423102">"දුරකථනය"</string>
-    <string name="map" msgid="6068210738233985748">"සිතියම්"</string>
-    <string name="browse" msgid="6993590095938149861">"බ්‍රවුසරය"</string>
+    <string name="dial" msgid="1253998302767701559">"අමතන්න"</string>
+    <string name="map" msgid="6521159124535543457">"ස්ථානගත කරන්න"</string>
+    <string name="browse" msgid="1245903488306147205">"විවෘත කරන්න"</string>
+    <string name="sms" msgid="4560537514610063430">"පණිවිඩය"</string>
+    <string name="add_contact" msgid="7867066569670597203">"එක් කරන්න"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"ආචයනය ඉඩ ප්‍රමාණය අඩු වී ඇත"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"සමහර පද්ධති කාර්යයන් ක්‍රියා නොකරනු ඇත"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"පද්ධතිය සඳහා ප්‍රමාණවත් ඉඩ නොමැත. ඔබට 250MB නිදහස් ඉඩක් තිබෙන ඔබට තිබෙන බව සහතික කරගෙන නැවත උත්සාහ කරන්න."</string>
@@ -1422,9 +1426,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ටැබ්ලට්ය"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"රූපවාහිනී"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"දුරකථනය"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ඉස් බණු"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"නාදක ඩොක් කරන්න"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ඉස් බණු"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"පද්ධතිය"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"බ්ලූටූත් ශ්‍රව්‍ය"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"නොරැහැන් සංදර්ශකය"</string>
@@ -1779,9 +1784,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"හදිසි පණිවිඩ පරීක්ෂණය"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"පිළිතුරු දෙන්න"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM එක සඳහා ඉඩ නොදේ"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM එක සක්‍රීය කර නොමැත"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM එක සඳහා ඉඩ නොදේ"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"දුරකථනය සඳහා ඉඩ නොදේ"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"උත්පතන කවුළුව"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"මෙම කෙටි මගට නවතම යෙදුම අවශ්‍යයි"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"යෙදුම උපස්ථ සහ ප්‍රතිසාධනය සඳහා සහාය නොදක්වන බැවින් කෙටි මග ප්‍රතිසාධනය කළ නොහැකි විය"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"යෙදුම් අත්සන නොගැළපෙන බැවින් කෙටි මග ප්‍රතිසාධනය කළ නොහැකි විය"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"කෙටි මග ප්‍රතිසාධනය කළ නොහැකි විය"</string>
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 11b9c88..9dacc7b 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Pracovný profil bol odstránený z dôvodu chýbajúcej aplikácie na správu"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Aplikácia na správu pracovného profilu buď chýba, alebo je poškodená. Z toho dôvodu bol odstránený pracovný profil aj k nemu priradené dáta. Ak potrebujete pomoc, kontaktujte svojho správcu."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Váš pracovný profil už v tomto zariadení nie je k dispozícii"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Príliš veľa pokusov o zadanie hesla"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Zariadenie je spravované"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Vaša organizácia spravuje toto zariadenie a môže sledovať sieťovú premávku. Klepnutím zobrazíte podrobnosti."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Vaše zariadenie bude vymazané"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Upozornenia"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Predajná ukážka"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Pripojenie USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikácia je spustená"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikácie spotrebúvajú batériu"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> používa batériu"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Aplikácie (<xliff:g id="NUMBER">%1$d</xliff:g>) používajú batériu"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metóda vstupu"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Operácie s textom"</string>
     <string name="email" msgid="4560673117055050403">"E-mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefón"</string>
-    <string name="map" msgid="6068210738233985748">"Mapy"</string>
-    <string name="browse" msgid="6993590095938149861">"Prehliadač"</string>
+    <string name="dial" msgid="1253998302767701559">"Volať"</string>
+    <string name="map" msgid="6521159124535543457">"Nájsť"</string>
+    <string name="browse" msgid="1245903488306147205">"Otvoriť"</string>
+    <string name="sms" msgid="4560537514610063430">"Správa"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Pridať"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Nedostatok ukladacieho priestoru"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Niektoré systémové funkcie nemusia fungovať"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"V úložisku nie je dostatok voľného miesta pre systém. Zaistite, aby ste mali 250 MB voľného miesta a zariadenie reštartujte."</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televízor"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefón"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slúchadlá"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Reproduktory doku"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slúchadlá"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Systém"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth audio"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Bezdrôtové zobrazenie"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test tiesňových správ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odpovedať"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM karta je zakázaná"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM karta nie je k dispozícii"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM karta je zakázaná"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefón je zakázaný"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Automaticky otvárané okno"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Tento odkaz vyžaduje najnovšiu aplikáciu"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Odkaz sa nepodarilo obnoviť, pretože aplikácia nepodporuje zálohovanie a obnovu"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Odkaz sa nepodarilo obnoviť pre nesúlad podpisov aplikácie"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Odkaz sa nepodarilo obnoviť"</string>
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 71b7126..722e88e 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Delovni profil izbrisan zaradi manjkajoče skrbniške aplikacije"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Skrbniška aplikacija delovnega profila manjka ali pa je poškodovana, zaradi česar je bil delovni profil s povezanimi podatki izbrisan. Za pomoč se obrnite na skrbnika."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Vaš delovni profil ni več na voljo v tej napravi"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Preveč poskusov vnosa gesla"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Naprava je upravljana"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Vaša organizacija upravlja to napravo in lahko nadzira omrežni promet. Dotaknite se za podrobnosti."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Podatki v napravi bodo izbrisani"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Opozorila"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Predstavitev za maloprodajo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Povezava USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikacija se izvaja"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacije, ki porabljajo energijo akumulatorja"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> porablja energijo akumulatorja"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Toliko aplikacij porablja energijo akumulatorja: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Način vnosa"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Besedilna dejanja"</string>
     <string name="email" msgid="4560673117055050403">"E-pošta"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Zemljevidi"</string>
-    <string name="browse" msgid="6993590095938149861">"Brskalnik"</string>
+    <string name="dial" msgid="1253998302767701559">"Pokliči"</string>
+    <string name="map" msgid="6521159124535543457">"Poišči na zemljevidu"</string>
+    <string name="browse" msgid="1245903488306147205">"Odpri"</string>
+    <string name="sms" msgid="4560537514610063430">"Sporočilo"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Dodaj"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Prostor za shranjevanje bo pošel"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Nekatere sistemske funkcije morda ne delujejo"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"V shrambi ni dovolj prostora za sistem. Sprostite 250 MB prostora in znova zaženite napravo."</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablični računalnik"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televizor"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalke"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Zvočniki stojala"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Slušalke"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Zvok prek Bluetootha"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Brezžični prikaz"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Preskus sporočil v sili"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Odgovor"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Kartica SIM ni dovoljena"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Kartica SIM ni omogočena za uporabo"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Kartica SIM ni dovoljena"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefon ni dovoljen"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pojavno okno"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"in še <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Za to bližnjico potrebujete najnovejšo aplikacijo"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Bližnjice ni bilo mogoče obnoviti, ker aplikacija ne podpira varnostnega kopiranja in obnavljanja"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Bližnjice ni bilo mogoče obnoviti zaradi neujemanja podpisa aplikacije"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Bližnjice ni bilo mogoče obnoviti"</string>
 </resources>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 3ad574a..1682557 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Profili i punës u fshi për shkak të mungesës së aplikacionit të administratorit"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Aplikacioni i administratorit të profilit të punës mungon ose është dëmtuar. Si rezultat i kësaj, profili yt i punës dhe të dhënat përkatëse janë fshirë. Kontakto me administratorin për ndihmë."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Profili yt i punës nuk është më i disponueshëm në këtë pajisje"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Shumë përpjekje për fjalëkalimin"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Pajisja është e menaxhuar"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organizata jote e menaxhon këtë pajisje dhe mund të monitorojë trafikun e rrjetit. Trokit për detaje."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Pajisja do të spastrohet"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Sinjalizimet"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demonstrimi i shitjes me pakicë"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Lidhja USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Aplikacioni është në ekzekutim"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacionet që konsumojnë baterinë"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> po përdor baterinë"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikacione po përdorin baterinë"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Metoda e hyrjes"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Veprimet e tekstit"</string>
     <string name="email" msgid="4560673117055050403">"Dërgo mail"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefoni"</string>
-    <string name="map" msgid="6068210738233985748">"Hartat"</string>
-    <string name="browse" msgid="6993590095938149861">"Shfletuesi"</string>
+    <string name="dial" msgid="1253998302767701559">"Telefono"</string>
+    <string name="map" msgid="6521159124535543457">"Gjej vendndodhjen"</string>
+    <string name="browse" msgid="1245903488306147205">"Hap"</string>
+    <string name="sms" msgid="4560537514610063430">"Mesazh"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Shto"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Hapësira ruajtëse po mbaron"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Disa funksione të sistemit mund të mos punojnë"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Nuk ka hapësirë të mjaftueshme ruajtjeje për sistemin. Sigurohu që të kesh 250 MB hapësirë të lirë dhe pastaj të rifillosh."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Televizori"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kufjet"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Altoparlantët e stacionit"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kufjet"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistemi"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audioja e \"bluetooth-it\""</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Ekran pa tel"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Testim për mesazhet e urgjencës"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Përgjigju"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Karta SIM nuk lejohet"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Karta SIM nuk është dhënë"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Karta SIM nuk lejohet"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefoni nuk lejohet"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Dritare kërcyese"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Kjo shkurtore kërkon aplikacionin më të fundit"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Nuk mund të restaurohej shkurtorja sepse aplikacioni nuk mbështet rezervimin dhe restaurimin"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Nuk mund të restaurohej shkurtorja për shkak të mospërputhjes së nënshkrimit të aplikacionit"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Nuk mund të restaurohej shkurtorja"</string>
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index f9b865d..b6de548 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -174,6 +174,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Профил за Work је избрисан јер недостаје апликација за администраторе"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Апликација за администраторе на профилу за Work недостаје или је оштећена. Због тога су профил за Work и повезани подаци избрисани. Обратите се администратору за помоћ."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Профил за Work више није доступан на овом уређају"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Превише покушаја уноса лозинке"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Уређајем се управља"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Организација управља овим уређајем и може да надгледа мрежни саобраћај. Додирните за детаље."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Уређај ће бити обрисан"</string>
@@ -251,6 +252,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Обавештења"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Режим демонстрације за малопродајне објекте"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB веза"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Апликација је покренута"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Апликације које троше батерију"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерију"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Апликације (<xliff:g id="NUMBER">%1$d</xliff:g>) користе батерију"</string>
@@ -276,8 +278,8 @@
     <string name="permgroupdesc_storage" msgid="637758554581589203">"приступа сликама, медијима и датотекама на уређају"</string>
     <string name="permgrouprequest_storage" msgid="7429669910547860218">"Дозволите &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да приступа сликама, медијским датотекама и датотекама на уређају"</string>
     <string name="permgrouplab_microphone" msgid="171539900250043464">"Микрофон"</string>
-    <string name="permgroupdesc_microphone" msgid="4988812113943554584">"снима аудио"</string>
-    <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Дозволите да &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; снима аудио снимке"</string>
+    <string name="permgroupdesc_microphone" msgid="4988812113943554584">"снима звук"</string>
+    <string name="permgrouprequest_microphone" msgid="8065941268709600606">"Дозволите да &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; снима звук"</string>
     <string name="permgrouplab_camera" msgid="4820372495894586615">"Камера"</string>
     <string name="permgroupdesc_camera" msgid="3250611594678347720">"снима слике и видео"</string>
     <string name="permgrouprequest_camera" msgid="810824326507258410">"Дозволите да &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; снима слике и видео снимке"</string>
@@ -997,9 +999,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Метод уноса"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Радње у вези са текстом"</string>
     <string name="email" msgid="4560673117055050403">"Пошаљи имејл"</string>
-    <string name="dial" msgid="4204975095406423102">"Позови"</string>
-    <string name="map" msgid="6068210738233985748">"Мапе"</string>
-    <string name="browse" msgid="6993590095938149861">"Прегледач"</string>
+    <string name="dial" msgid="1253998302767701559">"Позови"</string>
+    <string name="map" msgid="6521159124535543457">"Пронађи"</string>
+    <string name="browse" msgid="1245903488306147205">"Отвори"</string>
+    <string name="sms" msgid="4560537514610063430">"Пошаљи SMS"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Додај"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Меморијски простор је на измаку"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Неке системске функције можда не функционишу"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Нема довољно меморијског простора за систем. Уверите се да имате 250 MB слободног простора и поново покрените."</string>
@@ -1443,9 +1447,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Таблет"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ТВ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Слушалице"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Звучници базне станице"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Слушалице"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Систем"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth аудио"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Бежични екран"</string>
@@ -1646,7 +1651,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2. пословни <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3. пословни имејл <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Да бисте откачили овај екран, додирните и задржите дугмад Назад и Преглед"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Ова апликација не може да се откачи"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Екран је закачен"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Екран је откачен"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Тражи PIN пре откачињања"</string>
@@ -1812,9 +1816,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Тестирање порука у хитним случајевима"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Одговори"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM картица није дозвољена"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM картица није подешена"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM картица није дозвољена"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Телефон није дозвољен"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"SIM картица није прилагођена за гласовне услуге"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"SIM картица није подешена за гласовне услуге"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"SIM картица није прилагођена за гласовне услуге"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Телефон није прилагођен за гласовне услуге"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Искачући прозор"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"и још <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Ова пречица захтева најновију апликацију"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Враћање пречице није успело јер апликација не подржава прављење резервне копије и враћање"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Враћање пречице није успело јер се потписи апликација не подударају"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Враћање пречице није успело"</string>
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 3f7cecb..cd5ec95 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Jobbprofilen har raderats eftersom det saknas en administratörsapp"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Administratörsappen för jobbprofilen saknas eller är skadad. Det innebär att jobbprofilen och all relaterad data har raderats. Kontakta administratören om du vill ha hjälp."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Jobbprofilen är inte längre tillgänglig på enheten"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"För många försök med lösenord"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Enheten hanteras"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Organisationen hanterar den här enheten och kan övervaka nätverkstrafiken. Tryck om du vill veta mer."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Enheten kommer att rensas"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Varningar"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo för återförsäljare"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB-anslutning"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"App körs"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Appar som drar batteri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> drar batteri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> appar drar batteri"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Textåtgärder"</string>
     <string name="email" msgid="4560673117055050403">"Skicka e-post"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Kartor"</string>
-    <string name="browse" msgid="6993590095938149861">"Webbläsare"</string>
+    <string name="dial" msgid="1253998302767701559">"Ring"</string>
+    <string name="map" msgid="6521159124535543457">"Hitta"</string>
+    <string name="browse" msgid="1245903488306147205">"Öppna"</string>
+    <string name="sms" msgid="4560537514610063430">"Sms:a"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Lägg till"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Lagringsutrymmet börjar ta slut"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Det kan hända att vissa systemfunktioner inte fungerar"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Det finns inte tillräckligt med utrymme för systemet. Kontrollera att du har ett lagringsutrymme på minst 250 MB och starta om."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Surfplatta"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Mobil"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hörlurar"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Dockningsstationens högtalare"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Hörlurar"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth-ljud"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Trådlös skärm"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Test för nödmeddelanden"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Svara"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-kort tillåts inte"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-kort tillhandahålls inte"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-kort tillåts inte"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Mobil tillåts inte"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"popup-fönster"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"<xliff:g id="NUMBER">%1$d</xliff:g> till"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Den här genvägen kräver den senaste versionen av appen"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Det gick inte att återställa genvägen eftersom appen inte har stöd för säkerhetskopiering och återställning"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Det gick inte att återställa genvägen eftersom appens signatur inte stämmer"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Det gick inte att återställa genvägen"</string>
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 8bda7f8..0eaaf91 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -170,6 +170,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Wasifu wa kazini umefutwa kutokana na kupotea kwa programu ya msimamizi"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Programu ya msimamizi wa wasifu wa kazini imepotea au ina hitilafu. Kwa sababu hiyo, wasifu wako wa kazini na data husika imefutwa. Wasiliana na msimamizi wako kwa usaidizi."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Wasifu wako wa kazini haupatikani tena kwenye kifaa hiki"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Umejaribu kuweka nenosiri mara nyingi mno"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Kifaa kinadhibitiwa"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Shirika lako linadhibiti kifaa hiki na huenda likafuatilia shughuli kwenye mtandao. Gonga ili upate maelezo zaidi."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Data iliyomo kwenye kifaa chako itafutwa"</string>
@@ -246,6 +247,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Arifa"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Onyesho la duka la rejareja"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Muunganisho wa USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Programu inaendelea kutekelezwa"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Programu zinazotumia betri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
@@ -295,7 +297,7 @@
     <string name="capability_title_canCaptureFingerprintGestures" msgid="6309568287512278670">"Ishara za alama ya kidole"</string>
     <string name="capability_desc_canCaptureFingerprintGestures" msgid="7102111919385702482">"Inaweza kurekodi ishara zinazotekelezwa kwenye kitambua alama ya kidole."</string>
     <string name="permlab_statusBar" msgid="7417192629601890791">"zima au rekebisha mwambaa hali"</string>
-    <string name="permdesc_statusBar" msgid="8434669549504290975">"Inaruhusu programu kulemaza upau wa hali au kuongeza na kutoa ikoni za mfumo."</string>
+    <string name="permdesc_statusBar" msgid="8434669549504290975">"Inaruhusu programu kulemaza upau wa hali au kuongeza na kutoa aikoni za mfumo."</string>
     <string name="permlab_statusBarService" msgid="4826835508226139688">"kuwa sehemu ya arifa"</string>
     <string name="permdesc_statusBarService" msgid="716113660795976060">"Inaruhusu programu kuwa upau wa hali."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"panua/kunja mwambaa hali"</string>
@@ -975,9 +977,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Mbinu ya uingizaji"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Vitendo vya maandishi"</string>
     <string name="email" msgid="4560673117055050403">"Barua pepe"</string>
-    <string name="dial" msgid="4204975095406423102">"Simu"</string>
-    <string name="map" msgid="6068210738233985748">"Ramani"</string>
-    <string name="browse" msgid="6993590095938149861">"Kivinjari"</string>
+    <string name="dial" msgid="1253998302767701559">"Simu"</string>
+    <string name="map" msgid="6521159124535543457">"Tafuta"</string>
+    <string name="browse" msgid="1245903488306147205">"Fungua"</string>
+    <string name="sms" msgid="4560537514610063430">"Ujumbe"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Ongeza"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Nafasi ya kuhifadhi inakaribia kujaa"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Baadhi ya vipengee vya mfumo huenda visifanye kazi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Hifadhi haitoshi kwa ajili ya mfumo. Hakikisha una MB 250 za nafasi ya hifadhi isiyotumika na uanzishe upya."</string>
@@ -1066,7 +1070,7 @@
     <string name="new_app_action" msgid="5472756926945440706">"Anza <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
     <string name="new_app_description" msgid="1932143598371537340">"Komesha programu ya zamani bila kuhifadhi."</string>
     <string name="dump_heap_notification" msgid="2618183274836056542">"<xliff:g id="PROC">%1$s</xliff:g> imezidi kiwango cha hifadhi kinachotakikana"</string>
-    <string name="dump_heap_notification_detail" msgid="6901391084243999274">"Imezidi kikomo cha hifadhi; gonga ili uishiriki"</string>
+    <string name="dump_heap_notification_detail" msgid="6901391084243999274">"Imezidi kikomo cha hifadhi; gusa ili uishiriki"</string>
     <string name="dump_heap_title" msgid="5864292264307651673">"Ungependa kushiriki picha ya binafsi?"</string>
     <string name="dump_heap_text" msgid="4809417337240334941">"Mchakato wa <xliff:g id="PROC">%1$s</xliff:g> umezidi kiwango kinachotakikana cha hifadhi cha <xliff:g id="SIZE">%2$s</xliff:g>. Unaweza kupata picha ya hifadhi ili uishiriki na msadini programu wa picha. Tahadhari: picha hii ya hifadhi inaweza kuwa na maelezo yako ya binafsi ambayo yanaweza kufikiwa na programu."</string>
     <string name="sendText" msgid="5209874571959469142">"Chagua kitendo kwa ajili ya maandishi"</string>
@@ -1418,9 +1422,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Kompyuta kibao"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Runinga"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Simu"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Vipokeasauti"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Vipasa sauti vya kituo"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Vipokeasauti"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Mfumo"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Sauti ya Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Uonyeshaji usiotumia waya"</string>
@@ -1775,9 +1780,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Jaribio la ujumbe wa dharura"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Jibu"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM imekataliwa"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM haikubaliwi"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM imekataliwa"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Simu imekataliwa"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Dirisha Ibukizi"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Njia hii ya mkato inahitaji toleo jipya la programu"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Imeshindwa kurejesha njia ya mkato kwa sababu programu haitumii kipengele cha hifadhi rudufu na kurejesha upya"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Imeshindwa kurejesha njia ya mkato kwa sababu ufunguo wako wa kuambatisha cheti kwenye programu haulingani"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Imeshindwa kurejesha njia ya mkato"</string>
 </resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 9405608..429ecf1 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"நிர்வாகிப் பயன்பாடு இல்லாததால், பணி விவரம் நீக்கப்பட்டது"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"பணி விவர நிர்வாகிப் பயன்பாடு இல்லை அல்லது அது சிதைந்துள்ளது. இதன் விளைவாக, உங்கள் பணி விவரமும் அதனுடன் தொடர்புடைய தரவும் நீக்கப்பட்டன. உதவிக்கு, நிர்வாகியைத் தொடர்புகொள்ளவும்."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"இந்தச் சாதனத்தில் இனி பணி விவரம் கிடைக்காது"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"கடவுச்சொல்லை அதிக முறை தவறாக முயற்சித்துவிட்டீர்கள்"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"சாதனம் நிர்வகிக்கப்படுகிறது"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"உங்கள் நிறுவனம் இந்தச் சாதனத்தை நிர்வகிக்கும், அத்துடன் அது நெட்வொர்க் ட்ராஃபிக்கைக் கண்காணிக்கலாம். விவரங்களுக்கு, தட்டவும்."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"சாதனத் தரவு அழிக்கப்படும்"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"விழிப்பூட்டல்கள்"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"விற்பனையாளர் டெமோ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB இணைப்பு"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"பயன்பாடு இயங்குகிறது"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"பேட்டரியைப் பயன்படுத்தும் பயன்பாடுகள்"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> பயன்பாடு பேட்டரியைப் பயன்படுத்துகிறது"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> பயன்பாடுகள் பேட்டரியைப் பயன்படுத்துகின்றன"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"உள்ளீட்டு முறை"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"உரை நடவடிக்கைகள்"</string>
     <string name="email" msgid="4560673117055050403">"மின்னஞ்சல்"</string>
-    <string name="dial" msgid="4204975095406423102">"ஃபோன்"</string>
-    <string name="map" msgid="6068210738233985748">"வரைபடம்"</string>
-    <string name="browse" msgid="6993590095938149861">"உலாவி"</string>
+    <string name="dial" msgid="1253998302767701559">"அழை"</string>
+    <string name="map" msgid="6521159124535543457">"கண்டுபிடி"</string>
+    <string name="browse" msgid="1245903488306147205">"திற"</string>
+    <string name="sms" msgid="4560537514610063430">"செய்தி"</string>
+    <string name="add_contact" msgid="7867066569670597203">"சேர்"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"சேமிப்பிடம் குறைகிறது"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"சில அமைப்பு செயல்பாடுகள் வேலை செய்யாமல் போகலாம்"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"முறைமையில் போதுமான சேமிப்பகம் இல்லை. 250மெ.பை. அளவு காலி இடவசதி இருப்பதை உறுதிசெய்து மீண்டும் தொடங்கவும்."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"டேப்லெட்"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"டிவி"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ஃபோன்"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ஹெட்ஃபோன்கள்"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"மொபைல் வைக்கும் கருவியின் ஸ்பீக்கர்கள்"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ஹெட்ஃபோன்கள்"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"சிஸ்டம்"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"புளூடூத் ஆடியோ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"வயர்லெஸ் காட்சி"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2வது பணி <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3வது பணி <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"இந்தத் திரையை அகற்ற, முந்தையது, மேலோட்டப் பார்வை ஆகிய இரண்டு பொத்தானையும் தொட்டுப் பிடித்திருக்கவும்"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"இந்தப் பயன்பாட்டை அகற்ற முடியாது"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"திரை பின் செய்யப்பட்டது"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"திரையின் பின் அகற்றப்பட்டது"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"அகற்றும் முன் PINஐக் கேள்"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"அவசரக் காலச் செய்திகளுக்கான சோதனை"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"பதிலளிக்கும்"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"சிம் அனுமதிக்கப்படவில்லை"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"சிம் அமைக்கப்படவில்லை"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"சிம் அனுமதிக்கப்படவில்லை"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ஃபோன் அனுமதிக்கப்படவில்லை"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"குரல் அழைப்பை மேற்கொள்ள இந்த சிம்மிற்கு அனுமதி இல்லை"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"குரல் அழைப்பை மேற்கொள்ளும் வசதி இந்த சிம்மிற்கு இல்லை"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"குரல் அழைப்பை மேற்கொள்ள இந்த சிம்மிற்கு அனுமதி இல்லை"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"குரல் அழைப்பை மேற்கொள்ள இந்த ஃபோனுக்கு அனுமதி இல்லை"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"பாப்அப் சாளரம்"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"இந்த ஷார்ட்கட்டைப் பயன்படுத்த, சமீபத்திய பயன்பாடு வேண்டும்"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"காப்புப் பிரதி மற்றும் மீட்டமைவைப் பயன்பாடு ஆதரிக்காத காரணத்தால், ஷார்ட்கட்டை மீட்டமைக்க முடியவில்லை"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"பயன்பாட்டுச் சான்றுகள் பொருந்தாத காரணத்தினால், ஷார்ட்கட்டை மீட்டமைக்க முடியவில்லை"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"ஷார்ட்கட்டை மீட்டமைக்க முடியவில்லை"</string>
 </resources>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index bd984c5..9b3b89b 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"నిర్వాహక యాప్ లేనందున కార్యాలయ ప్రొఫైల్ తొలగించబడింది"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"కార్యాలయ ప్రొఫైల్ నిర్వాహక యాప్ లేదు లేదా పాడైంది. తత్ఫలితంగా, మీ కార్యాలయ ప్రొఫైల్ మరియు సంబంధిత డేటా తొలగించబడ్డాయి. సహాయం కోసం మీ నిర్వాహకులను సంప్రదించండి."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"ఈ పరికరంలో మీ కార్యాలయ ప్రొఫైల్ ఇప్పుడు అందుబాటులో లేదు"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"చాలా ఎక్కువ పాస్‌వర్డ్ ప్రయత్నాలు చేసారు"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"పరికరం నిర్వహించబడింది"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"మీ సంస్థ ఈ పరికరాన్ని నిర్వహిస్తుంది మరియు నెట్‌వర్క్ ట్రాఫిక్‌ని పర్యవేక్షించవచ్చు. వివరాల కోసం నొక్కండి."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"మీ పరికరంలోని డేటా తొలగించబడుతుంది"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"హెచ్చరికలు"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"రిటైల్ డెమో"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB కనెక్షన్"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"యాప్ అమలవుతోంది"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"బ్యాటరీని ఉపయోగిస్తున్న యాప్‌లు"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> బ్యాటరీని ఉపయోగిస్తోంది"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> యాప్‌లు బ్యాటరీని ఉపయోగిస్తున్నాయి"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"ఇన్‌పుట్ పద్ధతి"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"వచనానికి సంబంధించిన చర్యలు"</string>
     <string name="email" msgid="4560673117055050403">"ఇమెయిల్"</string>
-    <string name="dial" msgid="4204975095406423102">"ఫోన్"</string>
-    <string name="map" msgid="6068210738233985748">"మ్యాప్స్"</string>
-    <string name="browse" msgid="6993590095938149861">"బ్రౌజర్"</string>
+    <string name="dial" msgid="1253998302767701559">"కాల్ చేయండి"</string>
+    <string name="map" msgid="6521159124535543457">"గుర్తించండి"</string>
+    <string name="browse" msgid="1245903488306147205">"తెరవండి"</string>
+    <string name="sms" msgid="4560537514610063430">"సందేశం"</string>
+    <string name="add_contact" msgid="7867066569670597203">"జోడించండి"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"నిల్వ ఖాళీ అయిపోతోంది"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"కొన్ని సిస్టమ్ కార్యాచరణలు పని చేయకపోవచ్చు"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"సిస్టమ్ కోసం తగినంత నిల్వ లేదు. మీకు 250MB ఖాళీ స్థలం ఉందని నిర్ధారించుకుని, పునఃప్రారంభించండి."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"టాబ్లెట్"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"టీవీ"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"ఫోన్"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"హెడ్‌ఫోన్‌లు"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"డాక్ స్పీకర్‌లు"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"హెడ్‌ఫోన్‌లు"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"సిస్టమ్"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"బ్లూటూత్ ఆడియో"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"వైర్‌లెస్ డిస్‌ప్లే"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"అత్యవసర సందేశాల పరీక్ష"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"ప్రత్యుత్తరం పంపండి"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM అనుమతించబడదు"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM సక్రియం కాలేదు"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM అనుమతించబడదు"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ఫోన్ అనుమతించబడదు"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"పాప్అప్ విండో"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ఈ సత్వరమార్గానికి తాజా యాప్ అవసరం"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"బ్యాకప్ మరియు పునరుద్ధరణకు యాప్ మద్దతు ఇవ్వని కారణంగా సత్వరమార్గాన్ని పునరుద్ధరించడం సాధ్యపడలేదు"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"యాప్ సంతకం సరిపోలని కారణంగా సత్వరమార్గాన్ని పునరుద్ధరించడం సాధ్యపడలేదు"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"సత్వరమార్గాన్ని పునరుద్ధరించడం సాధ్యపడలేదు"</string>
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 2653dea..1ccb751 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"ลบโปรไฟล์งานแล้วเนื่องจากไม่มีแอปผู้ดูแลระบบ"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"แอปผู้ดูแลระบบโปรไฟล์งานไม่มีอยู่หรือเสียหาย ระบบจึงทำการลบโปรไฟล์งานและข้อมูลที่เกี่ยวข้องของคุณออก โปรดติดต่อผู้ดูแลระบบเพื่อรับความช่วยเหลือ"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"โปรไฟล์งานของคุณไม่สามารถใช้ในอุปกรณ์นี้อีกต่อไป"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"ลองป้อนรหัสผ่านหลายครั้งเกินไป"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"อุปกรณ์มีการจัดการ"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"องค์กรของคุณจัดการอุปกรณ์นี้และอาจตรวจสอบการจราจรของข้อมูลในเครือข่าย แตะเพื่อดูรายละเอียด"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"ระบบจะลบข้อมูลในอุปกรณ์ของคุณ"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"การแจ้งเตือน"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"การสาธิตสำหรับผู้ค้าปลีก"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"การเชื่อมต่อ USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"แอปที่ทำงานอยู่"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"แอปหลายแอปกำลังใช้แบตเตอรี่"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้แบตเตอรี่"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"แอป <xliff:g id="NUMBER">%1$d</xliff:g> แอปกำลังใช้แบตเตอรี่"</string>
@@ -666,7 +668,7 @@
     <string name="imProtocolYahoo" msgid="8271439408469021273">"Yahoo"</string>
     <string name="imProtocolSkype" msgid="9019296744622832951">"Skype"</string>
     <string name="imProtocolQq" msgid="8887484379494111884">"QQ"</string>
-    <string name="imProtocolGoogleTalk" msgid="493902321140277304">"แฮงเอาท์"</string>
+    <string name="imProtocolGoogleTalk" msgid="493902321140277304">" Hangouts"</string>
     <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string>
     <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string>
     <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"วิธีป้อนข้อมูล"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"การทำงานของข้อความ"</string>
     <string name="email" msgid="4560673117055050403">"อีเมล"</string>
-    <string name="dial" msgid="4204975095406423102">"โทรศัพท์"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"เบราว์เซอร์"</string>
+    <string name="dial" msgid="1253998302767701559">"โทร"</string>
+    <string name="map" msgid="6521159124535543457">"ค้นหา"</string>
+    <string name="browse" msgid="1245903488306147205">"เปิด"</string>
+    <string name="sms" msgid="4560537514610063430">"ข้อความ"</string>
+    <string name="add_contact" msgid="7867066569670597203">"เพิ่ม"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"พื้นที่จัดเก็บเหลือน้อย"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"บางฟังก์ชันระบบอาจไม่ทำงาน"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"พื้นที่เก็บข้อมูลไม่เพียงพอสำหรับระบบ โปรดตรวจสอบว่าคุณมีพื้นที่ว่าง 250 MB แล้วรีสตาร์ท"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"แท็บเล็ต"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"ทีวี"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"โทรศัพท์"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"หูฟัง"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ลำโพงแท่นชาร์จ"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"หูฟัง"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"ระบบ"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"เสียงบลูทูธ"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"การแสดงผลแบบไร้สาย"</string>
@@ -1631,7 +1636,7 @@
     <string name="package_updated_device_owner" msgid="1847154566357862089">"อัปเดตโดยผู้ดูแลระบบ"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"ลบโดยผู้ดูแลระบบ"</string>
     <string name="battery_saver_description" msgid="1960431123816253034">"เพื่อช่วยยืดอายุการใช้งานแบตเตอรี่ โหมดประหยัดแบตเตอรี่จะลดการทำงานของอุปกรณ์และจำกัดการสั่น บริการตำแหน่ง และข้อมูลแบ็กกราวด์ส่วนใหญ่ สำหรับอีเมล การรับส่งข้อความ และแอปอื่นๆ ที่ใช้การซิงค์จะไม่อัปเดตหากคุณไม่เปิดขึ้นมา\n\nโหมดประหยัดแบตเตอรี่จะปิดโดยอัตโนมัติขณะชาร์จอุปกรณ์"</string>
-    <string name="data_saver_description" msgid="6015391409098303235">"เพื่อช่วยลดปริมาณการใช้อินเทอร์เน็ต โปรแกรมประหยัดอินเทอร์เน็ตจะช่วยป้องกันไม่ให้แอปบางส่วนส่งหรือรับข้อมูลเครือข่ายมือถือในพื้นหลัง แอปที่คุณกำลังใช้งานสามารถเข้าถึงข้อมูลเครือข่ายมือถือได้ แต่อาจไม่บ่อยเท่าเดิม ตัวอย่างเช่น ภาพต่างๆ จะไม่แสดงจนกว่าคุณจะแตะที่ภาพเหล่านั้น"</string>
+    <string name="data_saver_description" msgid="6015391409098303235">"เพื่อช่วยลดปริมาณการใช้อินเทอร์เน็ต โปรแกรมประหยัดอินเทอร์เน็ตจะช่วยป้องกันไม่ให้บางแอปส่งหรือรับข้อมูลเครือข่ายมือถือในการทำงานเบื้องหลัง แอปที่คุณกำลังใช้งานสามารถเข้าถึงข้อมูลเครือข่ายมือถือได้ แต่อาจไม่บ่อยเท่าเดิม ตัวอย่างเช่น ภาพต่างๆ จะไม่แสดงจนกว่าคุณจะแตะที่ภาพเหล่านั้น"</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"เปิดการประหยัดอินเทอร์เน็ตไหม"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"เปิด"</string>
     <plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="4367877408072000848">
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"การทดสอบข้อความกรณีฉุกเฉิน"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"ตอบ"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"ไม่อนุญาตให้ใช้ซิม"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"ไม่มีการจัดสรรซิม"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"ไม่อนุญาตให้ใช้ซิม"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"ไม่อนุญาตให้ใช้โทรศัพท์"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"หน้าต่างป๊อปอัป"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"ทางลัดนี้ต้องใช้แอปล่าสุด"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"คืนค่าทางลัดไม่ได้เนื่องจากแอปไม่รองรับการสำรองข้อมูลและคืนค่า"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"คืนค่าทางลัดไม่ได้เนื่องจากการลงนามแอปไม่ตรงกัน"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"คืนค่าทางลัดไม่ได้"</string>
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index da41b9e..5a9d118 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Na-delete ang profile sa trabaho dahil wala itong admin app"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Nawawala o nasira ang admin app ng profile sa trabaho. Dahil dito, na-delete ang profile mo sa trabaho at nauugnay na data. Makipag-ugnayan sa iyong admin para sa tulong."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Hindi na available sa device na ito ang iyong profile sa trabaho"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Masyadong maraming pagsubok sa password"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Pinamamahalaan ang device"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Pinamamahalaan ng iyong organisasyon ang device na ito, at maaari nitong subaybayan ang trapiko sa network. I-tap para sa mga detalye."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Buburahin ang iyong device"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Mga Alerto"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Retail demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Koneksyon ng USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Tumatakbo ang app"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Mga app na kumokonsumo ng baterya"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Gumagamit ng baterya ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Gumagamit ng baterya ang <xliff:g id="NUMBER">%1$d</xliff:g> (na) app"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Pamamaraan ng pag-input"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Pagkilos ng teksto"</string>
     <string name="email" msgid="4560673117055050403">"Mag-email"</string>
-    <string name="dial" msgid="4204975095406423102">"Telepono"</string>
-    <string name="map" msgid="6068210738233985748">"Mga Mapa"</string>
-    <string name="browse" msgid="6993590095938149861">"Browser"</string>
+    <string name="dial" msgid="1253998302767701559">"Tawagan"</string>
+    <string name="map" msgid="6521159124535543457">"Hanapin"</string>
+    <string name="browse" msgid="1245903488306147205">"Buksan"</string>
+    <string name="sms" msgid="4560537514610063430">"Padalhan ng Mensahe"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Magdagdag"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Nauubusan na ang puwang ng storage"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Maaaring hindi gumana nang tama ang ilang paggana ng system"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Walang sapat na storage para sa system. Tiyaking mayroon kang 250MB na libreng espasyo at i-restart."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telepono"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Mga Headphone"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Mga speaker ng dock"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Mga Headphone"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"System"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Audio sa Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Wireless display"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Pagsubok sa mga mensaheng pang-emergency"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Tumugon"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"Hindi pinahihintulutan ang SIM"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"Hindi naprobisyon ang SIM"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"Hindi pinahihintulutan ang SIM"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Hindi pinahihintulutan ang telepono"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Window ng Popup"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Kinakailangan ng shortcut na ito ang pinakabagong app"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Hindi ma-restore ang shortcut dahil hindi sinusuportahan ng app ang pag-back up at pag-restore"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Hindi ma-restore ang shortcut dahil hindi magkatugma ang signature ng app"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Hindi ma-restore ang shortcut"</string>
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 41a3eae..8b77850 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Eksik yönetici uygulaması nedeniyle iş profili silindi"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"İş profili yönetici uygulaması eksik ya da bozuk. Bunun sonucunda iş profiliniz ve ilgili veriler silindi. Yardım almak için yöneticiniz ile iletişim kurun."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"İş profiliniz arık bu cihazda kullanılamıyor"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Çok fazla şifre denemesi yapıldı"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Cihaz yönetiliyor"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Kuruluşunuz bu cihazı yönetmekte olup ağ trafiğini izleyebilir. Ayrıntılar için dokunun."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Cihazınız silinecek"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Uyarılar"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Mağaza demo"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB bağlantısı"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Uygulama çalışıyor"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Pil kullanan uygulamalar"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> pil kullanıyor"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> uygulama pil kullanıyor"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Metin eylemleri"</string>
     <string name="email" msgid="4560673117055050403">"E-posta"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Haritalar"</string>
-    <string name="browse" msgid="6993590095938149861">"Tarayıcı"</string>
+    <string name="dial" msgid="1253998302767701559">"Telefon et"</string>
+    <string name="map" msgid="6521159124535543457">"Yerini bul"</string>
+    <string name="browse" msgid="1245903488306147205">"Aç"</string>
+    <string name="sms" msgid="4560537514610063430">"Mesaj"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Ekle"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Depolama alanı bitiyor"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Bazı sistem işlevleri çalışmayabilir"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Sistem için yeterli depolama alanı yok. 250 MB boş alanınızın bulunduğundan emin olun ve yeniden başlatın."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Tablet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kulaklıklar"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Yuva hoparlörleri"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Kulaklıklar"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Sistem"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Bluetooth ses"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Kablosuz ekran"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"İş için 2. <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"İş için 3. <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Bu ekranın sabitlemesini kaldırmak için Geri\'ye ve Genel Bakış\'a dokunup basılı tutun"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Bu uygulamanın sabitlemesi kaldırılamaz"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Ekran sabitlendi"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran sabitlemesi kaldırıldı"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Sabitlemeyi kaldırmadan önce PIN\'i sor"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Acil durum mesajları testi"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Yanıtla"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM\'e izin verilmiyor"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM için temel hazırlık yapılmadı"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM\'e izin verilmiyor"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Telefona izin verilmiyor"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Ses için SIM\'e izin verilmiyor"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Ses için SIM\'in temel hazırlığı yapılmadı"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"Ses için SIM\'e izin verilmiyor"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Ses için telefona izin verilmiyor"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Pop-up Pencere"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Bu kısayol, en son uygulamayı gerektiriyor"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Uygulama, yedekleme ve geri yüklemeyi desteklemediğinden kısayol geri yüklenemedi"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Uygulama imzası eşleşmediğinden kısayol geri yüklenemedi"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Kısayol geri yüklenemedi"</string>
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index f0e2c1d..7d14e59 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -176,6 +176,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Робочий профіль видалено через відсутність додатка адміністратора"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Додаток адміністратора в робочому профілі відсутній або пошкоджений. У результаті ваш робочий профіль і пов’язані з ним дані видалено. Зверніться до свого адміністратора по допомогу."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Робочий профіль більше не доступний на цьому пристрої"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Забагато спроб ввести пароль"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Пристрій контролюється"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Адміністратор вашої організації контролює цей пристрій і відстежує мережевий трафік. Торкніться, щоб дізнатися більше."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"З вашого пристрою буде стерто всі дані"</string>
@@ -254,6 +255,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Сповіщення"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Демо-режим для роздрібної торгівлі"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"З’єднання USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Працює додаток"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Додатки, що використовують заряд акумулятора"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує заряд акумулятора"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"Додатків, що використовують заряд акумулятора: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
@@ -1017,9 +1019,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Метод введення"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Дії з текстом"</string>
     <string name="email" msgid="4560673117055050403">"Електронна пошта"</string>
-    <string name="dial" msgid="4204975095406423102">"Телефонувати"</string>
-    <string name="map" msgid="6068210738233985748">"Карти"</string>
-    <string name="browse" msgid="6993590095938149861">"Веб-переглядач"</string>
+    <string name="dial" msgid="1253998302767701559">"Зателефонувати"</string>
+    <string name="map" msgid="6521159124535543457">"Знайти"</string>
+    <string name="browse" msgid="1245903488306147205">"Відкрити"</string>
+    <string name="sms" msgid="4560537514610063430">"Повідомлення"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Додати"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Закінчується пам’ять"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Деякі системні функції можуть не працювати"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Недостатньо місця для системи. Переконайтесь, що на пристрої є 250 МБ вільного місця, і повторіть спробу."</string>
@@ -1466,9 +1470,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Планшетний ПК"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"Телевізор"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Телефон"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Навушники"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Динаміки док-станції"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Навушники"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Система"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Аудіо Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Бездротовий екран"</string>
@@ -1847,9 +1852,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Перевірка екстрених повідомлень"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Відповісти"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM-карта заборонена"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM-карту не затверджено"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM-карта заборонена"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Телефон заборонено"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Спливаюче вікно"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Для цього ярлика потрібна найновіша версія додатка"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Не вдалося відновити ярлик, оскільки додаток не підтримує резервне копіювання та відновлення"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Не вдалося відновити ярлик, оскільки підписи додатків не збігаються"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Не вдалося відновити ярлик"</string>
 </resources>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 2d9717d..b89af42 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"گمشدہ منتظم ایپ کی وجہ سے دفتری پروفائل حذف کر دیا گیا"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"دفتری پروفائل کی منتظم ایپ یا تو غائب ہے یا خراب ہے۔ اس کی وجہ سے، آپ کا دفتری پروفائل اور متعلقہ ڈیٹا حذف کر دیے گئے ہیں۔ مدد کیلئے اپنے منتظم سے رابطہ کریں۔"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"آپ کا دفتری پروفائل اس آلہ پر مزید دستیاب نہیں ہے"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"پاس ورڈ کی بہت ساری کوششیں"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"آلہ زیر انتظام ہے"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"آپ کی تنظیم اس آلے کا نظم کرتی ہے اور وہ نیٹ ورک ٹریفک کی نگرانی کر سکتی ہے۔ تفاصیل کیلئے تھپتھپائیں۔"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"آپ کا آلہ صاف کر دیا جائے گا"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"الرٹس"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"ریٹیل ڈیمو"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"‏USB کنکشن"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"ایپ چل رہی ہے"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"ایپس بیٹری خرچ کر رہی ہیں"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> بیٹری کا استعمال کر رہی ہے"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ایپس بیٹری کا استعمال کر رہی ہیں"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"اندراج کا طریقہ"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"متن کی کارروائیاں"</string>
     <string name="email" msgid="4560673117055050403">"ای میل"</string>
-    <string name="dial" msgid="4204975095406423102">"فون کریں"</string>
-    <string name="map" msgid="6068210738233985748">"Maps"</string>
-    <string name="browse" msgid="6993590095938149861">"براؤزر"</string>
+    <string name="dial" msgid="1253998302767701559">"کال کریں"</string>
+    <string name="map" msgid="6521159124535543457">"پتا لگائیں"</string>
+    <string name="browse" msgid="1245903488306147205">"کھولیں"</string>
+    <string name="sms" msgid="4560537514610063430">"پیغام"</string>
+    <string name="add_contact" msgid="7867066569670597203">"شامل کریں"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"اسٹوریج کی جگہ ختم ہو رہی ہے"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"ممکن ہے سسٹم کے کچھ فنکشنز کام نہ کریں"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"‏سسٹم کیلئے کافی اسٹوریج نہیں ہے۔ اس بات کو یقینی بنائیں کہ آپ کے پاس 250MB خالی جگہ ہے اور دوبارہ شروع کریں۔"</string>
@@ -1303,9 +1307,9 @@
     <string name="submit" msgid="1602335572089911941">"جمع کرائیں"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"کار وضع فعال ہے"</string>
     <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"کار موڈ سے خارج ہونے کیلئے تھپتھپائیں۔"</string>
-    <string name="tethered_notification_title" msgid="3146694234398202601">"ٹیتھرنگ یا ہاٹ اسپاٹ فعال"</string>
+    <string name="tethered_notification_title" msgid="3146694234398202601">"ٹیدرنگ یا ہاٹ اسپاٹ فعال"</string>
     <string name="tethered_notification_message" msgid="2113628520792055377">"سیٹ اپ کرنے کیلئے تھپتھپائیں۔"</string>
-    <string name="disable_tether_notification_title" msgid="7526977944111313195">"ربط بنانا غیر فعال ہے"</string>
+    <string name="disable_tether_notification_title" msgid="7526977944111313195">"ٹیدرنگ غیر فعال ہے"</string>
     <string name="disable_tether_notification_message" msgid="2913366428516852495">"تفصیلات کے لئے اپنے منتظم سے رابطہ کریں"</string>
     <string name="back_button_label" msgid="2300470004503343439">"واپس جائیں"</string>
     <string name="next_button_label" msgid="1080555104677992408">"اگلا"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"ٹیبلیٹ"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"فون"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ہیڈ فونز"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"ڈاک اسپیکرز"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"ہیڈ فونز"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"سسٹم"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"بلوٹوتھ آڈیو"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"وائرلیس ڈسپلے"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"ایمرجنسی پیغامات کی جانچ"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"جواب دیں"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"‏SIM کی اجازت نہیں ہے"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"‏SIM فراہم کردہ نہیں ہے"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"‏SIM کی اجازت نہیں ہے"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"فون کی اجازت نہیں ہے"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"پاپ اپ ونڈو"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"‎‎‎‎‎+ <xliff:g id="NUMBER">%1$d</xliff:g>‎‎"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"اس شارٹ کٹ کیلئے جدید ترین ایپ درکار ہے"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"شارٹ کٹ کو بحال نہیں کیا جا سکا، کیونکہ ایپ بیک اپ اور بحالی کو سپورٹ نہیں کرتی ہے"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"ایپ کی دستخط کے غیر مماثل ہونے کی وجہ سے شارٹ کٹ کو بحال نہیں کیا جا سکا"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"شارٹ کٹ کو بحال نہیں کیا جا سکا"</string>
 </resources>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index f639e80..af5e0bd 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Administrator ilovasi yo‘qligi sababli ishchi profil o‘chirib tashlandi"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Ishchi profilning administrator ilovasi yo‘q yoki buzilgan. Shuning uchun, ishchi profilingiz va unga aloqador ma’lumotlar o‘chirib tashlandi. Yordam olish uchun administratoringizga murojaat qiling."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Bu qurilmada endi ishchi profilingiz mavjud emas"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Parol ko‘p marta xato kiritildi"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Bu – boshqariladigan qurilma"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Tashkilotingiz bu qurilmani boshqaradi va tarmoq trafigini nazorat qilishi mumkin. Tafsilotlar uchun bosing."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Qurilmangizdagi ma’lumotlar o‘chirib tashlanadi"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Ogohlantirishlar"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Demo rejim"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB orqali ulanish"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Ilova ishlamoqda"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Batareya quvvatini sarflayotgan ilovalar"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi batareya quvvatini sarflamoqda"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ta ilova batareya quvvatini sarflamoqda"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Kiritish uslubi"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Matn yozish"</string>
     <string name="email" msgid="4560673117055050403">"E-pochta"</string>
-    <string name="dial" msgid="4204975095406423102">"Telefon"</string>
-    <string name="map" msgid="6068210738233985748">"Xaritalar"</string>
-    <string name="browse" msgid="6993590095938149861">"Brauzer"</string>
+    <string name="dial" msgid="1253998302767701559">"Chaqiruv"</string>
+    <string name="map" msgid="6521159124535543457">"Xaritadan topish"</string>
+    <string name="browse" msgid="1245903488306147205">"Ochish"</string>
+    <string name="sms" msgid="4560537514610063430">"Xabar"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Qo‘shish"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Xotirada bo‘sh joy tugamoqda"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Ba‘zi tizim funksiyalari ishlamasligi mumkin"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Tizim uchun xotirada joy yetarli emas. Avval 250 megabayt joy bo‘shatib, keyin qurilmani o‘chirib yoqing."</string>
@@ -1002,9 +1006,9 @@
     <string name="whichEditApplication" msgid="144727838241402655">"Tahrirlash…"</string>
     <string name="whichEditApplicationNamed" msgid="1775815530156447790">"“%1$s” yordamida tahrirlash"</string>
     <string name="whichEditApplicationLabel" msgid="7183524181625290300">"Tahrirlash"</string>
-    <string name="whichSendApplication" msgid="6902512414057341668">"Baham ko‘rish"</string>
+    <string name="whichSendApplication" msgid="6902512414057341668">"Ulashish"</string>
     <string name="whichSendApplicationNamed" msgid="2799370240005424391">"“%1$s” orqali ulashish"</string>
-    <string name="whichSendApplicationLabel" msgid="4579076294675975354">"Baham ko‘rish"</string>
+    <string name="whichSendApplicationLabel" msgid="4579076294675975354">"Ulashish"</string>
     <string name="whichSendToApplication" msgid="8272422260066642057">"Ilovani tanlang"</string>
     <string name="whichSendToApplicationNamed" msgid="7768387871529295325">"%1$s orqali yuborish"</string>
     <string name="whichSendToApplicationLabel" msgid="8878962419005813500">"Yuborish"</string>
@@ -1410,7 +1414,7 @@
     <string name="sha1_fingerprint" msgid="7930330235269404581">"SHA-1 barmoq izi:"</string>
     <string name="activity_chooser_view_see_all" msgid="4292569383976636200">"Barchasini ko‘rish"</string>
     <string name="activity_chooser_view_dialog_title_default" msgid="4710013864974040615">"Harakat turini tanlang"</string>
-    <string name="share_action_provider_share_with" msgid="5247684435979149216">"Baham ko‘rish"</string>
+    <string name="share_action_provider_share_with" msgid="5247684435979149216">"Ulashish"</string>
     <string name="sending" msgid="3245653681008218030">"Jo‘natilmoqda…"</string>
     <string name="launchBrowserDefault" msgid="2057951947297614725">"Brauzer ishga tushirilsinmi?"</string>
     <string name="SetupCallDefault" msgid="5834948469253758575">"Qo‘ng‘iroqni qabul qilasizmi?"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Planshet"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Telefon"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Quloq karnaychalari"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Taglik karnaylar"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Quloq karnaychalari"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Tizim"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Ovozni Bluetooth orqali chiqarish"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Simsiz monitor"</string>
@@ -1621,7 +1626,6 @@
     <string name="managed_profile_label_badge_2" msgid="5048136430082124036">"2-ishxona <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="managed_profile_label_badge_3" msgid="2808305070321719040">"3-ishxona <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="lock_to_app_toast" msgid="6820571533009838261">"Bu ekrandan chiqish uchun Orqaga va Menyu tugmalarini bosib turing"</string>
-    <string name="lock_to_app_toast_locked" msgid="7849470948648628704">"Bu ilovani olib tashlab bo‘lmaydi"</string>
     <string name="lock_to_app_start" msgid="6643342070839862795">"Ekran qadab qo‘yildi"</string>
     <string name="lock_to_app_exit" msgid="8598219838213787430">"Ekran bo‘shatildi"</string>
     <string name="lock_to_app_unlock_pin" msgid="2552556656504331634">"Yechishda PIN-kod so‘ralsin"</string>
@@ -1777,9 +1781,14 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Favqulodda holatlar uchun sinov xabarlari"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Javob berish"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM karta ishlatish taqiqlangan"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM karta yo‘q"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM karta ishlatish taqiqlangan"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Chaqiruvlar taqiqlangan"</string>
+    <string name="mmcc_authentication_reject" msgid="5767701075994754356">"Ovoz uchun SIM karta ishlatish taqiqlangan"</string>
+    <string name="mmcc_imsi_unknown_in_hlr" msgid="5316658473301462825">"Ovoz uchun SIM karta taqdim etilmagan"</string>
+    <string name="mmcc_illegal_ms" msgid="807334478177362062">"Ovoz uchun SIM karta ishlatish taqiqlangan"</string>
+    <string name="mmcc_illegal_me" msgid="1950705155760872972">"Ovoz uchun telefon taqiqlangan"</string>
     <string name="popup_window_default_title" msgid="4874318849712115433">"Qalqib chiquvchi oyna"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Bu yorliq uchun eng oxirgi versiyadagi ilova zarur"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Ilovada zaxiralash va tiklash ishlamagani uchun yorliq tiklanmadi"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Ilova imzosi mos kelmagani uchun yorliq tiklanmadi"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Yorliq tiklanmadi"</string>
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 58e0e34..c3938aa 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Đã xóa hồ sơ công việc do thiếu ứng dụng quản trị"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Ứng dụng quản trị hồ sơ công việc bị thiếu hoặc hỏng. Do vậy, hồ sơ công việc của bạn và dữ liệu liên quan đã bị xóa. Hãy liên hệ với quản trị viên của bạn để được trợ giúp."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Hồ sơ công việc của bạn không có sẵn trên thiết bị này nữa"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Quá nhiều lần nhập mật khẩu"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Thiết bị được quản lý"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Tổ chức của bạn sẽ quản lý thiết bị này và có thể theo dõi lưu lượng truy cập mạng. Nhấn để biết chi tiết."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Thiết bị của bạn sẽ bị xóa"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Cảnh báo"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Giới thiệu bán lẻ"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Kết nối USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Ứng dụng đang chạy"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Các ứng dụng tiêu thụ pin"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang sử dụng pin"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng đang sử dụng pin"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Phương thức nhập"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Tác vụ văn bản"</string>
     <string name="email" msgid="4560673117055050403">"Email"</string>
-    <string name="dial" msgid="4204975095406423102">"Điện thoại"</string>
-    <string name="map" msgid="6068210738233985748">"Bản đồ"</string>
-    <string name="browse" msgid="6993590095938149861">"Trình duyệt"</string>
+    <string name="dial" msgid="1253998302767701559">"Gọi"</string>
+    <string name="map" msgid="6521159124535543457">"Định vị"</string>
+    <string name="browse" msgid="1245903488306147205">"Mở"</string>
+    <string name="sms" msgid="4560537514610063430">"Gửi tin nhắn"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Thêm"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Sắp hết dung lượng lưu trữ"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Một số chức năng hệ thống có thể không hoạt động"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Không đủ bộ nhớ cho hệ thống. Đảm bảo bạn có 250 MB dung lượng trống và khởi động lại."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Máy tính bảng"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Điện thoại"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Tai nghe"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Loa đế"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Tai nghe"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Hệ thống"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Âm thanh Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Hiển thị không dây"</string>
@@ -1630,7 +1635,7 @@
     <string name="package_installed_device_owner" msgid="6875717669960212648">"Do quản trị viên của bạn cài đặt"</string>
     <string name="package_updated_device_owner" msgid="1847154566357862089">"Do quản trị viên của bạn cập nhật"</string>
     <string name="package_deleted_device_owner" msgid="2307122077550236438">"Do quản trị viên của bạn xóa"</string>
-    <string name="battery_saver_description" msgid="1960431123816253034">"Để giúp tăng tuổi thọ pin, trình tiết kiệm pin sẽ giảm hiệu suất thiết bị của bạn và hạn chế rung, dịch vụ vị trí và hầu hết dữ liệu nền. Email, nhắn tin và các ứng dụng khác dựa trên đồng bộ hóa có thể không cập nhật nếu bạn không mở chúng.\n\nTrình tiết kiệm pin tự động tắt khi thiết bị của bạn đang sạc."</string>
+    <string name="battery_saver_description" msgid="1960431123816253034">"Để giúp tăng thời lượng pin, trình tiết kiệm pin sẽ giảm hiệu suất thiết bị và hạn chế rung, dịch vụ vị trí và hầu hết dữ liệu nền. Ứng dụng email, nhắn tin và các ứng dụng khác dựa trên đồng bộ hóa có thể không cập nhật nếu bạn không mở.\n\nTrình tiết kiệm pin tự động tắt khi thiết bị của bạn đang sạc."</string>
     <string name="data_saver_description" msgid="6015391409098303235">"Để giúp giảm mức sử dụng dữ liệu, Trình tiết kiệm dữ liệu chặn một số ứng dụng gửi hoặc nhận dữ liệu trong nền. Ứng dụng mà bạn hiện sử dụng có thể truy cập dữ liệu nhưng có thể thực hiện việc đó ít thường xuyên hơn. Ví dụ: hình ảnh sẽ không hiển thị cho đến khi bạn nhấn vào hình ảnh đó."</string>
     <string name="data_saver_enable_title" msgid="4674073932722787417">"Bật Trình tiết kiệm dữ liệu?"</string>
     <string name="data_saver_enable_button" msgid="7147735965247211818">"Bật"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Kiểm tra thông báo khẩn cấp"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Trả lời"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"SIM không được cho phép"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"SIM không được cấp phép"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"SIM không được cho phép"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Điện thoại không được cho phép"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Cửa sổ bật lên"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Lối tắt này yêu cầu ứng dụng mới nhất"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Không thể khôi phục lối tắt do ứng dụng không hỗ trợ sao lưu và khôi phục"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Không thể khôi phục lối tắt do không khớp chữ ký ứng dụng"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Không thể khôi phục lối tắt"</string>
 </resources>
diff --git a/core/res/res/values-xlarge/strings.xml b/core/res/res/values-xlarge/strings.xml
deleted file mode 100644
index fc20be6..0000000
--- a/core/res/res/values-xlarge/strings.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/assets/res/any/strings.xml
-**
-** Copyright 2010, 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.
-*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-
-    <!-- Do not translate.  WebView User Agent targeted content -->
-    <string name="web_user_agent_target_content" translatable="false"></string>
-
-</resources>
\ No newline at end of file
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index e87f76c..5629df1 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"由于缺少管理应用,工作资料已被删除"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"工作资料管理应用缺失或损坏,因此系统已删除您的工作资料及相关数据。如需帮助,请与您的管理员联系。"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"您的工作资料已不在此设备上"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"密码尝试次数过多"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"设备为受管理设备"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"贵单位会管理该设备,且可能会监控网络流量。点按即可了解详情。"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"系统将清空您的设备"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"提醒"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"零售演示模式"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB 连接"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"应用正在运行中"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"消耗电量的应用"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在消耗电量"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> 个应用正在消耗电量"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"输入法"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"文字操作"</string>
     <string name="email" msgid="4560673117055050403">"电子邮件"</string>
-    <string name="dial" msgid="4204975095406423102">"电话"</string>
-    <string name="map" msgid="6068210738233985748">"地图"</string>
-    <string name="browse" msgid="6993590095938149861">"浏览器"</string>
+    <string name="dial" msgid="1253998302767701559">"通话"</string>
+    <string name="map" msgid="6521159124535543457">"定位"</string>
+    <string name="browse" msgid="1245903488306147205">"打开"</string>
+    <string name="sms" msgid="4560537514610063430">"短信"</string>
+    <string name="add_contact" msgid="7867066569670597203">"添加"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"存储空间不足"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"某些系统功能可能无法正常使用"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"系统存储空间不足。请确保您有250MB的可用空间,然后重新启动。"</string>
@@ -1137,7 +1141,7 @@
     <string name="wifi_p2p_enabled_notification_message" msgid="8064677407830620023">"点按即可查看相关设置"</string>
     <string name="accept" msgid="1645267259272829559">"接受"</string>
     <string name="decline" msgid="2112225451706137894">"拒绝"</string>
-    <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"邀请已发送"</string>
+    <string name="wifi_p2p_invitation_sent_title" msgid="1318975185112070734">"已发出邀请"</string>
     <string name="wifi_p2p_invitation_to_connect_title" msgid="4958803948658533637">"连接邀请"</string>
     <string name="wifi_p2p_from_message" msgid="570389174731951769">"发件人:"</string>
     <string name="wifi_p2p_to_message" msgid="248968974522044099">"收件人:"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"平板电脑"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"电视"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"手机"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"耳机"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"基座扬声器"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"耳机"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"系统"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"蓝牙音频"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"无线显示"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"紧急消息测试"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"回复"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"不受允许的 SIM 卡"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"未配置的 SIM 卡"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"不受允许的 SIM 卡"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"不受允许的手机"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"弹出式窗口"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"您必须拥有最新版的应用才能使用此快捷方式"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"无法恢复快捷方式,因为应用不支持备份和恢复功能"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"无法恢复快捷方式,因为应用签名不相符"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"无法恢复快捷方式"</string>
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 56ea6a0..b9c83b3 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"由於沒有管理員應用程式,工作設定檔已刪除"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"工作設定檔管理員應用程式已遺失或損毀。因此,您的工作設定檔和相關資料已刪除。請聯絡您的管理員以取得協助。"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"您的工作設定檔無法再在此裝置上使用"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"密碼輸入錯誤的次數過多"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"裝置已受管理"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"您的機構會管理此裝置,並可能會監控網絡流量。輕按即可瞭解詳情。"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"您的裝置將被清除"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"通知"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"零售示範"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB 連線"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"應用程式正在執行"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"耗用電量的應用程式"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用電量"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在使用電量"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"輸入法"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"文字操作"</string>
     <string name="email" msgid="4560673117055050403">"電郵"</string>
-    <string name="dial" msgid="4204975095406423102">"撥打電話"</string>
-    <string name="map" msgid="6068210738233985748">"地圖"</string>
-    <string name="browse" msgid="6993590095938149861">"瀏覽器"</string>
+    <string name="dial" msgid="1253998302767701559">"通話"</string>
+    <string name="map" msgid="6521159124535543457">"尋找"</string>
+    <string name="browse" msgid="1245903488306147205">"開啟"</string>
+    <string name="sms" msgid="4560537514610063430">"短訊"</string>
+    <string name="add_contact" msgid="7867066569670597203">"新增"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"儲存空間即將用盡"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"部分系統功能可能無法運作"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"系統儲存空間不足。請確認裝置有 250 MB 的可用空間,然後重新啟動。"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"平板電腦"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"電視"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"手機"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"耳機"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"插座喇叭"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"耳機"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"系統"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"藍牙音頻"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"無線螢幕分享"</string>
@@ -1607,7 +1612,7 @@
     </plurals>
     <string name="restr_pin_try_later" msgid="973144472490532377">"稍後再試"</string>
     <string name="immersive_cling_title" msgid="8394201622932303336">"開啟全螢幕"</string>
-    <string name="immersive_cling_description" msgid="3482371193207536040">"由上往下刷退出。"</string>
+    <string name="immersive_cling_description" msgid="3482371193207536040">"由頂部向下快速滑動即可退出。"</string>
     <string name="immersive_cling_positive" msgid="5016839404568297683">"知道了"</string>
     <string name="done_label" msgid="2093726099505892398">"完成"</string>
     <string name="hour_picker_description" msgid="6698199186859736512">"小時環形滑桿"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"緊急訊息測試"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"回覆"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"不允許使用 SIM 卡"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"無法識別 SIM 卡"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"不允許使用 SIM 卡"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"不允許使用手機"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"彈出式視窗"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"您需要最新的應用程式,才能使用這個捷徑"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"由於應用程式不支援備份和還原功能,因此無法還原捷徑"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"由於應用程式簽署不相符,因此無法還原捷徑"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"無法還原捷徑"</string>
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 17c80b5..a5b91eb 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Work 設定檔因管理員應用程式遺失而遭到刪除"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Work 設定檔管理員應用程式遺失或已毀損,因此系統刪除了你的 Work 設定檔和相關資料。如需協助,請與你的管理員聯絡。"</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"你的 Work 設定檔已不在這個裝置上"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"密碼輸入錯誤的次數過多"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"裝置受到管理"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"貴機構會管理這個裝置,且可能監控網路流量。輕觸即可瞭解詳情。"</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"你的裝置資料將遭到清除"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"快訊"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"零售商示範模式"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"USB 連線"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"應用程式執行中"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"正在耗用電量的應用程式"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在耗用電量"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在耗用電量"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"輸入法"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"文字動作"</string>
     <string name="email" msgid="4560673117055050403">"電子郵件"</string>
-    <string name="dial" msgid="4204975095406423102">"電話"</string>
-    <string name="map" msgid="6068210738233985748">"地圖"</string>
-    <string name="browse" msgid="6993590095938149861">"瀏覽器"</string>
+    <string name="dial" msgid="1253998302767701559">"通話"</string>
+    <string name="map" msgid="6521159124535543457">"定位"</string>
+    <string name="browse" msgid="1245903488306147205">"開啟"</string>
+    <string name="sms" msgid="4560537514610063430">"訊息"</string>
+    <string name="add_contact" msgid="7867066569670597203">"新增"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"儲存空間即將用盡"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"部分系統功能可能無法運作"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"系統儲存空間不足。請確定你已釋出 250MB 的可用空間,然後重新啟動。"</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"平板電腦"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"電視"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"手機"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"耳機"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"座架喇叭"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"耳機"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"系統"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"藍牙音訊"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"無線螢幕分享"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"緊急訊息測試"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"回覆"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"不受允許的 SIM 卡"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"未佈建的 SIM 卡"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"不受允許的 SIM 卡"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"不受允許的手機"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"彈出式視窗"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"你必須擁有最新版的應用程式,才能使用這個捷徑"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"應用程式不支援備份與還原功能,因此無法還原捷徑"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"應用程式簽署不相符,因此無法還原捷徑"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"無法還原捷徑"</string>
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 88fb1d7..4703e79 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -172,6 +172,7 @@
     <string name="work_profile_deleted_description" msgid="1100529432509639864">"Iphrofayela yomsebenzi isuswe ngenxa yohlelo lokusebenza olungekho lomlawuli"</string>
     <string name="work_profile_deleted_details" msgid="6307630639269092360">"Uhlelo lokusebenza lokulawula lephrofayela yomsebenzi kungenzeka alukho noma lonakele. Njengomphumela, iphrofayela yakho yomsebenzi nedatha ehlobene isusiwe. Xhumana nomlawuli wakho ukuze uthole usizo."</string>
     <string name="work_profile_deleted_description_dpm_wipe" msgid="8823792115612348820">"Iphrofayela yakho yomsebenzi ayisatholakali kule divayisi"</string>
+    <string name="work_profile_deleted_reason_maximum_password_failure" msgid="8986903510053359694">"Imizamo yamaphasiwedi eminingi kakhulu"</string>
     <string name="network_logging_notification_title" msgid="6399790108123704477">"Idivayisi iphethwe"</string>
     <string name="network_logging_notification_text" msgid="7930089249949354026">"Inhlangano yakho iphethe le divayisi futhi kungenzeka ingaqaphi ithrafikhi yenethiwekhi. Thephela imininingwane."</string>
     <string name="factory_reset_warning" msgid="5423253125642394387">"Idivayisi yakho izosulwa"</string>
@@ -248,6 +249,7 @@
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Izexwayiso"</string>
     <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Idemo yokuthenga"</string>
     <string name="notification_channel_usb" msgid="9006850475328924681">"Ukuxhumeka kwe-USB"</string>
+    <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Uhlelo loksuebenza olusebenzayo"</string>
     <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Izinhlelo zokusebenza ezidla ibhethri"</string>
     <string name="foreground_service_app_in_background" msgid="1060198778219731292">"<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa ibhethri"</string>
     <string name="foreground_service_apps_in_background" msgid="7175032677643332242">"<xliff:g id="NUMBER">%1$d</xliff:g> izinhlelo zokusebenza zisebenzisa ibhethri"</string>
@@ -977,9 +979,11 @@
     <string name="inputMethod" msgid="1653630062304567879">"Indlela yokufakwayo"</string>
     <string name="editTextMenuTitle" msgid="4909135564941815494">"Izenzo zombhalo"</string>
     <string name="email" msgid="4560673117055050403">"I-imeyili"</string>
-    <string name="dial" msgid="4204975095406423102">"Ifoni"</string>
-    <string name="map" msgid="6068210738233985748">"Amamephu"</string>
-    <string name="browse" msgid="6993590095938149861">"Isiphequluli"</string>
+    <string name="dial" msgid="1253998302767701559">"Shaya"</string>
+    <string name="map" msgid="6521159124535543457">"Beka"</string>
+    <string name="browse" msgid="1245903488306147205">"Vula"</string>
+    <string name="sms" msgid="4560537514610063430">"Umlayezo"</string>
+    <string name="add_contact" msgid="7867066569670597203">"Engeza"</string>
     <string name="low_internal_storage_view_title" msgid="5576272496365684834">"Isikhala sokulondoloza siyaphela"</string>
     <string name="low_internal_storage_view_text" msgid="6640505817617414371">"Eminye imisebenzi yohlelo ingahle ingasebenzi"</string>
     <string name="low_internal_storage_view_text_no_boot" msgid="6935190099204693424">"Akusona isitoreji esanele sesistimu. Qiniseka ukuthi unesikhala esikhululekile esingu-250MB uphinde uqalise kabusha."</string>
@@ -1420,9 +1424,10 @@
     <string name="default_audio_route_name" product="tablet" msgid="4617053898167127471">"Ithebulethi"</string>
     <string name="default_audio_route_name" product="tv" msgid="9158088547603019321">"I-TV"</string>
     <string name="default_audio_route_name" product="default" msgid="4239291273420140123">"Ifoni"</string>
-    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ama-headphone"</string>
     <string name="default_audio_route_name_dock_speakers" msgid="6240602982276591864">"Izipikha ze-Dock"</string>
-    <string name="default_media_route_name_hdmi" msgid="2450970399023478055">"HDMI"</string>
+    <string name="default_audio_route_name_hdmi" msgid="1486254205617081251">"HDMI"</string>
+    <string name="default_audio_route_name_headphones" msgid="8119971843803439110">"Ama-headphone"</string>
+    <string name="default_audio_route_name_usb" msgid="1234984851352637769">"I-USB"</string>
     <string name="default_audio_route_category_name" msgid="3722811174003886946">"Isistimu"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="8575624030406771015">"Umsindo we-Bluetooth"</string>
     <string name="wireless_display_route_description" msgid="9070346425023979651">"Ukubonisa okungenazintambo"</string>
@@ -1777,9 +1782,18 @@
     <string name="etws_primary_default_message_test" msgid="2709597093560037455">"Ukuhlolwa kwemilayezo yesimo esiphuthumayo"</string>
     <string name="notification_reply_button_accessibility" msgid="3621714652387814344">"Phendula"</string>
     <string name="etws_primary_default_message_others" msgid="6293148756130398971"></string>
-    <string name="mmcc_authentication_reject" msgid="7729819349669603406">"I-SIM ayivunyelwe"</string>
-    <string name="mmcc_imsi_unknown_in_hlr" msgid="6321202257374418726">"I-SIM ayinikezelwe"</string>
-    <string name="mmcc_illegal_ms" msgid="2769452751852211112">"I-SIM ayivunyelwe"</string>
-    <string name="mmcc_illegal_me" msgid="4438696681169345015">"Ifoni ayivunyelwe"</string>
+    <!-- no translation found for mmcc_authentication_reject (5767701075994754356) -->
+    <skip />
+    <!-- no translation found for mmcc_imsi_unknown_in_hlr (5316658473301462825) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_ms (807334478177362062) -->
+    <skip />
+    <!-- no translation found for mmcc_illegal_me (1950705155760872972) -->
+    <skip />
     <string name="popup_window_default_title" msgid="4874318849712115433">"Iwindi lesigelekeqe"</string>
+    <string name="slice_more_content" msgid="8504342889413274608">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
+    <string name="shortcut_restored_on_lower_version" msgid="5270675146351613828">"Lesi sinqamuleli sidinga uhlelo lokusebenza lwakamuva"</string>
+    <string name="shortcut_restore_not_supported" msgid="5028808567940014190">"Ayikwazanga ukubuyisa isinqamuleli ngoba uhlelo lokusebenza alusekeli isipele nokubuyisa"</string>
+    <string name="shortcut_restore_signature_mismatch" msgid="2406209324521327518">"Ayikwazanga ukubuyisa isinqamuleli ngoba isignisha yohlelo lokusebenza ayifani"</string>
+    <string name="shortcut_restore_unknown_issue" msgid="8703738064603262597">"Ayikwazanga ukubuyisa isinqamuleli"</string>
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 61b4123..db4dcd2 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2056,11 +2056,11 @@
              Corresponds to {@link android.view.Window#setNavigationBarColor(int)}. -->
         <attr name="navigationBarColor" format="color" />
 
-        <!-- @hide
-             Shows 1dp line of the specified color between the navigation bar and the app content.
+        <!-- Shows a thin line of the specified color between the navigation bar and the app
+             content.
              <p>For this to take effect, the window must be drawing the system bar backgrounds with
              {@link android.R.attr#windowDrawsSystemBarBackgrounds} and the navigation bar must not
-               have been requested to be translucent with
+             have been requested to be translucent with
              {@link android.R.attr#windowTranslucentNavigation}. -->
         <attr name="navigationBarDividerColor" format="color" />
 
@@ -2546,16 +2546,24 @@
         <attr name="filterTouchesWhenObscured" format="boolean" />
 
         <!-- Defines the quality of translucent drawing caches. This property is used
-             only when the drawing cache is enabled and translucent. The default value is auto. -->
+             only when the drawing cache is enabled and translucent. The default value is auto.
+             Deprecated: The view drawing cache was largely made obsolete with the introduction of
+             hardware-accelerated rendering in API 11. -->
         <attr name="drawingCacheQuality">
             <!-- Lets the framework decide what quality level should be used
-                 for the drawing cache. -->
+                 for the drawing cache.
+                 Deprecated: The view drawing cache was largely made obsolete with the introduction
+                 of hardware-accelerated rendering in API 11. -->
             <enum name="auto" value="0" />
             <!-- Low quality. When set to low quality, the drawing cache uses a lower color
-                 depth, thus losing precision in rendering gradients, but uses less memory. -->
+                 depth, thus losing precision in rendering gradients, but uses less memory.
+                 Deprecated: The view drawing cache was largely made obsolete with the introduction
+                 of hardware-accelerated rendering in API 11. -->
             <enum name="low" value="1" />
             <!-- High quality. When set to high quality, the drawing cache uses a higher
-                 color depth but uses more memory. -->
+                 color depth but uses more memory.
+                 Deprecated: The view drawing cache was largely made obsolete with the introduction
+                 of hardware-accelerated rendering in API 11. -->
             <enum name="high" value="2" />
         </attr>
 
@@ -3060,7 +3068,9 @@
              instance during a scrolling.) This property lets you persist the cache
              in memory after its initial usage. Persisting the cache consumes more
              memory but may prevent frequent garbage collection is the cache is created
-             over and over again. By default the persistence is set to scrolling. -->
+             over and over again. By default the persistence is set to scrolling.
+             Deprecated: The view drawing cache was largely made obsolete with the introduction of
+             hardware-accelerated rendering in API 11. -->
         <attr name="persistentDrawingCache">
             <!-- The drawing cache is not persisted after use. -->
             <flag name="none" value="0x0" />
@@ -3072,7 +3082,9 @@
             <flag name="all" value="0x3" />
         </attr>
         <!-- Defines whether the ViewGroup should always draw its children using their
-             drawing cache or not. The default value is true. -->
+             drawing cache or not. The default value is true.
+             Deprecated: The view drawing cache was largely made obsolete with the introduction of
+             hardware-accelerated rendering in API 11. -->
         <attr name="alwaysDrawnWithCache" format="boolean" />
         <!-- Sets whether this ViewGroup's drawable states also include
              its children's drawable states.  This is used, for example, to
@@ -3642,7 +3654,7 @@
     </declare-styleable>
 
     <!-- Specify one or more <code>t3tPmm-filter</code> elements inside a
-         <code>host-nfcf-service</code> element to specify a LF_T3T_PMM -->
+         <code>host-nfcf-service</code> element to specify a LF_T3T_PMM. -->
     <declare-styleable name="T3tPmmFilter">
         <attr name="name" />
 
@@ -4518,7 +4530,7 @@
         <attr name="maxHeight" />
         <!-- Makes the TextView be exactly this many lines tall. -->
         <attr name="lines" format="integer" min="0" />
-        <!-- Makes the TextView be exactly this many pixels tall.
+        <!-- Makes the TextView be exactly this tall.
              You could get the same effect by specifying this number in the
              layout parameters. -->
         <attr name="height" format="dimension" />
@@ -4535,7 +4547,7 @@
         <attr name="maxWidth" />
         <!-- Makes the TextView be exactly this many ems wide. -->
         <attr name="ems" format="integer" min="0" />
-        <!-- Makes the TextView be exactly this many pixels wide.
+        <!-- Makes the TextView be exactly this wide.
              You could get the same effect by specifying this number in the
              layout parameters. -->
         <attr name="width" format="dimension" />
@@ -8658,6 +8670,14 @@
          common values are 400 for regular weight and 700 for bold weight. If unspecified, the value
          in the font's header tables will be used. -->
         <attr name="fontWeight" format="integer" />
+        <!-- The index of the font in the tcc font file. If the font file referenced is not in the
+         tcc format, this attribute needs not be specified. -->
+        <attr name="ttcIndex" format="integer" />
+        <!-- The variation settings to be applied to the font. The string should be in the following
+         format: "'tag1' value1, 'tag2' value2, ...". If the default variation settings should be
+         used, or the font used does not support variation settings, this attribute needs not be
+         specified. -->
+        <attr name="fontVariationSettings" format="string" />
     </declare-styleable>
 
     <!-- Attributes that are read when parsing a <fontfamily> tag. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index e6c829f..be0f6d9 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1334,7 +1334,7 @@
          split that contains the defined component. -->
     <attr name="splitName" format="string" />
 
-    <!-- Specifies the target sandbox this app wants to use. Higher sanbox versions
+    <!-- Specifies the target sandbox this app wants to use. Higher sandbox versions
          will have increasing levels of security.
 
          <p>The default value of this attribute is <code>1</code>. -->
@@ -1449,8 +1449,7 @@
              quit it.  Only one such app can be running at a time; if the user
              tries to launch a second such app, they will be prompted
              to quit the first before doing so.  While the
-             application is running, the user will be informed of this.
-             @hide -->
+             application is running, the user will be informed of this. -->
         <attr name="cantSaveState" format="boolean" />
         <attr name="uiOptions" />
         <!-- Declare that your application will be able to deal with RTL (right to left) layouts.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 83bb443..4a8bc13 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -901,6 +901,18 @@
     <!-- Maximum color temperature, in Kelvin, supported by Night display. -->
     <integer name="config_nightDisplayColorTemperatureMax">4082</integer>
 
+    <string-array name="config_nightDisplayColorTemperatureCoefficientsNative">
+        <!-- R a-coefficient --> <item>0.0</item>
+        <!-- R b-coefficient --> <item>0.0</item>
+        <!-- R y-intercept --> <item>1.0</item>
+        <!-- G a-coefficient --> <item>-0.00000000962353339</item>
+        <!-- G b-coefficient --> <item>0.000153045476</item>
+        <!-- G y-intercept --> <item>0.390782778</item>
+        <!-- B a-coefficient --> <item>-0.0000000189359041</item>
+        <!-- B b-coefficient --> <item>0.000302412211</item>
+        <!-- B y-intercept --> <item>-0.198650895</item>
+    </string-array>
+
     <string-array name="config_nightDisplayColorTemperatureCoefficients">
         <!-- R a-coefficient --> <item>0.0</item>
         <!-- R b-coefficient --> <item>0.0</item>
@@ -1127,6 +1139,11 @@
     <!-- Is the lock-screen disabled for new users by default -->
     <bool name="config_disableLockscreenByDefault">false</bool>
 
+    <!-- If true, enables verification of the lockscreen credential in the factory reset protection
+        flow. This should be true if gatekeeper / weaver credentials can still be checked after a
+        factory reset. -->
+    <bool name="config_enableCredentialFactoryResetProtection">true</bool>
+
     <!-- Control the behavior when the user long presses the home button.
             0 - Nothing
             1 - Launch all apps intent
@@ -1363,7 +1380,7 @@
 
     <!-- The package of the time zone rules updater application. Expected to be the same
          for all Android devices that support APK-based time zone rule updates.
-         A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+         A package-targeted com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
          will be sent to the updater app if the system server detects an update to the updater or
          data app packages.
          The package referenced here must have the android.permission.UPDATE_TIME_ZONE_RULES
@@ -1374,7 +1391,7 @@
 
     <!-- The package of the time zone rules data application. Expected to be configured
          by OEMs to reference their own priv-app APK package.
-         A package-targeted android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
+         A package-targeted com.android.intent.action.timezone.TRIGGER_RULES_UPDATE_CHECK intent
          will be sent to the updater app if the system server detects an update to the updater or
          data app packages.
          [This is only used if config_enableUpdateableTimeZoneRules and
@@ -1736,7 +1753,7 @@
          Build.MODEL. The format string shall not be escaped. -->
     <string name="config_useragentprofile_url" translatable="false"></string>
 
-    <!-- When a database query is executed, the results retuned are paginated
+    <!-- When a database query is executed, the results returned are paginated
          in pages of size (in KB) indicated by this value -->
     <integer name="config_cursorWindowSize">2048</integer>
 
@@ -1748,9 +1765,6 @@
     <string-array name="config_twoDigitNumberPattern" translatable="false">
     </string-array>
 
-    <!-- The VoiceMail default value is displayed to my own number if it is true -->
-    <bool name="config_telephony_use_own_number_for_voicemail">false</bool>
-
     <!-- If this value is true, Sms encoded as octet is decoded by utf8 decoder.
          If false, decoded by Latin decoder. -->
     <bool name="config_sms_utf8_support">false</bool>
@@ -2068,6 +2082,9 @@
     <!--  Maximum number of supported users -->
     <integer name="config_multiuserMaximumUsers">1</integer>
 
+    <!-- Maximum number of users we allow to be running at a time -->
+    <integer name="config_multiuserMaxRunningUsers">3</integer>
+
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
@@ -2347,9 +2364,41 @@
     <!-- Package name for default network scorer app; overridden by product overlays. -->
     <string name="config_defaultNetworkScorerPackageName"></string>
 
-    <!-- default device has recents property -->
+    <!-- Determines whether recent tasks are provided to the user. Default device has recents
+         property. If this is false, then the following recents config flags are ignored. -->
     <bool name="config_hasRecents">true</bool>
 
+    <!-- Component name for the activity that will be presenting the Recents UI, which will receive
+         special permissions for API related to fetching and presenting recent tasks. -->
+    <string name="config_recentsComponentName" translatable="false">com.android.systemui/.recents.RecentsActivity</string>
+
+    <!-- The minimum number of visible recent tasks to be presented to the user through the
+         SystemUI. Can be -1 if there is no minimum limit. -->
+    <integer name="config_minNumVisibleRecentTasks_grid">-1</integer>
+
+    <!-- The maximum number of visible recent tasks to be presented to the user through the
+         SystemUI. Can be -1 if there is no maximum limit. -->
+    <integer name="config_maxNumVisibleRecentTasks_grid">9</integer>
+
+    <!-- The minimum number of visible recent tasks to be presented to the user through the
+         SystemUI. Can be -1 if there is no minimum limit. -->
+    <integer name="config_minNumVisibleRecentTasks_lowRam">-1</integer>
+
+    <!-- The maximum number of visible recent tasks to be presented to the user through the
+         SystemUI. Can be -1 if there is no maximum limit. -->
+    <integer name="config_maxNumVisibleRecentTasks_lowRam">9</integer>
+
+    <!-- The minimum number of visible recent tasks to be presented to the user through the
+         SystemUI. Can be -1 if there is no minimum limit. -->
+    <integer name="config_minNumVisibleRecentTasks">5</integer>
+
+    <!-- The maximum number of visible recent tasks to be presented to the user through the
+         SystemUI. Can be -1 if there is no maximum limit. -->
+    <integer name="config_maxNumVisibleRecentTasks">-1</integer>
+
+    <!-- The duration in which a recent task is considered in session and should be visible. -->
+    <integer name="config_activeTaskDurationHours">6</integer>
+
     <!-- default window ShowCircularMask property -->
     <bool name="config_windowShowCircularMask">false</bool>
 
@@ -3035,7 +3084,7 @@
     <bool name="config_handleVolumeKeysInWindowManager">false</bool>
 
     <!-- Volume level of in-call notification tone playback [0..1] -->
-    <item name="config_inCallNotificationVolume" format="float" type="dimen">.25</item>
+    <item name="config_inCallNotificationVolume" format="float" type="dimen">.10</item>
 
     <!-- URI for in call notification sound -->
     <string translatable="false" name="config_inCallNotificationSound">/system/media/audio/ui/InCallNotification.ogg</string>
@@ -3087,4 +3136,15 @@
          booted.  -->
     <integer name="config_stableDeviceDisplayWidth">-1</integer>
     <integer name="config_stableDeviceDisplayHeight">-1</integer>
+
+    <!-- Decide whether to display 'No service' on status bar instead of 'Emergency calls only'
+         when SIM is unready. -->
+    <bool name="config_display_no_service_when_sim_unready">false</bool>
+
+    <!-- Class names of device specific services inheriting com.android.server.SystemService. The
+         classes are instantiated in the order of the array. -->
+    <string-array translatable="false" name="config_deviceSpecificSystemServices"></string-array>
+
+    <!-- Component name of media projection permission dialog -->
+    <string name="config_mediaProjectionPermissionDialogComponent" translateable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 16c8578..dc75ba6 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -520,6 +520,13 @@
     <dimen name="floating_toolbar_vertical_margin">8dp</dimen>
     <dimen name="content_rect_bottom_clip_allowance">20dp</dimen>
 
+    <!-- Magnifier dimensions -->
+    <dimen name="magnifier_width">164dp</dimen>
+    <dimen name="magnifier_height">48dp</dimen>
+    <dimen name="magnifier_elevation">2dp</dimen>
+    <dimen name="magnifier_offset">42dp</dimen>
+    <item type="dimen" format="float" name="magnifier_zoom_scale">1.25</item>
+
     <dimen name="chooser_grid_padding">0dp</dimen>
     <!-- Spacing around the background change frome service to non-service -->
     <dimen name="chooser_service_spacing">8dp</dimen>
@@ -611,4 +618,10 @@
     -->
     <dimen name="autofill_save_icon_max_size">300dp</dimen>
 
+    <!-- Size of a slice shortcut view -->
+    <dimen name="slice_shortcut_size">56dp</dimen>
+    <!-- Size of action icons in a slice -->
+    <dimen name="slice_icon_size">24dp</dimen>
+    <!-- Standard padding used in a slice view -->
+    <dimen name="slice_padding">16dp</dimen>
 </resources>
diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml
index 04ea077..2c4058a 100644
--- a/core/res/res/values/locale_config.xml
+++ b/core/res/res/values/locale_config.xml
@@ -27,6 +27,7 @@
         <item>ar-DJ</item> <!-- Arabic (Djibouti) -->
         <item>ar-DZ</item> <!-- Arabic (Algeria) -->
         <item>ar-EG</item> <!-- Arabic (Egypt) -->
+        <item>ar-EG-u-nu-latn</item> <!-- Arabic (Egypt,Western Digits) -->
         <item>ar-EH</item> <!-- Arabic (Western Sahara) -->
         <item>ar-ER</item> <!-- Arabic (Eritrea) -->
         <item>ar-IL</item> <!-- Arabic (Israel) -->
@@ -48,6 +49,7 @@
         <item>ar-SY</item> <!-- Arabic (Syria) -->
         <item>ar-TD</item> <!-- Arabic (Chad) -->
         <item>ar-TN</item> <!-- Arabic (Tunisia) -->
+        <item>ar-TN-u-nu-arab</item> <!-- Arabic (Tunisia,Arabic-Indic Digits) -->
         <item>ar-XB</item> <!-- Right-to-left pseudolocale -->
         <item>ar-YE</item> <!-- Arabic (Yemen) -->
         <item>as-IN</item> <!-- Assamese (India) -->
@@ -366,6 +368,7 @@
         <item>ms-SG</item> <!-- Malay (Singapore) -->
         <item>mt-MT</item> <!-- Maltese (Malta) -->
         <item>my-MM</item> <!-- Burmese (Myanmar (Burma)) -->
+        <item>my-MM-u-nu-latn</item> <!-- Burmese (Myanmar (Burma), Western Digits) -->
         <item>mzn-IR</item> <!-- Mazanderani (Iran) -->
         <item>naq-NA</item> <!-- Nama (Namibia) -->
         <item>nb-NO</item> <!-- Norwegian Bokmål (Norway) -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9b2f185..083bf90 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2745,18 +2745,6 @@
 
   <!-- ===============================================================
        Resources added in version O of the platform
-
-       NOTE: add <public> elements within a <public-group> like so:
-
-       <public-group type="attr" first-id="0x01010531">
-           <public name="exampleAttr1" />
-           <public name="exampleAttr2" />
-       </public-group>
-
-       To add a new public-group block, choose an id value that is 1 greater
-       than the last of that item above. For example, the last "attr" id
-       value above is 0x01010530, so the public-group of attrs below has
-       the id value of 0x01010531.
        =============================================================== -->
     <eat-comment />
 
@@ -2827,36 +2815,16 @@
 
   <!-- ===============================================================
        Resources added in version O MR1 of the platform
-
-       NOTE: add <public> elements within a <public-group> like so:
-
-       <public-group type="attr" first-id="0x01010531">
-           <public name="exampleAttr1" />
-           <public name="exampleAttr2" />
-       </public-group>
-
-       To add a new public-group block, choose an id value that is 1 greater
-       than the last of that item above. For example, the last "attr" id
-       value above is 0x01010530, so the public-group of attrs below has
-       the id value of 0x01010531.
        =============================================================== -->
     <eat-comment />
 
-    <public-group type="attr" first-id="0x01010569">
-        <public name="showWhenLocked"/>
-        <public name="turnScreenOn"/>
-        <public name="classLoader" />
-    </public-group>
+    <public type="attr" name="showWhenLocked" id="0x01010569" />
+    <public type="attr" name="turnScreenOn" id="0x0101056a" />
+    <public type="attr" name="classLoader" id="0x0101056b" />
+    <public type="attr" name="windowLightNavigationBar" id="0x0101056c" />
+    <public type="attr" name="navigationBarDividerColor" id="0x0101056d" />
 
-    <public-group type="style" first-id="0x010302e0">
-    </public-group>
-
-    <public-group type="id" first-id="0x01020044">
-    </public-group>
-
-    <public-group type="string" first-id="0x0104001a">
-      <public name="autofill"/>
-    </public-group>
+    <public type="string" name="autofill" id="0x0104001a"/>
 
     <!-- ===============================================================
          Resources added in version P of the platform
@@ -2875,8 +2843,19 @@
          =============================================================== -->
     <eat-comment />
 
-    <public-group type="attr" first-id="0x0101056c">
-      <public name="windowLightNavigationBar" />
+    <public-group type="attr" first-id="0x0101056e">
+      <public name="cantSaveState" />
+      <public name="ttcIndex" />
+      <public name="fontVariationSettings" />
+    </public-group>
+
+    <public-group type="style" first-id="0x010302e0">
+    </public-group>
+
+    <public-group type="id" first-id="0x01020044">
+    </public-group>
+
+    <public-group type="string" first-id="0x0104001b">
     </public-group>
 
   <!-- ===============================================================
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7416113..78a8e2a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -340,18 +340,19 @@
     <!-- Work profile deleted notification--> <skip />
     <!-- Shows up in the notification's title when the system deletes the work profile. [CHAR LIMIT=NONE] -->
     <string name="work_profile_deleted">Work profile deleted</string>
-    <!-- Content text for a notification. The Title of the notification is "work_profile_deleted",
-         i.e. "Work profile deleted". This says that the profile is deleted by the system as a result of
-         the current profile owner gone missing. [CHAR LIMIT=100]-->
+    <!-- Content text for a notification. The Title of the notification is "Work profile deleted".
+        This says that the profile is deleted by the system as a result of the current profile owner gone missing. [CHAR LIMIT=100]-->
     <string name="work_profile_deleted_description">Work profile deleted due to missing admin app</string>
-    <!-- Content text for an expanded notification. The Title of the notification is "work_profile_deleted",
-         i.e. "Work profile deleted". This further explains that the profile is deleted by the system
-         as a result of the current profile admin gone missing. [CHAR LIMIT=NONE]-->
+    <!-- Content text for an expanded notification. The Title of the notification is "Work profile deleted".
+        This further explains that the profile is deleted by the system as a result of the current profile admin gone missing. [CHAR LIMIT=NONE]-->
     <string name="work_profile_deleted_details">The work profile admin app is either missing or corrupted.
          As a result, your work profile and related data have been deleted. Contact your admin for assistance.</string>
-    <!-- Content text for a notification. The Title of the notification is "work_profile_deleted",
+    <!-- Content text for a notification. The Title of the notification is "Work profile deleted",
         This indicates that a work profile has been deleted. [CHAR LIMIT=NONE]-->
     <string name="work_profile_deleted_description_dpm_wipe">Your work profile is no longer available on this device</string>
+    <!-- Content text for a notification. The Title of the notification is "Work profile deleted",
+        This indicates that a work profile has been deleted because the maximum failed password attempts as been reached. [CHAR LIMIT=NONE]-->
+    <string name="work_profile_deleted_reason_maximum_password_failure">Too many password attempts</string>
 
     <!-- Content title for a notification. This notification indicates that the device is managed
          and network logging was activated by a device owner. [CHAR LIMIT=NONE]-->
@@ -590,6 +591,11 @@
     <!-- Text shown when viewing channel settings for notifications related to a usb connection -->
     <string name="notification_channel_usb">USB connection</string>
 
+    <!-- Text shown when viewing channel settings for notification about a heavy-weight app
+         currently running.
+         [CHAR_LIMIT=NONE] -->
+    <string name="notification_channel_heavy_weight_app">App running</string>
+
     <!-- This is the label for the notification channel settings that controls the behavior
         of the notification about applications that are running in the background (that is,
         perhaps confusingly, running foreground services but not the foreground UI on the screen).
@@ -2100,12 +2106,6 @@
     <!-- Do not translate.  datepicker mode, overridden for watch -->
     <string name="date_picker_mode" translatable="false">"calendar"</string>
 
-    <!-- Do not translate.  WebView User Agent string -->
-    <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
-        AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.30</string>
-    <!-- Do not translate.  WebView User Agent targeted content -->
-    <string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
-
     <!-- Title for a JavaScript dialog. "The page at <url of current page> says:" -->
     <string name="js_dialog_title">The page at \"<xliff:g id="title">%s</xliff:g>\" says:</string>
     <!-- Default title for a javascript dialog -->
@@ -2677,13 +2677,19 @@
     <string name="email">Email</string>
 
     <!-- Label for item in the text selection menu to trigger a Dialer app [CHAR LIMIT=20] -->
-    <string name="dial">Phone</string>
+    <string name="dial">Call</string>
 
     <!-- Label for item in the text selection menu to trigger a Map app [CHAR LIMIT=20] -->
-    <string name="map">Maps</string>
+    <string name="map">Locate</string>
 
     <!-- Label for item in the text selection menu to trigger a Browser app [CHAR LIMIT=20] -->
-    <string name="browse">Browser</string>
+    <string name="browse">Open</string>
+
+    <!-- Label for item in the text selection menu to trigger an SMS app [CHAR LIMIT=20] -->
+    <string name="sms">Message</string>
+
+    <!-- Label for item in the text selection menu to trigger adding a contact [CHAR LIMIT=20] -->
+    <string name="add_contact">Add</string>
 
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
@@ -4340,8 +4346,6 @@
     <string name="lock_to_app_toast">To unpin this screen, touch &amp; hold Back and Overview
         buttons</string>
 
-    <!-- Notify user that they are locked in lock-to-app mode -->
-    <string name="lock_to_app_toast_locked">This app can\'t be unpinned</string>
     <!-- Starting lock-to-app indication. -->
     <string name="lock_to_app_start">Screen pinned</string>
     <!-- Exting lock-to-app indication. -->
@@ -4705,12 +4709,39 @@
     <!-- Primary ETWS (Earthquake and Tsunami Warning System) default message for others -->
     <string name="etws_primary_default_message_others"></string>
 
-    <!-- Title of notification when UE fails to register network with MM reject cause code. -->
-    <string name="mmcc_authentication_reject">SIM not allowed</string>
-    <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned</string>
-    <string name="mmcc_illegal_ms">SIM not allowed</string>
-    <string name="mmcc_illegal_me">Phone not allowed</string>
+    <!-- Title of notification when UE fails CS registration with MM reject cause code from network. -->
+    <string name="mmcc_authentication_reject">SIM not allowed for voice</string>
+    <string name="mmcc_imsi_unknown_in_hlr">SIM not provisioned for voice</string>
+    <string name="mmcc_illegal_ms">SIM not allowed for voice</string>
+    <string name="mmcc_illegal_me">Phone not allowed for voice</string>
 
     <!-- Popup window default title to be read by a screen reader-->
     <string name="popup_window_default_title">Popup Window</string>
+
+    <!-- Format string for indicating there is more content in a slice view -->
+    <string name="slice_more_content">+ <xliff:g id="number" example="5">%1$d</xliff:g></string>
+
+    <!--
+    A toast message shown when an app shortcut that was restored from a previous device is clicked,
+    but it cannot be started because the shortcut was created by a newer version of the app.
+    -->
+    <string name="shortcut_restored_on_lower_version">This shortcut requires latest app</string>
+
+    <!--
+    A toast message shown when an app shortcut that was restored from a previous device is clicked,
+    but it cannot be started because the shortcut was created by an app that doesn't support backup
+    and restore.
+    -->
+    <string name="shortcut_restore_not_supported">Couldn\u2019t restore shortcut because app doesn\u2019t support backup and restore</string>
+
+    <!--
+    A toast message shown when an app shortcut that was restored from a previous device is clicked,
+    but it cannot be started because the shortcut was created by an app with a different signature.
+    -->
+    <string name="shortcut_restore_signature_mismatch">Couldn\u2019t restore shortcut because of app signature mismatch</string>
+
+    <!--
+    A toast message shown when an app shortcut that wasn't restored due to an unknown issue is clicked,
+    -->
+    <string name="shortcut_restore_unknown_issue">Couldn\u2019t restore shortcut</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1877d42..e513816 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -273,7 +273,6 @@
   <java-symbol type="bool" name="config_suspendWhenScreenOffDueToProximity" />
   <java-symbol type="bool" name="config_swipeDisambiguation" />
   <java-symbol type="bool" name="config_syncstorageengine_masterSyncAutomatically" />
-  <java-symbol type="bool" name="config_telephony_use_own_number_for_voicemail" />
   <java-symbol type="bool" name="config_ui_enableFadingMarquee" />
   <java-symbol type="bool" name="config_enableHapticTextHandle" />
   <java-symbol type="bool" name="config_use_strict_phone_number_comparation" />
@@ -311,6 +310,14 @@
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="bool" name="config_hasRecents" />
+  <java-symbol type="string" name="config_recentsComponentName" />
+  <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
+  <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
+  <java-symbol type="integer" name="config_minNumVisibleRecentTasks_grid" />
+  <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_grid" />
+  <java-symbol type="integer" name="config_minNumVisibleRecentTasks" />
+  <java-symbol type="integer" name="config_maxNumVisibleRecentTasks" />
+  <java-symbol type="integer" name="config_activeTaskDurationHours" />
   <java-symbol type="bool" name="config_windowShowCircularMask" />
   <java-symbol type="bool" name="config_windowEnableCircularEmulatorDisplayOverlay" />
   <java-symbol type="bool" name="config_wifi_framework_enable_associated_network_selection" />
@@ -437,6 +444,7 @@
   <java-symbol type="integer" name="config_soundEffectVolumeDb" />
   <java-symbol type="integer" name="config_lockSoundVolumeDb" />
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
+  <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
   <java-symbol type="integer" name="config_mobile_mtu" />
   <java-symbol type="array"   name="config_mobile_tcp_buffers" />
@@ -447,6 +455,7 @@
   <java-symbol type="integer" name="config_keepPreloadsMinDays" />
   <java-symbol type="bool" name="config_hasPermanentDpad" />
   <java-symbol type="bool" name="config_useDefaultFocusHighlight" />
+  <java-symbol type="array" name="config_deviceSpecificSystemServices" />
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
@@ -516,6 +525,8 @@
   <java-symbol type="string" name="dial" />
   <java-symbol type="string" name="map" />
   <java-symbol type="string" name="browse" />
+  <java-symbol type="string" name="sms" />
+  <java-symbol type="string" name="add_contact" />
   <java-symbol type="string" name="textSelectionCABTitle" />
   <java-symbol type="string" name="BaMmi" />
   <java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
@@ -744,7 +755,6 @@
   <java-symbol type="string" name="last_month" />
   <java-symbol type="string" name="launchBrowserDefault" />
   <java-symbol type="string" name="lock_to_app_toast" />
-  <java-symbol type="string" name="lock_to_app_toast_locked" />
   <java-symbol type="string" name="lock_to_app_start" />
   <java-symbol type="string" name="lock_to_app_exit" />
   <java-symbol type="string" name="lock_to_app_unlock_pin" />
@@ -985,8 +995,6 @@
   <java-symbol type="string" name="volume_icon_description_notification" />
   <java-symbol type="string" name="volume_icon_description_ringer" />
   <java-symbol type="string" name="wait" />
-  <java-symbol type="string" name="web_user_agent" />
-  <java-symbol type="string" name="web_user_agent_target_content" />
   <java-symbol type="string" name="webpage_unresponsive" />
   <java-symbol type="string" name="whichApplication" />
   <java-symbol type="string" name="whichHomeApplication" />
@@ -1153,6 +1161,7 @@
   <java-symbol type="string" name="work_profile_deleted_description" />
   <java-symbol type="string" name="work_profile_deleted_details" />
   <java-symbol type="string" name="work_profile_deleted_description_dpm_wipe" />
+  <java-symbol type="string" name="work_profile_deleted_reason_maximum_password_failure" />
   <java-symbol type="string" name="network_logging_notification_title" />
   <java-symbol type="string" name="network_logging_notification_text" />
   <java-symbol type="string" name="factory_reset_warning" />
@@ -2469,6 +2478,16 @@
   <java-symbol type="drawable" name="ft_avd_tooverflow_animation" />
   <java-symbol type="attr" name="floatingToolbarDividerColor" />
 
+  <!-- Magnifier -->
+  <java-symbol type="id" name="magnifier_image" />
+  <java-symbol type="id" name="magnifier_inner" />
+  <java-symbol type="layout" name="magnifier" />
+  <java-symbol type="dimen" name="magnifier_width" />
+  <java-symbol type="dimen" name="magnifier_height" />
+  <java-symbol type="dimen" name="magnifier_elevation" />
+  <java-symbol type="dimen" name="magnifier_zoom_scale" />
+  <java-symbol type="dimen" name="magnifier_offset" />
+
   <java-symbol type="string" name="date_picker_prev_month_button" />
   <java-symbol type="string" name="date_picker_next_month_button" />
   <java-symbol type="layout" name="date_picker_month_item_material" />
@@ -2822,6 +2841,7 @@
   <java-symbol type="integer" name="config_nightDisplayColorTemperatureMin" />
   <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
+  <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
 
   <!-- Default first user restrictions -->
   <java-symbol type="array" name="config_defaultFirstUserRestrictions" />
@@ -3019,6 +3039,7 @@
   <java-symbol type="string" name="notification_channel_alerts" />
   <java-symbol type="string" name="notification_channel_retail_mode" />
   <java-symbol type="string" name="notification_channel_usb" />
+  <java-symbol type="string" name="notification_channel_heavy_weight_app" />
   <java-symbol type="string" name="config_defaultAutofillService" />
 
   <java-symbol type="string" name="notification_channel_foreground_service" />
@@ -3027,6 +3048,8 @@
   <java-symbol type="string" name="foreground_service_tap_for_details" />
   <java-symbol type="string" name="foreground_service_multiple_separator" />
 
+  <java-symbol type="bool" name="config_enableCredentialFactoryResetProtection" />
+
   <!-- ETWS primary messages -->
   <java-symbol type="string" name="etws_primary_default_message_earthquake" />
   <java-symbol type="string" name="etws_primary_default_message_tsunami" />
@@ -3083,4 +3106,28 @@
 
   <java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
   <java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
+  <java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
+
+  <java-symbol type="layout" name="slice_grid" />
+  <java-symbol type="layout" name="slice_message_local" />
+  <java-symbol type="layout" name="slice_message" />
+  <java-symbol type="layout" name="slice_title" />
+  <java-symbol type="layout" name="slice_secondary_text" />
+  <java-symbol type="layout" name="slice_remote_input" />
+  <java-symbol type="layout" name="slice_small_template" />
+  <java-symbol type="id" name="remote_input_progress" />
+  <java-symbol type="id" name="remote_input_send" />
+  <java-symbol type="id" name="remote_input" />
+  <java-symbol type="dimen" name="slice_shortcut_size" />
+  <java-symbol type="dimen" name="slice_icon_size" />
+  <java-symbol type="dimen" name="slice_padding" />
+  <java-symbol type="string" name="slice_more_content" />
+
+  <java-symbol type="string" name="shortcut_restored_on_lower_version" />
+  <java-symbol type="string" name="shortcut_restore_not_supported" />
+  <java-symbol type="string" name="shortcut_restore_signature_mismatch" />
+  <java-symbol type="string" name="shortcut_restore_unknown_issue" />
+
+  <!-- From media projection -->
+  <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
 </resources>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 9dafa7a..9bea3ee 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -1336,6 +1336,7 @@
 
     <!-- Default theme for Settings and activities launched from Settings. -->
     <style name="Theme.Material.Settings" parent="Theme.Material.Light.LightStatusBar">
+        <item name="homeAsUpIndicator">@drawable/ic_ab_back_material_settings</item>
         <item name="colorPrimary">@color/primary_material_settings_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_material_settings_light</item>
         <item name="colorSecondary">@color/secondary_material_settings_light</item>
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 5f4199a..1e4c03e 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -19,5 +19,5 @@
 
 <!-- Default configuration for zen mode.  See android.service.notification.ZenModeConfig. -->
 <zen version="2">
-    <allow calls="true" messages="false" reminders="true" events="true" />
+    <allow alarms="true" media_system_other="true" calls="false" messages="false" reminders="false" events="false" />
 </zen>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index fbaf0f3..0806fa0 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -20,7 +20,6 @@
 import android.test.InstrumentationTestRunner;
 import android.test.InstrumentationTestSuite;
 
-import com.android.connectivitymanagertest.stress.WifiApStress;
 import com.android.connectivitymanagertest.stress.WifiStressTest;
 
 import junit.framework.TestSuite;
@@ -35,7 +34,7 @@
  */
 
 public class ConnectivityManagerStressTestRunner extends InstrumentationTestRunner {
-    private int mSoftApIterations = 100;
+    private int mSoftApIterations = 0;
     private int mScanIterations = 100;
     private int mReconnectIterations = 100;
     // sleep time before restart wifi, default is set to 2 minutes
@@ -47,7 +46,6 @@
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
-        suite.addTestSuite(WifiApStress.class);
         suite.addTestSuite(WifiStressTest.class);
         return suite;
     }
@@ -60,13 +58,6 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        String valueStr = icicle.getString("softap_iterations");
-        if (valueStr != null) {
-            int iteration = Integer.parseInt(valueStr);
-            if (iteration > 0) {
-                mSoftApIterations = iteration;
-            }
-        }
 
         String scanIterationStr = icicle.getString("scan_iterations");
         if (scanIterationStr != null) {
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
index 64fed7f..3706e4b 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestBase.java
@@ -129,12 +129,6 @@
         // Get an instance of WifiManager
         mWifiManager =(WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
 
-        if (mWifiManager.isWifiApEnabled()) {
-            // if soft AP is enabled, disable it
-            mWifiManager.setWifiApEnabled(null, false);
-            logv("Disable soft ap");
-        }
-
         // register a connectivity receiver for CONNECTIVITY_ACTION;
         mConnectivityReceiver = new ConnectivityReceiver();
         mContext.registerReceiver(mConnectivityReceiver,
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
index 0e57a00..746cb84 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java
@@ -19,7 +19,6 @@
 import android.test.InstrumentationTestRunner;
 import android.test.InstrumentationTestSuite;
 import com.android.connectivitymanagertest.unit.WifiClientTest;
-import com.android.connectivitymanagertest.unit.WifiSoftAPTest;
 
 import junit.framework.TestSuite;
 
@@ -35,7 +34,6 @@
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
         suite.addTestSuite(WifiClientTest.class);
-        suite.addTestSuite(WifiSoftAPTest.class);
         return suite;
     }
 
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java
deleted file mode 100644
index de934b9..0000000
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiApStress.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2010, 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.connectivitymanagertest.stress;
-
-
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.AuthAlgorithm;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-import android.net.wifi.WifiManager;
-import android.os.Environment;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
-import com.android.connectivitymanagertest.ConnectivityManagerTestBase;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-
-/**
- * Stress test setting up device as wifi hotspot
- */
-public class WifiApStress extends ConnectivityManagerTestBase {
-    private static String NETWORK_ID = "AndroidAPTest";
-    private static String PASSWD = "androidwifi";
-    private final static String OUTPUT_FILE = "WifiStressTestOutput.txt";
-    private int mTotalIterations;
-    private BufferedWriter mOutputWriter = null;
-    private int mLastIteration = 0;
-    private boolean mWifiOnlyFlag;
-
-    public WifiApStress() {
-        super(WifiApStress.class.getSimpleName());
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        ConnectivityManagerStressTestRunner mRunner =
-            (ConnectivityManagerStressTestRunner)getInstrumentation();
-        mTotalIterations = mRunner.getSoftApInterations();
-        mWifiOnlyFlag = mRunner.isWifiOnly();
-        turnScreenOn();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // write the total number of iterations into output file
-        mOutputWriter = new BufferedWriter(new FileWriter(new File(
-                Environment.getExternalStorageDirectory(), OUTPUT_FILE)));
-        mOutputWriter.write(String.format("iteration %d out of %d\n",
-                mLastIteration + 1, mTotalIterations));
-        mOutputWriter.flush();
-        mOutputWriter.close();
-        super.tearDown();
-    }
-
-    @LargeTest
-    public void testWifiHotSpot() {
-        if (mWifiOnlyFlag) {
-            logv(getName() + " is excluded for wi-fi only test");
-            return;
-        }
-        WifiConfiguration config = new WifiConfiguration();
-        config.SSID = NETWORK_ID;
-        config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
-        config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
-        config.preSharedKey = PASSWD;
-
-        // if wifiap enabled, disable it
-        assertTrue("failed to disable wifi hotspot",
-                mWifiManager.setWifiApEnabled(config, false));
-        assertTrue("wifi hotspot not enabled", waitForWifiApState(
-                WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT));
-
-        // if Wifi is enabled, disable it
-        if (mWifiManager.isWifiEnabled()) {
-            assertTrue("failed to disable wifi", disableWifi());
-            // wait for the wifi state to be DISABLED
-            assertTrue("wifi state not disabled", waitForWifiState(
-                    WifiManager.WIFI_STATE_DISABLED, LONG_TIMEOUT));
-        }
-        int i;
-        for (i = 0; i < mTotalIterations; i++) {
-            logv("iteration: " + i);
-            mLastIteration = i;
-            // enable Wifi tethering
-            assertTrue("failed to enable wifi hotspot",
-                    mWifiManager.setWifiApEnabled(config, true));
-            // wait for wifi ap state to be ENABLED
-            assertTrue("wifi hotspot not enabled", waitForWifiApState(
-                    WifiManager.WIFI_AP_STATE_ENABLED, 2 * LONG_TIMEOUT));
-            // wait for wifi tethering result
-            assertTrue("tether state not changed", waitForTetherStateChange(LONG_TIMEOUT));
-            // allow the wifi tethering to be enabled for 10 seconds
-            try {
-                Thread.sleep(2 * SHORT_TIMEOUT);
-            } catch (Exception e) {
-                // ignore
-            }
-            assertTrue("no uplink data connection after Wi-Fi tethering", pingTest());
-            // disable wifi hotspot
-            assertTrue("failed to disable wifi hotspot",
-                    mWifiManager.setWifiApEnabled(config, false));
-            assertTrue("wifi hotspot not enabled", waitForWifiApState(
-                    WifiManager.WIFI_AP_STATE_DISABLED, 2 * LONG_TIMEOUT));
-            assertFalse("wifi hotspot still enabled", mWifiManager.isWifiApEnabled());
-        }
-    }
-
-}
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java
deleted file mode 100644
index f202862..0000000
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2010 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.connectivitymanagertest.unit;
-
-import android.content.Context;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
-
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.AndroidTestCase;
-
-import android.util.Log;
-
-/**
- * Test Wifi soft AP configuration
- */
-public class WifiSoftAPTest extends AndroidTestCase {
-
-    private WifiManager mWifiManager;
-    private WifiConfiguration mWifiConfig = null;
-    private final String TAG = "WifiSoftAPTest";
-    private final int DURATION = 10000;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
-        assertNotNull(mWifiManager);
-        assertTrue(mWifiManager.setWifiApEnabled(null, true));
-        mWifiConfig = mWifiManager.getWifiApConfiguration();
-        if (mWifiConfig != null) {
-            Log.v(TAG, "mWifiConfig is " + mWifiConfig.toString());
-        } else {
-            Log.v(TAG, "mWifiConfig is null.");
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        Log.v(TAG, "turn off wifi tethering");
-        mWifiManager.setWifiApEnabled(null, false);
-        super.tearDown();
-    }
-
-    // Test case 1: Test the soft AP SSID with letters
-    @LargeTest
-    public void testApSsidWithAlphabet() {
-        WifiConfiguration config = new WifiConfiguration();
-        config.SSID = "abcdefghijklmnopqrstuvwxyz";
-        config.allowedKeyManagement.set(KeyMgmt.NONE);
-        mWifiConfig = config;
-        assertTrue(mWifiManager.setWifiApEnabled(mWifiConfig, true));
-        try {
-            Thread.sleep(DURATION);
-        } catch (InterruptedException e) {
-            Log.v(TAG, "exception " + e.getStackTrace());
-            assertFalse(true);
-        }
-        assertNotNull(mWifiManager.getWifiApConfiguration());
-        assertEquals("wifi AP state is not enabled", WifiManager.WIFI_AP_STATE_ENABLED,
-                     mWifiManager.getWifiApState());
-    }
-}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
index 31ce95e..4b32cea 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java
@@ -256,13 +256,13 @@
         mTestUtils.unpair(mAdapter, device);
         mTestUtils.pair(mAdapter, device, BluetoothTestRunner.sDevicePairPasskey,
                 BluetoothTestRunner.sDevicePairPin);
-        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE, null);
+        mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST, null);
 
         for (int i = 0; i < iterations; i++) {
             mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations);
-            mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE,
+            mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.HID_HOST,
                     String.format("connectInput(device=%s)", device));
-            mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.INPUT_DEVICE,
+            mTestUtils.disconnectProfile(mAdapter, device, BluetoothProfile.HID_HOST,
                     String.format("disconnectInput(device=%s)", device));
         }
 
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index ee15978..ada0366 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -227,8 +227,8 @@
                 case BluetoothProfile.HEADSET:
                     mConnectionAction = BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED;
                     break;
-                case BluetoothProfile.INPUT_DEVICE:
-                    mConnectionAction = BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED;
+                case BluetoothProfile.HID_HOST:
+                    mConnectionAction = BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED;
                     break;
                 case BluetoothProfile.PAN:
                     mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED;
@@ -322,8 +322,8 @@
                     case BluetoothProfile.HEADSET:
                         mHeadset = (BluetoothHeadset) proxy;
                         break;
-                    case BluetoothProfile.INPUT_DEVICE:
-                        mInput = (BluetoothInputDevice) proxy;
+                    case BluetoothProfile.HID_HOST:
+                        mInput = (BluetoothHidHost) proxy;
                         break;
                     case BluetoothProfile.PAN:
                         mPan = (BluetoothPan) proxy;
@@ -342,7 +342,7 @@
                     case BluetoothProfile.HEADSET:
                         mHeadset = null;
                         break;
-                    case BluetoothProfile.INPUT_DEVICE:
+                    case BluetoothProfile.HID_HOST:
                         mInput = null;
                         break;
                     case BluetoothProfile.PAN:
@@ -362,7 +362,7 @@
     private Context mContext;
     private BluetoothA2dp mA2dp = null;
     private BluetoothHeadset mHeadset = null;
-    private BluetoothInputDevice mInput = null;
+    private BluetoothHidHost mInput = null;
     private BluetoothPan mPan = null;
 
     /**
@@ -894,7 +894,7 @@
      * @param adapter The BT adapter.
      * @param device The remote device.
      * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
-     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
+     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}.
      * @param methodName The method name to printed in the logs.  If null, will be
      * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
      */
@@ -935,8 +935,8 @@
                     assertTrue(((BluetoothA2dp)proxy).connect(device));
                 } else if (profile == BluetoothProfile.HEADSET) {
                     assertTrue(((BluetoothHeadset)proxy).connect(device));
-                } else if (profile == BluetoothProfile.INPUT_DEVICE) {
-                    assertTrue(((BluetoothInputDevice)proxy).connect(device));
+                } else if (profile == BluetoothProfile.HID_HOST) {
+                    assertTrue(((BluetoothHidHost)proxy).connect(device));
                 }
                 break;
             default:
@@ -975,7 +975,7 @@
      * @param adapter The BT adapter.
      * @param device The remote device.
      * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP},
-     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#INPUT_DEVICE}.
+     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}.
      * @param methodName The method name to printed in the logs.  If null, will be
      * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
      */
@@ -1010,8 +1010,8 @@
                     assertTrue(((BluetoothA2dp)proxy).disconnect(device));
                 } else if (profile == BluetoothProfile.HEADSET) {
                     assertTrue(((BluetoothHeadset)proxy).disconnect(device));
-                } else if (profile == BluetoothProfile.INPUT_DEVICE) {
-                    assertTrue(((BluetoothInputDevice)proxy).disconnect(device));
+                } else if (profile == BluetoothProfile.HID_HOST) {
+                    assertTrue(((BluetoothHidHost)proxy).disconnect(device));
                 }
                 break;
             case BluetoothProfile.STATE_DISCONNECTED:
@@ -1237,7 +1237,7 @@
         long s = System.currentTimeMillis();
         while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
             state = mPan.getConnectionState(device);
-            if (state == BluetoothInputDevice.STATE_DISCONNECTED
+            if (state == BluetoothHidHost.STATE_DISCONNECTED
                     && (receiver.getFiredFlags() & mask) == mask) {
                 long finish = receiver.getCompletedTime();
                 if (start != -1 && finish != -1) {
@@ -1255,7 +1255,7 @@
         int firedFlags = receiver.getFiredFlags();
         removeReceiver(receiver);
         fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
-                methodName, state, BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
+                methodName, state, BluetoothHidHost.STATE_DISCONNECTED, firedFlags, mask));
     }
 
     /**
@@ -1404,7 +1404,7 @@
         String[] actions = {
                 BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
                 BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
-                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED};
+                BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED};
         ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
                 expectedFlags);
         addReceiver(receiver, actions);
@@ -1443,7 +1443,7 @@
                     return mHeadset;
                 }
                 break;
-            case BluetoothProfile.INPUT_DEVICE:
+            case BluetoothProfile.HID_HOST:
                 if (mInput != null) {
                     return mInput;
                 }
@@ -1469,7 +1469,7 @@
                     sleep(POLL_TIME);
                 }
                 return mHeadset;
-            case BluetoothProfile.INPUT_DEVICE:
+            case BluetoothProfile.HID_HOST:
                 while (mInput == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
                     sleep(POLL_TIME);
                 }
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index dbc9e5d..f2eb872 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -19,7 +19,12 @@
 	$(call all-java-files-under, src) \
 	$(call all-Iaidl-files-under, src) \
 	$(call all-java-files-under, DisabledTestApp/src) \
-	$(call all-java-files-under, EnabledTestApp/src)
+	$(call all-java-files-under, EnabledTestApp/src) \
+	$(call all-java-files-under, BinderProxyCountingTestApp/src) \
+	$(call all-java-files-under, BinderProxyCountingTestService/src) \
+	$(call all-Iaidl-files-under, aidl)
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
 
 LOCAL_DX_FLAGS := --core-library
 LOCAL_JACK_FLAGS := --multi-dex native
@@ -36,7 +41,8 @@
     ub-uiautomator \
     platform-test-annotations \
     compatibility-device-util \
-    truth-prebuilt
+    truth-prebuilt \
+    print-test-util-lib
 
 LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt telephony-common org.apache.http.legacy
 LOCAL_PACKAGE_NAME := FrameworksCoreTests
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index ac5d224..51bfc20 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -86,6 +86,7 @@
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
 
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.KILL_UID" />
 
     <!-- location test permissions -->
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
@@ -102,6 +103,7 @@
     <!-- os storage test permissions -->
     <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
     <uses-permission android:name="android.permission.ASEC_ACCESS" />
+    <uses-permission android:name="android.permission.ASEC_ACCESS" />
     <uses-permission android:name="android.permission.ASEC_CREATE" />
     <uses-permission android:name="android.permission.ASEC_DESTROY" />
     <uses-permission android:name="android.permission.ASEC_MOUNT_UNMOUNT" />
@@ -1345,10 +1347,12 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.print.PrintTestActivity"/>
+        <activity
+            android:name="android.print.test.PrintDocumentActivity"
+            android:theme="@style/Theme" />
 
         <service
-            android:name="android.print.mockservice.MockPrintService"
+            android:name="android.print.test.services.FirstPrintService"
             android:permission="android.permission.BIND_PRINT_SERVICE">
             <intent-filter>
                 <action android:name="android.printservice.PrintService" />
@@ -1360,9 +1364,10 @@
         </service>
 
         <activity
-            android:name="android.print.mockservice.SettingsActivity"
+            android:name="android.print.test.services.SettingsActivity"
             android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
-            android:exported="true">
+            android:exported="true"
+            android:theme="@style/Theme">
         </activity>
 
         <activity
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/Android.mk b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk
new file mode 100644
index 0000000..e31d50f
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BinderProxyCountingTestApp
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..a971730
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.binderproxycountingtestapp">
+
+    <application>
+        <service android:name=".BpcTestAppCmdService"
+                 android:exported="true"/>
+    </application>
+</manifest>
diff --git a/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
new file mode 100644
index 0000000..5aae120
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestApp/src/com/android/frameworks/coretests/binderproxycountingtestapp/BpcTestAppCmdService.java
@@ -0,0 +1,182 @@
+/*
+ * 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.frameworks.coretests.binderproxycountingtestapp;
+
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
+import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class BpcTestAppCmdService extends Service {
+    private static final String TAG = BpcTestAppCmdService.class.getSimpleName();
+
+    private static final String TEST_SERVICE_PKG =
+            "com.android.frameworks.coretests.binderproxycountingtestservice";
+    private static final String TEST_SERVICE_CLASS =
+            TEST_SERVICE_PKG + ".BinderProxyCountingService";
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+
+    private static ServiceConnection mServiceConnection;
+    private static IBinderProxyCountingService mBpcService;
+
+    private IBpcTestAppCmdService.Stub mBinder = new IBpcTestAppCmdService.Stub() {
+
+        private ArrayList<BroadcastReceiver> mBrList = new ArrayList();
+        private ArrayList<ITestRemoteCallback> mTrcList = new ArrayList();
+
+        @Override
+        public void createSystemBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+                BroadcastReceiver br = new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+
+                    }
+                };
+                IntentFilter filt = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED);
+                synchronized (mBrList) {
+                    mBrList.add(br);
+                }
+                registerReceiver(br, filt);
+            }
+        }
+
+        @Override
+        public void releaseSystemBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+                BroadcastReceiver br;
+                synchronized (mBrList) {
+                    br = mBrList.remove(0);
+                }
+                unregisterReceiver(br);
+            }
+        }
+
+        @Override
+        public void createTestBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+                ITestRemoteCallback cb = new ITestRemoteCallback.Stub() {};
+                synchronized (mTrcList) {
+                    mTrcList.add(cb);
+                }
+                try {
+                    mBpcService.registerCallback(cb);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException caught! " + e);
+                }
+            }
+        }
+
+        @Override
+        public void releaseTestBinders(int count) {
+            int i = 0;
+            while (i++ < count) {
+
+                ITestRemoteCallback cb;
+                synchronized (mTrcList) {
+                    cb = mTrcList.remove(0);
+                }
+                try {
+                    mBpcService.unregisterCallback(cb);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException caught! " + e);
+                }
+            }
+        }
+
+        @Override
+        public void releaseAllBinders() {
+            synchronized (mBrList) {
+                while (mBrList.size() > 0) {
+                    unregisterReceiver(mBrList.remove(0));
+                }
+            }
+            synchronized (mTrcList) {
+                while (mTrcList.size() > 0) {
+                    try {
+                        mBpcService.unregisterCallback(mTrcList.remove(0));
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "RemoteException caught! " + e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public String bindToTestService() {
+            try {
+                final CountDownLatch bindLatch = new CountDownLatch(1);
+                mServiceConnection = new ServiceConnection() {
+                    @Override
+                    public void onServiceConnected(ComponentName name, IBinder service) {
+                        Log.i(TAG, "Service connected");
+                        mBpcService = IBinderProxyCountingService.Stub.asInterface(service);
+                        bindLatch.countDown();
+                    }
+
+                    @Override
+                    public void onServiceDisconnected(ComponentName name) {
+                        Log.i(TAG, "Service disconnected");
+                    }
+                };
+                final Intent intent = new Intent()
+                        .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CLASS));
+                bindService(intent, mServiceConnection,
+                        Context.BIND_AUTO_CREATE
+                                | Context.BIND_ALLOW_OOM_MANAGEMENT
+                                | Context.BIND_NOT_FOREGROUND);
+                if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                    throw new RuntimeException("Failed to bind to " + TEST_SERVICE_CLASS);
+                }
+            } catch (Exception e) {
+                unbindFromTestService();
+                Log.e(TAG, e.toString());
+                return e.toString();
+            }
+            return null;
+        }
+
+        @Override
+        public void unbindFromTestService() {
+            if (mBpcService != null) {
+                unbindService(mServiceConnection);
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/BinderProxyCountingTestService/Android.mk b/core/tests/coretests/BinderProxyCountingTestService/Android.mk
new file mode 100644
index 0000000..a63cf0e
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/Android.mk
@@ -0,0 +1,27 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := BinderProxyCountingTestService
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml
new file mode 100644
index 0000000..777bd20
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.binderproxycountingtestservice">
+
+    <application>
+        <service android:name=".BpcTestServiceCmdService"
+                 android:exported="true" />
+        <service android:name=".BinderProxyCountingService"
+                 android:exported="true" />
+    </application>
+</manifest>
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
new file mode 100644
index 0000000..41b4c69
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BinderProxyCountingService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.frameworks.coretests.binderproxycountingtestservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+
+import com.android.frameworks.coretests.aidl.IBinderProxyCountingService;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+public class BinderProxyCountingService extends Service {
+    private static final String TAG = BinderProxyCountingService.class.getSimpleName();
+
+    private IBinderProxyCountingService.Stub mBinder = new IBinderProxyCountingService.Stub() {
+
+        final RemoteCallbackList<ITestRemoteCallback> mTestCallbacks = new RemoteCallbackList<>();
+
+        @Override
+        public void registerCallback(ITestRemoteCallback callback) {
+            synchronized (this) {
+                mTestCallbacks.register(callback);
+            }
+        }
+
+        @Override
+        public void unregisterCallback(ITestRemoteCallback callback) {
+            synchronized (this) {
+                mTestCallbacks.unregister(callback);
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
new file mode 100644
index 0000000..6bed2a2
--- /dev/null
+++ b/core/tests/coretests/BinderProxyCountingTestService/src/com/android/frameworks/coretests/binderproxycountingtestservice/BpcTestServiceCmdService.java
@@ -0,0 +1,101 @@
+/*
+ * 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.frameworks.coretests.binderproxycountingtestservice;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
+import com.android.internal.os.BinderInternal;
+
+public class BpcTestServiceCmdService extends Service {
+    private static final String TAG = BpcTestServiceCmdService.class.getSimpleName();
+
+    //ServiceThread mHandlerThread;
+    Handler mHandler;
+    HandlerThread mHandlerThread;
+
+    private IBpcTestServiceCmdService.Stub mBinder = new IBpcTestServiceCmdService.Stub() {
+        IBpcCallbackObserver mCallbackObserver;
+
+        @Override
+        public void forceGc() {
+            int gcCount = Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count"));
+            int i = 20;
+            while (gcCount == Integer.parseInt(Debug.getRuntimeStat("art.gc.gc-count")) && i > 0) {
+                System.gc();
+                System.runFinalization();
+                i--;
+            }
+        }
+
+        @Override
+        public int getBinderProxyCount(int uid) {
+            return BinderInternal.nGetBinderProxyCount(uid);
+        }
+
+        @Override
+        public void setBinderProxyWatermarks(int high, int low) {
+            BinderInternal.nSetBinderProxyCountWatermarks(high, low);
+        }
+
+        @Override
+        public void enableBinderProxyLimit(boolean enable) {
+            BinderInternal.nSetBinderProxyCountEnabled(enable);
+        }
+
+        @Override
+        public void setBinderProxyCountCallback(IBpcCallbackObserver observer) {
+            if (observer != null) {
+                BinderInternal.setBinderProxyCountCallback(
+                        new BinderInternal.BinderProxyLimitListener() {
+                            @Override
+                            public void onLimitReached(int uid) {
+                                try {
+                                    synchronized (observer) {
+                                        observer.onCallback(uid);
+                                    }
+                                } catch (Exception e) {
+                                    Log.e(TAG, e.toString());
+                                }
+                            }
+                        }, mHandler);
+            } else {
+                BinderInternal.clearBinderProxyCountCallback();
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public void onCreate()
+    {
+        mHandlerThread = new HandlerThread("BinderProxyCountingServiceThread");
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/Android.mk b/core/tests/coretests/aidl/Android.mk
new file mode 100644
index 0000000..86e36b6
--- /dev/null
+++ b/core/tests/coretests/aidl/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-subdir-Iaidl-files)
+LOCAL_MODULE := coretests-aidl
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
new file mode 100644
index 0000000..a69b0c5
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBinderProxyCountingService.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.frameworks.coretests.aidl;
+import com.android.frameworks.coretests.aidl.ITestRemoteCallback;
+
+interface IBinderProxyCountingService {
+    void registerCallback(in ITestRemoteCallback callback);
+    void unregisterCallback(in ITestRemoteCallback callback);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
new file mode 100644
index 0000000..c4ebd56
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcCallbackObserver.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.frameworks.coretests.aidl;
+
+interface IBpcCallbackObserver {
+    void onCallback(int uid);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl
new file mode 100644
index 0000000..86a0aa0f
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestAppCmdService.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.frameworks.coretests.aidl;
+
+interface IBpcTestAppCmdService {
+   void createSystemBinders(int count);
+   void releaseSystemBinders(int count);
+
+   void createTestBinders(int count);
+   void releaseTestBinders(int count);
+
+   void releaseAllBinders();
+
+   String bindToTestService();
+   void unbindFromTestService();
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
new file mode 100644
index 0000000..abdab41
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/IBpcTestServiceCmdService.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.frameworks.coretests.aidl;
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+
+interface IBpcTestServiceCmdService {
+   void forceGc();
+   int getBinderProxyCount(int uid);
+   void setBinderProxyWatermarks(int high, int low);
+   void enableBinderProxyLimit(boolean enable);
+   void setBinderProxyCountCallback(IBpcCallbackObserver observer);
+}
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
new file mode 100644
index 0000000..36bdb6c
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestRemoteCallback.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.frameworks.coretests.aidl;
+
+interface ITestRemoteCallback {
+}
\ No newline at end of file
diff --git a/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttf b/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttf
new file mode 100644
index 0000000..cf769ed
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttx b/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttx
new file mode 100644
index 0000000..04d85922
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/LineBreakingOverhangsTestFont.ttx
@@ -0,0 +1,234 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9">
+
+  <GlyphOrder>
+    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="space"/>
+    <GlyphID id="2" name="R"/>
+    <GlyphID id="3" name="a"/>
+    <GlyphID id="4" name="y"/>
+  </GlyphOrder>
+
+  <head>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x26d0d624"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Tue Oct  3 23:00:00 2017"/>
+    <modified value="Tue Oct  3 23:33:15 2017"/>
+    <xMin value="-1500"/>
+    <yMin value="0"/>
+    <xMax value="5000"/>
+    <yMax value="1000"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <indexToLocFormat value="0"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <advanceWidthMax value="1000"/>
+    <minLeftSideBearing value="-1500"/>
+    <minRightSideBearing value="-4000"/>
+    <xMaxExtent value="5000"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+    <numberOfHMetrics value="1"/>
+  </hhea>
+
+  <maxp>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="0x10000"/>
+    <numGlyphs value="5"/>
+    <maxPoints value="4"/>
+    <maxContours value="1"/>
+    <maxCompositePoints value="0"/>
+    <maxCompositeContours value="0"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+    <maxComponentDepth value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="121"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="1000" lsb="0"/>
+    <mtx name="R" width="1000" lsb="0"/>
+    <mtx name="a" width="1000" lsb="0"/>
+    <mtx name="space" width="1000" lsb="0"/>
+    <mtx name="y" width="1000" lsb="-1500"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="64" language="0" nGroups="4">
+      <map code="0x20" name="space"/><!-- SPACE -->
+      <map code="0x52" name="R"/><!-- LATIN CAPITAL LETTER R -->
+      <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+      <map code="0x79" name="y"/><!-- LATIN SMALL LETTER Y -->
+    </cmap_format_12>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+
+    <!-- The xMin, yMin, xMax and yMax values
+         will be recalculated by the compiler. -->
+
+    <TTGlyph name=".notdef"/><!-- contains no outline data -->
+
+    <TTGlyph name="R" xMin="0" yMin="0" xMax="5000" yMax="1000">
+      <contour>
+        <pt x="0" y="0" on="1"/>
+        <pt x="0" y="1000" on="1"/>
+        <pt x="5000" y="1000" on="1"/>
+        <pt x="5000" y="0" on="1"/>
+      </contour>
+      <instructions/>
+    </TTGlyph>
+
+    <TTGlyph name="a"/><!-- contains no outline data -->
+
+    <TTGlyph name="space"/><!-- contains no outline data -->
+
+    <TTGlyph name="y" xMin="-1500" yMin="0" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="-1500" y="0" on="1"/>
+        <pt x="-1500" y="1000" on="1"/>
+        <pt x="1000" y="1000" on="1"/>
+        <pt x="1000" y="0" on="1"/>
+      </contour>
+      <instructions/>
+    </TTGlyph>
+
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for LineBreakingOverhangsTest
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Font for LineBreakingOverhangsTest
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/core/tests/coretests/res/font/samplexmlfont.xml b/core/tests/coretests/res/font/samplexmlfont.xml
index bb813e1..f1d14ff 100644
--- a/core/tests/coretests/res/font/samplexmlfont.xml
+++ b/core/tests/coretests/res/font/samplexmlfont.xml
@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?>
 <font-family xmlns:android="http://schemas.android.com/apk/res/android">
-    <font android:fontStyle="normal" android:fontWeight="400" android:font="@font/samplefont" />
-    <font android:fontStyle="italic" android:fontWeight="400" android:font="@font/samplefont2" />
-    <font android:fontStyle="normal" android:fontWeight="800" android:font="@font/samplefont3" />
+    <font android:fontStyle="normal" android:fontWeight="400" android:fontVariationSettings="'wdth' 0.8"
+          android:font="@font/samplefont" android:ttcIndex="0"/>
+    <font android:fontStyle="italic" android:fontWeight="400" android:fontVariationSettings="'contrast' 0.5"
+          android:font="@font/samplefont2" android:ttcIndex="1" />
+    <font android:fontStyle="normal" android:fontWeight="800" android:fontVariationSettings="'wdth' 500.0, 'wght' 300.0"
+          android:font="@font/samplefont3" android:ttcIndex="2" />
     <font android:fontStyle="italic" android:fontWeight="800" android:font="@font/samplefont4" />
-</font-family>
\ No newline at end of file
+</font-family>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 5457713..bec862a 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -22,10 +22,13 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.Intent;
 import android.media.session.MediaSession;
+import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.widget.RemoteViews;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -138,6 +141,44 @@
         assertFalse(n.hasCompletedProgress());
     }
 
+    @Test
+    public void allPendingIntents_recollectedAfterReusingBuilder() {
+        PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0);
+        PendingIntent intent2 = PendingIntent.getActivity(mContext, 0, new Intent("test2"), 0);
+
+        Notification.Builder builder = new Notification.Builder(mContext, "channel");
+        builder.setContentIntent(intent1);
+
+        Parcel p = Parcel.obtain();
+
+        Notification n1 = builder.build();
+        n1.writeToParcel(p, 0);
+
+        builder.setContentIntent(intent2);
+        Notification n2 = builder.build();
+        n2.writeToParcel(p, 0);
+
+        assertTrue(n2.allPendingIntents.contains(intent2));
+    }
+
+    @Test
+    public void allPendingIntents_containsCustomRemoteViews() {
+        PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent("test"), 0);
+
+        RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
+        contentView.setOnClickPendingIntent(1 /* id */, intent);
+
+        Notification.Builder builder = new Notification.Builder(mContext, "channel");
+        builder.setCustomContentView(contentView);
+
+        Parcel p = Parcel.obtain();
+
+        Notification n = builder.build();
+        n.writeToParcel(p, 0);
+
+        assertTrue(n.allPendingIntents.contains(intent));
+    }
+
     private Notification.Builder getMediaNotification() {
         MediaSession session = new MediaSession(mContext, "test");
         return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java
deleted file mode 100644
index a5fdef6..0000000
--- a/core/tests/coretests/src/android/content/pm/AppCacheTest.java
+++ /dev/null
@@ -1,725 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.StatFs;
-import android.os.UserHandle;
-import android.support.test.filters.LargeTest;
-import android.support.test.filters.MediumTest;
-import android.support.test.filters.SmallTest;
-import android.support.test.filters.Suppress;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class AppCacheTest extends AndroidTestCase {
-    private static final boolean localLOGV = false;
-    public static final String TAG="AppCacheTest";
-    public final long MAX_WAIT_TIME=60*1000;
-    public final long WAIT_TIME_INCR=10*1000;
-    private static final long THRESHOLD=5;
-    private static final long ACTUAL_THRESHOLD=10;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if(localLOGV) Log.i(TAG, "Cleaning up cache directory first");
-        cleanUpCacheDirectory();
-    }
-
-    void cleanUpDirectory(File pDir, String dirName) {
-       File testDir = new File(pDir,  dirName);
-       if(!testDir.exists()) {
-           return;
-       }
-        String fList[] = testDir.list();
-        for(int i = 0; i < fList.length; i++) {
-            File file = new File(testDir, fList[i]);
-            if(file.isDirectory()) {
-                cleanUpDirectory(testDir, fList[i]);
-            } else {
-                file.delete();
-            }
-        }
-        testDir.delete();
-    }
-
-    void cleanUpCacheDirectory() {
-        File testDir = mContext.getCacheDir();
-        if(!testDir.exists()) {
-            return;
-        }
-
-         String fList[] = testDir.list();
-         if(fList == null) {
-             testDir.delete();
-             return;
-         }
-         for(int i = 0; i < fList.length; i++) {
-             File file = new File(testDir, fList[i]);
-             if(file.isDirectory()) {
-                 cleanUpDirectory(testDir, fList[i]);
-             } else {
-                 file.delete();
-             }
-         }
-     }
-
-    @SmallTest
-    public void testDeleteAllCacheFiles() {
-        String testName="testDeleteAllCacheFiles";
-        cleanUpCacheDirectory();
-    }
-
-    void failStr(String errMsg) {
-        Log.w(TAG, "errMsg="+errMsg);
-        fail(errMsg);
-    }
-
-    void failStr(Exception e) {
-        Log.w(TAG, "e.getMessage="+e.getMessage());
-        Log.w(TAG, "e="+e);
-    }
-
-    long getFreeStorageBlks(StatFs st) {
-        st.restat("/data");
-        return st.getFreeBlocks();
-    }
-
-    long getFreeStorageSize(StatFs st) {
-        st.restat("/data");
-        return (long) st.getFreeBlocks() * (long) st.getBlockSize();
-    }
-
-    @LargeTest
-    @Suppress  // Failing.
-    public void testFreeApplicationCacheAllFiles() throws Exception {
-        boolean TRACKING = true;
-        StatFs st = new StatFs("/data");
-        long blks1 = getFreeStorageBlks(st);
-        long availableMem = getFreeStorageSize(st);
-        File cacheDir = mContext.getCacheDir();
-        assertNotNull(cacheDir);
-        createTestFiles1(cacheDir, "testtmpdir", 5);
-        long blks2 = getFreeStorageBlks(st);
-        if(localLOGV || TRACKING) Log.i(TAG, "blk1="+blks1+", blks2="+blks2);
-        //this should free up the test files that were created earlier
-        if (!invokePMFreeApplicationCache(availableMem)) {
-            fail("Could not successfully invoke PackageManager free app cache API");
-        }
-        long blks3 = getFreeStorageBlks(st);
-        if(localLOGV || TRACKING) Log.i(TAG, "blks3="+blks3);
-        verifyTestFiles1(cacheDir, "testtmpdir", 5);
-    }
-
-    public void testFreeApplicationCacheSomeFiles() throws Exception {
-        StatFs st = new StatFs("/data");
-        long blks1 = getFreeStorageBlks(st);
-        File cacheDir = mContext.getCacheDir();
-        assertNotNull(cacheDir);
-        createTestFiles1(cacheDir, "testtmpdir", 5);
-        long blks2 = getFreeStorageBlks(st);
-        Log.i(TAG, "blk1="+blks1+", blks2="+blks2);
-        long diff = (blks1-blks2-2);
-        if (!invokePMFreeApplicationCache(diff * st.getBlockSize())) {
-            fail("Could not successfully invoke PackageManager free app cache API");
-        }
-        long blks3 = getFreeStorageBlks(st);
-        //blks3 should be greater than blks2 and less than blks1
-        if(!((blks3 <= blks1) && (blks3 >= blks2))) {
-            failStr("Expected "+(blks1-blks2)+" number of blocks to be freed but freed only "
-                    +(blks1-blks3));
-        }
-    }
-
-    /**
-     * This method opens an output file writes to it, opens the same file as an input
-     * stream, reads the contents and verifies the data that was written earlier can be read
-     */
-    public void openOutFileInAppFilesDir(File pFile, String pFileOut) {
-        FileOutputStream fos = null;
-        try {
-            fos = new FileOutputStream(pFile);
-        } catch (FileNotFoundException e1) {
-            failStr("Error when opening file "+e1);
-            return;
-        }
-        try {
-            fos.write(pFileOut.getBytes());
-            fos.close();
-        } catch (FileNotFoundException e) {
-            failStr(e.getMessage());
-        } catch (IOException e) {
-            failStr(e.getMessage());
-        }
-        int count = pFileOut.getBytes().length;
-        byte[] buffer = new byte[count];
-        try {
-            FileInputStream fis = new FileInputStream(pFile);
-            fis.read(buffer, 0, count);
-            fis.close();
-        } catch (FileNotFoundException e) {
-            failStr("Failed when verifing output opening file "+e.getMessage());
-        } catch (IOException e) {
-            failStr("Failed when verifying output, reading from written file "+e);
-        }
-        String str = new String(buffer);
-        assertEquals(str, pFileOut);
-    }
-
-    /*
-     * This test case verifies that output written to a file
-     * using Context.openFileOutput has executed successfully.
-     * The operation is verified by invoking Context.openFileInput
-     */
-    @MediumTest
-    public void testAppFilesCreateFile() {
-        String fileName = "testFile1.txt";
-        String fileOut = "abcdefghijklmnopqrstuvwxyz";
-        Context con = super.getContext();
-        try {
-            FileOutputStream fos = con.openFileOutput(fileName, Context.MODE_PRIVATE);
-            fos.close();
-        } catch (FileNotFoundException e) {
-            failStr(e);
-        } catch (IOException e) {
-            failStr(e);
-        }
-    }
-
-    @SmallTest
-    public void testAppCacheCreateFile() {
-        String fileName = "testFile1.txt";
-        String fileOut = "abcdefghijklmnopqrstuvwxyz";
-        Context con = super.getContext();
-        File file = new File(con.getCacheDir(), fileName);
-        openOutFileInAppFilesDir(file, fileOut);
-        cleanUpCacheDirectory();
-    }
-
-    @MediumTest
-    public void testAppCreateCacheFiles() {
-        File cacheDir = mContext.getCacheDir();
-        String testDirName = "testtmp";
-        File testTmpDir = new File(cacheDir, testDirName);
-        testTmpDir.mkdir();
-        int numDirs = 3;
-        File fileArr[] = new File[numDirs];
-        for(int i = 0; i < numDirs; i++) {
-            fileArr[i] = new File(testTmpDir, "dir"+(i+1));
-            fileArr[i].mkdir();
-        }
-        byte buffer[] = getBuffer();
-        Log.i(TAG, "Size of bufer="+buffer.length);
-        for(int i = 0; i < numDirs; i++) {
-            for(int j = 1; j <= (i); j++) {
-                File file1 = new File(fileArr[i], "testFile"+j+".txt");
-                FileOutputStream fos = null;
-                try {
-                    fos = new FileOutputStream(file1);
-                    for(int k = 1; k < 10; k++) {
-                        fos.write(buffer);
-                    }
-                    Log.i(TAG, "wrote 10K bytes to "+file1);
-                    fos.close();
-                } catch (FileNotFoundException e) {
-                    Log.i(TAG, "Excetion ="+e);
-                    fail("Error when creating outputstream "+e);
-                } catch(IOException e) {
-                    Log.i(TAG, "Excetion ="+e);
-                    fail("Error when writing output "+e);
-                }
-            }
-        }
-    }
-
-    byte[] getBuffer() {
-        String sbuffer = "a";
-        for(int i = 0; i < 10; i++) {
-            sbuffer += sbuffer;
-        }
-        return sbuffer.getBytes();
-    }
-
-    long getFileNumBlocks(long fileSize, long blkSize) {
-        long ret = fileSize/blkSize;
-        if(ret*blkSize < fileSize) {
-            ret++;
-        }
-        return ret;
-    }
-
-    //@LargeTest
-    public void testAppCacheClear() {
-        String dataDir="/data/data";
-        StatFs st = new StatFs(dataDir);
-        long blkSize = st.getBlockSize();
-        long totBlks = st.getBlockCount();
-        long availableBlks = st.getFreeBlocks();
-        long thresholdBlks = (totBlks * THRESHOLD) / 100L;
-        String testDirName = "testdir";
-        //create directory in cache
-        File testDir = new File(mContext.getCacheDir(),  testDirName);
-        testDir.mkdirs();
-        byte[] buffer = getBuffer();
-        int i = 1;
-        if(localLOGV) Log.i(TAG, "availableBlks="+availableBlks+", thresholdBlks="+thresholdBlks);
-        long createdFileBlks = 0;
-        int imax = 300;
-        while((availableBlks > thresholdBlks) &&(i < imax)) {
-            File testFile = new File(testDir, "testFile"+i+".txt");
-            if(localLOGV) Log.i(TAG, "Creating "+i+"th test file "+testFile);
-            int jmax = i;
-            i++;
-            FileOutputStream fos;
-            try {
-                fos = new FileOutputStream(testFile);
-            } catch (FileNotFoundException e) {
-                Log.i(TAG, "Failed creating test file:"+testFile);
-                continue;
-            }
-            boolean err = false;
-            for(int j = 1; j <= jmax;j++) {
-                try {
-                    fos.write(buffer);
-                } catch (IOException e) {
-                    Log.i(TAG, "Failed to write to file:"+testFile);
-                    err = true;
-                }
-            }
-            try {
-                fos.close();
-            } catch (IOException e) {
-                Log.i(TAG, "Failed closing file:"+testFile);
-            }
-            if(err) {
-                continue;
-            }
-            createdFileBlks += getFileNumBlocks(testFile.length(), blkSize);
-            st.restat(dataDir);
-            availableBlks = st.getFreeBlocks();
-        }
-        st.restat(dataDir);
-        long availableBytes = st.getFreeBlocks()*blkSize;
-        long shouldFree = (ACTUAL_THRESHOLD-THRESHOLD)*totBlks;
-        //would have run out of memory
-        //wait for some time and confirm cache is deleted
-        try {
-            Log.i(TAG, "Sleeping for 2 minutes...");
-            Thread.sleep(2*60*1000);
-        } catch (InterruptedException e) {
-            fail("Exception when sleeping "+e);
-        }
-        boolean removedFlag = false;
-        long existingFileBlks = 0;
-        for(int k = 1; k <i; k++) {
-            File testFile = new File(testDir, "testFile"+k+".txt");
-            if(!testFile.exists()) {
-                removedFlag = true;
-                if(localLOGV) Log.i(TAG, testFile+" removed");
-            }  else {
-                existingFileBlks += getFileNumBlocks(testFile.length(), blkSize);
-            }
-        }
-        if(localLOGV) Log.i(TAG, "createdFileBlks="+createdFileBlks+
-                ", existingFileBlks="+existingFileBlks);
-        long fileSize = createdFileBlks-existingFileBlks;
-        //verify fileSize number of bytes have been cleared from cache
-        if(localLOGV) Log.i(TAG, "deletedFileBlks="+fileSize+" shouldFreeBlks="+shouldFree);
-        if((fileSize > (shouldFree-blkSize) && (fileSize < (shouldFree+blkSize)))) {
-            Log.i(TAG, "passed");
-        }
-        assertTrue("Files should have been removed", removedFlag);
-    }
-
-    //createTestFiles(new File(super.getContext().getCacheDir(), "testtmp", "dir", 3)
-    void createTestFiles1(File cacheDir, String testFilePrefix, int numTestFiles) {
-        byte buffer[] = getBuffer();
-        for(int i = 0; i < numTestFiles; i++) {
-            File file1 = new File(cacheDir, testFilePrefix+i+".txt");
-            FileOutputStream fos = null;
-            try {
-                fos = new FileOutputStream(file1);
-                for(int k = 1; k < 10; k++) {
-                    fos.write(buffer);
-               }
-                fos.close();
-            } catch (FileNotFoundException e) {
-                Log.i(TAG, "Exception ="+e);
-                fail("Error when creating outputstream "+e);
-            } catch(IOException e) {
-                Log.i(TAG, "Exception ="+e);
-                fail("Error when writing output "+e);
-            }
-            try {
-                //introduce sleep for 1 s to avoid common time stamps for files being created
-                Thread.sleep(1000);
-            } catch (InterruptedException e) {
-                fail("Exception when sleeping "+e);
-            }
-        }
-    }
-
-    void verifyTestFiles1(File cacheDir, String testFilePrefix, int numTestFiles) {
-        List<String> files = new ArrayList<String>();
-        for(int i = 0; i < numTestFiles; i++) {
-            File file1 = new File(cacheDir, testFilePrefix+i+".txt");
-            if(file1.exists()) {
-                files.add(file1.getName());
-            }
-        }
-        if (files.size() > 0) {
-            fail("Files should have been deleted: "
-                    + Arrays.toString(files.toArray(new String[files.size()])));
-        }
-    }
-
-    void createTestFiles2(File cacheDir, String rootTestDirName, String subDirPrefix, int numDirs, String testFilePrefix) {
-        Context con = super.getContext();
-        File testTmpDir = new File(cacheDir, rootTestDirName);
-        testTmpDir.mkdir();
-        File fileArr[] = new File[numDirs];
-        for(int i = 0; i < numDirs; i++) {
-            fileArr[i] = new File(testTmpDir, subDirPrefix+(i+1));
-            fileArr[i].mkdir();
-        }
-        byte buffer[] = getBuffer();
-        for(int i = 0; i < numDirs; i++) {
-            for(int j = 1; j <= (i); j++) {
-                File file1 = new File(fileArr[i], testFilePrefix+j+".txt");
-                FileOutputStream fos = null;
-                try {
-                    fos = new FileOutputStream(file1);
-                    for(int k = 1; k < 10; k++) {
-                        fos.write(buffer);
-                    }
-                    fos.close();
-                } catch (FileNotFoundException e) {
-                    Log.i(TAG, "Exception ="+e);
-                    fail("Error when creating outputstream "+e);
-                } catch(IOException e) {
-                    Log.i(TAG, "Exception ="+e);
-                    fail("Error when writing output "+e);
-                }
-                try {
-                    //introduce sleep for 10 ms to avoid common time stamps for files being created
-                    Thread.sleep(10);
-                } catch (InterruptedException e) {
-                    fail("Exception when sleeping "+e);
-                }
-            }
-        }
-    }
-
-    class PackageDataObserver extends IPackageDataObserver.Stub {
-        public boolean retValue = false;
-        private boolean doneFlag = false;
-        public void onRemoveCompleted(String packageName, boolean succeeded)
-                throws RemoteException {
-            synchronized(this) {
-                retValue = succeeded;
-                doneFlag = true;
-                notifyAll();
-            }
-        }
-        public boolean isDone() {
-            return doneFlag;
-        }
-    }
-
-    IPackageManager getPm() {
-        return  IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-    }
-
-    boolean invokePMDeleteAppCacheFiles() throws Exception {
-        try {
-            String packageName = mContext.getPackageName();
-            PackageDataObserver observer = new PackageDataObserver();
-            //wait on observer
-            synchronized(observer) {
-                getPm().deleteApplicationCacheFiles(packageName, observer);
-                long waitTime = 0;
-                while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!observer.isDone()) {
-                    throw new Exception("timed out waiting for PackageDataObserver.onRemoveCompleted");
-                }
-            }
-            return observer.retValue;
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
-            return false;
-        } catch (InterruptedException e) {
-            Log.w(TAG, "InterruptedException :"+e);
-            return false;
-        }
-    }
-
-    boolean invokePMFreeApplicationCache(long idealStorageSize) throws Exception {
-        try {
-            String packageName = mContext.getPackageName();
-            PackageDataObserver observer = new PackageDataObserver();
-            //wait on observer
-            synchronized(observer) {
-                getPm().freeStorageAndNotify(null, idealStorageSize, 0, observer);
-                long waitTime = 0;
-                while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!observer.isDone()) {
-                    throw new Exception("timed out waiting for PackageDataObserver.onRemoveCompleted");
-                }
-            }
-            return observer.retValue;
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
-            return false;
-        } catch (InterruptedException e) {
-            Log.w(TAG, "InterruptedException :"+e);
-            return false;
-        }
-    }
-
-    boolean invokePMFreeStorage(long idealStorageSize, FreeStorageReceiver r,
-            PendingIntent pi) throws Exception {
-        try {
-            // Spin lock waiting for call back
-            synchronized(r) {
-                getPm().freeStorage(null, idealStorageSize, 0, pi.getIntentSender());
-                long waitTime = 0;
-                while(!r.isDone() && (waitTime < MAX_WAIT_TIME)) {
-                    r.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!r.isDone()) {
-                    throw new Exception("timed out waiting for call back from PendingIntent");
-                }
-            }
-            return r.getResultCode() == 1;
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
-            return false;
-        } catch (InterruptedException e) {
-            Log.w(TAG, "InterruptedException :"+e);
-            return false;
-        }
-    }
-
-    @LargeTest
-    public void testDeleteAppCacheFiles() throws Exception {
-        String testName="testDeleteAppCacheFiles";
-        File cacheDir = mContext.getCacheDir();
-        createTestFiles1(cacheDir, "testtmpdir", 5);
-        assertTrue(invokePMDeleteAppCacheFiles());
-        //confirm files dont exist
-        verifyTestFiles1(cacheDir, "testtmpdir", 5);
-    }
-
-    class PackageStatsObserver extends IPackageStatsObserver.Stub {
-        public boolean retValue = false;
-        public PackageStats stats;
-        private boolean doneFlag = false;
-
-        public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
-                throws RemoteException {
-            synchronized(this) {
-                retValue = succeeded;
-                stats = pStats;
-                doneFlag = true;
-                notifyAll();
-            }
-        }
-        public boolean isDone() {
-            return doneFlag;
-        }
-    }
-
-    @SmallTest
-    public void testGetSystemSharedLibraryNames() throws Exception {
-        try {
-            String[] sharedLibs = getPm().getSystemSharedLibraryNames();
-            if (localLOGV) {
-                for (String str : sharedLibs) {
-                    Log.i(TAG, str);
-                }
-            }
-        } catch (RemoteException e) {
-            fail("Failed invoking getSystemSharedLibraryNames with exception:" + e);
-        }
-    }
-
-    class FreeStorageReceiver extends BroadcastReceiver {
-        public static final String ACTION_FREE = "com.android.unit_tests.testcallback";
-        private boolean doneFlag = false;
-
-        public boolean isDone() {
-            return doneFlag;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if(intent.getAction().equalsIgnoreCase(ACTION_FREE)) {
-                if (localLOGV) Log.i(TAG, "Got notification: clear cache succeeded "+getResultCode());
-                synchronized (this) {
-                    doneFlag = true;
-                    notifyAll();
-                }
-            }
-        }
-    }
-
-    // TODO: flaky test, omit from LargeTest for now
-    //@LargeTest
-    public void testFreeStorage() throws Exception {
-        boolean TRACKING = true;
-        StatFs st = new StatFs("/data");
-        long blks1 = getFreeStorageBlks(st);
-        if(localLOGV || TRACKING) Log.i(TAG, "Available free blocks="+blks1);
-        long availableMem = getFreeStorageSize(st);
-        File cacheDir = mContext.getCacheDir();
-        assertNotNull(cacheDir);
-        createTestFiles1(cacheDir, "testtmpdir", 5);
-        long blks2 = getFreeStorageBlks(st);
-        if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after writing test files in application cache="+blks2);
-        // Create receiver and register it
-        FreeStorageReceiver receiver = new FreeStorageReceiver();
-        mContext.registerReceiver(receiver, new IntentFilter(FreeStorageReceiver.ACTION_FREE));
-        PendingIntent pi = PendingIntent.getBroadcast(mContext,
-                0,  new Intent(FreeStorageReceiver.ACTION_FREE), 0);
-        // Invoke PackageManager api
-        if (!invokePMFreeStorage(availableMem, receiver, pi)) {
-            fail("Could not invoke PackageManager free storage API");
-        }
-        long blks3 = getFreeStorageBlks(st);
-        if(localLOGV || TRACKING) Log.i(TAG, "Available blocks after freeing cache"+blks3);
-        assertEquals(receiver.getResultCode(), 1);
-        mContext.unregisterReceiver(receiver);
-        // Verify result
-        verifyTestFiles1(cacheDir, "testtmpdir", 5);
-    }
-
-    /* utility method used to create observer and check async call back from PackageManager.
-     * ClearApplicationUserData
-     */
-    boolean invokePMClearApplicationUserData() throws Exception {
-        try {
-            String packageName = mContext.getPackageName();
-            PackageDataObserver observer = new PackageDataObserver();
-            //wait on observer
-            synchronized(observer) {
-                getPm().clearApplicationUserData(packageName, observer, 0 /* TODO: Other users */);
-                long waitTime = 0;
-                while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!observer.isDone()) {
-                    throw new Exception("timed out waiting for PackageDataObserver.onRemoveCompleted");
-                }
-            }
-            return observer.retValue;
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
-            return false;
-        } catch (InterruptedException e) {
-            Log.w(TAG, "InterruptedException :"+e);
-            return false;
-        }
-    }
-
-    void verifyUserDataCleared(File pDir) {
-        if(localLOGV) Log.i(TAG, "Verifying "+pDir);
-        if(pDir == null) {
-            return;
-        }
-        String fileList[] = pDir.list();
-        if(fileList == null) {
-            return;
-        }
-        int imax = fileList.length;
-       //look recursively in user data dir
-        for(int i = 0; i < imax; i++) {
-            if(localLOGV) Log.i(TAG, "Found entry "+fileList[i]+ "in "+pDir);
-            if("lib".equalsIgnoreCase(fileList[i])) {
-                if(localLOGV) Log.i(TAG, "Ignoring lib directory");
-                continue;
-            }
-            fail(pDir+" should be empty or contain only lib subdirectory. Found "+fileList[i]);
-        }
-    }
-
-    File getDataDir() {
-        try {
-            ApplicationInfo appInfo = getPm().getApplicationInfo(mContext.getPackageName(), 0,
-                    UserHandle.myUserId());
-            return new File(appInfo.dataDir);
-        } catch (RemoteException e) {
-            throw new RuntimeException("Pacakge manager dead", e);
-        }
-    }
-
-    @Suppress
-    @LargeTest
-    public void testClearApplicationUserDataWithTestData() throws Exception {
-        File cacheDir = mContext.getCacheDir();
-        createTestFiles1(cacheDir, "testtmpdir", 5);
-        if(localLOGV) {
-            Log.i(TAG, "Created test data Waiting for 60seconds before continuing");
-            Thread.sleep(60*1000);
-        }
-        assertTrue(invokePMClearApplicationUserData());
-        //confirm files dont exist
-        verifyUserDataCleared(getDataDir());
-    }
-
-    @Suppress
-    @SmallTest
-    public void testClearApplicationUserDataWithNoTestData() throws Exception {
-        assertTrue(invokePMClearApplicationUserData());
-        //confirm files dont exist
-        verifyUserDataCleared(getDataDir());
-    }
-
-    @Suppress
-    @LargeTest
-    public void testClearApplicationUserDataNoObserver() throws Exception {
-        getPm().clearApplicationUserData(mContext.getPackageName(), null, UserHandle.myUserId());
-        //sleep for 1 minute
-        Thread.sleep(60*1000);
-        //confirm files dont exist
-        verifyUserDataCleared(getDataDir());
-    }
-
-}
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
index 55092fa..b627619 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
@@ -16,11 +16,9 @@
 
 package android.content.pm;
 
+import static android.os.storage.VolumeInfo.STATE_MOUNTED;
+
 import android.content.Context;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.test.AndroidTestCase;
@@ -36,16 +34,10 @@
 import java.util.List;
 import java.util.UUID;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
-import static android.os.storage.VolumeInfo.STATE_MOUNTED;
-
 public class PackageHelperTests extends AndroidTestCase {
     private static final boolean localLOGV = true;
     public static final String TAG = "PackageHelperTests";
     protected final String PREFIX = "android.content.pm";
-    private IStorageManager mSm;
-    private String fullId;
-    private String fullId2;
 
     private static final String sInternalVolPath = "/data";
     private static final String sAdoptedVolPath = "/mnt/expand/123";
@@ -147,34 +139,11 @@
         }
     }
 
-    private IStorageManager getSm() {
-        IBinder service = ServiceManager.getService("mount");
-        if (service != null) {
-            return IStorageManager.Stub.asInterface(service);
-        } else {
-            Log.e(TAG, "Can't get mount service");
-        }
-        return null;
-    }
-
-    private void cleanupContainers() throws RemoteException {
-        Log.d(TAG,"cleanUp");
-        IStorageManager sm = getSm();
-        String[] containers = sm.getSecureContainerList();
-        for (int i = 0; i < containers.length; i++) {
-            if (containers[i].startsWith(PREFIX)) {
-                Log.d(TAG,"cleaing up "+containers[i]);
-                sm.destroySecureContainer(containers[i], true);
-            }
-        }
-    }
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         sStorageManager = createStorageManagerMock();
         if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
-        cleanupContainers();
     }
 
     @Override
@@ -182,55 +151,6 @@
         super.tearDown();
         sStorageManager = null;
         if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
-        cleanupContainers();
-    }
-
-    public void testMountAndPullSdCard() throws Exception {
-        fullId = PREFIX;
-        fullId2 = PackageHelper.createSdDir(1024 * MB_IN_BYTES, fullId, "none",
-                android.os.Process.myUid(), true);
-
-        Log.d(TAG, "getSdDir=" + PackageHelper.getSdDir(fullId));
-        PackageHelper.unMountSdDir(fullId);
-
-        Runnable r1 = getMountRunnable();
-        Runnable r2 = getDestroyRunnable();
-        Thread thread = new Thread(r1);
-        Thread thread2 = new Thread(r2);
-        thread2.start();
-        thread.start();
-    }
-
-    public Runnable getMountRunnable() {
-        Runnable r = new Runnable () {
-            public void run () {
-                try {
-                    Thread.sleep(5);
-                    String path = PackageHelper.mountSdDir(fullId, "none",
-                            android.os.Process.myUid());
-                    Log.e(TAG, "mount done " + path);
-                } catch (IllegalArgumentException iae) {
-                    throw iae;
-                } catch (Throwable t) {
-                    Log.e(TAG, "mount failed", t);
-                }
-            }
-        };
-        return r;
-    }
-
-    public Runnable getDestroyRunnable() {
-        Runnable r = new Runnable () {
-            public void run () {
-                try {
-                    PackageHelper.destroySdDir(fullId);
-                    Log.e(TAG, "destroy done: " + fullId);
-                } catch (Throwable t) {
-                    Log.e(TAG, "destroy failed", t);
-                }
-            }
-        };
-        return r;
     }
 
     public void testResolveInstallVolumeInternal_SystemApp() throws IOException {
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index b8fa06c..a317c99 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -46,13 +46,8 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.StatFs;
 import android.os.SystemClock;
-import android.os.storage.IStorageManager;
-import android.os.storage.StorageListener;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageResultCode;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.system.ErrnoException;
@@ -103,8 +98,6 @@
 
     private static final int APP_INSTALL_SDCARD = PackageHelper.APP_INSTALL_EXTERNAL;
 
-    private boolean mOrigState;
-
     void failStr(String errMsg) {
         Log.w(TAG, "errMsg=" + errMsg);
         fail(errMsg);
@@ -114,29 +107,6 @@
         failStr(e.getMessage());
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mOrigState = checkMediaState(Environment.MEDIA_MOUNTED);
-        if (!mountMedia()) {
-            Log.i(TAG, "sdcard not mounted? Some of these tests might fail");
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Restore media state.
-        boolean newState = checkMediaState(Environment.MEDIA_MOUNTED);
-        if (newState != mOrigState) {
-            if (mOrigState) {
-                mountMedia();
-            } else {
-                unmountMedia();
-            }
-        }
-        super.tearDown();
-    }
-
     private abstract static class GenericReceiver extends BroadcastReceiver {
         private boolean doneFlag = false;
 
@@ -782,17 +752,6 @@
         sampleInstallFromRawResource(0, true);
     }
 
-    @LargeTest
-    public void testInstallSdcard() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        mountMedia();
-        sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, true);
-    }
-
     /* ------------------------- Test replacing packages -------------- */
     class ReplaceReceiver extends GenericReceiver {
         String pkgName;
@@ -1081,240 +1040,6 @@
         deleteFromRawResource(PackageManager.INSTALL_EXTERNAL, PackageManager.DELETE_KEEP_DATA);
     }
 
-    /* sdcard mount/unmount tests ***** */
-
-    class SdMountReceiver extends GenericReceiver {
-        String pkgNames[];
-
-        boolean status = true;
-
-        SdMountReceiver(String[] pkgNames) {
-            this.pkgNames = pkgNames;
-            IntentFilter filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
-            super.setFilter(filter);
-        }
-
-        public boolean notifyNow(Intent intent) {
-            Log.i(TAG, "okay 1");
-            String action = intent.getAction();
-            if (!Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-                return false;
-            }
-            String rpkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            for (String pkg : pkgNames) {
-                boolean found = false;
-                for (String rpkg : rpkgList) {
-                    if (rpkg.equals(pkg)) {
-                        found = true;
-                        break;
-                    }
-                }
-                if (!found) {
-                    status = false;
-                    return true;
-                }
-            }
-            return true;
-        }
-    }
-
-    class SdUnMountReceiver extends GenericReceiver {
-        String pkgNames[];
-
-        boolean status = true;
-
-        SdUnMountReceiver(String[] pkgNames) {
-            this.pkgNames = pkgNames;
-            IntentFilter filter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
-            super.setFilter(filter);
-        }
-
-        public boolean notifyNow(Intent intent) {
-            String action = intent.getAction();
-            if (!Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-                return false;
-            }
-            String rpkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            for (String pkg : pkgNames) {
-                boolean found = false;
-                for (String rpkg : rpkgList) {
-                    if (rpkg.equals(pkg)) {
-                        found = true;
-                        break;
-                    }
-                }
-                if (!found) {
-                    status = false;
-                    return true;
-                }
-            }
-            return true;
-        }
-    }
-
-    IStorageManager getSm() {
-        IBinder service = ServiceManager.getService("mount");
-        if (service != null) {
-            return IStorageManager.Stub.asInterface(service);
-        } else {
-            Log.e(TAG, "Can't get storagemanager service");
-        }
-        return null;
-    }
-
-    boolean checkMediaState(String desired) {
-        String actual = Environment.getExternalStorageState();
-        if (desired.equals(actual)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    boolean mountMedia() {
-        // We can't mount emulated storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return true;
-        }
-
-        if (checkMediaState(Environment.MEDIA_MOUNTED)) {
-            return true;
-        }
-
-        final String path = Environment.getExternalStorageDirectory().toString();
-        StorageListener observer = new StorageListener(Environment.MEDIA_MOUNTED);
-        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
-        sm.registerListener(observer);
-        try {
-            // Wait on observer
-            synchronized (observer) {
-                int ret = getSm().mountVolume(path);
-                if (ret != StorageResultCode.OperationSucceeded) {
-                    throw new Exception("Could not mount the media");
-                }
-                long waitTime = 0;
-                while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if (!observer.isDone()) {
-                    throw new Exception("Timed out waiting for unmount media notification");
-                }
-                return true;
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Exception : " + e);
-            return false;
-        } finally {
-            sm.unregisterListener(observer);
-        }
-    }
-
-    private boolean unmountMedia() {
-        // We can't unmount emulated storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return true;
-        }
-
-        if (checkMediaState(Environment.MEDIA_UNMOUNTED)) {
-            return true;
-        }
-
-        final String path = Environment.getExternalStorageDirectory().getPath();
-        StorageListener observer = new StorageListener(Environment.MEDIA_UNMOUNTED);
-        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
-        sm.registerListener(observer);
-        try {
-            // Wait on observer
-            synchronized (observer) {
-                getSm().unmountVolume(path, true, false);
-                long waitTime = 0;
-                while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if (!observer.isDone()) {
-                    throw new Exception("Timed out waiting for unmount media notification");
-                }
-                return true;
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Exception : " + e);
-            return false;
-        } finally {
-            sm.unregisterListener(observer);
-        }
-    }
-
-    private boolean mountFromRawResource() throws Exception {
-        // Install pkg on sdcard
-        InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, false);
-        if (localLOGV) Log.i(TAG, "Installed pkg on sdcard");
-        boolean origState = checkMediaState(Environment.MEDIA_MOUNTED);
-        boolean registeredReceiver = false;
-        SdMountReceiver receiver = new SdMountReceiver(new String[]{ip.pkg.packageName});
-        try {
-            if (localLOGV) Log.i(TAG, "Unmounting media");
-            // Unmount media
-            assertTrue(unmountMedia());
-            if (localLOGV) Log.i(TAG, "Unmounted media");
-            // Register receiver here
-            PackageManager pm = getPm();
-            mContext.registerReceiver(receiver, receiver.filter);
-            registeredReceiver = true;
-
-            // Wait on receiver
-            synchronized (receiver) {
-                if (localLOGV) Log.i(TAG, "Mounting media");
-                // Mount media again
-                assertTrue(mountMedia());
-                if (localLOGV) Log.i(TAG, "Mounted media");
-                if (localLOGV) Log.i(TAG, "Waiting for notification");
-                long waitTime = 0;
-                // Verify we received the broadcast
-                waitTime = 0;
-                while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                    receiver.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!receiver.isDone()) {
-                    failStr("Timed out waiting for EXTERNAL_APPLICATIONS notification");
-                }
-                return receiver.received;
-            }
-        } catch (InterruptedException e) {
-            failStr(e);
-            return false;
-        } finally {
-            if (registeredReceiver) {
-                mContext.unregisterReceiver(receiver);
-            }
-            // Restore original media state
-            if (origState) {
-                mountMedia();
-            } else {
-                unmountMedia();
-            }
-            if (localLOGV) Log.i(TAG, "Cleaning up install");
-            cleanUpInstall(ip);
-        }
-    }
-
-    /*
-     * Install package on sdcard. Unmount and then mount the media.
-     * (Use PackageManagerService private api for now)
-     * Make sure the installed package is available.
-     */
-    @LargeTest
-    public void testMountSdNormalInternal() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertTrue(mountFromRawResource());
-    }
-
     void cleanUpInstall(InstallParams ip) throws Exception {
         if (ip == null) {
             return;
@@ -1713,64 +1438,6 @@
         }
     }
 
-    /*
-     * Test that an install error code is returned when media is unmounted
-     * and package installed on sdcard via package manager flag.
-     */
-    @LargeTest
-    public void testInstallSdcardUnmount() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean origState = checkMediaState(Environment.MEDIA_MOUNTED);
-        try {
-            // Unmount sdcard
-            assertTrue(unmountMedia());
-            // Try to install and make sure an error code is returned.
-            installFromRawResource("install.apk", R.raw.install,
-                    PackageManager.INSTALL_EXTERNAL, false,
-                    true, PackageInstaller.STATUS_FAILURE_STORAGE,
-                    PackageInfo.INSTALL_LOCATION_AUTO);
-        } finally {
-            // Restore original media state
-            if (origState) {
-                mountMedia();
-            } else {
-                unmountMedia();
-            }
-        }
-    }
-
-    /*
-     * Unmount sdcard. Try installing an app with manifest option to install
-     * on sdcard. Make sure it gets installed on internal flash.
-     */
-    @LargeTest
-    public void testInstallManifestSdcardUnmount() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean origState = checkMediaState(Environment.MEDIA_MOUNTED);
-        try {
-            // Unmount sdcard
-            assertTrue(unmountMedia());
-            InstallParams ip = new InstallParams("install.apk", R.raw.install_loc_sdcard);
-            installFromRawResource(ip, 0, true, false, -1,
-                    PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-        } finally {
-            // Restore original media state
-            if (origState) {
-                mountMedia();
-            } else {
-                unmountMedia();
-            }
-        }
-    }
-
     /*---------- Recommended install location tests ----*/
     /*
      * PrecedenceSuffixes:
@@ -2527,133 +2194,6 @@
     }
 
     /*
-     * Ensure that permissions are properly declared.
-     */
-    @LargeTest
-    public void testInstallOnSdPermissionsUnmount() throws Exception {
-        InstallParams ip = null;
-        boolean origMediaState = checkMediaState(Environment.MEDIA_MOUNTED);
-        try {
-            // **: Upon installing a package, are its declared permissions published?
-            int iFlags = PackageManager.INSTALL_INTERNAL;
-            int iApk = R.raw.install_decl_perm;
-            ip = installFromRawResource("install.apk", iApk,
-                    iFlags, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-            assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
-            assertPermissions(BASE_PERMISSIONS_DEFINED);
-            // Unmount media here
-            assertTrue(unmountMedia());
-            // Mount media again
-            mountMedia();
-            //Check permissions now
-            assertPermissions(BASE_PERMISSIONS_DEFINED);
-        } finally {
-            if (ip != null) {
-                cleanUpInstall(ip);
-            }
-        }
-    }
-
-    /* This test creates a stale container via StorageManagerService and then installs
-     * a package and verifies that the stale container is cleaned up and install
-     * is successful.
-     * Please note that this test is very closely tied to the framework's
-     * naming convention for secure containers.
-     */
-    @LargeTest
-    public void testInstallSdcardStaleContainer() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean origMediaState = checkMediaState(Environment.MEDIA_MOUNTED);
-        try {
-            // Mount media first
-            mountMedia();
-            String outFileName = "install.apk";
-            int rawResId = R.raw.install;
-            PackageManager pm = mContext.getPackageManager();
-            File filesDir = mContext.getFilesDir();
-            File outFile = new File(filesDir, outFileName);
-            Uri packageURI = getInstallablePackage(rawResId, outFile);
-            PackageParser.Package pkg = parsePackage(packageURI);
-            assertNotNull(pkg);
-            // Install an app on sdcard.
-            installFromRawResource(outFileName, rawResId,
-                    PackageManager.INSTALL_EXTERNAL, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            // Unmount sdcard
-            unmountMedia();
-            // Delete the app on sdcard to leave a stale container on sdcard.
-            GenericReceiver receiver = new DeleteReceiver(pkg.packageName);
-            assertTrue(invokeDeletePackage(pkg.packageName, 0, receiver));
-            mountMedia();
-            // Reinstall the app and make sure it gets installed.
-            installFromRawResource(outFileName, rawResId,
-                    PackageManager.INSTALL_EXTERNAL, true,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-        } catch (Exception e) {
-            failStr(e.getMessage());
-        } finally {
-            if (origMediaState) {
-                mountMedia();
-            } else {
-                unmountMedia();
-            }
-
-        }
-    }
-
-    /* This test installs an application on sdcard and unmounts media.
-     * The app is then re-installed on internal storage. The sdcard is mounted
-     * and verified that the re-installation on internal storage takes precedence.
-     */
-    @LargeTest
-    public void testInstallSdcardStaleContainerReinstall() throws Exception {
-        // Do not run on devices with emulated external storage.
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean origMediaState = checkMediaState(Environment.MEDIA_MOUNTED);
-        try {
-            // Mount media first
-            mountMedia();
-            String outFileName = "install.apk";
-            int rawResId = R.raw.install;
-            PackageManager pm = mContext.getPackageManager();
-            File filesDir = mContext.getFilesDir();
-            File outFile = new File(filesDir, outFileName);
-            Uri packageURI = getInstallablePackage(rawResId, outFile);
-            PackageParser.Package pkg = parsePackage(packageURI);
-            assertNotNull(pkg);
-            // Install an app on sdcard.
-            installFromRawResource(outFileName, rawResId,
-                    PackageManager.INSTALL_EXTERNAL, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            // Unmount sdcard
-            unmountMedia();
-            // Reinstall the app and make sure it gets installed on internal storage.
-            installFromRawResource(outFileName, rawResId,
-                    PackageManager.INSTALL_REPLACE_EXISTING, false,
-                    false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED);
-            mountMedia();
-            // Verify that the app installed is on internal storage.
-            assertInstall(pkg, 0, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
-        } catch (Exception e) {
-            failStr(e.getMessage());
-        } finally {
-            if (origMediaState) {
-                mountMedia();
-            } else {
-                unmountMedia();
-            }
-        }
-    }
-
-    /*
      * The following series of tests are related to upgrading apps with
      * different certificates.
      */
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 9c904d1..a12a0b8 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -69,18 +69,26 @@
         FontFileResourceEntry font1 = fileEntries[0];
         assertEquals(400, font1.getWeight());
         assertEquals(0, font1.getItalic());
+        assertEquals("'wdth' 0.8", font1.getVariationSettings());
+        assertEquals(0, font1.getTtcIndex());
         assertEquals("res/font/samplefont.ttf", font1.getFileName());
         FontFileResourceEntry font2 = fileEntries[1];
         assertEquals(400, font2.getWeight());
         assertEquals(1, font2.getItalic());
+        assertEquals(1, font2.getTtcIndex());
+        assertEquals("'contrast' 0.5", font2.getVariationSettings());
         assertEquals("res/font/samplefont2.ttf", font2.getFileName());
         FontFileResourceEntry font3 = fileEntries[2];
         assertEquals(800, font3.getWeight());
         assertEquals(0, font3.getItalic());
+        assertEquals(2, font3.getTtcIndex());
+        assertEquals("'wdth' 500.0, 'wght' 300.0", font3.getVariationSettings());
         assertEquals("res/font/samplefont3.ttf", font3.getFileName());
         FontFileResourceEntry font4 = fileEntries[3];
         assertEquals(800, font4.getWeight());
         assertEquals(1, font4.getItalic());
+        assertEquals(0, font4.getTtcIndex());
+        assertEquals(null, font4.getVariationSettings());
         assertEquals("res/font/samplefont4.ttf", font4.getFileName());
     }
 
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index 9ccc6e8..c52cf6e 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -18,18 +18,25 @@
 
 import android.content.ContentValues;
 import android.content.Context;
+import android.database.AbstractWindowedCursor;
 import android.database.Cursor;
+import android.database.CursorWindow;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
 import java.io.File;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
+@Presubmit
+@LargeTest
 public class SQLiteCursorTest extends AndroidTestCase {
+    private static final String TABLE_NAME = "testCursor";
     private SQLiteDatabase mDatabase;
     private File mDatabaseFile;
-    private static final String TABLE_NAME = "testCursor";
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -55,7 +62,6 @@
     /**
      * this test could take a while to execute. so, designate it as LargeTest
      */
-    @LargeTest
     public void testFillWindow() {
         // create schema
         final String testTable = "testV";
@@ -138,7 +144,7 @@
             // nothing to do - just scrolling to about half-point in the resultset
         }
         mDatabase.beginTransaction();
-        mDatabase.delete(testTable, "col1 < ?", new String[]{ (3 * M / 4) + ""});
+        mDatabase.delete(testTable, "col1 < ?", new String[]{(3 * M / 4) + ""});
         mDatabase.setTransactionSuccessful();
         mDatabase.endTransaction();
         c.requery();
@@ -149,4 +155,35 @@
         }
         c.close();
     }
-}
+
+    public void testCustomWindowSize() {
+        mDatabase.execSQL("CREATE TABLE Tst (Txt BLOB NOT NULL);");
+        byte[] testArr = new byte[10000];
+        Arrays.fill(testArr, (byte) 1);
+        for (int i = 0; i < 10; i++) {
+            mDatabase.execSQL("INSERT INTO Tst VALUES (?)", new Object[]{testArr});
+        }
+        Cursor cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
+        // With default window size, all rows should fit in RAM
+        AbstractWindowedCursor ac = (AbstractWindowedCursor) cursor;
+        int n = 0;
+        while (ac.moveToNext()) {
+            n++;
+            assertEquals(10, ac.getWindow().getNumRows());
+        }
+        assertEquals("All rows should be visited", 10, n);
+
+        // Now reduce window size, so that only 1 row can fit
+        cursor = mDatabase.rawQuery("SELECT * FROM TST", null);
+        CursorWindow cw = new CursorWindow("test", 11000);
+        ac = (AbstractWindowedCursor) cursor;
+        ac.setWindow(cw);
+        ac.move(-10);
+        n = 0;
+        while (ac.moveToNext()) {
+            n++;
+            assertEquals(1, cw.getNumRows());
+        }
+        assertEquals("All rows should be visited", 10, n);
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
deleted file mode 100644
index 1cb0ecd..0000000
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ /dev/null
@@ -1,789 +0,0 @@
-/*
- * Copyright (C) 2010 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.net;
-
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.LinkProperties.CompareResult;
-import android.net.LinkProperties.ProvisioningChange;
-import android.net.RouteInfo;
-import android.system.OsConstants;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.ArraySet;
-
-import junit.framework.TestCase;
-
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-
-public class LinkPropertiesTest extends TestCase {
-    private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
-    private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
-            "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
-    private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
-    private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
-    private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
-    private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
-    private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
-    private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
-    private static InetAddress GATEWAY62 = NetworkUtils.numericToInetAddress("fe80::6:2222");
-    private static String NAME = "qmi0";
-    private static int MTU = 1500;
-
-    private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
-    private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
-    private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
-
-    // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
-    private InetAddress Address(String addrString) {
-        return NetworkUtils.numericToInetAddress(addrString);
-    }
-
-    public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
-        // Check implementation of equals(), element by element.
-        assertTrue(source.isIdenticalInterfaceName(target));
-        assertTrue(target.isIdenticalInterfaceName(source));
-
-        assertTrue(source.isIdenticalAddresses(target));
-        assertTrue(target.isIdenticalAddresses(source));
-
-        assertTrue(source.isIdenticalDnses(target));
-        assertTrue(target.isIdenticalDnses(source));
-
-        assertTrue(source.isIdenticalRoutes(target));
-        assertTrue(target.isIdenticalRoutes(source));
-
-        assertTrue(source.isIdenticalHttpProxy(target));
-        assertTrue(target.isIdenticalHttpProxy(source));
-
-        assertTrue(source.isIdenticalStackedLinks(target));
-        assertTrue(target.isIdenticalStackedLinks(source));
-
-        assertTrue(source.isIdenticalMtu(target));
-        assertTrue(target.isIdenticalMtu(source));
-
-        // Check result of equals().
-        assertTrue(source.equals(target));
-        assertTrue(target.equals(source));
-
-        // Check hashCode.
-        assertEquals(source.hashCode(), target.hashCode());
-    }
-
-    @SmallTest
-    public void testEqualsNull() {
-        LinkProperties source = new LinkProperties();
-        LinkProperties target = new LinkProperties();
-
-        assertFalse(source == target);
-        assertLinkPropertiesEqual(source, target);
-    }
-
-    @SmallTest
-    public void testEqualsSameOrder() {
-        try {
-            LinkProperties source = new LinkProperties();
-            source.setInterfaceName(NAME);
-            // set 2 link addresses
-            source.addLinkAddress(LINKADDRV4);
-            source.addLinkAddress(LINKADDRV6);
-            // set 2 dnses
-            source.addDnsServer(DNS1);
-            source.addDnsServer(DNS2);
-            // set 2 gateways
-            source.addRoute(new RouteInfo(GATEWAY1));
-            source.addRoute(new RouteInfo(GATEWAY2));
-            source.setMtu(MTU);
-
-            LinkProperties target = new LinkProperties();
-
-            // All fields are same
-            target.setInterfaceName(NAME);
-            target.addLinkAddress(LINKADDRV4);
-            target.addLinkAddress(LINKADDRV6);
-            target.addDnsServer(DNS1);
-            target.addDnsServer(DNS2);
-            target.addRoute(new RouteInfo(GATEWAY1));
-            target.addRoute(new RouteInfo(GATEWAY2));
-            target.setMtu(MTU);
-
-            assertLinkPropertiesEqual(source, target);
-
-            target.clear();
-            // change Interface Name
-            target.setInterfaceName("qmi1");
-            target.addLinkAddress(LINKADDRV4);
-            target.addLinkAddress(LINKADDRV6);
-            target.addDnsServer(DNS1);
-            target.addDnsServer(DNS2);
-            target.addRoute(new RouteInfo(GATEWAY1));
-            target.addRoute(new RouteInfo(GATEWAY2));
-            target.setMtu(MTU);
-            assertFalse(source.equals(target));
-
-            target.clear();
-            target.setInterfaceName(NAME);
-            // change link addresses
-            target.addLinkAddress(new LinkAddress(
-                    NetworkUtils.numericToInetAddress("75.208.6.2"), 32));
-            target.addLinkAddress(LINKADDRV6);
-            target.addDnsServer(DNS1);
-            target.addDnsServer(DNS2);
-            target.addRoute(new RouteInfo(GATEWAY1));
-            target.addRoute(new RouteInfo(GATEWAY2));
-            target.setMtu(MTU);
-            assertFalse(source.equals(target));
-
-            target.clear();
-            target.setInterfaceName(NAME);
-            target.addLinkAddress(LINKADDRV4);
-            target.addLinkAddress(LINKADDRV6);
-            // change dnses
-            target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
-            target.addDnsServer(DNS2);
-            target.addRoute(new RouteInfo(GATEWAY1));
-            target.addRoute(new RouteInfo(GATEWAY2));
-            target.setMtu(MTU);
-            assertFalse(source.equals(target));
-
-            target.clear();
-            target.setInterfaceName(NAME);
-            target.addLinkAddress(LINKADDRV4);
-            target.addLinkAddress(LINKADDRV6);
-            target.addDnsServer(DNS1);
-            target.addDnsServer(DNS2);
-            // change gateway
-            target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
-            target.addRoute(new RouteInfo(GATEWAY2));
-            target.setMtu(MTU);
-            assertFalse(source.equals(target));
-
-            target.clear();
-            target.setInterfaceName(NAME);
-            target.addLinkAddress(LINKADDRV4);
-            target.addLinkAddress(LINKADDRV6);
-            target.addDnsServer(DNS1);
-            target.addDnsServer(DNS2);
-            target.addRoute(new RouteInfo(GATEWAY1));
-            target.addRoute(new RouteInfo(GATEWAY2));
-            // change mtu
-            target.setMtu(1440);
-            assertFalse(source.equals(target));
-
-        } catch (Exception e) {
-            throw new RuntimeException(e.toString());
-            //fail();
-        }
-    }
-
-    @SmallTest
-    public void testEqualsDifferentOrder() {
-        try {
-            LinkProperties source = new LinkProperties();
-            source.setInterfaceName(NAME);
-            // set 2 link addresses
-            source.addLinkAddress(LINKADDRV4);
-            source.addLinkAddress(LINKADDRV6);
-            // set 2 dnses
-            source.addDnsServer(DNS1);
-            source.addDnsServer(DNS2);
-            // set 2 gateways
-            source.addRoute(new RouteInfo(GATEWAY1));
-            source.addRoute(new RouteInfo(GATEWAY2));
-            source.setMtu(MTU);
-
-            LinkProperties target = new LinkProperties();
-            // Exchange order
-            target.setInterfaceName(NAME);
-            target.addLinkAddress(LINKADDRV6);
-            target.addLinkAddress(LINKADDRV4);
-            target.addDnsServer(DNS2);
-            target.addDnsServer(DNS1);
-            target.addRoute(new RouteInfo(GATEWAY2));
-            target.addRoute(new RouteInfo(GATEWAY1));
-            target.setMtu(MTU);
-
-            assertLinkPropertiesEqual(source, target);
-        } catch (Exception e) {
-            fail();
-        }
-    }
-
-    @SmallTest
-    public void testEqualsDuplicated() {
-        try {
-            LinkProperties source = new LinkProperties();
-            // set 3 link addresses, eg, [A, A, B]
-            source.addLinkAddress(LINKADDRV4);
-            source.addLinkAddress(LINKADDRV4);
-            source.addLinkAddress(LINKADDRV6);
-
-            LinkProperties target = new LinkProperties();
-            // set 3 link addresses, eg, [A, B, B]
-            target.addLinkAddress(LINKADDRV4);
-            target.addLinkAddress(LINKADDRV6);
-            target.addLinkAddress(LINKADDRV6);
-
-            assertLinkPropertiesEqual(source, target);
-        } catch (Exception e) {
-            fail();
-        }
-    }
-
-    private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) {
-        for (RouteInfo r : lp.getRoutes()) {
-            assertEquals(iface, r.getInterface());
-        }
-    }
-
-    @SmallTest
-    public void testRouteInterfaces() {
-        LinkAddress prefix = new LinkAddress(
-            NetworkUtils.numericToInetAddress("2001:db8::"), 32);
-        InetAddress address = ADDRV6;
-
-        // Add a route with no interface to a LinkProperties with no interface. No errors.
-        LinkProperties lp = new LinkProperties();
-        RouteInfo r = new RouteInfo(prefix, address, null);
-        assertTrue(lp.addRoute(r));
-        assertEquals(1, lp.getRoutes().size());
-        assertAllRoutesHaveInterface(null, lp);
-
-        // Adding the same route twice has no effect.
-        assertFalse(lp.addRoute(r));
-        assertEquals(1, lp.getRoutes().size());
-
-        // Add a route with an interface. Expect an exception.
-        r = new RouteInfo(prefix, address, "wlan0");
-        try {
-          lp.addRoute(r);
-          fail("Adding wlan0 route to LP with no interface, expect exception");
-        } catch (IllegalArgumentException expected) {}
-
-        // Change the interface name. All the routes should change their interface name too.
-        lp.setInterfaceName("rmnet0");
-        assertAllRoutesHaveInterface("rmnet0", lp);
-
-        // Now add a route with the wrong interface. This causes an exception too.
-        try {
-          lp.addRoute(r);
-          fail("Adding wlan0 route to rmnet0 LP, expect exception");
-        } catch (IllegalArgumentException expected) {}
-
-        // If the interface name matches, the route is added.
-        r = new RouteInfo(prefix, null, "wlan0");
-        lp.setInterfaceName("wlan0");
-        lp.addRoute(r);
-        assertEquals(2, lp.getRoutes().size());
-        assertAllRoutesHaveInterface("wlan0", lp);
-
-        // Routes with null interfaces are converted to wlan0.
-        r = RouteInfo.makeHostRoute(ADDRV6, null);
-        lp.addRoute(r);
-        assertEquals(3, lp.getRoutes().size());
-        assertAllRoutesHaveInterface("wlan0", lp);
-
-        // Check comparisons work.
-        LinkProperties lp2 = new LinkProperties(lp);
-        assertAllRoutesHaveInterface("wlan0", lp);
-        assertEquals(0, lp.compareAllRoutes(lp2).added.size());
-        assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
-
-        lp2.setInterfaceName("p2p0");
-        assertAllRoutesHaveInterface("p2p0", lp2);
-        assertEquals(3, lp.compareAllRoutes(lp2).added.size());
-        assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
-    }
-
-    @SmallTest
-    public void testStackedInterfaces() {
-        LinkProperties rmnet0 = new LinkProperties();
-        rmnet0.setInterfaceName("rmnet0");
-        rmnet0.addLinkAddress(LINKADDRV6);
-
-        LinkProperties clat4 = new LinkProperties();
-        clat4.setInterfaceName("clat4");
-        clat4.addLinkAddress(LINKADDRV4);
-
-        assertEquals(0, rmnet0.getStackedLinks().size());
-        assertEquals(1, rmnet0.getAddresses().size());
-        assertEquals(1, rmnet0.getLinkAddresses().size());
-        assertEquals(1, rmnet0.getAllAddresses().size());
-        assertEquals(1, rmnet0.getAllLinkAddresses().size());
-
-        rmnet0.addStackedLink(clat4);
-        assertEquals(1, rmnet0.getStackedLinks().size());
-        assertEquals(1, rmnet0.getAddresses().size());
-        assertEquals(1, rmnet0.getLinkAddresses().size());
-        assertEquals(2, rmnet0.getAllAddresses().size());
-        assertEquals(2, rmnet0.getAllLinkAddresses().size());
-
-        rmnet0.addStackedLink(clat4);
-        assertEquals(1, rmnet0.getStackedLinks().size());
-        assertEquals(1, rmnet0.getAddresses().size());
-        assertEquals(1, rmnet0.getLinkAddresses().size());
-        assertEquals(2, rmnet0.getAllAddresses().size());
-        assertEquals(2, rmnet0.getAllLinkAddresses().size());
-
-        assertEquals(0, clat4.getStackedLinks().size());
-
-        // Modify an item in the returned collection to see what happens.
-        for (LinkProperties link : rmnet0.getStackedLinks()) {
-            if (link.getInterfaceName().equals("clat4")) {
-               link.setInterfaceName("newname");
-            }
-        }
-        for (LinkProperties link : rmnet0.getStackedLinks()) {
-            assertFalse("newname".equals(link.getInterfaceName()));
-        }
-
-        assertTrue(rmnet0.removeStackedLink("clat4"));
-        assertEquals(0, rmnet0.getStackedLinks().size());
-        assertEquals(1, rmnet0.getAddresses().size());
-        assertEquals(1, rmnet0.getLinkAddresses().size());
-        assertEquals(1, rmnet0.getAllAddresses().size());
-        assertEquals(1, rmnet0.getAllLinkAddresses().size());
-
-        assertFalse(rmnet0.removeStackedLink("clat4"));
-    }
-
-    private LinkAddress getFirstLinkAddress(LinkProperties lp) {
-        return lp.getLinkAddresses().iterator().next();
-    }
-
-    @SmallTest
-    public void testAddressMethods() {
-        LinkProperties lp = new LinkProperties();
-
-        // No addresses.
-        assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasGlobalIPv6Address());
-
-        // Addresses on stacked links don't count.
-        LinkProperties stacked = new LinkProperties();
-        stacked.setInterfaceName("stacked");
-        lp.addStackedLink(stacked);
-        stacked.addLinkAddress(LINKADDRV4);
-        stacked.addLinkAddress(LINKADDRV6);
-        assertTrue(stacked.hasIPv4Address());
-        assertTrue(stacked.hasGlobalIPv6Address());
-        assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasGlobalIPv6Address());
-        lp.removeStackedLink("stacked");
-        assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.hasGlobalIPv6Address());
-
-        // Addresses on the base link.
-        // Check the return values of hasIPvXAddress and ensure the add/remove methods return true
-        // iff something changes.
-        assertEquals(0, lp.getLinkAddresses().size());
-        assertTrue(lp.addLinkAddress(LINKADDRV6));
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertFalse(lp.hasIPv4Address());
-        assertTrue(lp.hasGlobalIPv6Address());
-
-        assertTrue(lp.removeLinkAddress(LINKADDRV6));
-        assertEquals(0, lp.getLinkAddresses().size());
-
-        assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL));
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertFalse(lp.hasGlobalIPv6Address());
-
-        assertTrue(lp.addLinkAddress(LINKADDRV4));
-        assertEquals(2, lp.getLinkAddresses().size());
-        assertTrue(lp.hasIPv4Address());
-        assertFalse(lp.hasGlobalIPv6Address());
-
-        assertTrue(lp.addLinkAddress(LINKADDRV6));
-        assertEquals(3, lp.getLinkAddresses().size());
-        assertTrue(lp.hasIPv4Address());
-        assertTrue(lp.hasGlobalIPv6Address());
-
-        assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL));
-        assertEquals(2, lp.getLinkAddresses().size());
-        assertTrue(lp.hasIPv4Address());
-        assertTrue(lp.hasGlobalIPv6Address());
-
-        // Adding an address twice has no effect.
-        // Removing an address that's not present has no effect.
-        assertFalse(lp.addLinkAddress(LINKADDRV4));
-        assertEquals(2, lp.getLinkAddresses().size());
-        assertTrue(lp.hasIPv4Address());
-        assertTrue(lp.removeLinkAddress(LINKADDRV4));
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertFalse(lp.hasIPv4Address());
-        assertFalse(lp.removeLinkAddress(LINKADDRV4));
-        assertEquals(1, lp.getLinkAddresses().size());
-
-        // Adding an address that's already present but with different properties causes the
-        // existing address to be updated and returns true.
-        // Start with only LINKADDRV6.
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertEquals(LINKADDRV6, getFirstLinkAddress(lp));
-
-        // Create a LinkAddress object for the same address, but with different flags.
-        LinkAddress deprecated = new LinkAddress(ADDRV6, 128,
-                OsConstants.IFA_F_DEPRECATED, OsConstants.RT_SCOPE_UNIVERSE);
-        assertTrue(deprecated.isSameAddressAs(LINKADDRV6));
-        assertFalse(deprecated.equals(LINKADDRV6));
-
-        // Check that adding it updates the existing address instead of adding a new one.
-        assertTrue(lp.addLinkAddress(deprecated));
-        assertEquals(1, lp.getLinkAddresses().size());
-        assertEquals(deprecated, getFirstLinkAddress(lp));
-        assertFalse(LINKADDRV6.equals(getFirstLinkAddress(lp)));
-
-        // Removing LINKADDRV6 removes deprecated, because removing addresses ignores properties.
-        assertTrue(lp.removeLinkAddress(LINKADDRV6));
-        assertEquals(0, lp.getLinkAddresses().size());
-    }
-
-    @SmallTest
-    public void testSetLinkAddresses() {
-        LinkProperties lp = new LinkProperties();
-        lp.addLinkAddress(LINKADDRV4);
-        lp.addLinkAddress(LINKADDRV6);
-
-        LinkProperties lp2 = new LinkProperties();
-        lp2.addLinkAddress(LINKADDRV6);
-
-        assertFalse(lp.equals(lp2));
-
-        lp2.setLinkAddresses(lp.getLinkAddresses());
-        assertTrue(lp.equals(lp));
-    }
-
-    @SmallTest
-    public void testIsProvisioned() {
-        LinkProperties lp4 = new LinkProperties();
-        assertFalse("v4only:empty", lp4.isProvisioned());
-        lp4.addLinkAddress(LINKADDRV4);
-        assertFalse("v4only:addr-only", lp4.isProvisioned());
-        lp4.addDnsServer(DNS1);
-        assertFalse("v4only:addr+dns", lp4.isProvisioned());
-        lp4.addRoute(new RouteInfo(GATEWAY1));
-        assertTrue("v4only:addr+dns+route", lp4.isProvisioned());
-        assertTrue("v4only:addr+dns+route", lp4.isIPv4Provisioned());
-        assertFalse("v4only:addr+dns+route", lp4.isIPv6Provisioned());
-
-        LinkProperties lp6 = new LinkProperties();
-        assertFalse("v6only:empty", lp6.isProvisioned());
-        lp6.addLinkAddress(LINKADDRV6LINKLOCAL);
-        assertFalse("v6only:fe80-only", lp6.isProvisioned());
-        lp6.addDnsServer(DNS6);
-        assertFalse("v6only:fe80+dns", lp6.isProvisioned());
-        lp6.addRoute(new RouteInfo(GATEWAY61));
-        assertFalse("v6only:fe80+dns+route", lp6.isProvisioned());
-        lp6.addLinkAddress(LINKADDRV6);
-        assertTrue("v6only:fe80+global+dns+route", lp6.isIPv6Provisioned());
-        assertTrue("v6only:fe80+global+dns+route", lp6.isProvisioned());
-        lp6.removeLinkAddress(LINKADDRV6LINKLOCAL);
-        assertFalse("v6only:global+dns+route", lp6.isIPv4Provisioned());
-        assertTrue("v6only:global+dns+route", lp6.isIPv6Provisioned());
-        assertTrue("v6only:global+dns+route", lp6.isProvisioned());
-
-        LinkProperties lp46 = new LinkProperties();
-        lp46.addLinkAddress(LINKADDRV4);
-        lp46.addLinkAddress(LINKADDRV6);
-        lp46.addDnsServer(DNS1);
-        lp46.addDnsServer(DNS6);
-        assertFalse("dualstack:missing-routes", lp46.isProvisioned());
-        lp46.addRoute(new RouteInfo(GATEWAY1));
-        assertTrue("dualstack:v4-provisioned", lp46.isIPv4Provisioned());
-        assertFalse("dualstack:v4-provisioned", lp46.isIPv6Provisioned());
-        assertTrue("dualstack:v4-provisioned", lp46.isProvisioned());
-        lp46.addRoute(new RouteInfo(GATEWAY61));
-        assertTrue("dualstack:both-provisioned", lp46.isIPv4Provisioned());
-        assertTrue("dualstack:both-provisioned", lp46.isIPv6Provisioned());
-        assertTrue("dualstack:both-provisioned", lp46.isProvisioned());
-
-        // A link with an IPv6 address and default route, but IPv4 DNS server.
-        LinkProperties mixed = new LinkProperties();
-        mixed.addLinkAddress(LINKADDRV6);
-        mixed.addDnsServer(DNS1);
-        mixed.addRoute(new RouteInfo(GATEWAY61));
-        assertFalse("mixed:addr6+route6+dns4", mixed.isIPv4Provisioned());
-        assertFalse("mixed:addr6+route6+dns4", mixed.isIPv6Provisioned());
-        assertFalse("mixed:addr6+route6+dns4", mixed.isProvisioned());
-    }
-
-    @SmallTest
-    public void testCompareProvisioning() {
-        LinkProperties v4lp = new LinkProperties();
-        v4lp.addLinkAddress(LINKADDRV4);
-        v4lp.addRoute(new RouteInfo(GATEWAY1));
-        v4lp.addDnsServer(DNS1);
-        assertTrue(v4lp.isProvisioned());
-
-        LinkProperties v4r = new LinkProperties(v4lp);
-        v4r.removeDnsServer(DNS1);
-        assertFalse(v4r.isProvisioned());
-
-        assertEquals(ProvisioningChange.STILL_NOT_PROVISIONED,
-                LinkProperties.compareProvisioning(v4r, v4r));
-        assertEquals(ProvisioningChange.LOST_PROVISIONING,
-                LinkProperties.compareProvisioning(v4lp, v4r));
-        assertEquals(ProvisioningChange.GAINED_PROVISIONING,
-                LinkProperties.compareProvisioning(v4r, v4lp));
-        assertEquals(ProvisioningChange.STILL_PROVISIONED,
-                LinkProperties.compareProvisioning(v4lp, v4lp));
-
-        // Check that losing IPv4 provisioning on a dualstack network is
-        // seen as a total loss of provisioning.
-        LinkProperties v6lp = new LinkProperties();
-        v6lp.addLinkAddress(LINKADDRV6);
-        v6lp.addRoute(new RouteInfo(GATEWAY61));
-        v6lp.addDnsServer(DNS6);
-        assertFalse(v6lp.isIPv4Provisioned());
-        assertTrue(v6lp.isIPv6Provisioned());
-        assertTrue(v6lp.isProvisioned());
-
-        LinkProperties v46lp = new LinkProperties(v6lp);
-        v46lp.addLinkAddress(LINKADDRV4);
-        v46lp.addRoute(new RouteInfo(GATEWAY1));
-        v46lp.addDnsServer(DNS1);
-        assertTrue(v46lp.isIPv4Provisioned());
-        assertTrue(v46lp.isIPv6Provisioned());
-        assertTrue(v46lp.isProvisioned());
-
-        assertEquals(ProvisioningChange.STILL_PROVISIONED,
-                LinkProperties.compareProvisioning(v4lp, v46lp));
-        assertEquals(ProvisioningChange.STILL_PROVISIONED,
-                LinkProperties.compareProvisioning(v6lp, v46lp));
-        assertEquals(ProvisioningChange.LOST_PROVISIONING,
-                LinkProperties.compareProvisioning(v46lp, v6lp));
-        assertEquals(ProvisioningChange.LOST_PROVISIONING,
-                LinkProperties.compareProvisioning(v46lp, v4lp));
-
-        // Check that losing and gaining a secondary router does not change
-        // the provisioning status.
-        LinkProperties v6lp2 = new LinkProperties(v6lp);
-        v6lp2.addRoute(new RouteInfo(GATEWAY62));
-        assertTrue(v6lp2.isProvisioned());
-
-        assertEquals(ProvisioningChange.STILL_PROVISIONED,
-                LinkProperties.compareProvisioning(v6lp2, v6lp));
-        assertEquals(ProvisioningChange.STILL_PROVISIONED,
-                LinkProperties.compareProvisioning(v6lp, v6lp2));
-    }
-
-    @SmallTest
-    @Suppress  // Failing.
-    public void testIsReachable() {
-        final LinkProperties v4lp = new LinkProperties();
-        assertFalse(v4lp.isReachable(DNS1));
-        assertFalse(v4lp.isReachable(DNS2));
-
-        // Add an on-link route, making the on-link DNS server reachable,
-        // but there is still no IPv4 address.
-        assertTrue(v4lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("75.208.0.0"), 16))));
-        assertFalse(v4lp.isReachable(DNS1));
-        assertFalse(v4lp.isReachable(DNS2));
-
-        // Adding an IPv4 address (right now, any IPv4 address) means we use
-        // the routes to compute likely reachability.
-        assertTrue(v4lp.addLinkAddress(new LinkAddress(ADDRV4, 16)));
-        assertTrue(v4lp.isReachable(DNS1));
-        assertFalse(v4lp.isReachable(DNS2));
-
-        // Adding a default route makes the off-link DNS server reachable.
-        assertTrue(v4lp.addRoute(new RouteInfo(GATEWAY1)));
-        assertTrue(v4lp.isReachable(DNS1));
-        assertTrue(v4lp.isReachable(DNS2));
-
-        final LinkProperties v6lp = new LinkProperties();
-        final InetAddress kLinkLocalDns = NetworkUtils.numericToInetAddress("fe80::6:1");
-        final InetAddress kLinkLocalDnsWithScope = NetworkUtils.numericToInetAddress("fe80::6:2%43");
-        final InetAddress kOnLinkDns = NetworkUtils.numericToInetAddress("2001:db8:85a3::53");
-        assertFalse(v6lp.isReachable(kLinkLocalDns));
-        assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
-        assertFalse(v6lp.isReachable(kOnLinkDns));
-        assertFalse(v6lp.isReachable(DNS6));
-
-        // Add a link-local route, making the link-local DNS servers reachable. Because
-        // we assume the presence of an IPv6 link-local address, link-local DNS servers
-        // are considered reachable, but only those with a non-zero scope identifier.
-        assertTrue(v6lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("fe80::"), 64))));
-        assertFalse(v6lp.isReachable(kLinkLocalDns));
-        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
-        assertFalse(v6lp.isReachable(kOnLinkDns));
-        assertFalse(v6lp.isReachable(DNS6));
-
-        // Add a link-local address--nothing changes.
-        assertTrue(v6lp.addLinkAddress(LINKADDRV6LINKLOCAL));
-        assertFalse(v6lp.isReachable(kLinkLocalDns));
-        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
-        assertFalse(v6lp.isReachable(kOnLinkDns));
-        assertFalse(v6lp.isReachable(DNS6));
-
-        // Add a global route on link, but no global address yet. DNS servers reachable
-        // via a route that doesn't require a gateway: give them the benefit of the
-        // doubt and hope the link-local source address suffices for communication.
-        assertTrue(v6lp.addRoute(new RouteInfo(
-                new IpPrefix(NetworkUtils.numericToInetAddress("2001:db8:85a3::"), 64))));
-        assertFalse(v6lp.isReachable(kLinkLocalDns));
-        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
-        assertTrue(v6lp.isReachable(kOnLinkDns));
-        assertFalse(v6lp.isReachable(DNS6));
-
-        // Add a global address; the on-link global address DNS server is (still)
-        // presumed reachable.
-        assertTrue(v6lp.addLinkAddress(new LinkAddress(ADDRV6, 64)));
-        assertFalse(v6lp.isReachable(kLinkLocalDns));
-        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
-        assertTrue(v6lp.isReachable(kOnLinkDns));
-        assertFalse(v6lp.isReachable(DNS6));
-
-        // Adding a default route makes the off-link DNS server reachable.
-        assertTrue(v6lp.addRoute(new RouteInfo(GATEWAY62)));
-        assertFalse(v6lp.isReachable(kLinkLocalDns));
-        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
-        assertTrue(v6lp.isReachable(kOnLinkDns));
-        assertTrue(v6lp.isReachable(DNS6));
-
-        // Check isReachable on stacked links. This requires that the source IP address be assigned
-        // on the interface returned by the route lookup.
-        LinkProperties stacked = new LinkProperties();
-
-        // Can't add a stacked link without an interface name.
-        stacked.setInterfaceName("v4-test0");
-        v6lp.addStackedLink(stacked);
-
-        InetAddress stackedAddress = Address("192.0.0.4");
-        LinkAddress stackedLinkAddress = new LinkAddress(stackedAddress, 32);
-        assertFalse(v6lp.isReachable(stackedAddress));
-        stacked.addLinkAddress(stackedLinkAddress);
-        assertFalse(v6lp.isReachable(stackedAddress));
-        stacked.addRoute(new RouteInfo(stackedLinkAddress));
-        assertTrue(stacked.isReachable(stackedAddress));
-        assertTrue(v6lp.isReachable(stackedAddress));
-
-        assertFalse(v6lp.isReachable(DNS1));
-        stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress));
-        assertTrue(v6lp.isReachable(DNS1));
-    }
-
-    @SmallTest
-    public void testLinkPropertiesEnsureDirectlyConnectedRoutes() {
-        // IPv4 case: no route added initially
-        LinkProperties rmnet0 = new LinkProperties();
-        rmnet0.setInterfaceName("rmnet0");
-        rmnet0.addLinkAddress(new LinkAddress("10.0.0.2/8"));
-        RouteInfo directRoute0 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
-                rmnet0.getInterfaceName());
-
-        // Since no routes is added explicitly, getAllRoutes() should return empty.
-        assertTrue(rmnet0.getAllRoutes().isEmpty());
-        rmnet0.ensureDirectlyConnectedRoutes();
-        // ensureDirectlyConnectedRoutes() should have added the missing local route.
-        assertEqualRoutes(Collections.singletonList(directRoute0), rmnet0.getAllRoutes());
-
-        // IPv4 case: both direct and default routes added initially
-        LinkProperties rmnet1 = new LinkProperties();
-        rmnet1.setInterfaceName("rmnet1");
-        rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
-        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
-                NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
-        RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
-                rmnet1.getInterfaceName());
-        rmnet1.addRoute(defaultRoute1);
-        rmnet1.addRoute(directRoute1);
-
-        // Check added routes
-        assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
-        // ensureDirectlyConnectedRoutes() shouldn't change the routes since direct connected
-        // route is already part of the configuration.
-        rmnet1.ensureDirectlyConnectedRoutes();
-        assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
-
-        // IPv6 case: only default routes added initially
-        LinkProperties rmnet2 = new LinkProperties();
-        rmnet2.setInterfaceName("rmnet2");
-        rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
-        rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
-        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
-                NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
-        RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
-                rmnet2.getInterfaceName());
-        RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
-                rmnet2.getInterfaceName());
-        rmnet2.addRoute(defaultRoute2);
-
-        assertEqualRoutes(Arrays.asList(defaultRoute2), rmnet2.getAllRoutes());
-        rmnet2.ensureDirectlyConnectedRoutes();
-        assertEqualRoutes(Arrays.asList(defaultRoute2, directRoute2, linkLocalRoute2),
-                rmnet2.getAllRoutes());
-
-        // Corner case: no interface name
-        LinkProperties rmnet3 = new LinkProperties();
-        rmnet3.addLinkAddress(new LinkAddress("192.168.0.2/24"));
-        RouteInfo directRoute3 = new RouteInfo(new IpPrefix("192.168.0.0/24"), null,
-                rmnet3.getInterfaceName());
-
-        assertTrue(rmnet3.getAllRoutes().isEmpty());
-        rmnet3.ensureDirectlyConnectedRoutes();
-        assertEqualRoutes(Collections.singletonList(directRoute3), rmnet3.getAllRoutes());
-
-    }
-
-    @SmallTest
-    public void testCompareResult() {
-        // Either adding or removing items
-        testCompareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
-                Arrays.asList(2, 3, 4), new ArrayList<>());
-        testCompareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4),
-                new ArrayList<>(), Arrays.asList(3, 4));
-
-
-        // adding and removing items at the same time
-        testCompareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(2, 3, 4, 5),
-                Arrays.asList(1), Arrays.asList(5));
-        testCompareResult(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6),
-                Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6));
-
-        // null cases
-        testCompareResult(Arrays.asList(1, 2, 3), null, Arrays.asList(1, 2, 3), new ArrayList<>());
-        testCompareResult(null, Arrays.asList(3, 2, 1), new ArrayList<>(), Arrays.asList(1, 2, 3));
-        testCompareResult(null, null, new ArrayList<>(), new ArrayList<>());
-    }
-
-    private void assertEqualRoutes(Collection<RouteInfo> expected, Collection<RouteInfo> actual) {
-        Set<RouteInfo> expectedSet = new ArraySet<>(expected);
-        Set<RouteInfo> actualSet = new ArraySet<>(actual);
-        // Duplicated entries in actual routes are considered failures
-        assertEquals(actual.size(), actualSet.size());
-
-        assertEquals(expectedSet, actualSet);
-    }
-
-    private <T> void testCompareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved,
-            List<T> expectAdded) {
-        CompareResult<T> result = new CompareResult<>(oldItems, newItems);
-        assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added));
-        assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
-    }
-}
diff --git a/core/tests/coretests/src/android/net/NetworkTest.java b/core/tests/coretests/src/android/net/NetworkTest.java
deleted file mode 100644
index 74b6d98..0000000
--- a/core/tests/coretests/src/android/net/NetworkTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2015 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.net;
-
-import static android.test.MoreAsserts.assertNotEqual;
-
-import android.net.LocalServerSocket;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.net.Network;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.Inet6Address;
-import java.net.SocketException;
-
-import junit.framework.TestCase;
-
-public class NetworkTest extends TestCase {
-    final Network mNetwork = new Network(99);
-
-    @SmallTest
-    public void testBindSocketOfInvalidFdThrows() throws Exception {
-
-        final FileDescriptor fd = new FileDescriptor();
-        assertFalse(fd.valid());
-
-        try {
-            mNetwork.bindSocket(fd);
-            fail("SocketException not thrown");
-        } catch (SocketException expected) {}
-    }
-
-    @SmallTest
-    public void testBindSocketOfNonSocketFdThrows() throws Exception {
-        final File devNull = new File("/dev/null");
-        assertTrue(devNull.canRead());
-
-        final FileInputStream fis = new FileInputStream(devNull);
-        assertTrue(null != fis.getFD());
-        assertTrue(fis.getFD().valid());
-
-        try {
-            mNetwork.bindSocket(fis.getFD());
-            fail("SocketException not thrown");
-        } catch (SocketException expected) {}
-    }
-
-    @SmallTest
-    public void testBindSocketOfConnectedDatagramSocketThrows() throws Exception {
-        final DatagramSocket mDgramSocket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY);
-        mDgramSocket.connect((InetAddress) Inet6Address.LOOPBACK, 53);
-        assertTrue(mDgramSocket.isConnected());
-
-        try {
-            mNetwork.bindSocket(mDgramSocket);
-            fail("SocketException not thrown");
-        } catch (SocketException expected) {}
-    }
-
-    @SmallTest
-    public void testBindSocketOfLocalSocketThrows() throws Exception {
-        final LocalSocket mLocalClient = new LocalSocket();
-        mLocalClient.bind(new LocalSocketAddress("testClient"));
-        assertTrue(mLocalClient.getFileDescriptor().valid());
-
-        try {
-            mNetwork.bindSocket(mLocalClient.getFileDescriptor());
-            fail("SocketException not thrown");
-        } catch (SocketException expected) {}
-
-        final LocalServerSocket mLocalServer = new LocalServerSocket("testServer");
-        mLocalClient.connect(mLocalServer.getLocalSocketAddress());
-        assertTrue(mLocalClient.isConnected());
-
-        try {
-            mNetwork.bindSocket(mLocalClient.getFileDescriptor());
-            fail("SocketException not thrown");
-        } catch (SocketException expected) {}
-    }
-
-    @SmallTest
-    public void testZeroIsObviousForDebugging() {
-        Network zero = new Network(0);
-        assertEquals(0, zero.hashCode());
-        assertEquals(0, zero.getNetworkHandle());
-        assertEquals("0", zero.toString());
-    }
-
-    @SmallTest
-    public void testGetNetworkHandle() {
-        Network one = new Network(1);
-        Network two = new Network(2);
-        Network three = new Network(3);
-
-        // None of the hashcodes are zero.
-        assertNotEqual(0, one.hashCode());
-        assertNotEqual(0, two.hashCode());
-        assertNotEqual(0, three.hashCode());
-
-        // All the hashcodes are distinct.
-        assertNotEqual(one.hashCode(), two.hashCode());
-        assertNotEqual(one.hashCode(), three.hashCode());
-        assertNotEqual(two.hashCode(), three.hashCode());
-
-        // None of the handles are zero.
-        assertNotEqual(0, one.getNetworkHandle());
-        assertNotEqual(0, two.getNetworkHandle());
-        assertNotEqual(0, three.getNetworkHandle());
-
-        // All the handles are distinct.
-        assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle());
-        assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle());
-        assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle());
-
-        // The handles are not equal to the hashcodes.
-        assertNotEqual(one.hashCode(), one.getNetworkHandle());
-        assertNotEqual(two.hashCode(), two.getNetworkHandle());
-        assertNotEqual(three.hashCode(), three.getNetworkHandle());
-
-        // Adjust as necessary to test an implementation's specific constants.
-        // When running with runtest, "adb logcat -s TestRunner" can be useful.
-        assertEquals(4311403230L, one.getNetworkHandle());
-        assertEquals(8606370526L, two.getNetworkHandle());
-        assertEquals(12901337822L, three.getNetworkHandle());
-    }
-}
diff --git a/core/tests/coretests/src/android/net/StaticIpConfigurationTest.java b/core/tests/coretests/src/android/net/StaticIpConfigurationTest.java
deleted file mode 100644
index 59f780f..0000000
--- a/core/tests/coretests/src/android/net/StaticIpConfigurationTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.RouteInfo;
-import android.net.StaticIpConfiguration;
-import android.os.Parcel;
-
-import java.net.InetAddress;
-import java.util.HashSet;
-
-import junit.framework.TestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import static org.junit.Assert.*;
-
-
-public class StaticIpConfigurationTest extends TestCase {
-
-    private static final String ADDRSTR = "192.0.2.2/25";
-    private static final LinkAddress ADDR = new LinkAddress(ADDRSTR);
-    private static final InetAddress GATEWAY = IpAddress("192.0.2.1");
-    private static final InetAddress OFFLINKGATEWAY = IpAddress("192.0.2.129");
-    private static final InetAddress DNS1 = IpAddress("8.8.8.8");
-    private static final InetAddress DNS2 = IpAddress("8.8.4.4");
-    private static final InetAddress DNS3 = IpAddress("4.2.2.2");
-    private static final String IFACE = "eth0";
-
-    private static InetAddress IpAddress(String addr) {
-        return InetAddress.parseNumericAddress(addr);
-    }
-
-    private void checkEmpty(StaticIpConfiguration s) {
-        assertNull(s.ipAddress);
-        assertNull(s.gateway);
-        assertNull(s.domains);
-        assertEquals(0, s.dnsServers.size());
-    }
-
-    private boolean isEqual(StaticIpConfiguration s1, StaticIpConfiguration s2) {
-        return s1.equals(s2);
-    }
-
-    private void assertEquals(StaticIpConfiguration s1, StaticIpConfiguration s2) {
-        assertTrue(isEqual(s1, s2));
-    }
-
-    private void assertNotEquals(StaticIpConfiguration s1, StaticIpConfiguration s2) {
-        assertFalse(isEqual(s1, s2));
-    }
-
-    private StaticIpConfiguration makeTestObject() {
-        StaticIpConfiguration s = new StaticIpConfiguration();
-        s.ipAddress = ADDR;
-        s.gateway = GATEWAY;
-        s.dnsServers.add(DNS1);
-        s.dnsServers.add(DNS2);
-        s.dnsServers.add(DNS3);
-        s.domains = "google.com";
-        return s;
-    }
-
-    @SmallTest
-    public void testConstructor() {
-        StaticIpConfiguration s = new StaticIpConfiguration();
-        checkEmpty(s);
-    }
-
-    @SmallTest
-    public void testCopyAndClear() {
-        StaticIpConfiguration empty = new StaticIpConfiguration((StaticIpConfiguration) null);
-        checkEmpty(empty);
-
-        StaticIpConfiguration s1 = makeTestObject();
-        StaticIpConfiguration s2 = new StaticIpConfiguration(s1);
-        assertEquals(s1, s2);
-        s2.clear();
-        assertEquals(empty, s2);
-    }
-
-    @SmallTest
-    public void testHashCodeAndEquals() {
-        HashSet<Integer> hashCodes = new HashSet();
-        hashCodes.add(0);
-
-        StaticIpConfiguration s = new StaticIpConfiguration();
-        // Check that this hash code is nonzero and different from all the ones seen so far.
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        s.ipAddress = ADDR;
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        s.gateway = GATEWAY;
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        s.dnsServers.add(DNS1);
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        s.dnsServers.add(DNS2);
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        s.dnsServers.add(DNS3);
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        s.domains = "example.com";
-        assertTrue(hashCodes.add(s.hashCode()));
-
-        assertFalse(s.equals(null));
-        assertEquals(s, s);
-
-        StaticIpConfiguration s2 = new StaticIpConfiguration(s);
-        assertEquals(s, s2);
-
-        s.ipAddress = new LinkAddress(DNS1, 32);
-        assertNotEquals(s, s2);
-
-        s2 = new StaticIpConfiguration(s);
-        s.domains = "foo";
-        assertNotEquals(s, s2);
-
-        s2 = new StaticIpConfiguration(s);
-        s.gateway = DNS2;
-        assertNotEquals(s, s2);
-
-        s2 = new StaticIpConfiguration(s);
-        s.dnsServers.add(DNS3);
-        assertNotEquals(s, s2);
-    }
-
-    @SmallTest
-    public void testToLinkProperties() {
-        LinkProperties expected = new LinkProperties();
-        expected.setInterfaceName(IFACE);
-
-        StaticIpConfiguration s = new StaticIpConfiguration();
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        final RouteInfo connectedRoute = new RouteInfo(new IpPrefix(ADDRSTR), null, IFACE);
-        s.ipAddress = ADDR;
-        expected.addLinkAddress(ADDR);
-        expected.addRoute(connectedRoute);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        s.gateway = GATEWAY;
-        RouteInfo defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), GATEWAY, IFACE);
-        expected.addRoute(defaultRoute);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        s.gateway = OFFLINKGATEWAY;
-        expected.removeRoute(defaultRoute);
-        defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), OFFLINKGATEWAY, IFACE);
-        expected.addRoute(defaultRoute);
-
-        RouteInfo gatewayRoute = new RouteInfo(new IpPrefix("192.0.2.129/32"), null, IFACE);
-        expected.addRoute(gatewayRoute);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        s.dnsServers.add(DNS1);
-        expected.addDnsServer(DNS1);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        s.dnsServers.add(DNS2);
-        s.dnsServers.add(DNS3);
-        expected.addDnsServer(DNS2);
-        expected.addDnsServer(DNS3);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        s.domains = "google.com";
-        expected.setDomains("google.com");
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        s.gateway = null;
-        expected.removeRoute(defaultRoute);
-        expected.removeRoute(gatewayRoute);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-
-        // Without knowing the IP address, we don't have a directly-connected route, so we can't
-        // tell if the gateway is off-link or not and we don't add a host route. This isn't a real
-        // configuration, but we should at least not crash.
-        s.gateway = OFFLINKGATEWAY;
-        s.ipAddress = null;
-        expected.removeLinkAddress(ADDR);
-        expected.removeRoute(connectedRoute);
-        expected.addRoute(defaultRoute);
-        assertEquals(expected, s.toLinkProperties(IFACE));
-    }
-
-    private StaticIpConfiguration passThroughParcel(StaticIpConfiguration s) {
-        Parcel p = Parcel.obtain();
-        StaticIpConfiguration s2 = null;
-        try {
-            s.writeToParcel(p, 0);
-            p.setDataPosition(0);
-            s2 = StaticIpConfiguration.CREATOR.createFromParcel(p);
-        } finally {
-            p.recycle();
-        }
-        assertNotNull(s2);
-        return s2;
-    }
-
-    @SmallTest
-    public void testParceling() {
-        StaticIpConfiguration s = makeTestObject();
-        StaticIpConfiguration s2 = passThroughParcel(s);
-        assertEquals(s, s2);
-    }
-}
-
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
new file mode 100644
index 0000000..6cdb35ab
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
+import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
+import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Tests for verifying the Binder Proxy Counting and Limiting.
+ *
+ * To manually build and install relevant test apps
+ *
+ * Build:
+ * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestApp
+ * mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestService
+ * Install:
+ * adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestApp/BinderProxyCountingTestApp.apk
+ * adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestService/BinderProxyCountingTestService.apk
+ *
+ * To run the tests, use
+ *
+ * Build: m FrameworksCoreTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
+ * Run: adb shell am instrument -e class android.os.BinderProxyCountingTest -w \
+ * com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
+ *
+ * or
+ *
+ * bit FrameworksCoreTests:android.os.BinderProxyCountingTest
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BinderProxyCountingTest {
+    private static final String TAG = BinderProxyCountingTest.class.getSimpleName();
+
+    private static final String TEST_APP_PKG =
+            "com.android.frameworks.coretests.binderproxycountingtestapp";
+    private static final String TEST_APP_CMD_SERVICE = TEST_APP_PKG + ".BpcTestAppCmdService";
+    private static final String TEST_SERVICE_PKG =
+            "com.android.frameworks.coretests.binderproxycountingtestservice";
+    private static final String TEST_SERVICE_CMD_SERVICE =
+            TEST_SERVICE_PKG + ".BpcTestServiceCmdService";
+
+    private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
+    private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2;
+
+    // Keep in sync with sBinderProxyCountLimit in BpBinder.cpp
+    private static final int BINDER_PROXY_LIMIT = 2500;
+
+    private static Context sContext;
+    private static UiDevice sUiDevice;
+
+    private static ServiceConnection sTestAppConnection;
+    private static ServiceConnection sTestServiceConnection;
+    private static IBpcTestAppCmdService sBpcTestAppCmdService;
+    private static IBpcTestServiceCmdService sBpcTestServiceCmdService;
+    private static final Intent sTestAppIntent = new Intent()
+            .setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_CMD_SERVICE));
+    private static final Intent sTestServiceIntent = new Intent()
+            .setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CMD_SERVICE));
+    private static final Consumer<IBinder> sTestAppConsumer = (service) -> {
+        sBpcTestAppCmdService = IBpcTestAppCmdService.Stub.asInterface(service);
+    };
+    private static final Consumer<IBinder> sTestServiceConsumer = (service) -> {
+        sBpcTestServiceCmdService = IBpcTestServiceCmdService.Stub.asInterface(service);
+    };
+    private static int sTestPkgUid;
+
+    /**
+     * Setup any common data for the upcoming tests.
+     */
+    @BeforeClass
+    public static void setUpOnce() throws Exception {
+        sContext = InstrumentationRegistry.getContext();
+        sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0);
+        ((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid,
+                "Wiping Test Package");
+
+        sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    private ServiceConnection bindService(final Consumer<IBinder> consumer, Intent intent)
+            throws Exception {
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+        ServiceConnection connection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                Log.i(TAG, "Service connected");
+                consumer.accept(service);
+                bindLatch.countDown();
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                Log.i(TAG, "Service disconnected");
+            }
+        };
+        sContext.bindService(intent, connection,
+                Context.BIND_AUTO_CREATE
+                        | Context.BIND_ALLOW_OOM_MANAGEMENT
+                        | Context.BIND_NOT_FOREGROUND);
+        if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+            fail("Timed out waiting for the service to bind in " + sTestPkgUid);
+        }
+        return connection;
+    }
+
+
+    private void unbindService(ServiceConnection service) {
+        if (service != null) {
+            sContext.unbindService(service);
+        }
+    }
+
+    private void bindTestAppToTestService() throws Exception {
+        if (sBpcTestAppCmdService != null) {
+            String errorMessage = sBpcTestAppCmdService.bindToTestService();
+            if (errorMessage != null) {
+                fail(errorMessage);
+            }
+        }
+    }
+
+    private void unbindTestAppFromTestService() throws Exception {
+        if (sBpcTestAppCmdService != null) {
+            sBpcTestAppCmdService.unbindFromTestService();
+        }
+    }
+
+    private CountDownLatch createBinderLimitLatch() throws RemoteException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        sBpcTestServiceCmdService.setBinderProxyCountCallback(
+                new IBpcCallbackObserver.Stub() {
+                    @Override
+                    public void onCallback(int uid) {
+                        if (uid == sTestPkgUid) {
+                            latch.countDown();
+                        }
+                    }
+                });
+        return latch;
+    }
+
+    /**
+     * Get the Binder Proxy count held by SYSTEM for a given uid
+     */
+    private int getSystemBinderCount(int uid) throws Exception {
+        return Integer.parseInt(sUiDevice.executeShellCommand(
+                "dumpsys activity binder-proxies " + uid).trim());
+    }
+
+    @Test
+    public void testBinderProxyCount() throws Exception {
+        // Arbitrary list of Binder create and release
+        // Should cumulatively equal 0 and must never add up past the binder limit at any point
+        int[] testValues = {223, -103, -13, 25, 90, -222};
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            // Get the baseline of binders naturally held by the test Package
+            int expectedBinderCount = getSystemBinderCount(sTestPkgUid);
+
+            for (int testValue : testValues) {
+                if (testValue > 0) {
+                    sBpcTestAppCmdService.createSystemBinders(testValue);
+                } else {
+                    sBpcTestAppCmdService.releaseSystemBinders(-testValue);
+                }
+                expectedBinderCount += testValue;
+                int currentBinderCount = getSystemBinderCount(sTestPkgUid);
+                assertEquals("Current Binder Count (" + currentBinderCount
+                        + ") does not equal expected Binder Count (" + expectedBinderCount
+                        + ")", expectedBinderCount, currentBinderCount);
+            }
+        } finally {
+            unbindService(sTestAppConnection);
+        }
+    }
+
+    @Test
+    public void testBinderProxyLimitBoundary() throws Exception {
+        final int binderProxyLimit = 2000;
+        final int rearmThreshold = 1800;
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+            bindTestAppToTestService();
+            sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+            sBpcTestServiceCmdService.forceGc();
+            // Get the baseline of binders naturally held by the test Package
+            int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+
+            final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+
+            // Create Binder Proxies up to the limit
+            sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount);
+            if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+                        + " when proxy limit should not have been reached");
+            }
+
+            // Create one more Binder to cross the limit
+            sBpcTestAppCmdService.createTestBinders(1);
+            if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+            }
+
+            sBpcTestAppCmdService.releaseAllBinders();
+        } finally {
+            unbindTestAppFromTestService();
+            unbindService(sTestAppConnection);
+            unbindService(sTestServiceConnection);
+        }
+    }
+
+    @Test
+    public void testSetBinderProxyLimit() throws Exception {
+        int[] testLimits = {1000, 222, 800};
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+            bindTestAppToTestService();
+            sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+            sBpcTestServiceCmdService.forceGc();
+            int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            for (int testLimit : testLimits) {
+                final CountDownLatch binderLimitLatch = createBinderLimitLatch();
+                // Change the BinderProxyLimit
+                sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10);
+                // Exceed the new Binder Proxy Limit
+                sBpcTestAppCmdService.createTestBinders(testLimit + 1);
+                if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                    fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+                }
+
+                sBpcTestAppCmdService.releaseTestBinders(testLimit + 1);
+                sBpcTestServiceCmdService.forceGc();
+            }
+        } finally {
+            unbindTestAppFromTestService();
+            unbindService(sTestAppConnection);
+            unbindService(sTestServiceConnection);
+        }
+    }
+
+    @Test
+    public void testRearmCallbackThreshold() throws Exception {
+        final int binderProxyLimit = 2000;
+        final int exceedBinderProxyLimit = binderProxyLimit + 10;
+        final int rearmThreshold = 1800;
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
+            bindTestAppToTestService();
+            sBpcTestServiceCmdService.enableBinderProxyLimit(true);
+
+            sBpcTestServiceCmdService.forceGc();
+            final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch();
+            sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
+            // Exceed the Binder Proxy Limit
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit);
+            if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+            }
+
+            sBpcTestServiceCmdService.forceGc();
+            int currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Drop to the threshold, this should not rearm the callback
+            sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold);
+
+            sBpcTestServiceCmdService.forceGc();
+            currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+
+            final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch();
+            // Exceed the Binder Proxy limit which should not cause a callback since there has
+            // been no rearm
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
+            if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+                        + " when the callback has not been rearmed yet");
+            }
+
+            sBpcTestServiceCmdService.forceGc();
+            currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Drop below the rearmThreshold to rearm the BinderProxyLimitCallback
+            sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold + 1);
+
+            sBpcTestServiceCmdService.forceGc();
+            currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
+            // Exceed the Binder Proxy limit for the last time
+            sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
+
+            if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
+            }
+            sBpcTestAppCmdService.releaseTestBinders(currentBinderCount);
+        } finally {
+            unbindTestAppFromTestService();
+            unbindService(sTestAppConnection);
+            unbindService(sTestServiceConnection);
+        }
+    }
+
+    @Test
+    public void testKillBadBehavingApp() throws Exception {
+        final CountDownLatch binderDeathLatch = new CountDownLatch(1);
+        final int exceedBinderProxyLimit = BINDER_PROXY_LIMIT + 1;
+
+        try {
+            sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
+            sBpcTestAppCmdService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    Log.v(TAG, "BpcTestAppCmdService died!");
+                    binderDeathLatch.countDown();
+                }
+            }, 0);
+            try {
+                // Exceed the Binder Proxy Limit emulating a bad behaving app
+                sBpcTestAppCmdService.createSystemBinders(exceedBinderProxyLimit);
+            } catch (DeadObjectException doe) {
+                // We are expecting the service to get killed mid call, so a DeadObjectException
+                // is not unexpected
+            }
+
+            if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+                sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit);
+                fail("Timed out waiting for uid " + sTestPkgUid + " to die.");
+            }
+
+        } finally {
+            unbindService(sTestAppConnection);
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/os/storage/AsecTests.java b/core/tests/coretests/src/android/os/storage/AsecTests.java
deleted file mode 100644
index e9a810d..0000000
--- a/core/tests/coretests/src/android/os/storage/AsecTests.java
+++ /dev/null
@@ -1,756 +0,0 @@
-/*
- * Copyright (C) 2006 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.os.storage;
-
-import android.content.Context;
-import android.os.Environment;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-
-public class AsecTests extends AndroidTestCase {
-    private static final String SECURE_CONTAINER_PREFIX = "com.android.unittests.AsecTests.";
-    private static final boolean localLOGV = true;
-    public static final String TAG="AsecTests";
-
-    private static final String FS_FAT = "fat";
-    private static final String FS_EXT4 = "ext4";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
-        cleanupContainers();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
-        cleanupContainers();
-    }
-
-    private void cleanupContainers() throws RemoteException {
-        IStorageManager sm = getSm();
-        String[] containers = sm.getSecureContainerList();
-
-        for (int i = 0; i < containers.length; i++) {
-            if (containers[i].startsWith(SECURE_CONTAINER_PREFIX)) {
-                if (localLOGV)
-                    Log.i(TAG, "Cleaning: " + containers[i]);
-                sm.destroySecureContainer(containers[i], true);
-            }
-        }
-    }
-
-    private boolean containerExists(String localId) throws RemoteException {
-        IStorageManager sm = getSm();
-        String[] containers = sm.getSecureContainerList();
-        String fullId = SECURE_CONTAINER_PREFIX + localId;
-
-        for (int i = 0; i < containers.length; i++) {
-            if (containers[i].equals(fullId)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private int createContainer(String localId, int size, String key, String filesystem,
-            boolean isExternal) throws Exception {
-        assertTrue("Media should be mounted", isMediaMounted());
-        String fullId = SECURE_CONTAINER_PREFIX + localId;
-
-        IStorageManager sm = getSm();
-        return sm.createSecureContainer(fullId, size, filesystem, key, android.os.Process.myUid(),
-                isExternal);
-    }
-
-    private int mountContainer(String localId, String key) throws Exception {
-        assertTrue("Media should be mounted", isMediaMounted());
-        String fullId = SECURE_CONTAINER_PREFIX + localId;
-
-        IStorageManager sm = getSm();
-        return sm.mountSecureContainer(fullId, key, android.os.Process.myUid(), true);
-    }
-
-    private int renameContainer(String localId1, String localId2) throws Exception {
-        assertTrue("Media should be mounted", isMediaMounted());
-        String fullId1 = SECURE_CONTAINER_PREFIX + localId1;
-        String fullId2 = SECURE_CONTAINER_PREFIX + localId2;
-
-        IStorageManager sm = getSm();
-        return sm.renameSecureContainer(fullId1, fullId2);
-    }
-
-    private int unmountContainer(String localId, boolean force) throws Exception {
-        assertTrue("Media should be mounted", isMediaMounted());
-        String fullId = SECURE_CONTAINER_PREFIX + localId;
-
-        IStorageManager sm = getSm();
-        return sm.unmountSecureContainer(fullId, force);
-    }
-
-    private int destroyContainer(String localId, boolean force) throws Exception {
-        assertTrue("Media should be mounted", isMediaMounted());
-        String fullId = SECURE_CONTAINER_PREFIX + localId;
-
-        IStorageManager sm = getSm();
-        return sm.destroySecureContainer(fullId, force);
-    }
-
-    private boolean isContainerMounted(String localId) throws Exception {
-        assertTrue("Media should be mounted", isMediaMounted());
-        String fullId = SECURE_CONTAINER_PREFIX + localId;
-
-        IStorageManager sm = getSm();
-        return sm.isSecureContainerMounted(fullId);
-    }
-
-    private IStorageManager getSm() {
-        IBinder service = ServiceManager.getService("mount");
-        if (service != null) {
-            return IStorageManager.Stub.asInterface(service);
-        } else {
-            Log.e(TAG, "Can't get storagemanager service");
-        }
-        return null;
-    }
-
-    private boolean isMediaMounted() throws Exception {
-        String mPath = Environment.getExternalStorageDirectory().toString();
-        String state = getSm().getVolumeState(mPath);
-        return Environment.MEDIA_MOUNTED.equals(state);
-    }
-
-
-    /*
-     * CREATE
-     */
-
-    public void test_Fat_External_Create_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 4, "none", FS_FAT, true));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-    public void test_Ext4_External_Create_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 4, "none", FS_EXT4, true));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-    public void test_Fat_Internal_Create_Success() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 4, "none", FS_FAT, false));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-    public void test_Ext4_Internal_Create_Success() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 4, "none", FS_EXT4, false));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-
-    /*
-     * CREATE MIN SIZE
-     */
-
-    public void test_Fat_External_CreateMinSize_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 1, "none", FS_FAT, true));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-    public void test_Ext4_External_CreateMinSize_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 1, "none", FS_EXT4, true));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-    public void test_Fat_Internal_CreateMinSize_Success() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 1, "none", FS_FAT, false));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-    public void test_Ext4_Internal_CreateMinSize_Success() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateContainer", 1, "none", FS_EXT4, false));
-        assertTrue(containerExists("testCreateContainer"));
-    }
-
-
-    /*
-     * CREATE ZERO SIZE - FAIL CASE
-     */
-
-    public void test_Fat_External_CreateZeroSize_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateZeroContainer", 0, "none", FS_FAT, true));
-    }
-
-    public void test_Ext4_External_CreateZeroSize_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateZeroContainer", 0, "none", FS_EXT4, true));
-    }
-
-    public void test_Fat_Internal_CreateZeroSize_Failure() throws Exception {
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateZeroContainer", 0, "none", FS_FAT, false));
-    }
-
-    public void test_Ext4_Internal_CreateZeroSize_Failure() throws Exception {
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateZeroContainer", 0, "none", FS_EXT4, false));
-    }
-
-
-    /*
-     * CREATE DUPLICATE - FAIL CASE
-     */
-
-    public void test_Fat_External_CreateDuplicate_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateDupContainer", 4, "none", FS_FAT, true));
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateDupContainer", 4, "none", FS_FAT, true));
-    }
-
-    public void test_Ext4_External_CreateDuplicate_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateDupContainer", 4, "none", FS_EXT4, true));
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateDupContainer", 4, "none", FS_EXT4, true));
-    }
-
-    public void test_Fat_Internal_CreateDuplicate_Failure() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateDupContainer", 4, "none", FS_FAT, false));
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateDupContainer", 4, "none", FS_FAT, false));
-    }
-
-    public void test_Ext4_Internal_CreateDuplicate_Failure() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testCreateDupContainer", 4, "none", FS_EXT4, false));
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                createContainer("testCreateDupContainer", 4, "none", FS_EXT4, false));
-    }
-
-
-    /*
-     * DESTROY
-     */
-
-    public void test_Fat_External_Destroy_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testDestroyContainer", 4, "none", FS_FAT, true));
-        assertEquals(StorageResultCode.OperationSucceeded,
-                destroyContainer("testDestroyContainer", false));
-    }
-
-    public void test_Ext4_External_Destroy_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testDestroyContainer", 4, "none", FS_EXT4, true));
-        assertEquals(StorageResultCode.OperationSucceeded,
-                destroyContainer("testDestroyContainer", false));
-    }
-
-    public void test_Fat_Internal_Destroy_Success() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testDestroyContainer", 4, "none", FS_FAT, false));
-        assertEquals(StorageResultCode.OperationSucceeded,
-                destroyContainer("testDestroyContainer", false));
-    }
-
-    public void test_Ext4_Internal_Destroy_Success() throws Exception {
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testDestroyContainer", 4, "none", FS_EXT4, false));
-        assertEquals(StorageResultCode.OperationSucceeded,
-                destroyContainer("testDestroyContainer", false));
-    }
-
-
-    /*
-     * MOUNT
-     */
-
-    public void test_Fat_External_Mount() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testMountContainer", 4, "none", FS_FAT, true));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                unmountContainer("testMountContainer", false));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                mountContainer("testMountContainer", "none"));
-    }
-
-
-    /*
-     * MOUNT BAD KEY - FAIL CASE
-     */
-
-    public void test_Fat_External_MountBadKey_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testMountBadKey", 4, "00000000000000000000000000000000", FS_FAT,
-                        true));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                unmountContainer("testMountBadKey", false));
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                mountContainer("testMountContainer", "000000000000000000000000000000001"));
-
-        assertEquals(StorageResultCode.OperationFailedInternalError,
-                mountContainer("testMountContainer", "none"));
-    }
-
-
-    public void test_Fat_External_UnmountBusy_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        IStorageManager sm = getSm();
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testUnmountBusyContainer", 4, "none", FS_FAT, true));
-
-        String path = sm.getSecureContainerPath(SECURE_CONTAINER_PREFIX
-                + "testUnmountBusyContainer");
-
-        File f = new File(path, "reference");
-        FileOutputStream fos = new FileOutputStream(f);
-
-        assertEquals(StorageResultCode.OperationFailedStorageBusy,
-                unmountContainer("testUnmountBusyContainer", false));
-
-        fos.close();
-        assertEquals(StorageResultCode.OperationSucceeded,
-                unmountContainer("testUnmountBusyContainer", false));
-    }
-
-    public void test_Fat_External_DestroyBusy() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        IStorageManager sm = getSm();
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testDestroyBusyContainer", 4, "none", FS_FAT, true));
-
-        String path = sm.getSecureContainerPath(SECURE_CONTAINER_PREFIX
-                + "testDestroyBusyContainer");
-
-        File f = new File(path, "reference");
-        FileOutputStream fos = new FileOutputStream(f);
-
-        assertEquals(StorageResultCode.OperationFailedStorageBusy,
-                destroyContainer("testDestroyBusyContainer", false));
-
-        fos.close();
-        assertEquals(StorageResultCode.OperationSucceeded,
-                destroyContainer("testDestroyBusyContainer", false));
-    }
-
-    public void test_Fat_External_Rename_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testRenameContainer.1", 4, "none", FS_FAT, true));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                unmountContainer("testRenameContainer.1", false));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                renameContainer("testRenameContainer.1", "testRenameContainer.2"));
-
-        assertFalse(containerExists("testRenameContainer.1"));
-        assertTrue(containerExists("testRenameContainer.2"));
-    }
-
-    public void test_Fat_External_RenameSrcMounted_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testRenameContainer.1", 4, "none", FS_FAT, true));
-
-        assertEquals(StorageResultCode.OperationFailedStorageMounted,
-                renameContainer("testRenameContainer.1", "testRenameContainer.2"));
-    }
-
-    public void test_Fat_External_RenameDstMounted_Failure() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testRenameContainer.1", 4, "none", FS_FAT, true));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                unmountContainer("testRenameContainer.1", false));
-
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testRenameContainer.2", 4, "none", FS_FAT, true));
-
-        assertEquals(StorageResultCode.OperationFailedStorageMounted,
-                renameContainer("testRenameContainer.1", "testRenameContainer.2"));
-    }
-
-    public void test_Fat_External_Size_Success() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        IStorageManager sm = getSm();
-        assertEquals(StorageResultCode.OperationSucceeded,
-                createContainer("testContainerSize", 1, "none", FS_FAT, true));
-        String path = sm.getSecureContainerPath(SECURE_CONTAINER_PREFIX + "testContainerSize");
-
-        byte[] buf = new byte[4096];
-        File f = new File(path, "reference");
-        FileOutputStream fos = new FileOutputStream(f);
-        for (int i = 0; i < (1024 * 1024); i += buf.length) {
-            fos.write(buf);
-        }
-        fos.close();
-    }
-
-    public void testGetSecureContainerPath_NonExistPath_Failure() throws Exception {
-        IStorageManager sm = getSm();
-        assertNull("Getting the path for an invalid container should return null",
-                sm.getSecureContainerPath("jparks.broke.it"));
-    }
-
-    /*------------ Tests for unmounting volume ---*/
-    public final long MAX_WAIT_TIME=120*1000;
-    public final long WAIT_TIME_INCR=20*1000;
-
-    boolean getMediaState() throws Exception {
-        String mPath = Environment.getExternalStorageDirectory().toString();
-        String state = getSm().getVolumeState(mPath);
-        return Environment.MEDIA_MOUNTED.equals(state);
-    }
-
-    boolean mountMedia() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return true;
-        }
-
-        if (getMediaState()) {
-            return true;
-        }
-
-        String mPath = Environment.getExternalStorageDirectory().toString();
-        int ret = getSm().mountVolume(mPath);
-        return ret == StorageResultCode.OperationSucceeded;
-    }
-
-    class StorageListener extends StorageEventListener {
-        String oldState;
-        String newState;
-        String path;
-        private boolean doneFlag = false;
-
-        public void action() {
-            synchronized (this) {
-                doneFlag = true;
-                notifyAll();
-            }
-        }
-
-        public boolean isDone() {
-            return doneFlag;
-        }
-
-        @Override
-        public void onStorageStateChanged(String path, String oldState, String newState) {
-            if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState);
-            this.oldState = oldState;
-            this.newState = newState;
-            this.path = path;
-            action();
-        }
-    }
-
-    private void unmountMedia() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        if (!getMediaState()) {
-            return;
-        }
-
-        String path = Environment.getExternalStorageDirectory().toString();
-        StorageListener observer = new StorageListener();
-        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
-        sm.registerListener(observer);
-        try {
-            // Wait on observer
-            synchronized(observer) {
-                getSm().unmountVolume(path, false, false);
-                long waitTime = 0;
-                while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!observer.isDone()) {
-                    fail("Timed out waiting for packageInstalled callback");
-                }
-            }
-        } finally {
-            sm.unregisterListener(observer);
-        }
-    }
-
-    public void testUnmount() throws Exception {
-        boolean oldStatus = getMediaState();
-        Log.i(TAG, "oldStatus="+oldStatus);
-        try {
-            // Mount media firsts
-            if (!getMediaState()) {
-                mountMedia();
-            }
-            unmountMedia();
-        } finally {
-            // Restore old status
-            boolean currStatus = getMediaState();
-            if (oldStatus != currStatus) {
-                if (oldStatus) {
-                    // Mount media
-                    mountMedia();
-                } else {
-                    unmountMedia();
-                }
-            }
-        }
-    }
-
-    class MultipleStorageLis extends StorageListener {
-        int count = 0;
-        public void onStorageStateChanged(String path, String oldState, String newState) {
-            count++;
-            super.action();
-        }
-    }
-    /*
-     * This test invokes unmount multiple time and expects the call back
-     * to be invoked just once.
-     */
-    public void testUnmountMultiple() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean oldStatus = getMediaState();
-        StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
-        MultipleStorageLis observer = new MultipleStorageLis();
-        try {
-            // Mount media firsts
-            if (!getMediaState()) {
-                mountMedia();
-            }
-            String path = Environment.getExternalStorageDirectory().toString();
-            sm.registerListener(observer);
-            // Wait on observer
-            synchronized(observer) {
-                for (int i = 0; i < 5; i++) {
-                    getSm().unmountVolume(path, false, false);
-                }
-                long waitTime = 0;
-                while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if(!observer.isDone()) {
-                    fail("Timed out waiting for packageInstalled callback");
-                }
-            }
-            assertEquals(observer.count, 1);
-        } finally {
-            sm.unregisterListener(observer);
-            // Restore old status
-            boolean currStatus = getMediaState();
-            if (oldStatus != currStatus) {
-                if (oldStatus) {
-                    // Mount media
-                    mountMedia();
-                } else {
-                    unmountMedia();
-                }
-            }
-        }
-    }
-
-    class ShutdownObserver extends  IStorageShutdownObserver.Stub{
-        private boolean doneFlag = false;
-        int statusCode;
-
-        public void action() {
-            synchronized (this) {
-                doneFlag = true;
-                notifyAll();
-            }
-        }
-
-        public boolean isDone() {
-            return doneFlag;
-        }
-        public void onShutDownComplete(int statusCode) throws RemoteException {
-            this.statusCode = statusCode;
-            action();
-        }
-        
-    }
-
-    void invokeShutdown() throws Exception {
-        IStorageManager sm = getSm();
-        ShutdownObserver observer = new ShutdownObserver();
-        synchronized (observer) {
-            sm.shutdown(observer);
-        }
-    }
-
-    public void testShutdown() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean oldStatus = getMediaState();
-        try {
-            // Mount media firsts
-            if (!getMediaState()) {
-                mountMedia();
-            }
-            invokeShutdown();
-        } finally {
-            // Restore old status
-            boolean currStatus = getMediaState();
-            if (oldStatus != currStatus) {
-                if (oldStatus) {
-                    // Mount media
-                    mountMedia();
-                } else {
-                    unmountMedia();
-                }
-            }
-        }
-    }
-
-    /*
-     * This test invokes unmount multiple time and expects the call back
-     * to be invoked just once.
-     */
-    public void testShutdownMultiple() throws Exception {
-        if (Environment.isExternalStorageEmulated()) {
-            return;
-        }
-
-        boolean oldStatus = getMediaState();
-        try {
-            // Mount media firsts
-            if (!getMediaState()) {
-                mountMedia();
-            }
-            IStorageManager sm = getSm();
-            ShutdownObserver observer = new ShutdownObserver();
-            synchronized (observer) {
-                sm.shutdown(observer);
-                for (int i = 0; i < 4; i++) {
-                    sm.shutdown(null);
-                }
-            }
-        } finally {
-            // Restore old status
-            boolean currStatus = getMediaState();
-            if (oldStatus != currStatus) {
-                if (oldStatus) {
-                    // Mount media
-                    mountMedia();
-                } else {
-                    unmountMedia();
-                }
-            }
-        }
-    }
-
-}
diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java
index 90cb9a5..56629d4 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java
@@ -18,24 +18,21 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
-import android.os.Environment;
-import android.os.SystemClock;
 import android.test.InstrumentationTestCase;
 import android.util.Log;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.storage.OnObbStateChangeListener;
-import android.os.storage.StorageManager;
+
+import libcore.io.Streams;
 
 import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileReader;
-import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.StringReader;
 
 public class StorageManagerBaseTest extends InstrumentationTestCase {
@@ -219,46 +216,21 @@
     }
 
     /**
-     * Helper to copy a raw resource file to an actual specified file
-     *
-     * @param rawResId The raw resource ID of the OBB resource file
-     * @param outFile A File representing the file we want to copy the OBB to
-     * @throws NotFoundException If the resource file could not be found
-     */
-    private void copyRawToFile(int rawResId, File outFile) throws NotFoundException {
-        Resources res = mContext.getResources();
-        InputStream is = null;
-        try {
-            is = res.openRawResource(rawResId);
-        } catch (NotFoundException e) {
-            Log.i(LOG_TAG, "Failed to load resource with id: " + rawResId);
-            throw e;
-        }
-        FileUtils.setPermissions(outFile.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
-                | FileUtils.S_IRWXO, -1, -1);
-        assertTrue(FileUtils.copyToFile(is, outFile));
-        FileUtils.setPermissions(outFile.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
-                | FileUtils.S_IRWXO, -1, -1);
-    }
-
-    /**
      * Creates an OBB file (with the given name), into the app's standard files directory
      *
      * @param name The name of the OBB file we want to create/write to
      * @param rawResId The raw resource ID of the OBB file in the package
      * @return A {@link File} representing the file to write to
      */
-    protected File createObbFile(String name, int rawResId) {
-        File outFile = null;
-        try {
-            final File filesDir = mContext.getFilesDir();
-            outFile = new File(filesDir, name);
-            copyRawToFile(rawResId, outFile);
-        } catch (NotFoundException e) {
-            if (outFile != null) {
-                outFile.delete();
-            }
+    protected File createObbFile(String name, int rawResId) throws IOException, Resources.NotFoundException {
+        final File outFile = new File(mContext.getObbDir(), name);
+        outFile.delete();
+
+        try (InputStream in = mContext.getResources().openRawResource(rawResId);
+                OutputStream out = new FileOutputStream(outFile)) {
+            Streams.copy(in, out);
         }
+
         return outFile;
     }
 
diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
index fbba6ff..5f8bd03 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerIntegrationTest.java
@@ -16,61 +16,27 @@
 
 package android.os.storage;
 
-import android.content.Context;
-import android.os.Environment;
-import android.os.ProxyFileDescriptorCallback;
 import android.os.ParcelFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
 import android.system.ErrnoException;
-import android.system.Os;
-import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
 import com.android.frameworks.coretests.R;
-import com.android.internal.os.FuseAppLoop;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.util.concurrent.ThreadFactory;
-import java.io.File;
-import java.io.FileInputStream;
 
-import junit.framework.AssertionFailedError;
+import java.io.File;
+import java.util.concurrent.ThreadFactory;
 
 public class StorageManagerIntegrationTest extends StorageManagerBaseTest {
-
-    private static String LOG_TAG = "StorageManagerBaseTest.StorageManagerIntegrationTest";
-    protected File mFile = null;
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getContext();
-        mFile = null;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void tearDown() throws Exception {
-        if (mFile != null) {
-            mFile.delete();
-            mFile = null;
-        }
-        super.tearDown();
-    }
+    private static String LOG_TAG = "StorageManagerIntegrationTest";
 
     /**
      * Tests mounting a single OBB file and verifies its contents.
      */
     @LargeTest
-    public void testMountSingleObb() {
-        mFile = createObbFile(OBB_FILE_1, R.raw.obb_file1);
-        String filePath = mFile.getAbsolutePath();
+    public void testMountSingleObb() throws Exception {
+        final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1);
+        String filePath = file.getAbsolutePath();
         mountObb(filePath);
         verifyObb1Contents(filePath);
         unmountObb(filePath, DONT_FORCE);
@@ -80,7 +46,7 @@
      * Tests mounting several OBB files and verifies its contents.
      */
     @LargeTest
-    public void testMountMultipleObb() {
+    public void testMountMultipleObb() throws Exception {
         File file1 = null;
         File file2 = null;
         File file3 = null;
@@ -120,9 +86,9 @@
      * Tests mounting a single encrypted OBB file and verifies its contents.
      */
     @LargeTest
-    public void testMountSingleEncryptedObb() {
-        mFile = createObbFile(OBB_FILE_3_ENCRYPTED, R.raw.obb_enc_file100_orig3);
-        String filePath = mFile.getAbsolutePath();
+    public void testMountSingleEncryptedObb() throws Exception {
+        final File file = createObbFile(OBB_FILE_3_ENCRYPTED, R.raw.obb_enc_file100_orig3);
+        String filePath = file.getAbsolutePath();
         mountObb(filePath, OBB_FILE_3_PASSWORD, OnObbStateChangeListener.MOUNTED);
         verifyObb3Contents(filePath);
         unmountObb(filePath, DONT_FORCE);
@@ -132,19 +98,17 @@
      * Tests mounting a single encrypted OBB file using an invalid password.
      */
     @LargeTest
-    @Suppress  // Failing.
-    public void testMountSingleEncryptedObbInvalidPassword() {
-        mFile = createObbFile("bad password@$%#@^*(!&)", R.raw.obb_enc_file100_orig3);
-        String filePath = mFile.getAbsolutePath();
-        mountObb(filePath, OBB_FILE_3_PASSWORD, OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
-        unmountObb(filePath, DONT_FORCE);
+    public void testMountSingleEncryptedObbInvalidPassword() throws Exception {
+        final File file = createObbFile("bad password@$%#@^*(!&)", R.raw.obb_enc_file100_orig3);
+        String filePath = file.getAbsolutePath();
+        mountObb(filePath, OBB_FILE_1_PASSWORD, OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
     }
 
     /**
      * Tests simultaneously mounting 2 encrypted OBBs with different keys and verifies contents.
      */
     @LargeTest
-    public void testMountTwoEncryptedObb() {
+    public void testMountTwoEncryptedObb() throws Exception {
         File file3 = null;
         File file1 = null;
         try {
@@ -174,9 +138,9 @@
      * Tests that we can not force unmount when a file is currently open on the OBB.
      */
     @LargeTest
-    public void testUnmount_DontForce() {
-        mFile = createObbFile(OBB_FILE_1, R.raw.obb_file1);
-        String obbFilePath = mFile.getAbsolutePath();
+    public void testUnmount_DontForce() throws Exception {
+        final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1);
+        String obbFilePath = file.getAbsolutePath();
 
         MountingObbThread mountingThread = new MountingObbThread(obbFilePath,
                 OBB_FILE_1_CONTENTS_1);
@@ -218,9 +182,9 @@
      * Tests mounting a single OBB that isn't signed.
      */
     @LargeTest
-    public void testMountUnsignedObb() {
-        mFile = createObbFile(OBB_FILE_2_UNSIGNED, R.raw.obb_file2_nosign);
-        String filePath = mFile.getAbsolutePath();
+    public void testMountUnsignedObb() throws Exception {
+        final File file = createObbFile(OBB_FILE_2_UNSIGNED, R.raw.obb_file2_nosign);
+        String filePath = file.getAbsolutePath();
         mountObb(filePath, OBB_FILE_2_UNSIGNED, OnObbStateChangeListener.ERROR_INTERNAL);
     }
 
@@ -228,9 +192,9 @@
      * Tests mounting a single OBB that is signed with a different package.
      */
     @LargeTest
-    public void testMountBadPackageNameObb() {
-        mFile = createObbFile(OBB_FILE_3_BAD_PACKAGENAME, R.raw.obb_file3_bad_packagename);
-        String filePath = mFile.getAbsolutePath();
+    public void testMountBadPackageNameObb() throws Exception {
+        final File file = createObbFile(OBB_FILE_3_BAD_PACKAGENAME, R.raw.obb_file3_bad_packagename);
+        String filePath = file.getAbsolutePath();
         mountObb(filePath, OBB_FILE_3_BAD_PACKAGENAME,
                 OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
     }
@@ -239,9 +203,9 @@
      * Tests remounting a single OBB that has already been mounted.
      */
     @LargeTest
-    public void testRemountObb() {
-        mFile = createObbFile(OBB_FILE_1, R.raw.obb_file1);
-        String filePath = mFile.getAbsolutePath();
+    public void testRemountObb() throws Exception {
+        final File file = createObbFile(OBB_FILE_1, R.raw.obb_file1);
+        String filePath = file.getAbsolutePath();
         mountObb(filePath);
         verifyObb1Contents(filePath);
         mountObb(filePath, null, OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
@@ -291,4 +255,4 @@
             return thread;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/print/BasePrintTest.java b/core/tests/coretests/src/android/print/BasePrintTest.java
deleted file mode 100644
index a70c604..0000000
--- a/core/tests/coretests/src/android/print/BasePrintTest.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2016 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.print;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.print.mockservice.PrintServiceCallbacks;
-import android.print.mockservice.PrinterDiscoverySessionCallbacks;
-import android.print.mockservice.StubbablePrinterDiscoverySession;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.uiautomator.UiDevice;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.mockito.stubbing.Answer;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This is the base class for print tests.
- */
-abstract class BasePrintTest {
-    protected static final long OPERATION_TIMEOUT = 30000;
-    private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
-    private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
-
-    private android.print.PrintJob mPrintJob;
-
-    private CallCounter mStartCallCounter;
-    private CallCounter mStartSessionCallCounter;
-
-    private static Instrumentation sInstrumentation;
-    private static UiDevice sUiDevice;
-
-    @Rule
-    public ActivityTestRule<PrintTestActivity> mActivityRule =
-            new ActivityTestRule<>(PrintTestActivity.class, false, true);
-
-    /**
-     * {@link Runnable} that can throw and {@link Exception}
-     */
-    interface Invokable {
-        /**
-         * Execute the invokable
-         *
-         * @throws Exception
-         */
-        void run() throws Exception;
-    }
-
-    /**
-     * Assert that the invokable throws an expectedException
-     *
-     * @param invokable The {@link Invokable} to run
-     * @param expectedClass The {@link Exception} that is supposed to be thrown
-     */
-    void assertException(Invokable invokable, Class<? extends Exception> expectedClass)
-            throws Exception {
-        try {
-            invokable.run();
-        } catch (Exception e) {
-            if (e.getClass().isAssignableFrom(expectedClass)) {
-                return;
-            } else {
-                throw e;
-            }
-        }
-
-        throw new AssertionError("No exception thrown");
-    }
-
-    /**
-     * Return the UI device
-     *
-     * @return the UI device
-     */
-    public UiDevice getUiDevice() {
-        return sUiDevice;
-    }
-
-    protected static Instrumentation getInstrumentation() {
-        return sInstrumentation;
-    }
-
-    @BeforeClass
-    public static void setUpClass() throws Exception {
-        sInstrumentation = InstrumentationRegistry.getInstrumentation();
-        assumeTrue(sInstrumentation.getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_PRINTING));
-
-        sUiDevice = UiDevice.getInstance(sInstrumentation);
-
-        // Make sure we start with a clean slate.
-        clearPrintSpoolerData();
-
-        // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
-        // Dexmaker is used by mockito.
-        System.setProperty("dexmaker.dexcache", getInstrumentation()
-                .getTargetContext().getCacheDir().getPath());
-    }
-
-    @Before
-    public void initCounters() throws Exception {
-        // Initialize the latches.
-        mStartCallCounter = new CallCounter();
-        mStartSessionCallCounter = new CallCounter();
-    }
-
-    @Before
-    public void unlockScreen() throws Exception {
-        // Unlock screen.
-        runShellCommand(getInstrumentation(), "input keyevent KEYCODE_WAKEUP");
-        runShellCommand(getInstrumentation(), "wm dismiss-keyguard");
-    }
-
-    @After
-    public void exitActivities() throws Exception {
-        // Exit print spooler
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
-    }
-
-    protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
-            final PrintAttributes attributes) {
-        // Initiate printing as if coming from the app.
-        getInstrumentation().runOnMainSync(() -> {
-            PrintManager printManager = (PrintManager) getActivity()
-                    .getSystemService(Context.PRINT_SERVICE);
-            mPrintJob = printManager.print("Print job", adapter, attributes);
-        });
-
-        return mPrintJob;
-    }
-
-    protected void onStartCalled() {
-        mStartCallCounter.call();
-    }
-
-    protected void onPrinterDiscoverySessionStartCalled() {
-        mStartSessionCallCounter.call();
-    }
-
-    protected void waitForPrinterDiscoverySessionStartCallbackCalled() {
-        waitForCallbackCallCount(mStartSessionCallCounter, 1,
-                "Did not get expected call to onStartPrinterDiscoverySession.");
-    }
-
-    protected void waitForStartAdapterCallbackCalled() {
-        waitForCallbackCallCount(mStartCallCounter, 1, "Did not get expected call to start.");
-    }
-
-    private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
-        try {
-            counter.waitForCount(count, OPERATION_TIMEOUT);
-        } catch (TimeoutException te) {
-            fail(message);
-        }
-    }
-
-    protected PrintTestActivity getActivity() {
-        return mActivityRule.getActivity();
-    }
-
-    public static String runShellCommand(Instrumentation instrumentation, String cmd)
-            throws IOException {
-        ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd);
-        byte[] buf = new byte[512];
-        int bytesRead;
-        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-        StringBuilder stdout = new StringBuilder();
-        while ((bytesRead = fis.read(buf)) != -1) {
-            stdout.append(new String(buf, 0, bytesRead));
-        }
-        fis.close();
-        return stdout.toString();
-    }
-
-    protected static void clearPrintSpoolerData() throws Exception {
-        assertTrue("failed to clear print spooler data",
-                runShellCommand(getInstrumentation(), String.format(
-                        "pm clear --user %d %s", CURRENT_USER_ID,
-                        PrintManager.PRINT_SPOOLER_PACKAGE_NAME))
-                        .contains(PM_CLEAR_SUCCESS_OUTPUT));
-    }
-
-    @SuppressWarnings("unchecked")
-    protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
-            Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
-            Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
-            Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
-            Answer<Void> onDestroy) {
-        PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
-
-        doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
-        when(callbacks.getSession()).thenCallRealMethod();
-
-        if (onStartPrinterDiscovery != null) {
-            doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
-                    any(List.class));
-        }
-        if (onStopPrinterDiscovery != null) {
-            doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
-        }
-        if (onValidatePrinters != null) {
-            doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
-                    any(List.class));
-        }
-        if (onStartPrinterStateTracking != null) {
-            doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
-                    any(PrinterId.class));
-        }
-        if (onRequestCustomPrinterIcon != null) {
-            doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
-                    any(PrinterId.class), any(CancellationSignal.class),
-                    any(CustomPrinterIconCallback.class));
-        }
-        if (onStopPrinterStateTracking != null) {
-            doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
-                    any(PrinterId.class));
-        }
-        if (onDestroy != null) {
-            doAnswer(onDestroy).when(callbacks).onDestroy();
-        }
-
-        return callbacks;
-    }
-
-    protected PrintServiceCallbacks createMockPrintServiceCallbacks(
-            Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
-            Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
-        final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
-
-        doCallRealMethod().when(service).setService(any(PrintService.class));
-        when(service.getService()).thenCallRealMethod();
-
-        if (onCreatePrinterDiscoverySessionCallbacks != null) {
-            doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
-                    .onCreatePrinterDiscoverySessionCallbacks();
-        }
-        if (onPrintJobQueued != null) {
-            doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
-        }
-        if (onRequestCancelPrintJob != null) {
-            doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
-                    any(PrintJob.class));
-        }
-
-        return service;
-    }
-
-    private static final class CallCounter {
-        private final Object mLock = new Object();
-
-        private int mCallCount;
-
-        public void call() {
-            synchronized (mLock) {
-                mCallCount++;
-                mLock.notifyAll();
-            }
-        }
-
-        int getCallCount() {
-            synchronized (mLock) {
-                return mCallCount;
-            }
-        }
-
-        public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
-            synchronized (mLock) {
-                final long startTimeMillis = SystemClock.uptimeMillis();
-                while (mCallCount < count) {
-                    try {
-                        final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
-                        final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
-                        if (remainingTimeMillis <= 0) {
-                            throw new TimeoutException();
-                        }
-                        mLock.wait(timeoutMillis);
-                    } catch (InterruptedException ie) {
-                        /* ignore */
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
index 45e3f67..5d12f7e 100644
--- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
+++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java
@@ -16,6 +16,8 @@
 
 package android.print;
 
+import static android.print.test.Utils.assertException;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -33,10 +35,11 @@
 import android.print.PrintAttributes.Margins;
 import android.print.PrintAttributes.MediaSize;
 import android.print.PrintAttributes.Resolution;
-import android.print.mockservice.MockPrintService;
-import android.print.mockservice.PrintServiceCallbacks;
-import android.print.mockservice.PrinterDiscoverySessionCallbacks;
-import android.print.mockservice.StubbablePrinterDiscoverySession;
+import android.print.test.BasePrintTest;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrintServiceCallbacks;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.printservice.recommendation.IRecommendationsChangeListener;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
@@ -149,7 +152,7 @@
 
                         session.addPrinters(printers);
                     }
-                    onPrinterDiscoverySessionStartCalled();
+                    onPrinterDiscoverySessionCreateCalled();
                     return null;
                 }, null, null, null, null, null, null),
                 null, null);
@@ -200,13 +203,18 @@
     }
 
     private void startPrinting() {
-        mGoodPrintJob = print(createMockAdapter(), null);
+        mGoodPrintJob = print(createMockAdapter(), (PrintAttributes) null);
 
         // Wait for PrintActivity to be ready
-        waitForStartAdapterCallbackCalled();
+        waitForAdapterStartCallbackCalled();
 
         // Wait for printer discovery session to be ready
-        waitForPrinterDiscoverySessionStartCallbackCalled();
+        waitForPrinterDiscoverySessionCreateCallbackCalled();
+    }
+
+    private void endPrinting() {
+        getUiDevice().pressBack();
+        getUiDevice().pressBack();
     }
 
     /**
@@ -220,7 +228,7 @@
 
     @Before
     public void setUpMockService() throws Exception {
-        MockPrintService.setCallbacks(createMockCallbacks());
+        FirstPrintService.setCallbacks(createMockCallbacks());
 
         mIPrintManager = IPrintManager.Stub
                 .asInterface(ServiceManager.getService(Context.PRINT_SERVICE));
@@ -231,7 +239,7 @@
      */
     @LargeTest
     @Test
-    public void testGetPrintJobInfo() throws Exception {
+    public void testGetPrintJobInfo() throws Throwable {
         startPrinting();
 
         assertEquals(mGoodPrintJob.getId(), mIPrintManager.getPrintJobInfo(mGoodPrintJob.getId(),
@@ -244,6 +252,8 @@
                 SecurityException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -251,7 +261,7 @@
      */
     @LargeTest
     @Test
-    public void testGetPrintJobInfos() throws Exception {
+    public void testGetPrintJobInfos() throws Throwable {
         startPrinting();
 
         List<PrintJobInfo> infos = mIPrintManager.getPrintJobInfos(mAppId, mUserId);
@@ -269,6 +279,8 @@
                 SecurityException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -276,7 +288,7 @@
      */
     @LargeTest
     @Test
-    public void testPrint() throws Exception {
+    public void testPrint() throws Throwable {
         final String name = "dummy print job";
 
         final IPrintDocumentAdapter adapter = new PrintManager
@@ -303,6 +315,8 @@
                 getActivity().getPackageName(), BAD_APP_ID, mUserId), SecurityException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -310,7 +324,7 @@
      */
     @LargeTest
     @Test
-    public void testCancelPrintJob() throws Exception {
+    public void testCancelPrintJob() throws Throwable {
         startPrinting();
 
         // Invalid print jobs IDs do not produce an exception
@@ -325,6 +339,8 @@
 
         // Must be last as otherwise mGoodPrintJob will not be good anymore
         mIPrintManager.cancelPrintJob(mGoodPrintJob.getId(), mAppId, mUserId);
+
+        endPrinting();
     }
 
     /**
@@ -332,7 +348,7 @@
      */
     @LargeTest
     @Test
-    public void testRestartPrintJob() throws Exception {
+    public void testRestartPrintJob() throws Throwable {
         startPrinting();
 
         mIPrintManager.restartPrintJob(mGoodPrintJob.getId(), mAppId, mUserId);
@@ -346,6 +362,8 @@
                 SecurityException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -353,7 +371,8 @@
      */
     @MediumTest
     @Test
-    public void testAddPrintJobStateChangeListener() throws Exception {
+    @NoActivity
+    public void testAddPrintJobStateChangeListener() throws Throwable {
         final IPrintJobStateChangeListener listener = createMockIPrintJobStateChangeListener();
 
         mIPrintManager.addPrintJobStateChangeListener(listener, mAppId, mUserId);
@@ -373,7 +392,8 @@
      */
     @MediumTest
     @Test
-    public void testRemovePrintJobStateChangeListener() throws Exception {
+    @NoActivity
+    public void testRemovePrintJobStateChangeListener() throws Throwable {
         final IPrintJobStateChangeListener listener = createMockIPrintJobStateChangeListener();
 
         mIPrintManager.addPrintJobStateChangeListener(listener, mAppId, mUserId);
@@ -394,7 +414,8 @@
      */
     @MediumTest
     @Test
-    public void testAddPrintServicesChangeListener() throws Exception {
+    @NoActivity
+    public void testAddPrintServicesChangeListener() throws Throwable {
         final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener();
 
         assertException(() ->  mIPrintManager.addPrintServicesChangeListener(listener, mUserId),
@@ -411,7 +432,8 @@
      */
     @MediumTest
     @Test
-    public void testRemovePrintServicesChangeListener() throws Exception {
+    @NoActivity
+    public void testRemovePrintServicesChangeListener() throws Throwable {
         final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener();
 
         assertException(() ->  mIPrintManager.removePrintServicesChangeListener(listener, mUserId),
@@ -426,7 +448,8 @@
      */
     @MediumTest
     @Test
-    public void testGetPrintServices() throws Exception {
+    @NoActivity
+    public void testGetPrintServices() throws Throwable {
         assertException(() -> mIPrintManager.getPrintServices(PrintManager.ALL_SERVICES, mUserId),
                 SecurityException.class);
 
@@ -444,7 +467,8 @@
      */
     @MediumTest
     @Test
-    public void testSetPrintServiceEnabled() throws Exception {
+    @NoActivity
+    public void testSetPrintServiceEnabled() throws Throwable {
         assertException(
                 () -> mIPrintManager.setPrintServiceEnabled(new ComponentName("bad", "name"), true,
                                 mUserId), SecurityException.class);
@@ -460,7 +484,8 @@
      */
     @MediumTest
     @Test
-    public void testAddPrintServiceRecommendationsChangeListener() throws Exception {
+    @NoActivity
+    public void testAddPrintServiceRecommendationsChangeListener() throws Throwable {
         final IRecommendationsChangeListener listener =
                 createMockIPrintServiceRecommendationsChangeListener();
 
@@ -479,7 +504,8 @@
      */
     @MediumTest
     @Test
-    public void testRemovePrintServiceRecommendationsChangeListener() throws Exception {
+    @NoActivity
+    public void testRemovePrintServiceRecommendationsChangeListener() throws Throwable {
         final IRecommendationsChangeListener listener =
                 createMockIPrintServiceRecommendationsChangeListener();
 
@@ -498,7 +524,8 @@
      */
     @MediumTest
     @Test
-    public void testGetPrintServiceRecommendations() throws Exception {
+    @NoActivity
+    public void testGetPrintServiceRecommendations() throws Throwable {
         assertException(() -> mIPrintManager.getPrintServiceRecommendations(mUserId),
                 SecurityException.class);
 
@@ -510,7 +537,8 @@
      */
     @MediumTest
     @Test
-    public void testCreatePrinterDiscoverySession() throws Exception {
+    @NoActivity
+    public void testCreatePrinterDiscoverySession() throws Throwable {
         final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
 
         mIPrintManager.createPrinterDiscoverySession(listener, mUserId);
@@ -533,7 +561,7 @@
      */
     @LargeTest
     @Test
-    public void testStartPrinterDiscovery() throws Exception {
+    public void testStartPrinterDiscovery() throws Throwable {
         startPrinting();
 
         final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
@@ -562,6 +590,8 @@
                 NullPointerException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -569,7 +599,8 @@
      */
     @MediumTest
     @Test
-    public void testStopPrinterDiscovery() throws Exception {
+    @NoActivity
+    public void testStopPrinterDiscovery() throws Throwable {
         final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
 
         mIPrintManager.startPrinterDiscovery(listener, null, mUserId);
@@ -590,7 +621,7 @@
      */
     @LargeTest
     @Test
-    public void testValidatePrinters() throws Exception {
+    public void testValidatePrinters() throws Throwable {
         startPrinting();
 
         final List<PrinterId> goodPrinters = new ArrayList<>();
@@ -617,6 +648,8 @@
                 NullPointerException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -624,7 +657,7 @@
      */
     @LargeTest
     @Test
-    public void testStartPrinterStateTracking() throws Exception {
+    public void testStartPrinterStateTracking() throws Throwable {
         startPrinting();
 
         mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId);
@@ -636,6 +669,8 @@
                 NullPointerException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -643,7 +678,7 @@
      */
     @LargeTest
     @Test
-    public void testGetCustomPrinterIcon() throws Exception {
+    public void testGetCustomPrinterIcon() throws Throwable {
         startPrinting();
 
         mIPrintManager.getCustomPrinterIcon(mGoodPrinterId, mUserId);
@@ -655,6 +690,8 @@
                 NullPointerException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -662,7 +699,7 @@
      */
     @LargeTest
     @Test
-    public void testStopPrinterStateTracking() throws Exception {
+    public void testStopPrinterStateTracking() throws Throwable {
         startPrinting();
 
         mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId);
@@ -679,6 +716,8 @@
                 NullPointerException.class);
 
         // Cannot test bad user Id as these tests are allowed to call across users
+
+        endPrinting();
     }
 
     /**
@@ -686,7 +725,8 @@
      */
     @MediumTest
     @Test
-    public void testDestroyPrinterDiscoverySession() throws Exception {
+    @NoActivity
+    public void testDestroyPrinterDiscoverySession() throws Throwable {
         final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver();
 
         mIPrintManager.createPrinterDiscoverySession(listener, mUserId);
diff --git a/core/tests/coretests/src/android/print/PrintTestActivity.java b/core/tests/coretests/src/android/print/PrintTestActivity.java
deleted file mode 100644
index e9b001f..0000000
--- a/core/tests/coretests/src/android/print/PrintTestActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 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.print;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class PrintTestActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-    }
-}
diff --git a/core/tests/coretests/src/android/print/mockservice/MockPrintService.java b/core/tests/coretests/src/android/print/mockservice/MockPrintService.java
deleted file mode 100644
index 9c11c22..0000000
--- a/core/tests/coretests/src/android/print/mockservice/MockPrintService.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 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.print.mockservice;
-
-public class MockPrintService extends StubbablePrintService {
-
-    private static final Object sLock = new Object();
-
-    private static PrintServiceCallbacks sCallbacks;
-
-    public static void setCallbacks(PrintServiceCallbacks callbacks) {
-        synchronized (sLock) {
-            sCallbacks = callbacks;
-        }
-    }
-
-    @Override
-    protected PrintServiceCallbacks getCallbacks() {
-        synchronized (sLock) {
-            if (sCallbacks != null) {
-                sCallbacks.setService(this);
-            }
-            return sCallbacks;
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/print/mockservice/PrintServiceCallbacks.java b/core/tests/coretests/src/android/print/mockservice/PrintServiceCallbacks.java
deleted file mode 100644
index 4e89207..0000000
--- a/core/tests/coretests/src/android/print/mockservice/PrintServiceCallbacks.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 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.print.mockservice;
-
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-
-public abstract class PrintServiceCallbacks {
-
-    private PrintService mService;
-
-    public PrintService getService() {
-        return mService;
-    }
-
-    public void setService(PrintService service) {
-        mService = service;
-    }
-
-    public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
-
-    public abstract void onRequestCancelPrintJob(PrintJob printJob);
-
-    public abstract void onPrintJobQueued(PrintJob printJob);
-}
diff --git a/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java b/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
deleted file mode 100644
index be002e2..0000000
--- a/core/tests/coretests/src/android/print/mockservice/PrinterDiscoverySessionCallbacks.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 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.print.mockservice;
-
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-
-import java.util.List;
-
-public abstract class PrinterDiscoverySessionCallbacks {
-
-    private StubbablePrinterDiscoverySession mSession;
-
-    public void setSession(StubbablePrinterDiscoverySession session) {
-        mSession = session;
-    }
-
-    public StubbablePrinterDiscoverySession getSession() {
-        return mSession;
-    }
-
-    public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
-
-    public abstract void onStopPrinterDiscovery();
-
-    public abstract void onValidatePrinters(List<PrinterId> printerIds);
-
-    public abstract void onStartPrinterStateTracking(PrinterId printerId);
-
-    public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
-            CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
-
-    public abstract void onStopPrinterStateTracking(PrinterId printerId);
-
-    public abstract void onDestroy();
-}
diff --git a/core/tests/coretests/src/android/print/mockservice/StubbablePrintService.java b/core/tests/coretests/src/android/print/mockservice/StubbablePrintService.java
deleted file mode 100644
index b58b2735..0000000
--- a/core/tests/coretests/src/android/print/mockservice/StubbablePrintService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 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.print.mockservice;
-
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-public abstract class StubbablePrintService extends PrintService {
-
-    @Override
-    public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            return new StubbablePrinterDiscoverySession(this,
-                    getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
-        }
-        return null;
-    }
-
-    @Override
-    public void onRequestCancelPrintJob(PrintJob printJob) {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            callbacks.onRequestCancelPrintJob(printJob);
-        }
-    }
-
-    @Override
-    public void onPrintJobQueued(PrintJob printJob) {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            callbacks.onPrintJobQueued(printJob);
-        }
-    }
-
-    protected abstract PrintServiceCallbacks getCallbacks();
-}
diff --git a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java b/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
deleted file mode 100644
index f3a5373..0000000
--- a/core/tests/coretests/src/android/print/mockservice/StubbablePrinterDiscoverySession.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 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.print.mockservice;
-
-import android.support.annotation.NonNull;
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-import java.util.List;
-
-public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
-    private final PrintService mService;
-    private final PrinterDiscoverySessionCallbacks mCallbacks;
-
-    public StubbablePrinterDiscoverySession(PrintService service,
-            PrinterDiscoverySessionCallbacks callbacks) {
-        mService = service;
-        mCallbacks = callbacks;
-        if (mCallbacks != null) {
-            mCallbacks.setSession(this);
-        }
-    }
-
-    public PrintService getService() {
-        return mService;
-    }
-
-    @Override
-    public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
-        if (mCallbacks != null) {
-            mCallbacks.onStartPrinterDiscovery(priorityList);
-        }
-    }
-
-    @Override
-    public void onStopPrinterDiscovery() {
-        if (mCallbacks != null) {
-            mCallbacks.onStopPrinterDiscovery();
-        }
-    }
-
-    @Override
-    public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
-        if (mCallbacks != null) {
-            mCallbacks.onValidatePrinters(printerIds);
-        }
-    }
-
-    @Override
-    public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
-        if (mCallbacks != null) {
-            mCallbacks.onStartPrinterStateTracking(printerId);
-        }
-    }
-
-    @Override
-    public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull CustomPrinterIconCallback callback) {
-        if (mCallbacks != null) {
-            mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
-        }
-    }
-
-    @Override
-    public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
-        if (mCallbacks != null) {
-            mCallbacks.onStopPrinterStateTracking(printerId);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mCallbacks != null) {
-            mCallbacks.onDestroy();
-        }
-    }
-}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8395638..ebe0527 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -38,7 +38,6 @@
 import java.util.Set;
 
 /** Tests that ensure appropriate settings are backed up. */
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SettingsBackupTest {
@@ -168,7 +167,6 @@
                     Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
                     Settings.Global.DEVICE_DEMO_MODE,
                     Settings.Global.DEVICE_IDLE_CONSTANTS,
-                    Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
                     Settings.Global.BATTERY_SAVER_CONSTANTS,
                     Settings.Global.DEFAULT_SM_DP_PLUS,
                     Settings.Global.DEVICE_NAME,
@@ -182,6 +180,7 @@
                     Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
                     Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
                     Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+                    Settings.Global.DNS_TLS_DISABLED,
                     Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
                     Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
                     Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
@@ -277,6 +276,7 @@
                     Settings.Global.NEW_CONTACT_AGGREGATOR,
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
                     Settings.Global.NSD_ON,
                     Settings.Global.NTP_SERVER,
                     Settings.Global.NTP_TIMEOUT,
@@ -342,6 +342,7 @@
                     Settings.Global.TETHER_DUN_REQUIRED,
                     Settings.Global.TETHER_OFFLOAD_DISABLED,
                     Settings.Global.TETHER_SUPPORTED,
+                    Settings.Global.TEXT_CLASSIFIER_CONSTANTS,
                     Settings.Global.THEATER_MODE_ON,
                     Settings.Global.TRANSITION_ANIMATION_SCALE,
                     Settings.Global.TRUSTED_SOUND,
@@ -463,7 +464,6 @@
                  Settings.Secure.NFC_PAYMENT_FOREGROUND,
                  Settings.Secure.NIGHT_DISPLAY_ACTIVATED,
                  Settings.Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
-                 Settings.Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME,
                  Settings.Secure.PACKAGE_VERIFIER_STATE,
                  Settings.Secure.PACKAGE_VERIFIER_USER_CONSENT,
                  Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java b/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
index c158b9f..ab541a1 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/MockSuggestionService.java
@@ -16,11 +16,23 @@
 
 package android.service.settings.suggestions;
 
+import android.support.annotation.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.List;
 
 public class MockSuggestionService extends SuggestionService {
 
+    @VisibleForTesting
+    static boolean sOnSuggestionLaunchedCalled;
+    @VisibleForTesting
+    static boolean sOnSuggestionDismissedCalled;
+
+    public static void reset() {
+        sOnSuggestionLaunchedCalled = false;
+        sOnSuggestionDismissedCalled = false;
+    }
+
     @Override
     public List<Suggestion> onGetSuggestions() {
         final List<Suggestion> data = new ArrayList<>();
@@ -34,5 +46,11 @@
 
     @Override
     public void onSuggestionDismissed(Suggestion suggestion) {
+        sOnSuggestionDismissedCalled = true;
+    }
+
+    @Override
+    public void onSuggestionLaunched(Suggestion suggestion) {
+        sOnSuggestionLaunchedCalled = true;
     }
 }
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
index bc88231..dca8c09 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionServiceTest.java
@@ -16,12 +16,17 @@
 
 package android.service.settings.suggestions;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ServiceTestRule;
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -45,9 +50,36 @@
                 MockSuggestionService.class);
     }
 
+    @After
+    public void tearUp() {
+        MockSuggestionService.reset();
+    }
+
     @Test
     public void canStartService() throws TimeoutException {
         mServiceTestRule.startService(mMockServiceIntent);
         // Do nothing after starting service.
     }
+
+    @Test
+    public void dismissSuggestion_shouldCallImplementation()
+            throws TimeoutException, RemoteException {
+        MockSuggestionService service = new MockSuggestionService();
+        IBinder binder = mServiceTestRule.bindService(mMockServiceIntent);
+        ISuggestionService serviceBinder = ISuggestionService.Stub.asInterface(binder);
+        serviceBinder.dismissSuggestion(null);
+
+        assertThat(service.sOnSuggestionDismissedCalled).isTrue();
+    }
+
+    @Test
+    public void launchSuggestion_shouldCallImplementation()
+            throws TimeoutException, RemoteException {
+        MockSuggestionService service = new MockSuggestionService();
+        IBinder binder = mServiceTestRule.bindService(mMockServiceIntent);
+        ISuggestionService serviceBinder = ISuggestionService.Stub.asInterface(binder);
+        serviceBinder.launchSuggestion(null);
+
+        assertThat(service.sOnSuggestionLaunchedCalled).isTrue();
+    }
 }
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
index 5548e48..b0ec55d 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
@@ -21,6 +21,8 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -36,6 +38,8 @@
     private static final String TEST_ID = "id";
     private static final String TEST_TITLE = "title";
     private static final String TEST_SUMMARY = "summary";
+
+    private Icon mIcon;
     private PendingIntent mTestIntent;
 
 
@@ -44,6 +48,7 @@
         final Context context = InstrumentationRegistry.getContext();
         mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
                 new Intent(), 0 /* flags */);
+        mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
     }
 
     @Test
@@ -51,12 +56,15 @@
         final Suggestion suggestion = new Suggestion.Builder(TEST_ID)
                 .setTitle(TEST_TITLE)
                 .setSummary(TEST_SUMMARY)
+                .setIcon(mIcon)
                 .setPendingIntent(mTestIntent)
                 .build();
 
         assertThat(suggestion.getId()).isEqualTo(TEST_ID);
         assertThat(suggestion.getTitle()).isEqualTo(TEST_TITLE);
         assertThat(suggestion.getSummary()).isEqualTo(TEST_SUMMARY);
+        assertThat(suggestion.getIcon()).isEqualTo(mIcon);
+        assertThat(suggestion.getFlags()).isEqualTo(0);
         assertThat(suggestion.getPendingIntent()).isEqualTo(mTestIntent);
     }
 
@@ -66,6 +74,7 @@
                 .setTitle(TEST_TITLE)
                 .setSummary(TEST_SUMMARY)
                 .setPendingIntent(mTestIntent)
+                .setIcon(mIcon)
                 .build();
     }
 
@@ -75,6 +84,8 @@
         final Suggestion oldSuggestion = new Suggestion.Builder(TEST_ID)
                 .setTitle(TEST_TITLE)
                 .setSummary(TEST_SUMMARY)
+                .setIcon(mIcon)
+                .setFlags(Suggestion.FLAG_HAS_BUTTON)
                 .setPendingIntent(mTestIntent)
                 .build();
 
@@ -85,6 +96,9 @@
         assertThat(newSuggestion.getId()).isEqualTo(TEST_ID);
         assertThat(newSuggestion.getTitle()).isEqualTo(TEST_TITLE);
         assertThat(newSuggestion.getSummary()).isEqualTo(TEST_SUMMARY);
+        assertThat(newSuggestion.getIcon().toString()).isEqualTo(mIcon.toString());
+        assertThat(newSuggestion.getFlags())
+                .isEqualTo(Suggestion.FLAG_HAS_BUTTON);
         assertThat(newSuggestion.getPendingIntent()).isEqualTo(mTestIntent);
     }
 }
diff --git a/core/tests/coretests/src/android/text/LineBreakingOverhangsTest.java b/core/tests/coretests/src/android/text/LineBreakingOverhangsTest.java
new file mode 100644
index 0000000..4f18b0b
--- /dev/null
+++ b/core/tests/coretests/src/android/text/LineBreakingOverhangsTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Typeface;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LineBreakingOverhangsTest {
+    private static final int EM = 100;  // Make 1em == 100px.
+    private static final TextPaint sTextPaint = new TextPaint();
+
+    static {
+        // The test font has following coverage and overhangs.
+        // All the characters have a width of 1em.
+        // space: no overhangs
+        // R: 4em overhang on the right
+        // a: no overhang
+        // y: 1.5em overhang on the left
+        sTextPaint.setTypeface(Typeface.createFromAsset(
+                  InstrumentationRegistry.getTargetContext().getAssets(),
+                  "fonts/LineBreakingOverhangsTestFont.ttf"));
+        sTextPaint.setTextSize(EM);
+    }
+
+    private static void layout(@NonNull CharSequence source, @NonNull int[] breaks, double width,
+            @Nullable int[] leftPadding, @Nullable int[] rightPadding) {
+        layout(source, breaks, width, leftPadding, rightPadding, null /* indents */);
+    }
+
+    private static void layout(@NonNull CharSequence source, @NonNull int[] breaks, double width,
+            @Nullable int[] leftPadding, @Nullable int[] rightPadding, @Nullable int[] indents) {
+        final StaticLayout staticLayout = StaticLayout.Builder
+                .obtain(source, 0, source.length(), sTextPaint, (int) width)
+                .setAvailablePaddings(leftPadding, rightPadding)
+                .setIndents(indents, indents)
+                .build();
+
+        final int lineCount = breaks.length + 1;
+        assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
+
+        for (int line = 0; line < lineCount; line++) {
+            final int lineStart = staticLayout.getLineStart(line);
+            final int lineEnd = staticLayout.getLineEnd(line);
+
+            if (line == 0) {
+                assertEquals("Line start for first line", 0, lineStart);
+            } else {
+                assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
+            }
+
+            if (line == lineCount - 1) {
+                assertEquals("Line end for last line", source.length(), lineEnd);
+            } else {
+                assertEquals("Line end for line " + line, breaks[line], lineEnd);
+            }
+        }
+    }
+
+    private static final int[] NO_BREAK = new int[] {};
+
+    private static final int[] NO_PADDING = null;
+    // Maximum needed for left side of 'y'.
+    private static final int[] FULL_LEFT_PADDING = new int[] {(int) (1.5 * EM)};
+    // Maximum padding needed for right side of 'R'.
+    private static final int[] FULL_RIGHT_PADDING = new int[] {4 * EM};
+
+    private static final int[] ONE_EM_PADDING = new int[] {1 * EM};
+    private static final int[] HALF_EM_PADDING = new int[] {(int) (0.5 * EM)};
+    private static final int[] QUARTER_EM_PADDING = new int[] {(int) (0.25 * EM)};
+
+    @Test
+    public void testRightOverhang() {
+        // The advance of "aaa R" is 5em, but the right-side overhang of 'R' would need 4em more, so
+        // we break the line if there's not enough overhang.
+
+        // Enough right padding, so the whole line fits in 5em.
+        layout("aaa R", NO_BREAK, 5 * EM, NO_PADDING, FULL_RIGHT_PADDING);
+
+        // No right padding, so we'd need 9em to fit the advance and the right padding of 'R'.
+        layout("aaa R", new int[] {4}, 8.9 * EM, NO_PADDING, NO_PADDING);
+        layout("aaa R", NO_BREAK, 9 * EM, NO_PADDING, NO_PADDING);
+
+        // 1em of right padding means we can fit the string in 8em.
+        layout("aaa R", new int[] {4}, 7.9 * EM, NO_PADDING, ONE_EM_PADDING);
+        layout("aaa R", NO_BREAK, 8 * EM, NO_PADDING, ONE_EM_PADDING);
+    }
+
+    @Test
+    public void testLeftOverhang() {
+        // The advance of "y a" is 3em, but the left-side overhang of 'y' would need 1.5em more, so
+        // we break the line if there's not enough overhang.
+
+        // Enough left padding, so the whole line fits in 3em.
+        layout("y a", NO_BREAK, 3 * EM, FULL_LEFT_PADDING, NO_PADDING);
+
+        // No right padding, so we'd need 4.5em to fit the advance and the left padding of 'y'.
+        layout("y a", new int[] {2}, 4.4 * EM, NO_PADDING, NO_PADDING);
+        layout("y a", NO_BREAK, 4.5 * EM, NO_PADDING, NO_PADDING);
+
+        // 1em of left padding means we can fit the string in 3.5em.
+        layout("y a", new int[] {2}, 3.4 * EM, ONE_EM_PADDING, NO_PADDING);
+        layout("y a", NO_BREAK, 3.5 * EM, ONE_EM_PADDING, NO_PADDING);
+    }
+
+    @Test
+    public void testBothSidesOverhang() {
+        // The advance of "y a R" is 5em, but the left-side overhang of 'y' would need 1.5em more,
+        // and the right side overhang or 'R' would need 4em more, so we break the line if there's
+        // not enough overhang.
+
+        // Enough padding, so the whole line fits in 5em.
+        layout("y a R", NO_BREAK, 5 * EM, FULL_LEFT_PADDING, FULL_RIGHT_PADDING);
+
+        // No padding, so we'd need 10.5em to fit the advance and the paddings.
+        layout("y a R", new int[] {4}, 10.4 * EM, NO_PADDING, NO_PADDING);
+        layout("y a R", NO_BREAK, 10.5 * EM, NO_PADDING, NO_PADDING);
+
+        // 1em of padding on each side means we can fit the string in 8.5em.
+        layout("y a R", new int[] {4}, 8.4 * EM, ONE_EM_PADDING, ONE_EM_PADDING);
+        layout("y a R", NO_BREAK, 8.5 * EM, ONE_EM_PADDING, ONE_EM_PADDING);
+    }
+
+    @Test
+    public void testIndentsDontAffectPaddings() {
+        // This is identical to the previous test, except that it applies wide indents of 4em on
+        // each side and thus needs an extra 8em of width. This test makes sure that indents and
+        // paddings are independent.
+        final int[] indents = new int[] {4 * EM};
+        final int indentAdj = 8 * EM;
+
+        // Enough padding, so the whole line fits in 5em.
+        layout("y a R", NO_BREAK, 5 * EM + indentAdj, FULL_LEFT_PADDING, FULL_RIGHT_PADDING,
+                indents);
+
+        // No padding, so we'd need 10.5em to fit the advance and the paddings.
+        layout("y a R", new int[] {4}, 10.4 * EM + indentAdj, NO_PADDING, NO_PADDING, indents);
+        layout("y a R", NO_BREAK, 10.5 * EM + indentAdj, NO_PADDING, NO_PADDING, indents);
+
+        // 1em of padding on each side means we can fit the string in 8.5em.
+        layout("y a R", new int[] {4}, 8.4 * EM + indentAdj, ONE_EM_PADDING, ONE_EM_PADDING,
+                indents);
+        layout("y a R", NO_BREAK, 8.5 * EM + indentAdj, ONE_EM_PADDING, ONE_EM_PADDING, indents);
+    }
+}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index d16cce8..8092203 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -96,7 +96,7 @@
         int n = chs.length;
         byte[] chInfo = new byte[n];
 
-        int resultDir = AndroidBidi.bidi(dir, chs, chInfo, n, false);
+        int resultDir = AndroidBidi.bidi(dir, chs, chInfo);
 
         {
             StringBuilder sb = new StringBuilder("info:");
diff --git a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java b/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
deleted file mode 100644
index 2ee855e..0000000
--- a/core/tests/coretests/src/android/text/StaticLayoutLineBreakingTest.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import static org.junit.Assert.assertEquals;
-
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.text.Layout.Alignment;
-import android.text.style.MetricAffectingSpan;
-import android.util.Log;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class StaticLayoutLineBreakingTest {
-    // Span test are currently not supported because text measurement uses the MeasuredText
-    // internal mWorkPaint instead of the provided MockTestPaint.
-    private static final boolean SPAN_TESTS_SUPPORTED = false;
-    private static final boolean DEBUG = false;
-
-    private static final float SPACE_MULTI = 1.0f;
-    private static final float SPACE_ADD = 0.0f;
-    private static final int WIDTH = 100;
-    private static final Alignment ALIGN = Alignment.ALIGN_LEFT;
-
-    private static final char SURR_FIRST = '\uD800';
-    private static final char SURR_SECOND = '\uDF31';
-
-    private static final int[] NO_BREAK = new int[] {};
-
-    private static final TextPaint sTextPaint = new MockTextPaint();
-
-    private static class MockTextPaint extends TextPaint {
-
-        @Override
-        public float getTextRunAdvances(char[] chars, int index, int count,
-                int contextIndex, int contextCount, boolean isRtl, float[] advances,
-                int advancesIndex) {
-
-            // Conditions copy pasted from Paint
-            if (chars == null) {
-                throw new IllegalArgumentException("text cannot be null");
-            }
-
-            if ((index | count | contextIndex | contextCount | advancesIndex
-                    | (index - contextIndex) | (contextCount - count)
-                    | ((contextIndex + contextCount) - (index + count))
-                    | (chars.length - (contextIndex + contextCount))
-                    | (advances == null ? 0 :
-                        (advances.length - (advancesIndex + count)))) < 0) {
-                throw new IndexOutOfBoundsException();
-            }
-
-            float res = 0.0f;
-
-            if (advances != null) {
-                for (int i = 0; i < count; i++) {
-                    float width = getCharWidth(chars[index + i]);
-                    advances[advancesIndex + i] = width;
-                    res += width;
-                }
-            }
-
-            return res;
-        }
-    }
-
-    private static float getCharWidth(char c) {
-        switch (Character.toUpperCase(c)) {
-            // Roman figures
-            case 'I': return 1.0f;
-            case 'V': return 5.0f;
-            case 'X': return 10.0f;
-            case 'L': return 50.0f;
-            case 'C': return 100.0f; // equals to WIDTH
-            case ' ': return 10.0f;
-            case '_': return 0.0f; // 0-width character
-            case SURR_FIRST: return 7.0f;
-            case SURR_SECOND: return 3.0f; // Sum of SURR_FIRST-SURR_SECOND is 10
-            default: return 10.0f;
-        }
-    }
-
-    private static StaticLayout getStaticLayout(CharSequence source, int width) {
-        return new StaticLayout(source, sTextPaint, width, ALIGN, SPACE_MULTI, SPACE_ADD, false);
-    }
-
-    private static int[] getBreaks(CharSequence source) {
-        return getBreaks(source, WIDTH);
-    }
-
-    private static int[] getBreaks(CharSequence source, int width) {
-        StaticLayout staticLayout = getStaticLayout(source, width);
-
-        int[] breaks = new int[staticLayout.getLineCount() - 1];
-        for (int line = 0; line < breaks.length; line++) {
-            breaks[line] = staticLayout.getLineEnd(line);
-        }
-        return breaks;
-    }
-
-    private static void debugLayout(CharSequence source, StaticLayout staticLayout) {
-        if (DEBUG) {
-            int count = staticLayout.getLineCount();
-            Log.i("SLLBTest", "\"" + source.toString() + "\": "
-                    + count + " lines");
-            for (int line = 0; line < count; line++) {
-                int lineStart = staticLayout.getLineStart(line);
-                int lineEnd = staticLayout.getLineEnd(line);
-                Log.i("SLLBTest", "Line " + line + " [" + lineStart + ".."
-                        + lineEnd + "]\t" + source.subSequence(lineStart, lineEnd));
-            }
-        }
-    }
-
-    private static void layout(CharSequence source, int[] breaks) {
-        layout(source, breaks, WIDTH);
-    }
-
-    private static void layout(CharSequence source, int[] breaks, int width) {
-        StaticLayout staticLayout = getStaticLayout(source, width);
-
-        debugLayout(source, staticLayout);
-
-        int lineCount = breaks.length + 1;
-        assertEquals("Number of lines", lineCount, staticLayout.getLineCount());
-
-        for (int line = 0; line < lineCount; line++) {
-            int lineStart = staticLayout.getLineStart(line);
-            int lineEnd = staticLayout.getLineEnd(line);
-
-            if (line == 0) {
-                assertEquals("Line start for first line", 0, lineStart);
-            } else {
-                assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
-            }
-
-            if (line == lineCount - 1) {
-                assertEquals("Line end for last line", source.length(), lineEnd);
-            } else {
-                assertEquals("Line end for line " + line, breaks[line], lineEnd);
-            }
-        }
-    }
-
-    private static void layoutMaxLines(CharSequence source, int[] breaks, int maxLines) {
-        StaticLayout staticLayout = new StaticLayout(source, 0, source.length(), sTextPaint, WIDTH,
-                ALIGN, TextDirectionHeuristics.LTR, SPACE_MULTI, SPACE_ADD, false /* includePad */,
-                null, WIDTH, maxLines);
-
-        debugLayout(source, staticLayout);
-
-        final int lineCount = staticLayout.getLineCount();
-
-        for (int line = 0; line < lineCount; line++) {
-            int lineStart = staticLayout.getLineStart(line);
-            int lineEnd = staticLayout.getLineEnd(line);
-
-            if (line == 0) {
-                assertEquals("Line start for first line", 0, lineStart);
-            } else {
-                assertEquals("Line start for line " + line, breaks[line - 1], lineStart);
-            }
-
-            if (line == lineCount - 1 && line != breaks.length - 1) {
-                assertEquals("Line end for last line", source.length(), lineEnd);
-            } else {
-                assertEquals("Line end for line " + line, breaks[line], lineEnd);
-            }
-        }
-    }
-
-    private static final int MAX_SPAN_COUNT = 10;
-    private static final int[] sSpanStarts = new int[MAX_SPAN_COUNT];
-    private static final int[] sSpanEnds = new int[MAX_SPAN_COUNT];
-
-    private static MetricAffectingSpan getMetricAffectingSpan() {
-        return new MetricAffectingSpan() {
-            @Override
-            public void updateDrawState(TextPaint tp) { /* empty */ }
-
-            @Override
-            public void updateMeasureState(TextPaint p) { /* empty */ }
-        };
-    }
-
-    /**
-     * Replaces the "<...>" blocks by spans, assuming non overlapping, correctly defined spans
-     * @param text
-     * @return A CharSequence with '<' '>' replaced by MetricAffectingSpan
-     */
-    private static CharSequence spanify(String text) {
-        int startIndex = text.indexOf('<');
-        if (startIndex < 0) return text;
-
-        int spanCount = 0;
-        do {
-            int endIndex = text.indexOf('>');
-            if (endIndex < 0) throw new IllegalArgumentException("Unbalanced span markers");
-
-            text = text.substring(0, startIndex) + text.substring(startIndex + 1, endIndex)
-                    + text.substring(endIndex + 1);
-
-            sSpanStarts[spanCount] = startIndex;
-            sSpanEnds[spanCount] = endIndex - 2;
-            spanCount++;
-
-            startIndex = text.indexOf('<');
-        } while (startIndex >= 0);
-
-        SpannableStringBuilder result = new SpannableStringBuilder(text);
-        for (int i = 0; i < spanCount; i++) {
-            result.setSpan(getMetricAffectingSpan(), sSpanStarts[i], sSpanEnds[i],
-                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-        }
-        return result;
-    }
-
-    @Test
-    public void testNoLineBreak() {
-        // Width lower than WIDTH
-        layout("", NO_BREAK);
-        layout("I", NO_BREAK);
-        layout("V", NO_BREAK);
-        layout("X", NO_BREAK);
-        layout("L", NO_BREAK);
-        layout("I VILI", NO_BREAK);
-        layout("XXXX", NO_BREAK);
-        layout("LXXXX", NO_BREAK);
-
-        // Width equal to WIDTH
-        layout("C", NO_BREAK);
-        layout("LL", NO_BREAK);
-        layout("L XXXX", NO_BREAK);
-        layout("XXXXXXXXXX", NO_BREAK);
-        layout("XXX XXXXXX", NO_BREAK);
-        layout("XXX XXXX X", NO_BREAK);
-        layout("XXX XXXXX ", NO_BREAK);
-        layout(" XXXXXXXX ", NO_BREAK);
-        layout("  XX  XXX ", NO_BREAK);
-        //      0123456789
-
-        // Width greater than WIDTH, but no break
-        layout("  XX  XXX  ", NO_BREAK);
-        layout("XX XXX XXX ", NO_BREAK);
-        layout("XX XXX XXX     ", NO_BREAK);
-        layout("XXXXXXXXXX     ", NO_BREAK);
-        //      01234567890
-    }
-
-    @Test
-    public void testOneLineBreak() {
-        //      01234567890
-        layout("XX XXX XXXX", new int[] {7});
-        layout("XX XXXX XXX", new int[] {8});
-        layout("XX XXXXX XX", new int[] {9});
-        layout("XX XXXXXX X", new int[] {10});
-        //      01234567890
-        layout("XXXXXXXXXXX", new int[] {10});
-        layout("XXXXXXXXX X", new int[] {10});
-        layout("XXXXXXXX XX", new int[] {9});
-        layout("XXXXXXX XXX", new int[] {8});
-        layout("XXXXXX XXXX", new int[] {7});
-        //      01234567890
-        layout("LL LL", new int[] {3});
-        layout("LLLL", new int[] {2});
-        layout("C C", new int[] {2});
-        layout("CC", new int[] {1});
-    }
-
-    @Test
-    public void testSpaceAtBreak() {
-        //      0123456789012
-        layout("XXXX XXXXX X", new int[] {11});
-        layout("XXXXXXXXXX X", new int[] {11});
-        layout("XXXXXXXXXV X", new int[] {11});
-        layout("C X", new int[] {2});
-    }
-
-    @Test
-    public void testMultipleSpacesAtBreak() {
-        //      0123456789012
-        layout("LXX XXXX", new int[] {4});
-        layout("LXX  XXXX", new int[] {5});
-        layout("LXX   XXXX", new int[] {6});
-        layout("LXX    XXXX", new int[] {7});
-        layout("LXX     XXXX", new int[] {8});
-    }
-
-    @Test
-    public void testZeroWidthCharacters() {
-        //      0123456789012345678901234
-        layout("X_X_X_X_X_X_X_X_X_X", NO_BREAK);
-        layout("___X_X_X_X_X_X_X_X_X_X___", NO_BREAK);
-        layout("C_X", new int[] {2});
-        layout("C__X", new int[] {3});
-    }
-
-    /**
-     * Note that when the text has spans, StaticLayout does not use the provided TextPaint to
-     * measure text runs anymore. This is probably a bug.
-     * To be able to use the fake sTextPaint and make this test pass, use mPaint instead of
-     * mWorkPaint in MeasuredText#addStyleRun
-     */
-    @Test
-    public void testWithSpans() {
-        if (!SPAN_TESTS_SUPPORTED) return;
-
-        layout(spanify("<012 456 89>"), NO_BREAK);
-        layout(spanify("012 <456> 89"), NO_BREAK);
-        layout(spanify("<012> <456>< 89>"), NO_BREAK);
-        layout(spanify("<012> <456> <89>"), NO_BREAK);
-
-        layout(spanify("<012> <456> <89>012"), new int[] {8});
-        layout(spanify("<012> <456> 89<012>"), new int[] {8});
-        layout(spanify("<012> <456> <89><012>"), new int[] {8});
-        layout(spanify("<012> <456> 89 <123>"), new int[] {11});
-        layout(spanify("<012> <456> 89< 123>"), new int[] {11});
-        layout(spanify("<012> <456> <89> <123>"), new int[] {11});
-        layout(spanify("012 456 89 <LXX> XX XX"), new int[] {11, 18});
-    }
-
-    /*
-     * Adding a span to the string should not change the layout, since the metrics are unchanged.
-     */
-    @Test
-    public void testWithOneSpan() {
-        if (!SPAN_TESTS_SUPPORTED) return;
-
-        String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
-                "012 456 89012 456 89012", "0123456789012" };
-
-        MetricAffectingSpan metricAffectingSpan = getMetricAffectingSpan();
-
-        for (String text : texts) {
-            // Get the line breaks without any span
-            int[] breaks = getBreaks(text);
-
-            // Add spans on all possible offsets
-            for (int spanStart = 0; spanStart < text.length(); spanStart++) {
-                for (int spanEnd = spanStart; spanEnd < text.length(); spanEnd++) {
-                    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-                    ssb.setSpan(metricAffectingSpan, spanStart, spanEnd,
-                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                    layout(ssb, breaks);
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testWithTwoSpans() {
-        if (!SPAN_TESTS_SUPPORTED) return;
-
-        String[] texts = new String[] { "0123", "012 456", "012 456 89 123", "012 45678 012",
-                "012 456 89012 456 89012", "0123456789012" };
-
-        MetricAffectingSpan metricAffectingSpan1 = getMetricAffectingSpan();
-        MetricAffectingSpan metricAffectingSpan2 = getMetricAffectingSpan();
-
-        for (String text : texts) {
-            // Get the line breaks without any span
-            int[] breaks = getBreaks(text);
-
-            // Add spans on all possible offsets
-            for (int spanStart1 = 0; spanStart1 < text.length(); spanStart1++) {
-                for (int spanEnd1 = spanStart1; spanEnd1 < text.length(); spanEnd1++) {
-                    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-                    ssb.setSpan(metricAffectingSpan1, spanStart1, spanEnd1,
-                            Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-
-                    for (int spanStart2 = 0; spanStart2 < text.length(); spanStart2++) {
-                        for (int spanEnd2 = spanStart2; spanEnd2 < text.length(); spanEnd2++) {
-                            ssb.setSpan(metricAffectingSpan2, spanStart2, spanEnd2,
-                                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
-                            layout(ssb, breaks);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    public static String replace(String string, char c, char r) {
-        return string.replaceAll(String.valueOf(c), String.valueOf(r));
-    }
-
-    @Test
-    public void testWithSurrogate() {
-        layout("LX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
-        layout("LXXXX" + SURR_FIRST + SURR_SECOND, NO_BREAK);
-        // LXXXXI (91) + SURR_FIRST (7) fits. But we should not break the surrogate pair
-        // Bug: surrogate pair is broken, should be 6 (breaking after the 'V')
-        // Maybe not: may be ok if the second character of a pair always has a 0-width
-        layout("LXXXXI" + SURR_FIRST + SURR_SECOND, new int[] {7});
-
-        // LXXXXI (95) + SURR_SECOND (3) fits, but this is not a valid surrogate pair, breaking it
-        layout("LXXXXV" + SURR_SECOND + SURR_FIRST, new int[] {7});
-
-        layout("C" + SURR_FIRST + SURR_SECOND, new int[] {1});
-    }
-
-    @Test
-    public void testNarrowWidth() {
-        int[] widths = new int[] { 0, 4, 10 };
-        String[] texts = new String[] { "", "X", " ", "XX", " X", "XXX" };
-
-        for (String text: texts) {
-            // 15 is such that only one character will fit
-            int[] breaks = getBreaks(text, 15);
-
-            // Width under 15 should all lead to the same line break
-            for (int width: widths) {
-                layout(text, breaks, width);
-            }
-        }
-    }
-
-    @Test
-    public void testNarrowWidthZeroWidth() {
-        int[] widths = new int[] { 1, 4 };
-        for (int width: widths) {
-            layout("X.", new int[] {1}, width);
-            layout("X__", NO_BREAK, width);
-            layout("X__X", new int[] {3}, width);
-            layout("X__X_", new int[] {3}, width);
-
-            layout("_", NO_BREAK, width);
-            layout("__", NO_BREAK, width);
-            layout("_X", new int[] {1}, width);
-            layout("_X_", new int[] {1}, width);
-            layout("__X__", new int[] {2}, width);
-        }
-    }
-
-    @Test
-    public void testMaxLines() {
-        layoutMaxLines("C", NO_BREAK, 1);
-        layoutMaxLines("C C", new int[] {2}, 1);
-        layoutMaxLines("C C", new int[] {2}, 2);
-        layoutMaxLines("CC", new int[] {1}, 1);
-        layoutMaxLines("CC", new int[] {1}, 2);
-    }
-}
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index defb44a..078ed76 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -72,7 +72,7 @@
     }
 
     @Test
-    public void testBuilder() {
+    public void testBuilder_textDirection() {
         {
             // Obtain.
             final StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
@@ -88,8 +88,7 @@
                     LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
             builder.setTextDirection(TextDirectionHeuristics.RTL);
             final StaticLayout layout = builder.build();
-            // Always returns TextDirectionHeuristics.FIRSTSTRONG_LTR.
-            assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+            assertEquals(TextDirectionHeuristics.RTL,
                     layout.getTextDirectionHeuristic());
         }
     }
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
new file mode 100644
index 0000000..f8d6884
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.text;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.filters.Suppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLineTest {
+    private boolean stretchesToFullWidth(CharSequence line) {
+        final TextPaint paint = new TextPaint();
+        final TextLine tl = TextLine.obtain();
+        tl.set(paint, line, 0, line.length(), Layout.DIR_LEFT_TO_RIGHT,
+                Layout.DIRS_ALL_LEFT_TO_RIGHT, false /* hasTabs */, null /* tabStops */);
+        final float originalWidth = tl.metrics(null);
+        final float expandedWidth = 2 * originalWidth;
+
+        tl.justify(expandedWidth);
+        final float newWidth = tl.metrics(null);
+        TextLine.recycle(tl);
+        return Math.abs(newWidth - expandedWidth) < 0.5;
+    }
+
+    @Test
+    public void testJustify_spaces() {
+        // There are no spaces to stretch.
+        assertFalse(stretchesToFullWidth("text"));
+
+        assertTrue(stretchesToFullWidth("one space"));
+        assertTrue(stretchesToFullWidth("exactly two spaces"));
+        assertTrue(stretchesToFullWidth("up to three spaces"));
+    }
+
+    // NBSP should also stretch when it's not used as a base for a combining mark. This doesn't work
+    // yet (b/68204709).
+    @Suppress
+    public void disabledTestJustify_NBSP() {
+        final char nbsp = '\u00A0';
+        assertTrue(stretchesToFullWidth("non-breaking" + nbsp + "space"));
+        assertTrue(stretchesToFullWidth("mix" + nbsp + "and match"));
+
+        final char combining_acute = '\u0301';
+        assertFalse(stretchesToFullWidth("combining" + nbsp + combining_acute + "acute"));
+    }
+}
diff --git a/core/tests/coretests/src/android/util/ScrollViewScenario.java b/core/tests/coretests/src/android/util/ScrollViewScenario.java
index db3d9d0..b5140e3 100644
--- a/core/tests/coretests/src/android/util/ScrollViewScenario.java
+++ b/core/tests/coretests/src/android/util/ScrollViewScenario.java
@@ -16,8 +16,6 @@
 
 package android.util;
 
-import com.google.android.collect.Lists;
-
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
@@ -29,6 +27,8 @@
 import android.widget.ScrollView;
 import android.widget.TextView;
 
+import com.google.android.collect.Lists;
+
 import java.util.List;
 
 /**
@@ -269,5 +269,6 @@
         mScrollView.setSmoothScrollingEnabled(false);
 
         setContentView(mScrollView);
+        mScrollView.restoreDefaultFocus();
     }
 }
diff --git a/core/tests/coretests/src/android/view/DisabledLongpressTest.java b/core/tests/coretests/src/android/view/DisabledLongpressTest.java
index 3123897..faa0865 100644
--- a/core/tests/coretests/src/android/view/DisabledLongpressTest.java
+++ b/core/tests/coretests/src/android/view/DisabledLongpressTest.java
@@ -16,17 +16,15 @@
 
 package android.view;
 
-import android.view.Longpress;
-import com.android.frameworks.coretests.R;
-import android.util.KeyUtils;
+import android.test.ActivityInstrumentationTestCase;
 import android.test.TouchUtils;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
-
-import android.test.ActivityInstrumentationTestCase;
-import android.view.View;
+import android.util.KeyUtils;
 import android.view.View.OnLongClickListener;
 
+import com.android.frameworks.coretests.R;
+
 /**
  * Exercises {@link android.view.View}'s longpress plumbing by testing the
  * disabled case.
@@ -34,7 +32,7 @@
 public class DisabledLongpressTest extends ActivityInstrumentationTestCase<Longpress> {
     private View mSimpleView;
     private boolean mLongClicked;
-    
+
     public DisabledLongpressTest() {
         super("com.android.frameworks.coretests", Longpress.class);
     }
@@ -66,16 +64,15 @@
     @MediumTest
     public void testSetUpConditions() throws Exception {
         assertNotNull(mSimpleView);
-        assertTrue(mSimpleView.hasFocus());
         assertFalse(mLongClicked);
     }
 
     @LargeTest
     public void testKeypadLongClick() throws Exception {
-        mSimpleView.requestFocus();
+        getActivity().runOnUiThread(() -> mSimpleView.requestFocus());
         getInstrumentation().waitForIdleSync();
         KeyUtils.longClick(this);
-        
+
         getInstrumentation().waitForIdleSync();
         assertFalse(mLongClicked);
     }
diff --git a/core/tests/coretests/src/android/view/LongpressTest.java b/core/tests/coretests/src/android/view/LongpressTest.java
index 45ce331..d3d7589 100644
--- a/core/tests/coretests/src/android/view/LongpressTest.java
+++ b/core/tests/coretests/src/android/view/LongpressTest.java
@@ -16,24 +16,22 @@
 
 package android.view;
 
-import android.view.Longpress;
-import com.android.frameworks.coretests.R;
-import android.util.KeyUtils;
+import android.test.ActivityInstrumentationTestCase;
 import android.test.TouchUtils;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
-
-import android.test.ActivityInstrumentationTestCase;
-import android.view.View;
+import android.util.KeyUtils;
 import android.view.View.OnLongClickListener;
 
+import com.android.frameworks.coretests.R;
+
 /**
  * Exercises {@link android.view.View}'s longpress plumbing.
  */
 public class LongpressTest extends ActivityInstrumentationTestCase<Longpress> {
     private View mSimpleView;
     private boolean mLongClicked;
-    
+
     public LongpressTest() {
         super("com.android.frameworks.coretests", Longpress.class);
     }
@@ -62,16 +60,15 @@
     @MediumTest
     public void testSetUpConditions() throws Exception {
         assertNotNull(mSimpleView);
-        assertTrue(mSimpleView.hasFocus());
         assertFalse(mLongClicked);
     }
 
     @LargeTest
     public void testKeypadLongClick() throws Exception {
-        mSimpleView.requestFocus();
+        getActivity().runOnUiThread(() -> mSimpleView.requestFocus());
         getInstrumentation().waitForIdleSync();
         KeyUtils.longClick(this);
-        
+
         getInstrumentation().waitForIdleSync();
         assertTrue(mLongClicked);
     }
diff --git a/core/tests/coretests/src/android/view/VisibilityTest.java b/core/tests/coretests/src/android/view/VisibilityTest.java
index 17d2e3e..29c1c8a 100644
--- a/core/tests/coretests/src/android/view/VisibilityTest.java
+++ b/core/tests/coretests/src/android/view/VisibilityTest.java
@@ -16,16 +16,16 @@
 
 package android.view;
 
-import android.view.Visibility;
-import com.android.frameworks.coretests.R;
+import static android.view.KeyEvent.KEYCODE_DPAD_CENTER;
+import static android.view.KeyEvent.KEYCODE_DPAD_LEFT;
 
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.widget.Button;
 import android.widget.TextView;
-import android.view.View;
-import static android.view.KeyEvent.*;
+
+import com.android.frameworks.coretests.R;
 
 /**
  * Exercises {@link android.view.View}'s ability to change visibility between
@@ -64,14 +64,12 @@
         assertNotNull(mVisible);
         assertNotNull(mInvisible);
         assertNotNull(mGone);
-
-        assertTrue(mVisible.hasFocus());
     }
 
     @MediumTest
     public void testVisibleToInvisible() throws Exception {
-        sendKeys("DPAD_RIGHT");
-        assertTrue(mInvisible.hasFocus());
+        getActivity().runOnUiThread(() -> mInvisible.requestFocus());
+        getInstrumentation().waitForIdleSync();
 
         int oldTop = mVictim.getTop();
 
@@ -84,9 +82,8 @@
 
     @MediumTest
     public void testVisibleToGone() throws Exception {
-        //sendKeys("2*DPAD_RIGHT");
-        sendRepeatedKeys(2, KEYCODE_DPAD_RIGHT);
-        assertTrue(mGone.hasFocus());
+        getActivity().runOnUiThread(() -> mGone.requestFocus());
+        getInstrumentation().waitForIdleSync();
 
         int oldTop = mVictim.getTop();
 
@@ -99,8 +96,8 @@
 
     @LargeTest
     public void testGoneToVisible() throws Exception {
-        sendKeys("2*DPAD_RIGHT");
-        assertTrue(mGone.hasFocus());
+        getActivity().runOnUiThread(() -> mGone.requestFocus());
+        getInstrumentation().waitForIdleSync();
 
         int oldTop = mVictim.getTop();
 
@@ -119,8 +116,8 @@
 
     @MediumTest
     public void testGoneToInvisible() throws Exception {
-        sendKeys("2*DPAD_RIGHT");
-        assertTrue(mGone.hasFocus());
+        getActivity().runOnUiThread(() -> mGone.requestFocus());
+        getInstrumentation().waitForIdleSync();
 
         int oldTop = mVictim.getTop();
 
@@ -139,8 +136,8 @@
 
     @MediumTest
     public void testInvisibleToVisible() throws Exception {
-        sendKeys("DPAD_RIGHT");
-        assertTrue(mInvisible.hasFocus());
+        getActivity().runOnUiThread(() -> mInvisible.requestFocus());
+        getInstrumentation().waitForIdleSync();
 
         int oldTop = mVictim.getTop();
 
@@ -159,8 +156,8 @@
 
     @MediumTest
     public void testInvisibleToGone() throws Exception {
-        sendKeys("DPAD_RIGHT");        
-        assertTrue(mInvisible.hasFocus());
+        getActivity().runOnUiThread(() -> mInvisible.requestFocus());
+        getInstrumentation().waitForIdleSync();
 
         int oldTop = mVictim.getTop();
 
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
new file mode 100644
index 0000000..06b860a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IServiceConnection;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import com.android.internal.widget.IRemoteViewsFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * Tests for RemoteViewsAdapter.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteViewsAdapterTest {
+
+    @Mock AppWidgetManager mAppWidgetManager;
+    @Mock IServiceConnection mIServiceConnection;
+    @Mock RemoteViewsAdapter.RemoteAdapterConnectionCallback mCallback;
+
+    private Handler mMainHandler;
+    private TestContext mContext;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mAppWidgetManager
+                .bindRemoteViewsService(any(), anyInt(), any(), any(), anyInt())).thenReturn(true);
+        mContext = new TestContext();
+        mMainHandler = new Handler(Looper.getMainLooper());
+    }
+
+    @Test
+    public void onRemoteAdapterConnected_after_metadata_loaded() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+        assertFalse(adapter.isDataReady());
+
+        assertNotNull(mContext.conn.get());
+
+        ViewsFactory factory = new ViewsFactory(1);
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mMainHandler);
+        verify(mCallback, never()).onRemoteAdapterConnected();
+
+        factory.loadingView.set(createViews("loading"));
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        verify(mCallback, times(1)).onRemoteAdapterConnected();
+
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        // Service is unbound
+        assertTrue(isUnboundOrScheduled());
+    }
+
+    @Test
+    public void viewReplaced_after_mainView_loaded() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+        ViewsFactory factory = new ViewsFactory(1);
+        factory.loadingView.set(createViews("loading"));
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+
+        // Returned view contains the loading text
+        View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+        ArrayList<View> search = new ArrayList<>();
+        view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+        assertEquals(1, search.size());
+
+        // Send the final remoteViews
+        factory.views[0].set(createViews("updated"));
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+
+        // Existing view got updated with new text
+        search.clear();
+        view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+        assertTrue(search.isEmpty());
+        view.findViewsWithText(search, "updated", View.FIND_VIEWS_WITH_TEXT);
+        assertEquals(1, search.size());
+
+        // Service is unbound
+        assertTrue(isUnboundOrScheduled());
+    }
+
+    @Test
+    public void notifyDataSetChanged_deferred() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+        ViewsFactory factory = new ViewsFactory(1);
+        factory.loadingView.set(createViews("loading"));
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        // Reset the loading view so that next refresh is blocked
+        factory.loadingView = new LockedValue<>();
+        factory.mCount = 3;
+        DataSetObserver observer = mock(DataSetObserver.class);
+        getOnUiThread(() -> {
+            adapter.registerDataSetObserver(observer);
+            adapter.notifyDataSetChanged();
+            return null;
+        });
+
+        waitOnHandler(mMainHandler);
+        // Still giving the old values
+        verify(observer, never()).onChanged();
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        factory.loadingView.set(createViews("refreshed"));
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+
+        // When the service returns new data, UI is updated.
+        verify(observer, times(1)).onChanged();
+        assertEquals((Integer) 3, getOnUiThread(adapter::getCount));
+
+        // Service is unbound
+        assertTrue(isUnboundOrScheduled());
+    }
+
+    @Test
+    public void serviceDisconnected_before_getView() throws Throwable {
+        RemoteViewsAdapter adapter = getOnUiThread(
+                () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+        ViewsFactory factory = new ViewsFactory(1);
+        factory.loadingView.set(createViews("loading"));
+        mContext.sendConnect(factory);
+
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        verify(mCallback, times(1)).onRemoteAdapterConnected();
+        assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+        // Unbind the service
+        ServiceConnection conn = mContext.conn.get();
+        getOnHandler(mContext.handler.get(), () -> {
+            conn.onServiceDisconnected(null);
+            return null;
+        });
+
+        // Returned view contains the loading text
+        View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+        ArrayList<View> search = new ArrayList<>();
+        view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+        assertEquals(1, search.size());
+
+        // Unbind is not scheduled
+        assertFalse(isUnboundOrScheduled());
+
+        mContext.sendConnect(factory);
+        waitOnHandler(mContext.handler.get());
+        waitOnHandler(mMainHandler);
+        verify(mCallback, times(2)).onRemoteAdapterConnected();
+    }
+
+    private RemoteViews createViews(String text) {
+        RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.remote_views_text);
+        views.setTextViewText(R.id.text, text);
+        return views;
+    }
+
+    private <T> T getOnUiThread(Supplier<T> supplier) throws Throwable {
+        return getOnHandler(mMainHandler, supplier);
+    }
+
+    private boolean isUnboundOrScheduled() throws Throwable {
+        Handler handler = mContext.handler.get();
+        return getOnHandler(handler, () -> mContext.boundCount == 0
+                || handler.hasMessages(RemoteViewsAdapter.MSG_UNBIND_SERVICE));
+    }
+
+    private static <T> T getOnHandler(Handler handler, Supplier<T> supplier) throws Throwable {
+        LockedValue<T> result = new LockedValue<>();
+        handler.post(() -> result.set(supplier.get()));
+        return result.get();
+    }
+
+    private class TestContext extends ContextWrapper {
+
+        public final LockedValue<ServiceConnection> conn = new LockedValue<>();
+        public final LockedValue<Handler> handler = new LockedValue<>();
+        public int boundCount;
+
+        TestContext() {
+            super(InstrumentationRegistry.getContext());
+        }
+
+        @Override
+        public void unbindService(ServiceConnection conn) {
+            boundCount--;
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.APPWIDGET_SERVICE.equals(name)) {
+                return mAppWidgetManager;
+            }
+            return super.getSystemService(name);
+        }
+
+        @Override
+        public IServiceConnection getServiceDispatcher(
+                ServiceConnection conn, Handler handler, int flags) {
+            this.conn.set(conn);
+            this.handler.set(handler);
+            boundCount++;
+            return mIServiceConnection;
+        }
+
+        @Override
+        public Context getApplicationContext() {
+            return this;
+        }
+
+        public void sendConnect(ViewsFactory factory) throws Exception {
+            ServiceConnection connection = conn.get();
+            handler.get().post(() -> connection.onServiceConnected(null, factory.asBinder()));
+        }
+    }
+
+    private static void waitOnHandler(Handler handler) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        handler.post(() -> latch.countDown());
+        latch.await(20, TimeUnit.SECONDS);
+    }
+
+    private static class ViewsFactory extends IRemoteViewsFactory.Stub {
+
+        public LockedValue<RemoteViews> loadingView = new LockedValue<>();
+        public LockedValue<RemoteViews>[] views;
+
+        private int mCount;
+
+        ViewsFactory(int count) {
+            mCount = count;
+            views = new LockedValue[count];
+            for (int i = 0; i < count; i++) {
+                views[i] = new LockedValue<>();
+            }
+        }
+
+        @Override
+        public void onDataSetChanged() {}
+
+        @Override
+        public void onDataSetChangedAsync() { }
+
+        @Override
+        public void onDestroy(Intent intent) { }
+
+        @Override
+        public int getCount() throws RemoteException {
+            return mCount;
+        }
+
+        @Override
+        public RemoteViews getViewAt(int position) throws RemoteException {
+            try {
+                return views[position].get();
+            } catch (Exception e) {
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        @Override
+        public RemoteViews getLoadingView() throws RemoteException {
+            try {
+                return loadingView.get();
+            } catch (Exception e) {
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
+        }
+
+        @Override
+        public boolean isCreated() {
+            return false;
+        }
+    }
+
+    private static class DistinctIntent extends Intent {
+
+        @Override
+        public boolean filterEquals(Intent other) {
+            return false;
+        }
+    }
+
+    private static class LockedValue<T> {
+
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private T mValue;
+
+        public void set(T value) {
+            mValue = value;
+            mLatch.countDown();
+        }
+
+        public T get() throws Exception {
+            mLatch.await(10, TimeUnit.SECONDS);
+            return mValue;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index ddf9876..70cf097 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -27,6 +27,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.Binder;
 import android.os.Parcel;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
@@ -376,13 +377,24 @@
         parcelAndRecreate(views);
     }
 
-    private void parcelAndRecreate(RemoteViews views) {
-        Parcel p = Parcel.obtain();
-        views.writeToParcel(p, 0);
-        p.setDataPosition(0);
+    private RemoteViews parcelAndRecreate(RemoteViews views) {
+        return parcelAndRecreateWithPendingIntentCookie(views, null);
+    }
 
-        RemoteViews.CREATOR.createFromParcel(p);
-        p.recycle();
+    private RemoteViews parcelAndRecreateWithPendingIntentCookie(RemoteViews views, Object cookie) {
+        Parcel p = Parcel.obtain();
+        try {
+            views.writeToParcel(p, 0);
+            p.setDataPosition(0);
+
+            if (cookie != null) {
+                p.setClassCookie(PendingIntent.class, cookie);
+            }
+
+            return RemoteViews.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
     }
 
     @Test
@@ -399,4 +411,37 @@
             throw new Exception(t);
         }
     }
+
+    @Test
+    public void copy_keepsPendingIntentWhitelistToken() throws Exception {
+        Binder whitelistToken = new Binder();
+
+        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
+                new Intent("test"), PendingIntent.FLAG_ONE_SHOT);
+        views.setOnClickPendingIntent(1, pi);
+        RemoteViews withCookie = parcelAndRecreateWithPendingIntentCookie(views, whitelistToken);
+
+        RemoteViews cloned = new RemoteViews(withCookie);
+
+        PendingIntent found = extractAnyPendingIntent(cloned);
+        assertEquals(whitelistToken, found.getWhitelistToken());
+    }
+
+    private PendingIntent extractAnyPendingIntent(RemoteViews cloned) {
+        PendingIntent[] found = new PendingIntent[1];
+        Parcel p = Parcel.obtain();
+        try {
+            PendingIntent.setOnMarshaledListener((intent, parcel, flags) -> {
+                if (parcel == p) {
+                    found[0] = intent;
+                }
+            });
+            cloned.writeToParcel(p, 0);
+        } finally {
+            p.recycle();
+            PendingIntent.setOnMarshaledListener(null);
+        }
+        return found[0];
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
index b881053..28a3b67 100644
--- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
+++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import static java.util.function.Function.identity;
+
 import android.graphics.PointF;
 import android.graphics.RectF;
 
@@ -105,7 +107,7 @@
         final PointF point = new PointF(pointX, pointY);
         final PointF adjustedPoint =
                 SelectionActionModeHelper.movePointInsideNearestRectangle(point,
-                        mRectFList);
+                        mRectFList, identity());
 
         assertEquals(expectedPointX, adjustedPoint.x, 0.0f);
         assertEquals(expectedPointY, adjustedPoint.y, 0.0f);
@@ -254,7 +256,8 @@
         final List<RectF> result = new ArrayList<>();
         final int size = inputRectangles.length;
         for (int index = 0; index < size; ++index) {
-            SelectionActionModeHelper.mergeRectangleIntoList(result, inputRectangles[index]);
+            SelectionActionModeHelper.mergeRectangleIntoList(result, inputRectangles[index],
+                    identity(), identity());
         }
 
         assertEquals(expectedOutput, result);
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 3e03481..bbdbdb1 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -26,12 +26,10 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
 import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
-import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
 import static android.widget.espresso.DragHandleUtils.onHandleView;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
-import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
@@ -61,6 +59,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.Suppress;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.Spannable;
@@ -216,7 +215,6 @@
 
         onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
         onView(withId(R.id.textview)).check(hasSelection(""));
-        assertNoSelectionHandles();
         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
 
         // Test undo returns to the original state.
@@ -269,18 +267,12 @@
     @Test
     public void testToolbarAppearsAfterSelection() {
         final String text = "Toolbar appears after selection.";
-        assertFloatingToolbarIsNotDisplayed();
         onView(withId(R.id.textview)).perform(replaceText(text));
         onView(withId(R.id.textview)).perform(
                 longPressOnTextAtIndex(text.indexOf("appears")));
 
         sleepForFloatingToolbarPopup();
         assertFloatingToolbarIsDisplayed();
-
-        final String text2 = "Toolbar disappears after typing text.";
-        onView(withId(R.id.textview)).perform(replaceText(text2));
-        sleepForFloatingToolbarPopup();
-        assertFloatingToolbarIsNotDisplayed();
     }
 
     @Test
@@ -310,7 +302,6 @@
     @Test
     public void testToolbarAndInsertionHandle() {
         final String text = "text";
-        assertFloatingToolbarIsNotDisplayed();
         onView(withId(R.id.textview)).perform(replaceText(text));
         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
 
@@ -404,8 +395,6 @@
         final String text = "abcd efg hijk lmn";
         onView(withId(R.id.textview)).perform(replaceText(text));
 
-        assertNoSelectionHandles();
-
         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
 
         onHandleView(com.android.internal.R.id.selection_start_handle)
@@ -428,8 +417,6 @@
         final String text = "abc \u0621\u0622\u0623 def";
         onView(withId(R.id.textview)).perform(replaceText(text));
 
-        assertNoSelectionHandles();
-
         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
 
         onHandleView(com.android.internal.R.id.selection_start_handle)
@@ -491,6 +478,7 @@
         onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
     }
 
+    @Suppress // Consistently failing.
     @Test
     public void testSelectionHandles_multiLine_rtl() {
         // Arabic text.
@@ -649,13 +637,11 @@
         onView(withId(R.id.textview)).perform(replaceText(text));
 
         final TextView textView = mActivity.findViewById(R.id.textview);
-        assertFloatingToolbarIsNotDisplayed();
         mActivityRule.runOnUiThread(
                 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
         mInstrumentation.waitForIdleSync();
-        sleepForFloatingToolbarPopup();
         // Don't automatically start action mode.
-        assertFloatingToolbarIsNotDisplayed();
+        // TODO: Implement assertActionModeNotStarted()
         // Make sure that "Select All" is included in the selection action mode when the entire text
         // is not selected.
         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
@@ -685,8 +671,6 @@
                 () -> Selection.setSelection((Spannable) textView.getText(), 0));
         mInstrumentation.waitForIdleSync();
 
-        sleepForFloatingToolbarPopup();
-        assertFloatingToolbarIsNotDisplayed();
         // Make sure that user click can trigger the insertion action mode.
         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
         onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
diff --git a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
index 6a2233b..1693e54 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
@@ -36,6 +36,10 @@
 
     private DragHandleUtils() {}
 
+    /**
+     * @deprecated Negative assertions are taking too long to timeout in Espresso.
+     */
+    @Deprecated
     public static void assertNoSelectionHandles() {
         try {
             onView(isAssignableFrom(Editor.SelectionHandleView.class))
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
index f7069b3..b6986d5 100644
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
@@ -87,7 +87,9 @@
      * Asserts that the floating toolbar is not displayed on screen.
      *
      * @throws AssertionError if the assertion fails
+     * @deprecated Negative assertions are taking too long to timeout in Espresso.
      */
+    @Deprecated
     public static void assertFloatingToolbarIsNotDisplayed() {
         try {
             onFloatingToolBar().check(matches(isDisplayed()));
@@ -173,12 +175,31 @@
      * @throws AssertionError if the assertion fails
      */
     public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
-        try{
-            assertFloatingToolbarContainsItem(itemLabel);
-        } catch (AssertionError e) {
-            return;
-        }
-        throw new AssertionError("Floating toolbar contains " + itemLabel);
+        onFloatingToolBar().check(matches(new TypeSafeMatcher<View>() {
+            @Override
+            public boolean matchesSafely(View view) {
+                return doesNotContainItem(view);
+            }
+
+            @Override
+            public void describeTo(Description description) {}
+
+            private boolean doesNotContainItem(View view) {
+                if (view.getTag() instanceof MenuItem) {
+                    if (itemLabel.equals(((MenuItem) view.getTag()).getTitle().toString())) {
+                        return false;
+                    }
+                } else if (view instanceof ViewGroup) {
+                    ViewGroup viewGroup = (ViewGroup) view;
+                    for (int i = 0; i < viewGroup.getChildCount(); i++) {
+                        if (doesNotContainItem(viewGroup.getChildAt(i))) {
+                            return false;
+                        }
+                    }
+                }
+                return true;
+            }
+        }));
     }
 
     /**
diff --git a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
index 795e09c..06cb75d 100644
--- a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
+++ b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
@@ -54,6 +54,10 @@
         mListView.setVerticalFadingEdgeEnabled(true);
         mListView.setFadingEdgeLength(10);
         ensureNotInTouchMode();
+
+        // focus the listview
+        mActivity.runOnUiThread(() -> mListView.requestFocus());
+        getInstrumentation().waitForIdleSync();
     }
 
     @Override
diff --git a/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java b/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
index 8f62b2c..53eeb48 100644
--- a/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
+++ b/core/tests/coretests/src/android/widget/gridview/GridScrollListenerTest.java
@@ -18,15 +18,13 @@
 
 import android.app.Instrumentation;
 import android.test.ActivityInstrumentationTestCase;
+import android.test.TouchUtils;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.test.TouchUtils;
 import android.view.KeyEvent;
 import android.widget.AbsListView;
 import android.widget.GridView;
 
-import android.widget.gridview.GridScrollListener;
-
 public class GridScrollListenerTest extends ActivityInstrumentationTestCase<GridScrollListener> implements
         AbsListView.OnScrollListener {
     private GridScrollListener mActivity;
@@ -52,53 +50,61 @@
     public void testPreconditions() {
         assertNotNull(mActivity);
         assertNotNull(mGridView);
-        
+
         assertEquals(0, mFirstVisibleItem);
     }
-    
+
     @LargeTest
     public void testKeyScrolling() {
         Instrumentation inst = getInstrumentation();
-        
+        // focus the gridview
+        mActivity.runOnUiThread(() -> mGridView.requestFocus());
+        inst.waitForIdleSync();
+
         int firstVisibleItem = mFirstVisibleItem;
         for (int i = 0; i < mVisibleItemCount * 2; i++) {
             inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
         }
         inst.waitForIdleSync();
-        
+
         assertTrue("Arrow scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-        
+
         firstVisibleItem = mFirstVisibleItem;
-        inst.sendCharacterSync(KeyEvent.KEYCODE_SPACE);
+        KeyEvent upDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+        KeyEvent upUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+        inst.sendKeySync(upDown);
+        inst.sendKeySync(upUp);
         inst.waitForIdleSync();
-        
-        assertTrue("Page scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-        
+
+        assertTrue("Page scroll did not happen", mFirstVisibleItem < firstVisibleItem);
+
         firstVisibleItem = mFirstVisibleItem;
-        KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, 
+        KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
                 KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
-        KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP, 
+        KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
                 KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
         inst.sendKeySync(down);
         inst.sendKeySync(up);
         inst.waitForIdleSync();
-        
+
         assertTrue("Full scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-        assertEquals("Full scroll did not happen", mTotalItemCount, 
-                mFirstVisibleItem + mVisibleItemCount);    
+        assertEquals("Full scroll did not happen", mTotalItemCount,
+                mFirstVisibleItem + mVisibleItemCount);
     }
 
     @LargeTest
     public void testTouchScrolling() {
         Instrumentation inst = getInstrumentation();
-        
+
         int firstVisibleItem = mFirstVisibleItem;
         TouchUtils.dragQuarterScreenUp(this);
         TouchUtils.dragQuarterScreenUp(this);
         assertTrue("Touch scroll did not happen", mFirstVisibleItem > firstVisibleItem);
     }
 
-    
+
     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
         mFirstVisibleItem = firstVisibleItem;
         mVisibleItemCount = visibleItemCount;
diff --git a/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java b/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
index 2d6e75e..7b29a66 100644
--- a/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
+++ b/core/tests/coretests/src/android/widget/listview/ListScrollListenerTest.java
@@ -18,14 +18,13 @@
 
 import android.app.Instrumentation;
 import android.test.ActivityInstrumentationTestCase;
+import android.test.TouchUtils;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.view.KeyEvent;
 import android.widget.AbsListView;
 import android.widget.ListView;
 
-import android.test.TouchUtils;
-
 public class ListScrollListenerTest extends ActivityInstrumentationTestCase<ListScrollListener> implements
         AbsListView.OnScrollListener {
     private ListScrollListener mActivity;
@@ -51,38 +50,46 @@
     public void testPreconditions() {
         assertNotNull(mActivity);
         assertNotNull(mListView);
-        
+
         assertEquals(0, mFirstVisibleItem);
     }
-    
+
     @LargeTest
     public void testKeyScrolling() {
         Instrumentation inst = getInstrumentation();
-        
+        // focus the listview
+        mActivity.runOnUiThread(() -> mListView.requestFocus());
+        inst.waitForIdleSync();
+
         int firstVisibleItem = mFirstVisibleItem;
         for (int i = 0; i < mVisibleItemCount * 2; i++) {
             inst.sendCharacterSync(KeyEvent.KEYCODE_DPAD_DOWN);
         }
         inst.waitForIdleSync();
         assertTrue("Arrow scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-        
+
         firstVisibleItem = mFirstVisibleItem;
-        inst.sendCharacterSync(KeyEvent.KEYCODE_SPACE);
+        KeyEvent upDown = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+        KeyEvent upUp = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
+                KeyEvent.KEYCODE_DPAD_UP, 0, KeyEvent.META_ALT_ON);
+        inst.sendKeySync(upDown);
+        inst.sendKeySync(upUp);
         inst.waitForIdleSync();
-        assertTrue("Page scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-        
+        assertTrue("Page scroll did not happen", mFirstVisibleItem < firstVisibleItem);
+
         firstVisibleItem = mFirstVisibleItem;
-        KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, 
+        KeyEvent down = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
                 KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
-        KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP, 
+        KeyEvent up = new KeyEvent(0, 0, KeyEvent.ACTION_UP,
                 KeyEvent.KEYCODE_DPAD_DOWN, 0, KeyEvent.META_ALT_ON);
         inst.sendKeySync(down);
         inst.sendKeySync(up);
         inst.waitForIdleSync();
-        
+
         assertTrue("Full scroll did not happen", mFirstVisibleItem > firstVisibleItem);
-        assertEquals("Full scroll did not happen", mTotalItemCount, 
-                mFirstVisibleItem + mVisibleItemCount);    
+        assertEquals("Full scroll did not happen", mTotalItemCount,
+                mFirstVisibleItem + mVisibleItemCount);
     }
 
     @LargeTest
@@ -93,13 +100,13 @@
         assertTrue("Touch scroll did not happen", mFirstVisibleItem > firstVisibleItem);
     }
 
-    
+
     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
         mFirstVisibleItem = firstVisibleItem;
         mVisibleItemCount = visibleItemCount;
         mTotalItemCount = totalItemCount;
     }
 
-    public void onScrollStateChanged(AbsListView view, int scrollState) {        
+    public void onScrollStateChanged(AbsListView view, int scrollState) {
     }
 }
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
index 9b1cc0a..c191d71 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfThinItemsTest.java
@@ -50,6 +50,10 @@
 
     @LargeTest
     public void testScrollToBottom() {
+        // focus the listview
+        getActivity().runOnUiThread(() -> mListView.requestFocus());
+        getInstrumentation().waitForIdleSync();
+
         final int numItems = mListView.getAdapter().getCount();
         final int listBottom = mListView.getHeight() - mListView.getListPaddingBottom();
         for (int i = 0; i < numItems; i++) {
@@ -78,6 +82,10 @@
 
     @LargeTest
     public void testScrollToTop() {
+        // focus the listview
+        getActivity().runOnUiThread(() -> mListView.requestFocus());
+        getInstrumentation().waitForIdleSync();
+
         final int numItems = mListView.getAdapter().getCount();
 
         for (int i = 0; i < numItems - 1; i++) {
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
index 957be01..56ca009 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithNoFadingEdgeTest.java
@@ -16,13 +16,12 @@
 
 package android.widget.listview.arrowscroll;
 
-import android.widget.listview.ListWithNoFadingEdge;
-
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.widget.ListView;
 import android.view.KeyEvent;
+import android.widget.ListView;
+import android.widget.listview.ListWithNoFadingEdge;
 
 public class ListWithNoFadingEdgeTest extends ActivityInstrumentationTestCase<ListWithNoFadingEdge> {
 
@@ -49,17 +48,21 @@
 
     @MediumTest
     public void testScrollDownToBottom() {
+        getActivity().runOnUiThread(() -> mListView.requestFocus());
+        getInstrumentation().waitForIdleSync();
         final int numItems = mListView.getCount();
 
         for (int i = 0; i < numItems; i++) {
             assertEquals("selected position", i, mListView.getSelectedItemPosition());
             sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
         }
-        assertEquals("selected position", numItems - 1, mListView.getSelectedItemPosition());            
+        assertEquals("selected position", numItems - 1, mListView.getSelectedItemPosition());
     }
 
     @LargeTest
     public void testScrollFromBottomToTop() {
+        getActivity().runOnUiThread(() -> mListView.requestFocus());
+        getInstrumentation().waitForIdleSync();
         final int numItems = mListView.getCount();
 
         getActivity().runOnUiThread(new Runnable() {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
index eff6ad9..c611a01 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java
@@ -22,6 +22,7 @@
 import android.os.WorkSource;
 import android.support.test.filters.SmallTest;
 import android.util.ArrayMap;
+import android.view.Display;
 
 import junit.framework.TestCase;
 
@@ -44,7 +45,7 @@
         // Off-battery, non-existent
         clocks.realtime = clocks.uptime = 10;
         cur = clocks.realtime * 1000;
-        bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, cur, cur); // off battery
         assertFalse(bgtb.isRunning());
         assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
 
@@ -65,7 +66,7 @@
         // On-battery, background
         clocks.realtime = clocks.uptime = 303;
         cur = clocks.realtime * 1000;
-        bi.updateTimeBasesLocked(true, false, cur, cur); // on battery
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, cur, cur); // on battery
         // still in ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         assertTrue(bgtb.isRunning());
         assertEquals(0, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
@@ -73,7 +74,7 @@
         // On-battery, background - but change screen state
         clocks.realtime = clocks.uptime = 409;
         cur = clocks.realtime * 1000;
-        bi.updateTimeBasesLocked(true, true, cur, cur); // on battery (again)
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, cur, cur); // on battery (again)
         assertTrue(bgtb.isRunning());
         assertEquals(106_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
 
@@ -87,7 +88,7 @@
         // Off-battery, foreground
         clocks.realtime = clocks.uptime = 530;
         cur = clocks.realtime * 1000;
-        bi.updateTimeBasesLocked(false, false, cur, cur); // off battery
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, cur, cur); // off battery
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
         assertFalse(bgtb.isRunning());
         assertEquals(227_000, bgtb.computeRealtime(cur, STATS_SINCE_CHARGED));
@@ -112,17 +113,17 @@
         // battery=off, screen=off, background=off
         cur = (clocks.realtime = clocks.uptime = 100) * 1000;
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
-        bi.updateTimeBasesLocked(false, false, cur, cur);
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, cur, cur);
         assertFalse(bgtb.isRunning());
 
         // battery=on, screen=off, background=off
         cur = (clocks.realtime = clocks.uptime = 200) * 1000;
-        bi.updateTimeBasesLocked(true, false, cur, cur);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, cur, cur);
         assertFalse(bgtb.isRunning());
 
         // battery=on, screen=on, background=off
         cur = (clocks.realtime = clocks.uptime = 300) * 1000;
-        bi.updateTimeBasesLocked(true, true, cur, cur);
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, cur, cur);
         assertFalse(bgtb.isRunning());
 
         // battery=on, screen=on, background=on
@@ -133,12 +134,12 @@
 
         // battery=on, screen=off, background=on
         cur = (clocks.realtime = clocks.uptime = 550) * 1000;
-        bi.updateTimeBasesLocked(true, false, cur, cur);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, cur, cur);
         assertFalse(bgtb.isRunning());
 
         // battery=off, screen=off, background=on
         cur = (clocks.realtime = clocks.uptime = 660) * 1000;
-        bi.updateTimeBasesLocked(false, false, cur, cur);
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, cur, cur);
         assertFalse(bgtb.isRunning());
 
         // battery=off, screen=off, background=off
@@ -157,7 +158,7 @@
 
         // On battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery
         // App in foreground
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
@@ -171,7 +172,7 @@
 
         // Off battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 305);
-        bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr); // off battery
 
         // Stop timer
         curr = 1000 * (clocks.realtime = clocks.uptime = 409);
@@ -200,7 +201,7 @@
 
         // On battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery
 
         // App in foreground
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
@@ -215,7 +216,7 @@
 
         // Off battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 305);
-        bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr); // off battery
 
         // Start timer (unoptimized)
         curr = 1000 * (clocks.realtime = clocks.uptime = 1000);
@@ -223,7 +224,7 @@
 
         // On battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 2001);
-        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery
 
         // Move to foreground
         curr = 1000 * (clocks.realtime = clocks.uptime = 3004);
@@ -270,7 +271,7 @@
 
         // On battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery
         // App in foreground
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
@@ -292,7 +293,7 @@
 
         // Off battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 305);
-        bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr); // off battery
 
         // Stop timer
         curr = 1000 * (clocks.realtime = clocks.uptime = 409);
@@ -331,7 +332,7 @@
 
         // On battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(true, false, curr, curr); // on battery
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery
         // App in foreground
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
@@ -353,7 +354,7 @@
 
         // Off battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 305);
-        bi.updateTimeBasesLocked(false, false, curr, curr); // off battery
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr); // off battery
 
         // Stop timer
         curr = 1000 * (clocks.realtime = clocks.uptime = 409);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 97b54b1..6ff0ab2 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -39,6 +39,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.SparseLongArray;
+import android.view.Display;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -179,7 +180,7 @@
     @Test
     public void testUpdateClusterSpeedTimes() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
         final long[][] clusterSpeedTimesMs = {{20, 30}, {40, 50, 60}};
         initKernelCpuSpeedReaders(clusterSpeedTimesMs.length);
         for (int i = 0; i < clusterSpeedTimesMs.length; ++i) {
@@ -224,7 +225,7 @@
     @Test
     public void testReadKernelUidCpuTimesLocked() {
         //PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
         final int[] testUids = getUids(testUserId, new int[] {
@@ -295,7 +296,7 @@
     @Test
     public void testReadKernelUidCpuTimesLocked_isolatedUid() {
         //PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
         final int isolatedAppId = FIRST_ISOLATED_UID + 27;
@@ -382,7 +383,7 @@
     @Test
     public void testReadKernelUidCpuTimesLocked_invalidUid() {
         //PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
         final int testUserId = 11;
         final int invalidUserId = 15;
         final int invalidUid = UserHandle.getUid(invalidUserId, FIRST_APPLICATION_UID + 99);
@@ -427,7 +428,7 @@
     @Test
     public void testReadKernelUidCpuTimesLocked_withPartialTimers() {
         //PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
         final int[] testUids = getUids(testUserId, new int[] {
@@ -509,7 +510,7 @@
     @Test
     public void testReadKernelUidCpuFreqTimesLocked() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
@@ -550,7 +551,7 @@
         // Repeat the test when the screen is off.
 
         // PRECONDITIONS
-        updateTimeBasesLocked(true, true, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         final long[][] deltasMs = {
                 {3, 12, 55, 100, 32},
                 {3248327490475l, 232349349845043l, 123, 2398, 0},
@@ -584,7 +585,7 @@
     @Test
     public void testReadKernelUidCpuFreqTimesLocked_perClusterTimesAvailable() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
@@ -644,7 +645,7 @@
         // Repeat the test when the screen is off.
 
         // PRECONDITIONS
-        updateTimeBasesLocked(true, true, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         final long[][] deltasMs = {
                 {3, 12, 55, 100, 32},
                 {3248327490475l, 232349349845043l, 123, 2398, 0},
@@ -688,7 +689,7 @@
     @Test
     public void testReadKernelUidCpuFreqTimesLocked_partialTimers() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
@@ -792,7 +793,7 @@
     @Test
     public void testReadKernelUidCpuFreqTimesLocked_freqsChanged() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
@@ -833,7 +834,7 @@
         // Repeat the test with the freqs from proc file changed.
 
         // PRECONDITIONS
-        updateTimeBasesLocked(true, true, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         final long[][] deltasMs = {
                 {3, 12, 55, 100, 32, 34984, 27983},
                 {3248327490475l, 232349349845043l, 123, 2398, 0, 398, 0},
@@ -867,7 +868,7 @@
     @Test
     public void testReadKernelUidCpuFreqTimesLocked_isolatedUid() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int testUserId = 11;
         when(mUserInfoProvider.exists(testUserId)).thenReturn(true);
@@ -961,7 +962,7 @@
     @Test
     public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
         // PRECONDITIONS
-        updateTimeBasesLocked(true, false, 0, 0);
+        updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int testUserId = 11;
         final int invalidUserId = 15;
@@ -1008,12 +1009,12 @@
         verify(mKernelUidCpuFreqTimeReader).removeUid(invalidUid);
     }
 
-    private void updateTimeBasesLocked(boolean unplugged, boolean screenOff,
+    private void updateTimeBasesLocked(boolean unplugged, int screenState,
             long upTime, long realTime) {
         // Set PowerProfile=null before calling updateTimeBasesLocked to avoid execution of
         // BatteryStatsImpl.updateCpuTimeLocked
         mBatteryStatsImpl.setPowerProfile(null);
-        mBatteryStatsImpl.updateTimeBasesLocked(unplugged, screenOff, upTime, realTime);
+        mBatteryStatsImpl.updateTimeBasesLocked(unplugged, screenState, upTime, realTime);
         mBatteryStatsImpl.setPowerProfile(mPowerProfile);
     }
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 7769e69..4e83221 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -19,9 +19,11 @@
 import static android.os.BatteryStats.WAKE_TYPE_PARTIAL;
 
 import android.app.ActivityManager;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.WorkSource;
 import android.support.test.filters.SmallTest;
+import android.view.Display;
 
 import junit.framework.TestCase;
 
@@ -50,7 +52,7 @@
     @SmallTest
     public void testNoteBluetoothScanResultLocked() throws Exception {
         MockBatteryStatsImpl bi = new MockBatteryStatsImpl(new MockClocks());
-        bi.updateTimeBasesLocked(true, true, 0, 0);
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
 
         bi.noteBluetoothScanResultsFromSourceLocked(WS, 1);
@@ -82,7 +84,7 @@
         int pid = 10;
         String name = "name";
 
-        bi.updateTimeBasesLocked(true, true, 0, 0);
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
         bi.getUidStatsLocked(UID).noteStartWakeLocked(pid, name, WAKE_TYPE_PARTIAL, clocks.realtime);
 
@@ -124,7 +126,7 @@
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT, 5309);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_CACHED_EMPTY, 42);
 
-        bi.updateTimeBasesLocked(true, false, 0, 0);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         for (Map.Entry<Integer, Integer> entry : stateRuntimeMap.entrySet()) {
             bi.noteUidProcessStateLocked(UID, entry.getKey());
@@ -189,4 +191,94 @@
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
     }
+
+    /** Test BatteryStatsImpl.updateTimeBasesLocked. */
+    @SmallTest
+    public void testUpdateTimeBasesLocked() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        bi.updateTimeBasesLocked(false, Display.STATE_OFF, 0, 0);
+        assertFalse(bi.getOnBatteryTimeBase().isRunning());
+        bi.updateTimeBasesLocked(false, Display.STATE_DOZE, 10, 10);
+        assertFalse(bi.getOnBatteryTimeBase().isRunning());
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, 20, 20);
+        assertFalse(bi.getOnBatteryTimeBase().isRunning());
+
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, 30, 30);
+        assertTrue(bi.getOnBatteryTimeBase().isRunning());
+        assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+        bi.updateTimeBasesLocked(true, Display.STATE_DOZE, 40, 40);
+        assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 40, 40);
+        assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+    }
+
+    /** Test BatteryStatsImpl.noteScreenStateLocked sets timebases and screen states correctly. */
+    @SmallTest
+    public void testNoteScreenStateLocked() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+        bi.noteScreenStateLocked(Display.STATE_ON);
+        bi.noteScreenStateLocked(Display.STATE_DOZE);
+        assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+        assertEquals(bi.getScreenState(), Display.STATE_DOZE);
+        bi.noteScreenStateLocked(Display.STATE_ON);
+        assertFalse(bi.getOnBatteryScreenOffTimeBase().isRunning());
+        assertEquals(bi.getScreenState(), Display.STATE_ON);
+        bi.noteScreenStateLocked(Display.STATE_OFF);
+        assertTrue(bi.getOnBatteryScreenOffTimeBase().isRunning());
+        assertEquals(bi.getScreenState(), Display.STATE_OFF);
+    }
+
+    /** Test BatteryStatsImpl.noteScreenStateLocked updates timers correctly.
+     *
+     *  Unknown and doze should both be subset of off state
+     *
+     *  Timeline 0----100----200----310----400------------1000
+     *  Unknown         -------
+     *  On                     -------
+     *  Off             -------       ----------------------
+     *  Doze                                ----------------
+     */
+    @SmallTest
+    public void testNoteScreenStateTimersLocked() throws Exception {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+        clocks.realtime = clocks.uptime = 100;
+        // Device startup, setOnBatteryLocked calls updateTimebases
+        bi.updateTimeBasesLocked(true, Display.STATE_UNKNOWN, 100_000, 100_000);
+        // Turn on display at 200us
+        clocks.realtime = clocks.uptime = 200;
+        bi.noteScreenStateLocked(Display.STATE_ON);
+        assertEquals(150_000, bi.computeBatteryRealtime(250_000, STATS_SINCE_CHARGED));
+        assertEquals(100_000, bi.computeBatteryScreenOffRealtime(250_000, STATS_SINCE_CHARGED));
+        assertEquals(50_000, bi.getScreenOnTime(250_000, STATS_SINCE_CHARGED));
+        assertEquals(0, bi.getScreenDozeTime(250_000, STATS_SINCE_CHARGED));
+
+        clocks.realtime = clocks.uptime = 310;
+        bi.noteScreenStateLocked(Display.STATE_OFF);
+        assertEquals(250_000, bi.computeBatteryRealtime(350_000, STATS_SINCE_CHARGED));
+        assertEquals(140_000, bi.computeBatteryScreenOffRealtime(350_000, STATS_SINCE_CHARGED));
+        assertEquals(110_000, bi.getScreenOnTime(350_000, STATS_SINCE_CHARGED));
+        assertEquals(0, bi.getScreenDozeTime(350_000, STATS_SINCE_CHARGED));
+
+        clocks.realtime = clocks.uptime = 400;
+        bi.noteScreenStateLocked(Display.STATE_DOZE);
+        assertEquals(400_000, bi.computeBatteryRealtime(500_000, STATS_SINCE_CHARGED));
+        assertEquals(290_000, bi.computeBatteryScreenOffRealtime(500_000, STATS_SINCE_CHARGED));
+        assertEquals(110_000, bi.getScreenOnTime(500_000, STATS_SINCE_CHARGED));
+        assertEquals(100_000, bi.getScreenDozeTime(500_000, STATS_SINCE_CHARGED));
+
+        clocks.realtime = clocks.uptime = 1000;
+        bi.noteScreenStateLocked(Display.STATE_OFF);
+        assertEquals(1400_000, bi.computeBatteryRealtime(1500_000, STATS_SINCE_CHARGED));
+        assertEquals(1290_000, bi.computeBatteryScreenOffRealtime(1500_000, STATS_SINCE_CHARGED));
+        assertEquals(110_000, bi.getScreenOnTime(1500_000, STATS_SINCE_CHARGED));
+        assertEquals(600_000, bi.getScreenDozeTime(1500_000, STATS_SINCE_CHARGED));
+    }
+
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index 0294095..a751f90 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -18,6 +18,7 @@
 import android.app.ActivityManager;
 import android.os.BatteryStats;
 import android.support.test.filters.SmallTest;
+import android.view.Display;
 
 import junit.framework.TestCase;
 
@@ -74,7 +75,7 @@
 
         // Plugged-in (battery=off, sensor=off)
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(false, false, curr, curr);
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr);
 
 
         // Start sensor (battery=off, sensor=on)
@@ -110,7 +111,7 @@
 
         // Unplugged (battery=on, sensor=off)
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(true, false, curr, curr);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr);
 
         // Start sensor (battery=on, sensor=on)
         curr = 1000 * (clocks.realtime = clocks.uptime = 200);
@@ -145,7 +146,7 @@
 
         // On battery (battery=on, sensor=off)
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(true, false, curr, curr);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr);
         bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
 
         // Start sensor (battery=on, sensor=on)
@@ -154,7 +155,7 @@
 
         // Off battery (battery=off, sensor=on)
         curr = 1000 * (clocks.realtime = clocks.uptime = 305);
-        bi.updateTimeBasesLocked(false, false, curr, curr);
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr);
 
         // Stop sensor while off battery (battery=off, sensor=off)
         curr = 1000 * (clocks.realtime = clocks.uptime = 409);
@@ -191,7 +192,7 @@
 
         // Plugged-in (battery=off, sensor=off)
         curr = 1000 * (clocks.realtime = clocks.uptime = 100);
-        bi.updateTimeBasesLocked(false, false, curr, curr);
+        bi.updateTimeBasesLocked(false, Display.STATE_ON, curr, curr);
 
         // Start sensor (battery=off, sensor=on)
         curr = 1000 * (clocks.realtime = clocks.uptime = 200);
@@ -209,7 +210,7 @@
 
         // Unplug (battery=on, sensor=on)
         curr = 1000 * (clocks.realtime = clocks.uptime = 305);
-        bi.updateTimeBasesLocked(true, false, curr, curr);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr);
 
         //Test situation
         curr = 1000 * (clocks.realtime = clocks.uptime = 410);
@@ -243,7 +244,7 @@
         long curr = 0; // realtime in us
         // Entire test is on-battery
         curr = 1000 * (clocks.realtime = clocks.uptime = 1000);
-        bi.updateTimeBasesLocked(true, false, curr, curr);
+        bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr);
 
         // See below for a diagram of events.
 
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 73ee3e5..63d1e5a 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -28,6 +28,10 @@
     MockBatteryStatsImpl(Clocks clocks) {
         super(clocks);
         this.clocks = mClocks;
+        mScreenOnTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
+                mOnBatteryTimeBase);
+        mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
+                mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
     }
 
@@ -39,6 +43,14 @@
         return mOnBatteryTimeBase;
     }
 
+    public TimeBase getOnBatteryScreenOffTimeBase() {
+        return mOnBatteryScreenOffTimeBase;
+    }
+
+    public int getScreenState() {
+        return mScreenState;
+    }
+
     public boolean isOnBattery() {
         return mForceOnBattery ? true : super.isOnBattery();
     }
diff --git a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
index 8fee1d1..ebfa45c 100644
--- a/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
+++ b/core/tests/featureflagtests/src/android/util/FeatureFlagUtilsTest.java
@@ -16,6 +16,7 @@
 
 package android.util;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -65,6 +66,18 @@
     }
 
     @Test
+    public void testSetEnabled_shouldSetOverrideFlag() {
+        assertFalse(FeatureFlagUtils.isEnabled(TEST_FEATURE_NAME));
+
+        FeatureFlagUtils.setEnabled(TEST_FEATURE_NAME, true);
+
+        assertEquals(SystemProperties.get(FeatureFlagUtils.FFLAG_PREFIX + TEST_FEATURE_NAME, null),
+                "");
+        assertTrue(Boolean.parseBoolean(SystemProperties.get(
+                FeatureFlagUtils.FFLAG_OVERRIDE_PREFIX + TEST_FEATURE_NAME, "")));
+    }
+
+    @Test
     public void testGetFlag_notSet_shouldReturnFalse() {
         assertFalse(FeatureFlagUtils.isEnabled(TEST_FEATURE_NAME));
     }
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
index 2784193..c5e112b 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/Android.mk
@@ -33,29 +33,17 @@
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
 LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-    -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
-
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
-
-ifdef LOCAL_JACK_ENABLED
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
-endif
 
 LOCAL_MIN_SDK_VERSION := 8
 
 include $(BUILD_PACKAGE)
 
-ifndef LOCAL_JACK_ENABLED
 $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
 	$(hide) mkdir -p $(dir $@)
 	$(MAINDEXCLASSES) $< 1>$@
 	echo "com/android/multidexlegacytestapp/Test.class" >> $@
 
 $(built_dex_intermediate): $(mainDexList)
-endif
 
 ## The application with a full main dex
 include $(CLEAR_VARS)
@@ -76,26 +64,14 @@
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
 LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList2)
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=multidex -D jack.preprocessor=true\
-    -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
-
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
-
-ifdef LOCAL_JACK_ENABLED
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
-endif
 
 LOCAL_MIN_SDK_VERSION := 8
 
 include $(BUILD_PACKAGE)
 
-ifndef LOCAL_JACK_ENABLED
 $(mainDexList2): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
 	$(hide) mkdir -p $(dir $@)
 	$(MAINDEXCLASSES) $< 1>$@
 	echo "com/android/multidexlegacytestapp/Test.class" >> $@
 
 $(built_dex_intermediate): $(mainDexList2)
-endif
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/test.jpp b/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/test.jpp
deleted file mode 100644
index a1f5656..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestApp/test.jpp
+++ /dev/null
@@ -1,3 +0,0 @@
-test:
-  @@com.android.jack.annotations.ForceInMainDex
-  class com.android.multidexlegacytestapp.Test
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
index 1c7d807..da48df9 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/Android.mk
@@ -32,24 +32,12 @@
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
 LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-    -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
-
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
-
-ifdef LOCAL_JACK_ENABLED
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
-endif
 
 include $(BUILD_PACKAGE)
 
-ifndef LOCAL_JACK_ENABLED
 $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
 	$(hide) mkdir -p $(dir $@)
 	$(MAINDEXCLASSES) $< 1>$@
 	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
 
 $(built_dex_intermediate): $(mainDexList)
-endif
\ No newline at end of file
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/test.jpp b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/test.jpp
deleted file mode 100644
index 6d384e3..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v1/test.jpp
+++ /dev/null
@@ -1,3 +0,0 @@
-test:
-  @@com.android.jack.annotations.ForceInMainDex
-  class com.android.framework.multidexlegacyversionedtestapp.MultiDexUpdateTest
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
index b77cf31..02b3f53 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/Android.mk
@@ -32,24 +32,12 @@
 	$(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),$(LOCAL_IS_HOST_MODULE),common)/maindex.list
 
 LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-    -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
-
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
-
-ifdef LOCAL_JACK_ENABLED
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
-endif
 
 include $(BUILD_PACKAGE)
 
-ifndef LOCAL_JACK_ENABLED
 $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
 	$(hide) mkdir -p $(dir $@)
 	$(MAINDEXCLASSES) $< 1>$@
 	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
 
 $(built_dex_intermediate): $(mainDexList)
-endif
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/test.jpp b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/test.jpp
deleted file mode 100644
index 6d384e3..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v2/test.jpp
+++ /dev/null
@@ -1,3 +0,0 @@
-test:
-  @@com.android.jack.annotations.ForceInMainDex
-  class com.android.framework.multidexlegacyversionedtestapp.MultiDexUpdateTest
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
index 3631626..4808684 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/Android.mk
@@ -32,25 +32,13 @@
 LOCAL_DEX_PREOPT := false
 
 LOCAL_DX_FLAGS := --multi-dex --main-dex-list=$(mainDexList) --minimal-main-dex
-LOCAL_JACK_FLAGS := -D jack.dex.output.policy=minimal-multidex -D jack.preprocessor=true\
-    -D jack.preprocessor.file=$(LOCAL_PATH)/test.jpp -D jack.dex.output.multidex.legacy=true
-
-#################################
-include $(BUILD_SYSTEM)/configure_local_jack.mk
-#################################
-
-ifdef LOCAL_JACK_ENABLED
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/test.jpp
-endif
 
 include $(BUILD_PACKAGE)
 
-ifndef LOCAL_JACK_ENABLED
 $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES)
 	$(hide) mkdir -p $(dir $@)
 	$(MAINDEXCLASSES) $< 1>$@
 	echo "com/android/framework/multidexlegacyversionedtestapp/MultiDexUpdateTest.class" >> $@
 
 $(built_dex_intermediate): $(mainDexList)
-endif
 
diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/test.jpp b/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/test.jpp
deleted file mode 100644
index 6d384e3..0000000
--- a/core/tests/hosttests/test-apps/MultiDexLegacyVersionedTestApp_v3/test.jpp
+++ /dev/null
@@ -1,3 +0,0 @@
-test:
-  @@com.android.jack.annotations.ForceInMainDex
-  class com.android.framework.multidexlegacyversionedtestapp.MultiDexUpdateTest
diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index eb2a516..76aa93f 100644
--- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -24,6 +24,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.test.TestLooper;
 
 import android.test.suitebuilder.annotation.Suppress;
 import com.android.internal.util.State;
@@ -343,6 +344,100 @@
     }
 
     /**
+     * Tests {@link StateMachine#quitNow()} immediately after {@link StateMachine#start()}.
+     */
+    class StateMachineQuitNowAfterStartTest extends StateMachine {
+        Collection<LogRec> mLogRecs;
+
+        StateMachineQuitNowAfterStartTest(String name, Looper looper) {
+            super(name, looper);
+            mThisSm = this;
+            setDbg(DBG);
+
+            // Setup state machine with 1 state
+            addState(mS1);
+
+            // Set the initial state
+            setInitialState(mS1);
+        }
+
+        @Override
+        public void onQuitting() {
+            tlog("onQuitting");
+            addLogRec(ON_QUITTING);
+            mLogRecs = mThisSm.copyLogRecs();
+            synchronized (mThisSm) {
+                mThisSm.notifyAll();
+            }
+        }
+
+        class S1 extends State {
+            @Override
+            public void enter() {
+                tlog("S1.enter");
+                addLogRec(ENTER);
+            }
+            @Override
+            public void exit() {
+                tlog("S1.exit");
+                addLogRec(EXIT);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    // Sleep and assume the other messages will be queued up.
+                    case TEST_CMD_1: {
+                        tlog("TEST_CMD_1");
+                        sleep(500);
+                        break;
+                    }
+                    default: {
+                        tlog("default what=" + message.what);
+                        break;
+                    }
+                }
+                return HANDLED;
+            }
+        }
+
+        private StateMachineQuitNowAfterStartTest mThisSm;
+        private S1 mS1 = new S1();
+    }
+
+    /**
+     * When quitNow() is called immediately after start(), the QUIT_CMD gets processed
+     * before the INIT_CMD. This test ensures that the StateMachine can gracefully handle
+     * this sequencing of messages (QUIT before INIT).
+     */
+    @SmallTest
+    public void testStateMachineQuitNowAfterStart() throws Exception {
+        if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
+
+        TestLooper testLooper = new TestLooper();
+        StateMachineQuitNowAfterStartTest smQuitNowAfterStartTest =
+                new StateMachineQuitNowAfterStartTest(
+                        "smQuitNowAfterStartTest", testLooper.getLooper());
+        smQuitNowAfterStartTest.start();
+        smQuitNowAfterStartTest.quitNow();
+        if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart E");
+
+        testLooper.dispatchAll();
+        dumpLogRecs(smQuitNowAfterStartTest.mLogRecs);
+        assertEquals(2, smQuitNowAfterStartTest.mLogRecs.size());
+
+        LogRec lr;
+        Iterator<LogRec> itr = smQuitNowAfterStartTest.mLogRecs.iterator();
+        lr = itr.next();
+        assertEquals(EXIT, lr.getInfo());
+        assertEquals(smQuitNowAfterStartTest.mS1, lr.getState());
+
+        lr = itr.next();
+        assertEquals(ON_QUITTING, lr.getInfo());
+
+        if (smQuitNowAfterStartTest.isDbg()) tlog("testStateMachineQuitNowAfterStart X");
+    }
+
+    /**
      * Test enter/exit can use transitionTo
      */
     class StateMachineEnterExitTransitionToTest extends StateMachine {
diff --git a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
index 7935880..734ebef 100644
--- a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
@@ -47,6 +47,7 @@
     private static final int TEST_ARG2 = 182;
     private static final Object TEST_OBJ = "hello";
 
+    @Mock Context mContext;
     @Mock AlarmManager mAlarmManager;
     WakeupMessage mMessage;
     // Make a spy so that we can verify calls to it
@@ -86,13 +87,12 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        Context context = mock(Context.class);
-        when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+        when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
         // capture the listener for each AlarmManager.setExact call
         doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), any(String.class),
                 mListenerCaptor.capture(), any(Handler.class));
 
-        mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
+        mMessage = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
                 TEST_ARG2, TEST_OBJ);
     }
 
@@ -168,4 +168,19 @@
         verifyMessageDispatchedOnce();
     }
 
+    /**
+     * Verify that a Runnable is scheduled and dispatched.
+     */
+    @Test
+    public void scheduleRunnable() {
+        final long when = 1011;
+        final Runnable runnable = mock(Runnable.class);
+        WakeupMessage dut = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, runnable);
+        dut.schedule(when);
+        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
+                eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
+        mListenerCaptor.getValue().onAlarm();
+        verify(runnable, times(1)).run();
+    }
+
 }
diff --git a/core/tests/webkit/Android.mk b/core/tests/webkit/Android.mk
new file mode 100644
index 0000000..cd0c3d2
--- /dev/null
+++ b/core/tests/webkit/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+	$(call all-java-files-under, unit_tests_src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	android-support-test
+
+LOCAL_PACKAGE_NAME := WebViewLoadingTests
+LOCAL_CERTIFICATE := platform
+
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_REQUIRED_MODULES := \
+	WebViewLoadingOnDiskTestApk \
+	WebViewLoadingFromApkTestApk
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/webkit/AndroidManifest.xml b/core/tests/webkit/AndroidManifest.xml
new file mode 100644
index 0000000..42accdf
--- /dev/null
+++ b/core/tests/webkit/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.webkit.tests"
+          android:sharedUserId="android.uid.system">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.webkit.tests"
+            android:label="Frameworks WebView Loader Tests" />
+
+</manifest>
diff --git a/core/tests/webkit/AndroidTest.xml b/core/tests/webkit/AndroidTest.xml
new file mode 100644
index 0000000..78cfa46
--- /dev/null
+++ b/core/tests/webkit/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Frameworks WebView Loading Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="WebViewLoadingTests.apk" />
+        <option name="test-file-name" value="WebViewLoadingOnDiskTestApk.apk" />
+        <option name="test-file-name" value="WebViewLoadingFromApkTestApk.apk" />
+        <option name="cleanup-apks" value="true" />
+        <option name="alt-dir" value="out" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.webkit.tests" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/core/tests/webkit/apk_with_native_libs/Android.mk b/core/tests/webkit/apk_with_native_libs/Android.mk
new file mode 100644
index 0000000..7c6c36e
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+MY_PATH := $(LOCAL_PATH)
+
+# Set shared variables
+MY_MODULE_TAGS := optional
+MY_JNI_SHARED_LIBRARIES := libwebviewtest_jni
+MY_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+MY_SRC_FILES := $(call all-java-files-under, src)
+MY_SDK_VERSION := system_current
+MY_PROGUARD_ENABLED := disabled
+MY_MULTILIB := both
+
+# Recurse down the file tree.
+include $(call all-subdir-makefiles)
+
+
+
+# Builds an apk containing native libraries that will be unzipped on the device.
+include $(CLEAR_VARS)
+
+LOCAL_PATH := $(MY_PATH)
+LOCAL_PACKAGE_NAME := WebViewLoadingOnDiskTestApk
+LOCAL_MANIFEST_FILE := ondisk/AndroidManifest.xml
+
+LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS)
+LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES)
+LOCAL_MODULE_PATH := $(MY_MODULE_PATH)
+LOCAL_SRC_FILES := $(MY_SRC_FILES)
+LOCAL_SDK_VERSION := $(MY_SDK_VERSION)
+LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED)
+LOCAL_MULTILIB := $(MY_MULTILIB)
+
+include $(BUILD_PACKAGE)
+
+
+# Builds an apk containing uncompressed native libraries that have to be
+# accessed through the APK itself on the device.
+include $(CLEAR_VARS)
+
+LOCAL_PATH := $(MY_PATH)
+LOCAL_PACKAGE_NAME := WebViewLoadingFromApkTestApk
+LOCAL_MANIFEST_FILE := inapk/AndroidManifest.xml
+
+LOCAL_MODULE_TAGS := $(MY_MODULE_TAGS)
+LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES)
+LOCAL_MODULE_PATH := $(MY_MODULE_PATH)
+LOCAL_SRC_FILES := $(MY_SRC_FILES)
+LOCAL_SDK_VERSION := $(MY_SDK_VERSION)
+LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED)
+LOCAL_MULTILIB := $(MY_MULTILIB)
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml
new file mode 100644
index 0000000..868b238
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/inapk/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.webviewloading_test_from_apk"
+    android:versionCode="1"
+    android:versionName="0.0.0.1">
+
+    <application android:label="WebView Loading Test APK"
+      android:multiArch="true"
+      android:extractNativeLibs="false">
+        <meta-data android:name="com.android.webview.WebViewLibrary"
+            android:value="libwebviewtest_jni.so" />
+    </application>
+</manifest>
diff --git a/core/tests/webkit/apk_with_native_libs/jni/Android.mk b/core/tests/webkit/apk_with_native_libs/jni/Android.mk
new file mode 100644
index 0000000..fd5b5eb
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/jni/Android.mk
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libwebviewtest_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := WebViewTestJniOnLoad.cpp
+
+LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_MULTILIB := both
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
new file mode 100644
index 0000000..9b0502f
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
@@ -0,0 +1,21 @@
+/*
+ * 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 <jni.h>
+
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+    return JNI_VERSION_1_4;
+}
diff --git a/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml
new file mode 100644
index 0000000..ffffeb8
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/ondisk/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.webviewloading_test_on_disk"
+    android:versionCode="1"
+    android:versionName="0.0.0.1">
+
+    <application android:label="WebView Loading Test APK"
+      android:multiArch="true">
+        <meta-data android:name="com.android.webview.WebViewLibrary"
+            android:value="libwebviewtest_jni.so" />
+    </application>
+</manifest>
diff --git a/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java
new file mode 100644
index 0000000..0efa4b4
--- /dev/null
+++ b/core/tests/webkit/apk_with_native_libs/src/com/google/android/xts/webview_list/WebViewLoadingTestClass.java
@@ -0,0 +1,24 @@
+/*
+ * 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.webview.chromium;
+
+/**
+ * An empty class for testing purposes.
+ */
+public class WebViewLoadingTestClass {
+}
diff --git a/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java
new file mode 100644
index 0000000..e2f2d37
--- /dev/null
+++ b/core/tests/webkit/unit_tests_src/com/android/webkit/WebViewLibraryLoaderTest.java
@@ -0,0 +1,329 @@
+/*
+ * 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.webkit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link WebViewLibraryLoader}.
+ * Use the following command to run these tests:
+ * make WebViewLoadingTests \
+ * && adb install -r -d \
+ * ${ANDROID_PRODUCT_OUT}/data/app/WebViewLoadingTests/WebViewLoadingTests.apk \
+ * && adb shell am instrument -e class 'android.webkit.WebViewLibraryLoaderTest' -w \
+ * 'com.android.webkit.tests/android.support.test.runner.AndroidJUnitRunner'
+ */
+@RunWith(AndroidJUnit4.class)
+public final class WebViewLibraryLoaderTest {
+    private static final String WEBVIEW_LIBS_ON_DISK_TEST_APK =
+            "com.android.webviewloading_test_on_disk";
+    private static final String WEBVIEW_LIBS_IN_APK_TEST_APK =
+            "com.android.webviewloading_test_from_apk";
+    private static final String WEBVIEW_LOADING_TEST_NATIVE_LIB = "libwebviewtest_jni.so";
+
+    private PackageInfo webviewOnDiskPackageInfo;
+    private PackageInfo webviewFromApkPackageInfo;
+
+    @Before public void setUp() throws PackageManager.NameNotFoundException {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        webviewOnDiskPackageInfo =
+                pm.getPackageInfo(WEBVIEW_LIBS_ON_DISK_TEST_APK, PackageManager.GET_META_DATA);
+        webviewFromApkPackageInfo =
+                pm.getPackageInfo(WEBVIEW_LIBS_IN_APK_TEST_APK, PackageManager.GET_META_DATA);
+    }
+
+    private static boolean is64BitDevice() {
+        return Build.SUPPORTED_64_BIT_ABIS.length > 0;
+    }
+
+    // We test the getWebViewNativeLibraryDirectory method here because it handled several different
+    // cases/combinations and it seems unnecessary to create one test-apk for each such combination
+    // and arch.
+
+    /**
+     * Ensure we fetch the correct native library directories in the multi-arch case where
+     * the primary ABI is 64-bit.
+     */
+    @SmallTest
+    @Test public void testGetWebViewLibDirMultiArchPrimary64bit() {
+        final String nativeLib = "nativeLib";
+        final String secondaryNativeLib = "secondaryNativeLib";
+        PackageInfo packageInfo = new PackageInfo();
+        ApplicationInfo ai = new ApplicationInfoBuilder().
+                // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP
+                setPrimaryCpuAbi("arm64-v8a").
+                setNativeLibraryDir(nativeLib).
+                setSecondaryCpuAbi("armeabi").
+                setSecondaryNativeLibraryDir(secondaryNativeLib).
+                create();
+        packageInfo.applicationInfo = ai;
+        String actual32Lib =
+                WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */);
+        String actual64Lib =
+                WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */);
+        assertEquals(nativeLib, actual64Lib);
+        assertEquals(secondaryNativeLib, actual32Lib);
+    }
+
+    /**
+     * Ensure we fetch the correct native library directory in the 64-bit single-arch case.
+     */
+    @SmallTest
+    @Test public void testGetWebViewLibDirSingleArch64bit() {
+        final String nativeLib = "nativeLib";
+        PackageInfo packageInfo = new PackageInfo();
+        ApplicationInfo ai = new ApplicationInfoBuilder().
+                // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP
+                setPrimaryCpuAbi("arm64-v8a").
+                setNativeLibraryDir(nativeLib).
+                create();
+        packageInfo.applicationInfo = ai;
+        String actual64Lib =
+                WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, true /* is64bit */);
+        assertEquals(nativeLib, actual64Lib);
+    }
+
+    /**
+     * Ensure we fetch the correct native library directory in the 32-bit single-arch case.
+     */
+    @SmallTest
+    @Test public void testGetWebViewLibDirSingleArch32bit() {
+        final String nativeLib = "nativeLib";
+        PackageInfo packageInfo = new PackageInfo();
+        ApplicationInfo ai = new ApplicationInfoBuilder().
+                // See VMRuntime.ABI_TO_INSTRUCTION_SET_MAP
+                setPrimaryCpuAbi("armeabi-v7a").
+                setNativeLibraryDir(nativeLib).
+                create();
+        packageInfo.applicationInfo = ai;
+        String actual32Lib =
+                WebViewLibraryLoader.getWebViewNativeLibraryDirectory(ai, false /* is64bit */);
+        assertEquals(nativeLib, actual32Lib);
+    }
+
+    /**
+     * Ensure we fetch the correct 32-bit library path from an APK with 32-bit and 64-bit
+     * libraries unzipped onto disk.
+     */
+    @MediumTest
+    @Test public void testGetWebViewLibraryPathOnDisk32Bit()
+            throws WebViewFactory.MissingWebViewPackageException {
+        WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewOnDiskPackageInfo, false /* is64bit */);
+        String expectedLibaryDirectory = is64BitDevice() ?
+                webviewOnDiskPackageInfo.applicationInfo.secondaryNativeLibraryDir :
+                webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir;
+        String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB;
+        assertEquals("Fetched incorrect 32-bit path from WebView library.",
+                lib32Path, actualNativeLib.path);
+    }
+
+    /**
+     * Ensure we fetch the correct 64-bit library path from an APK with 32-bit and 64-bit
+     * libraries unzipped onto disk.
+     */
+    @MediumTest
+    @Test public void testGetWebViewLibraryPathOnDisk64Bit()
+            throws WebViewFactory.MissingWebViewPackageException {
+        // A 32-bit device will not unpack 64-bit libraries.
+        if (!is64BitDevice()) return;
+
+        WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewOnDiskPackageInfo, true /* is64bit */);
+        String lib64Path = webviewOnDiskPackageInfo.applicationInfo.nativeLibraryDir
+                + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB;
+        assertEquals("Fetched incorrect 64-bit path from WebView library.",
+                lib64Path, actualNativeLib.path);
+    }
+
+    /**
+     * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit
+     * libraries unzipped onto disk.
+     */
+    @MediumTest
+    @Test public void testGetWebView32BitLibrarySizeOnDiskIsNonZero()
+            throws WebViewFactory.MissingWebViewPackageException {
+        WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewOnDiskPackageInfo, false /* is64bit */);
+        assertTrue(actual32BitNativeLib.size > 0);
+    }
+
+    /**
+     * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit
+     * libraries unzipped onto disk.
+     */
+    @MediumTest
+    @Test public void testGetWebView64BitLibrarySizeOnDiskIsNonZero()
+            throws WebViewFactory.MissingWebViewPackageException {
+        // A 32-bit device will not unpack 64-bit libraries.
+        if (!is64BitDevice()) return;
+        WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewOnDiskPackageInfo, true /* is64bit */);
+        assertTrue(actual64BitNativeLib.size > 0);
+    }
+
+    /**
+     * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit
+     * libraries stored uncompressed in the APK.
+     */
+    @MediumTest
+    @Test public void testGetWebView32BitLibraryPathFromApk()
+            throws WebViewFactory.MissingWebViewPackageException, IOException {
+        WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewFromApkPackageInfo, false /* is64bit */);
+        // The device might have ignored the app's request to not extract native libs, so first
+        // check whether the library paths match those of extracted libraries.
+        String expectedLibaryDirectory = is64BitDevice() ?
+                webviewFromApkPackageInfo.applicationInfo.secondaryNativeLibraryDir :
+                webviewFromApkPackageInfo.applicationInfo.nativeLibraryDir;
+        String lib32Path = expectedLibaryDirectory + "/" + WEBVIEW_LOADING_TEST_NATIVE_LIB;
+        if (lib32Path.equals(actualNativeLib.path)) {
+            // If the libraries were extracted to disk, ensure that they're actually there.
+            assertTrue("The given WebView library doesn't exist.",
+                    new File(actualNativeLib.path).exists());
+        } else { // The libraries were not extracted to disk.
+            assertIsValidZipEntryPath(actualNativeLib.path,
+                    webviewFromApkPackageInfo.applicationInfo.sourceDir);
+        }
+    }
+
+    /**
+     * Ensure we fetch the correct 32-bit library path from an APK with both 32-bit and 64-bit
+     * libraries stored uncompressed in the APK.
+     */
+    @MediumTest
+    @Test public void testGetWebView64BitLibraryPathFromApk()
+            throws WebViewFactory.MissingWebViewPackageException, IOException {
+        // A 32-bit device will not unpack 64-bit libraries.
+        if (!is64BitDevice()) return;
+
+        WebViewLibraryLoader.WebViewNativeLibrary actualNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewFromApkPackageInfo, true /* is64bit */);
+        assertIsValidZipEntryPath(actualNativeLib.path,
+                webviewFromApkPackageInfo.applicationInfo.sourceDir);
+    }
+
+    private static void assertIsValidZipEntryPath(String path, String zipFilePath)
+            throws IOException {
+        assertTrue("The path to a zip entry must start with the path to the zip file itself."
+            + "Expected zip path: " + zipFilePath + ", actual zip entry: " + path,
+            path.startsWith(zipFilePath + "!/"));
+        String[] pathSplit = path.split("!/");
+        assertEquals("A zip file path should have two parts, the zip path, and the zip entry path.",
+                2, pathSplit.length);
+        ZipFile zipFile = new ZipFile(pathSplit[0]);
+        assertNotNull("Path doesn't point to a valid zip entry: " + path,
+                zipFile.getEntry(pathSplit[1]));
+    }
+
+
+    /**
+     * Check the size of the 32-bit library fetched from an APK with both 32-bit and 64-bit
+     * libraries stored uncompressed in the APK.
+     */
+    @MediumTest
+    @Test public void testGetWebView32BitLibrarySizeFromApkIsNonZero()
+            throws WebViewFactory.MissingWebViewPackageException {
+        WebViewLibraryLoader.WebViewNativeLibrary actual32BitNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewFromApkPackageInfo, false /* is64bit */);
+        assertTrue(actual32BitNativeLib.size > 0);
+    }
+
+    /**
+     * Check the size of the 64-bit library fetched from an APK with both 32-bit and 64-bit
+     * libraries stored uncompressed in the APK.
+     */
+    @MediumTest
+    @Test public void testGetWebView64BitLibrarySizeFromApkIsNonZero()
+            throws WebViewFactory.MissingWebViewPackageException {
+        // A 32-bit device will not unpack 64-bit libraries.
+        if (!is64BitDevice()) return;
+
+        WebViewLibraryLoader.WebViewNativeLibrary actual64BitNativeLib =
+                WebViewLibraryLoader.getWebViewNativeLibrary(
+                        webviewFromApkPackageInfo, true /* is64bit */);
+        assertTrue(actual64BitNativeLib.size > 0);
+    }
+
+    private static class ApplicationInfoBuilder {
+        ApplicationInfo ai;
+
+        public ApplicationInfoBuilder setPrimaryCpuAbi(String primaryCpuAbi) {
+            ai.primaryCpuAbi = primaryCpuAbi;
+            return this;
+        }
+
+        public ApplicationInfoBuilder setSecondaryCpuAbi(String secondaryCpuAbi) {
+            ai.secondaryCpuAbi = secondaryCpuAbi;
+            return this;
+        }
+
+        public ApplicationInfoBuilder setNativeLibraryDir(String nativeLibraryDir) {
+            ai.nativeLibraryDir = nativeLibraryDir;
+            return this;
+        }
+
+        public ApplicationInfoBuilder setSecondaryNativeLibraryDir(
+                String secondaryNativeLibraryDir) {
+            ai.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
+            return this;
+        }
+
+        public ApplicationInfoBuilder setMetaData(Bundle metaData) {
+            ai.metaData = metaData;
+            return this;
+        }
+
+        public ApplicationInfoBuilder() {
+            ai = new android.content.pm.ApplicationInfo();
+        }
+
+        public ApplicationInfo create() {
+            return ai;
+        }
+    }
+}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
new file mode 100644
index 0000000..f7a3e1a
--- /dev/null
+++ b/data/etc/OWNERS
@@ -0,0 +1,7 @@
+per-file privapp-permissions-platform.xml = bpoiesz@google.com
+per-file privapp-permissions-platform.xml = fkupolov@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com
+per-file privapp-permissions-platform.xml = jsharkey@android.com
+per-file privapp-permissions-platform.xml = svetoslavganov@google.com
+per-file privapp-permissions-platform.xml = toddke@google.com
+per-file privapp-permissions-platform.xml = yamasani@google.com
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 7a2df85..2f9ae57 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -20,6 +20,14 @@
 applications that come with the platform
 -->
 <permissions>
+    <privapp-permissions package="android.ext.services">
+        <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
+    </privapp-permissions>
+
+    <privapp-permissions package="com.android.apps.tag">
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.backupconfirm">
         <permission name="android.permission.BACKUP"/>
         <permission name="android.permission.CRYPT_KEEPER"/>
@@ -50,6 +58,7 @@
         <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
+        <permission name="android.permission.STATUS_BAR"/>
         <permission name="android.permission.STOP_APP_SWITCHES"/>
         <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/>
         <permission name="com.android.voicemail.permission.WRITE_VOICEMAIL"/>
@@ -64,7 +73,7 @@
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.android.launcher">
+    <privapp-permissions package="com.android.launcher3">
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
     </privapp-permissions>
@@ -253,12 +262,14 @@
     </privapp-permissions>
 
     <privapp-permissions package="com.android.shell">
+        <permission name="android.permission.ACCESS_LOWPAN_STATE"/>
         <permission name="android.permission.BACKUP"/>
         <permission name="android.permission.BATTERY_STATS"/>
         <permission name="android.permission.BIND_APPWIDGET"/>
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
+        <permission name="android.permission.CHANGE_LOWPAN_STATE"/>
         <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
         <permission name="android.permission.CLEAR_APP_CACHE"/>
         <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
@@ -282,6 +293,7 @@
         <permission name="android.permission.MOVE_PACKAGE"/>
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
         <permission name="android.permission.READ_FRAME_BUFFER"/>
+        <permission name="android.permission.READ_LOWPAN_CREDENTIAL"/>
         <permission name="android.permission.REAL_GET_TASKS"/>
         <permission name="android.permission.REGISTER_CALL_PROVIDER"/>
         <permission name="android.permission.REGISTER_CONNECTION_MANAGER"/>
@@ -346,14 +358,18 @@
         <permission name="android.permission.WRITE_DREAM_STATE"/>
         <permission name="android.permission.WRITE_MEDIA_STORAGE"/>
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+        <permission name="com.android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.tv">
-        <permission name="android.permission.DVB_DEVICE" />
-        <permission name="android.permission.GLOBAL_SEARCH" />
-        <permission name="android.permission.MODIFY_PARENTAL_CONTROLS" />
-        <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA" />
-        <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS" />
+        <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
+        <permission name="android.permission.DVB_DEVICE"/>
+        <permission name="android.permission.GLOBAL_SEARCH"/>
+        <permission name="android.permission.HDMI_CEC"/>
+        <permission name="android.permission.MODIFY_PARENTAL_CONTROLS"/>
+        <permission name="android.permission.READ_CONTENT_RATING_SYSTEMS"/>
+        <permission name="com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA"/>
+        <permission name="com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"/>
     </privapp-permissions>
 
     <privapp-permissions package="com.android.vpndialogs">
@@ -361,8 +377,4 @@
         <permission name="android.permission.CONTROL_VPN"/>
     </privapp-permissions>
 
-    <privapp-permissions package="com.google.android.ext.services">
-        <permission name="android.permission.PROVIDE_RESOLVER_RANKER_SERVICE" />
-    </privapp-permissions>
-
 </permissions>
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 57c7549..0072012 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.annotation.WorkerThread;
 import android.content.res.ResourcesImpl;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1233,6 +1234,7 @@
      * @param stream   The outputstream to write the compressed data.
      * @return true if successfully compressed to the specified stream.
      */
+    @WorkerThread
     public boolean compress(CompressFormat format, int quality, OutputStream stream) {
         checkRecycled("Can't compress a recycled bitmap");
         // do explicit check before calling the native method
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index ffb39e3..f5bf754 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -354,6 +354,7 @@
          * decode, in the case of which a more accurate, but slightly slower,
          * IDCT method will be used instead.
          */
+        @Deprecated
         public boolean inPreferQualityOverSpeed;
 
         /**
@@ -412,6 +413,7 @@
          * can check, inbetween the bounds decode and the image decode, to see
          * if the operation is canceled.
          */
+        @Deprecated
         public boolean mCancel;
 
         /**
@@ -426,6 +428,7 @@
          *  or if inJustDecodeBounds is true, will set outWidth/outHeight
          *  to -1
          */
+        @Deprecated
         public void requestCancelDecode() {
             mCancel = true;
         }
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index e3527e3..43fd270 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -658,6 +658,14 @@
      *    float confidence = floatDepthBuffer.get();
      * </pre>
      *
+     * For camera devices that support the
+     * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT DEPTH_OUTPUT}
+     * capability, DEPTH_POINT_CLOUD coordinates have units of meters, and the coordinate system is
+     * defined by the camera's pose transforms:
+     * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_TRANSLATION} and
+     * {@link android.hardware.camera2.CameraCharacteristics#LENS_POSE_ROTATION}. That means the origin is
+     * the optical center of the camera device, and the positive Z axis points along the camera's optical axis,
+     * toward the scene.
      */
     public static final int DEPTH_POINT_CLOUD = 0x101;
 
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index f93886d..96d6eee 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -185,4 +185,52 @@
 
         return false;
     }
+
+    /**
+     * @hide
+     */
+    public static String formatToString(@Format int format) {
+        switch (format) {
+            case UNKNOWN:
+                return "UNKNOWN";
+            case TRANSLUCENT:
+                return "TRANSLUCENT";
+            case TRANSPARENT:
+                return "TRANSPARENT";
+            case RGBA_8888:
+                return "RGBA_8888";
+            case RGBX_8888:
+                return "RGBX_8888";
+            case RGB_888:
+                return "RGB_888";
+            case RGB_565:
+                return "RGB_565";
+            case RGBA_5551:
+                return "RGBA_5551";
+            case RGBA_4444:
+                return "RGBA_4444";
+            case A_8:
+                return "A_8";
+            case L_8:
+                return "L_8";
+            case LA_88:
+                return "LA_88";
+            case RGB_332:
+                return "RGB_332";
+            case YCbCr_422_SP:
+                return "YCbCr_422_SP";
+            case YCbCr_420_SP:
+                return "YCbCr_420_SP";
+            case YCbCr_422_I:
+                return "YCbCr_422_I";
+            case RGBA_F16:
+                return "RGBA_F16";
+            case RGBA_1010102:
+                return "RGBA_1010102";
+            case JPEG:
+                return "JPEG";
+            default:
+                return Integer.toString(format);
+        }
+    }
 }
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 0209cea..40288f5 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -159,8 +159,10 @@
         if (mNativeInstance == 0) {
             mNativeInstance = createNativeInstance(mLocalMatrix == null
                     ? 0 : mLocalMatrix.native_instance);
-            mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
-                    this, mNativeInstance);
+            if (mNativeInstance != 0) {
+                mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
+                        this, mNativeInstance);
+            }
         }
         return mNativeInstance;
     }
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index cfc389f..3d65bd2 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -250,10 +250,10 @@
 
         FontFamily fontFamily = new FontFamily();
         for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
-            // TODO: Add ttc and variation font support. (b/37853920)
             if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
-                    0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
-                    fontFile.getWeight(), fontFile.getItalic(), null /* axes */)) {
+                    0 /* resourceCookie */, false /* isAsset */, fontFile.getTtcIndex(),
+                    fontFile.getWeight(), fontFile.getItalic(),
+                    FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
                 return null;
             }
         }
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 90d6ab8..e74dc6d 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -132,7 +132,7 @@
  *         <td>translateY</td>
  *     </tr>
  *     <tr>
- *         <td rowspan="8">&lt;path&gt;</td>
+ *         <td rowspan="9">&lt;path&gt;</td>
  *         <td>pathData</td>
  *     </tr>
  *     <tr>
@@ -154,6 +154,9 @@
  *         <td>trimPathStart</td>
  *     </tr>
  *     <tr>
+ *         <td>trimPathEnd</td>
+ *     </tr>
+ *     <tr>
  *         <td>trimPathOffset</td>
  *     </tr>
  *     <tr>
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 6bd2646..3bf4f90 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -104,7 +104,7 @@
         final AnimatorSet set = new AnimatorSet();
 
         // Linear exit after enter is completed.
-        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 0);
+        final ObjectAnimator exit = ObjectAnimator.ofFloat(this, OPACITY, 0);
         exit.setInterpolator(LINEAR_INTERPOLATOR);
         exit.setDuration(OPACITY_EXIT_DURATION);
         exit.setAutoCancel(true);
@@ -115,7 +115,7 @@
         final int fastEnterDuration = mIsBounded ?
                 (int) ((1 - mOpacity) * OPACITY_ENTER_DURATION_FAST) : 0;
         if (fastEnterDuration > 0) {
-            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, RippleBackground.OPACITY, 1);
+            final ObjectAnimator enter = ObjectAnimator.ofFloat(this, OPACITY, 1);
             enter.setInterpolator(LINEAR_INTERPOLATOR);
             enter.setDuration(fastEnterDuration);
             enter.setAutoCancel(true);
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 8f314c9..1727eca 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -266,9 +266,9 @@
             }
         }
 
-        setRippleActive(enabled && pressed);
-        setBackgroundActive(hovered || focused || (enabled && pressed), focused || hovered);
+        setRippleActive(focused || (enabled && pressed));
 
+        setBackgroundActive(hovered, hovered);
         return changed;
     }
 
@@ -693,7 +693,9 @@
         // have a mask or content and the ripple bounds if we're projecting.
         final Rect bounds = getDirtyBounds();
         final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
-        canvas.clipRect(bounds);
+        if (isBounded()) {
+            canvas.clipRect(bounds);
+        }
 
         drawContent(canvas);
         drawBackgroundAndRipples(canvas);
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index 829733e..a675eaf 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -41,7 +41,6 @@
 
     // Pixel-based accelerations and velocities.
     private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024;
-    private static final float WAVE_TOUCH_UP_ACCELERATION = 3400;
     private static final float WAVE_OPACITY_DECAY_VELOCITY = 3;
 
     // Bounded ripple animation properties.
@@ -80,17 +79,18 @@
     private float mTweenX = 0;
     private float mTweenY = 0;
 
-    /** Whether this ripple is bounded. */
-    private boolean mIsBounded;
-
     /** Whether this ripple has finished its exit animation. */
     private boolean mHasFinishedExit;
 
+    /**
+     * If we have a bound, don't start from 0. Start from 60% of the max out of width and height.
+     */
+    private float mStartRadius = 0;
+
     public RippleForeground(RippleDrawable owner, Rect bounds, float startingX, float startingY,
             boolean isBounded, boolean forceSoftware) {
         super(owner, bounds, forceSoftware);
 
-        mIsBounded = isBounded;
         mStartingX = startingX;
         mStartingY = startingY;
 
@@ -100,6 +100,8 @@
         } else {
             mBoundedRadius = 0;
         }
+        // Take 60% of the maximum of the width and height, then divided half to get the radius.
+        mStartRadius = Math.max(bounds.width(), bounds.height()) * 0.3f;
     }
 
     @Override
@@ -162,24 +164,18 @@
 
     @Override
     protected Animator createSoftwareEnter(boolean fast) {
-        // Bounded ripples don't have enter animations.
-        if (mIsBounded) {
-            return null;
-        }
-
-        final int duration = (int)
-                (1000 * Math.sqrt(mTargetRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensityScale) + 0.5);
+        final int duration = getRadiusDuration();
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
         tweenRadius.setAutoCancel(true);
         tweenRadius.setDuration(duration);
-        tweenRadius.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenRadius.setInterpolator(DECELERATE_INTERPOLATOR);
         tweenRadius.setStartDelay(RIPPLE_ENTER_DELAY);
 
         final ObjectAnimator tweenOrigin = ObjectAnimator.ofFloat(this, TWEEN_ORIGIN, 1);
         tweenOrigin.setAutoCancel(true);
         tweenOrigin.setDuration(duration);
-        tweenOrigin.setInterpolator(LINEAR_INTERPOLATOR);
+        tweenOrigin.setInterpolator(DECELERATE_INTERPOLATOR);
         tweenOrigin.setStartDelay(RIPPLE_ENTER_DELAY);
 
         final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, OPACITY, 1);
@@ -201,45 +197,29 @@
         return MathUtils.lerp(mClampedStartingY - mBounds.exactCenterY(), mTargetY, mTweenY);
     }
 
-    private int getRadiusExitDuration() {
+    private int getRadiusDuration() {
         final float remainingRadius = mTargetRadius - getCurrentRadius();
-        return (int) (1000 * Math.sqrt(remainingRadius / (WAVE_TOUCH_UP_ACCELERATION
-                + WAVE_TOUCH_DOWN_ACCELERATION) * mDensityScale) + 0.5);
+        return (int) (1000 * Math.sqrt(remainingRadius / WAVE_TOUCH_DOWN_ACCELERATION *
+                mDensityScale) + 0.5);
     }
 
     private float getCurrentRadius() {
-        return MathUtils.lerp(0, mTargetRadius, mTweenRadius);
+        return MathUtils.lerp(mStartRadius, mTargetRadius, mTweenRadius);
     }
 
     private int getOpacityExitDuration() {
         return (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
     }
 
-    /**
-     * Compute target values that are dependent on bounding.
-     */
-    private void computeBoundedTargetValues() {
-        mTargetX = (mClampedStartingX - mBounds.exactCenterX()) * .7f;
-        mTargetY = (mClampedStartingY - mBounds.exactCenterY()) * .7f;
-        mTargetRadius = mBoundedRadius;
-    }
-
     @Override
     protected Animator createSoftwareExit() {
         final int radiusDuration;
         final int originDuration;
         final int opacityDuration;
-        if (mIsBounded) {
-            computeBoundedTargetValues();
 
-            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
-            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
-            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
-        } else {
-            radiusDuration = getRadiusExitDuration();
-            originDuration = radiusDuration;
-            opacityDuration = getOpacityExitDuration();
-        }
+        radiusDuration = getRadiusDuration();
+        originDuration = radiusDuration;
+        opacityDuration = getOpacityExitDuration();
 
         final ObjectAnimator tweenRadius = ObjectAnimator.ofFloat(this, TWEEN_RADIUS, 1);
         tweenRadius.setAutoCancel(true);
@@ -268,17 +248,10 @@
         final int radiusDuration;
         final int originDuration;
         final int opacityDuration;
-        if (mIsBounded) {
-            computeBoundedTargetValues();
 
-            radiusDuration = BOUNDED_RADIUS_EXIT_DURATION;
-            originDuration = BOUNDED_ORIGIN_EXIT_DURATION;
-            opacityDuration = BOUNDED_OPACITY_EXIT_DURATION;
-        } else {
-            radiusDuration = getRadiusExitDuration();
-            originDuration = radiusDuration;
-            opacityDuration = getOpacityExitDuration();
-        }
+        radiusDuration = getRadiusDuration();
+        originDuration = radiusDuration;
+        opacityDuration = getOpacityExitDuration();
 
         final float startX = getCurrentX();
         final float startY = getCurrentY();
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index ceac325..7b2e21a 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -213,12 +213,79 @@
  * &lt;/vector&gt;
  * </pre>
  * </li>
- * <li>And here is an example of linear gradient color, which is supported in SDK 24+.
+ * <h4>Gradient support</h4>
+ * We support 3 types of gradients: {@link android.graphics.LinearGradient},
+ * {@link android.graphics.RadialGradient}, or {@link android.graphics.SweepGradient}.
+ * <p/>
+ * And we support all of 3 types of tile modes {@link android.graphics.Shader.TileMode}:
+ * CLAMP, REPEAT, MIRROR.
+ * <p/>
+ * All of the attributes are listed in {@link android.R.styleable#GradientColor}.
+ * Note that different attributes are relevant for different types of gradient.
+ * <table border="2" align="center" cellpadding="5">
+ *     <thead>
+ *         <tr>
+ *             <th>LinearGradient</th>
+ *             <th>RadialGradient</th>
+ *             <th>SweepGradient</th>
+ *         </tr>
+ *     </thead>
+ *     <tr>
+ *         <td>startColor </td>
+ *         <td>startColor</td>
+ *         <td>startColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>centerColor</td>
+ *         <td>centerColor</td>
+ *         <td>centerColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>endColor</td>
+ *         <td>endColor</td>
+ *         <td>endColor</td>
+ *     </tr>
+ *     <tr>
+ *         <td>type</td>
+ *         <td>type</td>
+ *         <td>type</td>
+ *     </tr>
+ *     <tr>
+ *         <td>tileMode</td>
+ *         <td>tileMode</td>
+ *         <td>tileMode</td>
+ *     </tr>
+ *     <tr>
+ *         <td>startX</td>
+ *         <td>centerX</td>
+ *         <td>centerX</td>
+ *     </tr>
+ *     <tr>
+ *         <td>startY</td>
+ *         <td>centerY</td>
+ *         <td>centerY</td>
+ *     </tr>
+ *     <tr>
+ *         <td>endX</td>
+ *         <td>gradientRadius</td>
+ *         <td></td>
+ *     </tr>
+ *     <tr>
+ *         <td>endY</td>
+ *         <td></td>
+ *         <td></td>
+ *     </tr>
+ * </table>
+ * <p/>
+ * Also note that if any color item {@link android.R.styleable#GradientColorItem} is defined, then
+ * startColor, centerColor and endColor will be ignored.
+ * <p/>
  * See more details in {@link android.R.styleable#GradientColor} and
  * {@link android.R.styleable#GradientColorItem}.
+ * <p/>
+ * Here is a simple example that defines a linear gradient.
  * <pre>
  * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
- *     android:angle="90"
  *     android:startColor="?android:attr/colorPrimary"
  *     android:endColor="?android:attr/colorControlActivated"
  *     android:centerColor="#f00"
@@ -229,7 +296,18 @@
  *     android:type="linear"&gt;
  * &lt;/gradient&gt;
  * </pre>
- * </li>
+ * And here is a simple example that defines a radial gradient using color items.
+ * <pre>
+ * &lt;gradient xmlns:android="http://schemas.android.com/apk/res/android"
+ *     android:centerX="300"
+ *     android:centerY="300"
+ *     android:gradientRadius="100"
+ *     android:type="radial"&gt;
+ *     &lt;item android:offset="0.1" android:color="#0ff"/&gt;
+ *     &lt;item android:offset="0.4" android:color="#fff"/&gt;
+ *     &lt;item android:offset="0.9" android:color="#ff0"/&gt;
+ * &lt;/gradient&gt;
+ * </pre>
  *
  */
 
diff --git a/graphics/java/android/graphics/pdf/PdfEditor.java b/graphics/java/android/graphics/pdf/PdfEditor.java
index 0c509b7..3821bc7 100644
--- a/graphics/java/android/graphics/pdf/PdfEditor.java
+++ b/graphics/java/android/graphics/pdf/PdfEditor.java
@@ -23,6 +23,7 @@
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import dalvik.system.CloseGuard;
 import libcore.io.IoUtils;
@@ -72,8 +73,8 @@
 
         final long size;
         try {
-            Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
-            size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+            size = Os.fstat(input.getFileDescriptor()).st_size;
         } catch (ErrnoException ee) {
             throw new IllegalArgumentException("file descriptor not seekable");
         }
diff --git a/graphics/java/android/graphics/pdf/PdfRenderer.java b/graphics/java/android/graphics/pdf/PdfRenderer.java
index c82ab0d..4a91705 100644
--- a/graphics/java/android/graphics/pdf/PdfRenderer.java
+++ b/graphics/java/android/graphics/pdf/PdfRenderer.java
@@ -26,12 +26,12 @@
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import com.android.internal.util.Preconditions;
 import dalvik.system.CloseGuard;
 
 import libcore.io.IoUtils;
-import libcore.io.Libcore;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -156,8 +156,8 @@
 
         final long size;
         try {
-            Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
-            size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
+            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
+            size = Os.fstat(input.getFileDescriptor()).st_size;
         } catch (ErrnoException ee) {
             throw new IllegalArgumentException("file descriptor not seekable");
         }
diff --git a/legacy-test/Android.mk b/legacy-test/Android.mk
index 0e6b31e..bec22c9 100644
--- a/legacy-test/Android.mk
+++ b/legacy-test/Android.mk
@@ -93,6 +93,8 @@
 LOCAL_MODULE := legacy.test.stubs
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
+LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
 
 # Make sure to run droiddoc first to generate the stub source files.
 LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
@@ -162,3 +164,5 @@
 
 include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
 endif  # HOST_OS == linux
+
+legacy_test_api_gen_stamp :=
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index e764034..5484cf0 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -14,15 +14,27 @@
 
 // libandroidfw is partially built for the host (used by obbtool, aapt, and others)
 
-cc_library {
-    name: "libandroidfw",
-    host_supported: true,
+cc_defaults {
+    name: "libandroidfw_defaults",
     cflags: [
-        "-Wall",
         "-Werror",
-        "-Wunused",
         "-Wunreachable-code",
     ],
+    target: {
+        windows: {
+            // The Windows compiler warns incorrectly for value initialization with {}.
+            cppflags: ["-Wno-missing-field-initializers"],
+        },
+        host: {
+            cflags: ["-DSTATIC_ANDROIDFW_FOR_TOOLS"],
+        },
+    },
+}
+
+cc_library {
+    name: "libandroidfw",
+    defaults: ["libandroidfw_defaults"],
+    host_supported: true,
     srcs: [
         "ApkAssets.cpp",
         "Asset.cpp",
@@ -31,6 +43,7 @@
         "AssetManager2.cpp",
         "AttributeResolution.cpp",
         "ChunkIterator.cpp",
+        "Idmap.cpp",
         "LoadedArsc.cpp",
         "LocaleData.cpp",
         "misc.cpp",
@@ -67,7 +80,6 @@
             },
         },
         host: {
-            cflags: ["-DSTATIC_ANDROIDFW_FOR_TOOLS"],
             shared: {
                 enabled: false,
             },
@@ -79,14 +91,87 @@
                 "libutils",
             ],
             shared_libs: [
-                "libz-host",
+                "libz",
             ],
         },
         windows: {
             enabled: true,
-            cppflags: ["-Wno-missing-field-initializers"],  // The Windows compiler warns
-                                                            // incorrectly for value
-                                                            // initialization with {}.
         },
     },
 }
+
+common_test_libs = [
+    "libandroidfw",
+    "libbase",
+    "libcutils",
+    "libutils",
+    "libziparchive",
+]
+
+cc_test {
+    name: "libandroidfw_tests",
+    host_supported: true,
+    defaults: ["libandroidfw_defaults"],
+    cppflags: [
+        // This is to suppress warnings/errors from gtest
+        "-Wno-unnamed-type-template-args",
+    ],
+    srcs: [
+        // Helpers/infra for testing.
+        "tests/CommonHelpers.cpp",
+        "tests/TestHelpers.cpp",
+        "tests/TestMain.cpp",
+
+        // Actual tests.
+        "tests/ApkAssets_test.cpp",
+        "tests/AppAsLib_test.cpp",
+        "tests/Asset_test.cpp",
+        "tests/AssetManager2_test.cpp",
+        "tests/AttributeFinder_test.cpp",
+        "tests/AttributeResolution_test.cpp",
+        "tests/ByteBucketArray_test.cpp",
+        "tests/Config_test.cpp",
+        "tests/ConfigLocale_test.cpp",
+        "tests/Idmap_test.cpp",
+        "tests/LoadedArsc_test.cpp",
+        "tests/ResourceUtils_test.cpp",
+        "tests/ResTable_test.cpp",
+        "tests/Split_test.cpp",
+        "tests/StringPiece_test.cpp",
+        "tests/Theme_test.cpp",
+        "tests/TypeWrappers_test.cpp",
+        "tests/ZipUtils_test.cpp",
+    ],
+    target: {
+        android: {
+            srcs: [
+                "tests/BackupData_test.cpp",
+                "tests/ObbFile_test.cpp",
+            ],
+            shared_libs: common_test_libs + ["libui"],
+        },
+        host: {
+            static_libs: common_test_libs + ["liblog", "libz"],
+        },
+    },
+    data: ["tests/data/**/*.apk"],
+}
+
+cc_benchmark {
+    name: "libandroidfw_benchmarks",
+    defaults: ["libandroidfw_defaults"],
+    srcs: [
+        // Helpers/infra for benchmarking.
+        "tests/BenchMain.cpp",
+        "tests/BenchmarkHelpers.cpp",
+        "tests/CommonHelpers.cpp",
+
+        // Actual benchmarks.
+        "tests/AssetManager2_bench.cpp",
+        "tests/SparseEntry_bench.cpp",
+        "tests/Theme_bench.cpp",
+    ],
+    shared_libs: common_test_libs,
+    data: ["tests/data/**/*.apk"],
+}
+
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
deleted file mode 100644
index 68c51ef..0000000
--- a/libs/androidfw/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH:= $(call my-dir)
-
-# Include subdirectory makefiles
-# ============================================================
-
-# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
-# team really wants is to build the stuff defined by this makefile.
-ifeq (,$(ONE_SHOT_MAKEFILE))
-include $(call first-makefiles-under,$(LOCAL_PATH))
-endif
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index a5b1d29..158df13 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -20,62 +20,126 @@
 
 #include <algorithm>
 
+#include "android-base/errors.h"
+#include "android-base/file.h"
 #include "android-base/logging.h"
+#include "android-base/unique_fd.h"
+#include "android-base/utf8.h"
+#include "utils/Compat.h"
 #include "utils/FileMap.h"
 #include "utils/Trace.h"
 #include "ziparchive/zip_archive.h"
 
 #include "androidfw/Asset.h"
+#include "androidfw/Idmap.h"
+#include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 
 namespace android {
 
+using base::SystemErrorCodeToString;
+using base::unique_fd;
+
+static const std::string kResourcesArsc("resources.arsc");
+
+ApkAssets::ApkAssets(void* unmanaged_handle, const std::string& path)
+    : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path) {
+}
+
 std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
-  return ApkAssets::LoadImpl(path, system, false /*load_as_shared_library*/);
+  return ApkAssets::LoadImpl(path, nullptr, nullptr, system, false /*load_as_shared_library*/);
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
                                                                 bool system) {
-  return ApkAssets::LoadImpl(path, system, true /*load_as_shared_library*/);
+  return ApkAssets::LoadImpl(path, nullptr, nullptr, system, true /*load_as_shared_library*/);
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(const std::string& path, bool system,
-                                                     bool load_as_shared_library) {
+std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
+                                                        bool system) {
+  std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
+  if (idmap_asset == nullptr) {
+    return {};
+  }
+
+  const StringPiece idmap_data(
+      reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
+      static_cast<size_t>(idmap_asset->getLength()));
+  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data);
+  if (loaded_idmap == nullptr) {
+    LOG(ERROR) << "failed to load IDMAP " << idmap_path;
+    return {};
+  }
+  return LoadImpl(loaded_idmap->OverlayApkPath(), std::move(idmap_asset), std::move(loaded_idmap),
+                  system, false /*load_as_shared_library*/);
+}
+
+std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
+  unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
+  if (fd == -1) {
+    LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
+    return {};
+  }
+
+  const off64_t file_len = lseek64(fd, 0, SEEK_END);
+  if (file_len < 0) {
+    LOG(ERROR) << "Failed to get size of file '" << path << "': " << SystemErrorCodeToString(errno);
+    return {};
+  }
+
+  std::unique_ptr<FileMap> file_map = util::make_unique<FileMap>();
+  if (!file_map->create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
+    LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno);
+    return {};
+  }
+  return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
+    const std::string& path, std::unique_ptr<Asset> idmap_asset,
+    std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
   ATRACE_CALL();
   ::ZipArchiveHandle unmanaged_handle;
   int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
   if (result != 0) {
-    LOG(ERROR) << ::ErrorCodeString(result);
+    LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
     return {};
   }
 
   // Wrap the handle in a unique_ptr so it gets automatically closed.
-  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets());
-  loaded_apk->zip_handle_.reset(unmanaged_handle);
+  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));
 
-  ::ZipString entry_name("resources.arsc");
+  // Find the resource table.
+  ::ZipString entry_name(kResourcesArsc.c_str());
   ::ZipEntry entry;
   result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
   if (result != 0) {
-    LOG(ERROR) << ::ErrorCodeString(result);
-    return {};
+    // There is no resources.arsc, so create an empty LoadedArsc and return.
+    loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
+    return std::move(loaded_apk);
   }
 
   if (entry.method == kCompressDeflated) {
-    LOG(WARNING) << "resources.arsc is compressed.";
+    LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed.";
   }
 
-  loaded_apk->path_ = path;
-  loaded_apk->resources_asset_ =
-      loaded_apk->Open("resources.arsc", Asset::AccessMode::ACCESS_BUFFER);
+  // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
+  loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
   if (loaded_apk->resources_asset_ == nullptr) {
+    LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
     return {};
   }
 
+  // Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
+  loaded_apk->idmap_asset_ = std::move(idmap_asset);
+
+  const StringPiece data(
+      reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
+      loaded_apk->resources_asset_->getLength());
   loaded_apk->loaded_arsc_ =
-      LoadedArsc::Load(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/),
-                       loaded_apk->resources_asset_->getLength(), system, load_as_shared_library);
+      LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
   if (loaded_apk->loaded_arsc_ == nullptr) {
+    LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
     return {};
   }
 
@@ -91,7 +155,6 @@
   ::ZipEntry entry;
   int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
   if (result != 0) {
-    LOG(ERROR) << "No entry '" << path << "' found in APK '" << path_ << "'";
     return {};
   }
 
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 5603508..3c8736e 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -150,6 +150,14 @@
         ALOGI("Destroying AssetManager in %p #%d\n", this, count);
     }
 
+    // Manually close any fd paths for which we have not yet opened their zip (which
+    // will take ownership of the fd and close it when done).
+    for (size_t i=0; i<mAssetPaths.size(); i++) {
+        if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) {
+            close(mAssetPaths[i].rawFd);
+        }
+    }
+
     delete mConfig;
     delete mResources;
 
@@ -280,7 +288,35 @@
     }
 
     return true;
- }
+}
+
+bool AssetManager::addAssetFd(
+        int fd, const String8& debugPathName, int32_t* cookie, bool appAsLib,
+        bool assume_ownership) {
+    AutoMutex _l(mLock);
+
+    asset_path ap;
+
+    ap.path = debugPathName;
+    ap.rawFd = fd;
+    ap.type = kFileTypeRegular;
+    ap.assumeOwnership = assume_ownership;
+
+    ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string());
+
+    mAssetPaths.add(ap);
+
+    // new paths are always added at the end
+    if (cookie) {
+        *cookie = static_cast<int32_t>(mAssetPaths.size());
+    }
+
+    if (mResources != NULL) {
+        appendPathToResTable(ap, appAsLib);
+    }
+
+    return true;
+}
 
 bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath,
         uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize)
@@ -505,7 +541,7 @@
     Asset* idmap = openIdmapLocked(ap);
     size_t nextEntryIdx = mResources->getTableCount();
     ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
-    if (ap.type != kFileTypeDirectory) {
+    if (ap.type != kFileTypeDirectory && ap.rawFd < 0) {
         if (nextEntryIdx == 0) {
             // The first item is typically the framework resources,
             // which we want to avoid parsing every time.
@@ -738,6 +774,8 @@
 {
     Asset* pAsset = NULL;
 
+    ALOGV("openNonAssetInPath: name=%s type=%d fd=%d", fileName, ap.type, ap.rawFd);
+
     /* look at the filesystem on disk */
     if (ap.type == kFileTypeDirectory) {
         String8 path(ap.path);
@@ -752,7 +790,7 @@
         }
 
         if (pAsset != NULL) {
-            //printf("FOUND NA '%s' on disk\n", fileName);
+            ALOGV("FOUND NA '%s' on disk", fileName);
             pAsset->setAssetSource(path);
         }
 
@@ -763,10 +801,10 @@
         /* check the appropriate Zip file */
         ZipFileRO* pZip = getZipFileLocked(ap);
         if (pZip != NULL) {
-            //printf("GOT zip, checking NA '%s'\n", (const char*) path);
+            ALOGV("GOT zip, checking NA '%s'", (const char*) path);
             ZipEntryRO entry = pZip->findEntryByName(path.string());
             if (entry != NULL) {
-                //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+                ALOGV("FOUND NA in Zip file for %s", (const char*) path);
                 pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
                 pZip->releaseEntry(entry);
             }
@@ -817,7 +855,17 @@
 {
     ALOGV("getZipFileLocked() in %p\n", this);
 
-    return mZipSet.getZip(ap.path);
+    if (ap.zip != NULL) {
+        return ap.zip->getZip();
+    }
+
+    if (ap.rawFd < 0) {
+        ap.zip = mZipSet.getSharedZip(ap.path);
+    } else {
+        ap.zip = SharedZip::create(ap.rawFd, ap.path);
+
+    }
+    return ap.zip != NULL ? ap.zip->getZip() : NULL;
 }
 
 /*
@@ -1374,6 +1422,21 @@
     }
 }
 
+AssetManager::SharedZip::SharedZip(int fd, const String8& path)
+    : mPath(path), mZipFile(NULL), mModWhen(0),
+      mResourceTableAsset(NULL), mResourceTable(NULL)
+{
+    if (kIsDebug) {
+        ALOGI("Creating SharedZip %p fd=%d %s\n", this, fd, (const char*)mPath);
+    }
+    ALOGV("+++ opening zip fd=%d '%s'\n", fd, mPath.string());
+    mZipFile = ZipFileRO::openFd(fd, mPath.string());
+    if (mZipFile == NULL) {
+        ::close(fd);
+        ALOGD("failed to open Zip archive fd=%d '%s'\n", fd, mPath.string());
+    }
+}
+
 sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path,
         bool createIfNotPresent)
 {
@@ -1389,7 +1452,11 @@
     zip = new SharedZip(path, modWhen);
     gOpen.add(path, zip);
     return zip;
+}
 
+sp<AssetManager::SharedZip> AssetManager::SharedZip::create(int fd, const String8& path)
+{
+    return new SharedZip(fd, path);
 }
 
 ZipFileRO* AssetManager::SharedZip::getZip()
@@ -1500,19 +1567,23 @@
     mZipFile.editItemAt(idx) = NULL;
 }
 
-
 /*
  * Retrieve the appropriate Zip file from the set.
  */
 ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
 {
+    return getSharedZip(path)->getZip();
+}
+
+const sp<AssetManager::SharedZip> AssetManager::ZipSet::getSharedZip(const String8& path)
+{
     int idx = getIndex(path);
     sp<SharedZip> zip = mZipFile[idx];
     if (zip == NULL) {
         zip = SharedZip::get(path);
         mZipFile.editItemAt(idx) = zip;
     }
-    return zip->getZip();
+    return zip;
 }
 
 Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ab7e14d..83c82af 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -18,6 +18,7 @@
 
 #include "androidfw/AssetManager2.h"
 
+#include <iterator>
 #include <set>
 
 #include "android-base/logging.h"
@@ -35,7 +36,9 @@
 
 namespace android {
 
-AssetManager2::AssetManager2() { memset(&configuration_, 0, sizeof(configuration_)); }
+AssetManager2::AssetManager2() {
+  memset(&configuration_, 0, sizeof(configuration_));
+}
 
 bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets,
                                  bool invalidate_caches) {
@@ -55,9 +58,9 @@
   int next_package_id = 0x02;
   const size_t apk_assets_count = apk_assets_.size();
   for (size_t i = 0; i < apk_assets_count; i++) {
-    const ApkAssets* apk_asset = apk_assets_[i];
-    for (const std::unique_ptr<const LoadedPackage>& package :
-         apk_asset->GetLoadedArsc()->GetPackages()) {
+    const LoadedArsc* loaded_arsc = apk_assets_[i]->GetLoadedArsc();
+
+    for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
       // Get the package ID or assign one if a shared library.
       int package_id;
       if (package->IsDynamic()) {
@@ -261,9 +264,7 @@
 }
 
 ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
-                                         bool stop_at_first_match, LoadedArscEntry* out_entry,
-                                         ResTable_config* out_selected_config,
-                                         uint32_t* out_flags) {
+                                         bool stop_at_first_match, FindEntryResult* out_entry) {
   ATRACE_CALL();
 
   // Might use this if density_override != 0.
@@ -292,29 +293,28 @@
     return kInvalidCookie;
   }
 
-  LoadedArscEntry best_entry;
-  ResTable_config best_config;
+  FindEntryResult best_entry;
   ApkAssetsCookie best_cookie = kInvalidCookie;
   uint32_t cumulated_flags = 0u;
 
   const PackageGroup& package_group = package_groups_[idx];
   const size_t package_count = package_group.packages_.size();
   for (size_t i = 0; i < package_count; i++) {
-    LoadedArscEntry current_entry;
-    ResTable_config current_config;
-    uint32_t current_flags = 0;
-
     const LoadedPackage* loaded_package = package_group.packages_[i];
-    if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, &current_entry,
-                                   &current_config, &current_flags)) {
+
+    FindEntryResult current_entry;
+    if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, &current_entry)) {
       continue;
     }
 
-    cumulated_flags |= current_flags;
+    cumulated_flags |= current_entry.type_flags;
 
-    if (best_cookie == kInvalidCookie || current_config.isBetterThan(best_config, desired_config)) {
+    const ResTable_config* current_config = current_entry.config;
+    const ResTable_config* best_config = best_entry.config;
+    if (best_cookie == kInvalidCookie ||
+        current_config->isBetterThan(*best_config, desired_config) ||
+        (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) {
       best_entry = current_entry;
-      best_config = current_config;
       best_cookie = package_group.cookies_[i];
       if (stop_at_first_match) {
         break;
@@ -328,19 +328,16 @@
 
   *out_entry = best_entry;
   out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
-  *out_selected_config = best_config;
-  *out_flags = cumulated_flags;
+  out_entry->type_flags = cumulated_flags;
   return best_cookie;
 }
 
 bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
   ATRACE_CALL();
 
-  LoadedArscEntry entry;
-  ResTable_config config;
-  uint32_t flags = 0u;
-  ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
-                                     true /* stop_at_first_match */, &entry, &config, &flags);
+  FindEntryResult entry;
+  ApkAssetsCookie cookie =
+      FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */, &entry);
   if (cookie == kInvalidCookie) {
     return false;
   }
@@ -374,11 +371,14 @@
 }
 
 bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
-  LoadedArscEntry entry;
-  ResTable_config config;
-  ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
-                                     false /* stop_at_first_match */, &entry, &config, out_flags);
-  return cookie != kInvalidCookie;
+  FindEntryResult entry;
+  ApkAssetsCookie cookie =
+      FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
+  if (cookie != kInvalidCookie) {
+    *out_flags = entry.type_flags;
+    return cookie;
+  }
+  return kInvalidCookie;
 }
 
 ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
@@ -387,11 +387,9 @@
                                            uint32_t* out_flags) {
   ATRACE_CALL();
 
-  LoadedArscEntry entry;
-  ResTable_config config;
-  uint32_t flags = 0u;
+  FindEntryResult entry;
   ApkAssetsCookie cookie =
-      FindEntry(resid, density_override, false /* stop_at_first_match */, &entry, &config, &flags);
+      FindEntry(resid, density_override, false /* stop_at_first_match */, &entry);
   if (cookie == kInvalidCookie) {
     return kInvalidCookie;
   }
@@ -405,8 +403,8 @@
     // Create a reference since we can't represent this complex type as a Res_value.
     out_value->dataType = Res_value::TYPE_REFERENCE;
     out_value->data = resid;
-    *out_selected_config = config;
-    *out_flags = flags;
+    *out_selected_config = *entry.config;
+    *out_flags = entry.type_flags;
     return cookie;
   }
 
@@ -417,8 +415,8 @@
   // Convert the package ID to the runtime assigned package ID.
   entry.dynamic_ref_table->lookupResourceValue(out_value);
 
-  *out_selected_config = config;
-  *out_flags = flags;
+  *out_selected_config = *entry.config;
+  *out_flags = entry.type_flags;
   return cookie;
 }
 
@@ -461,11 +459,9 @@
     return cached_iter->second.get();
   }
 
-  LoadedArscEntry entry;
-  ResTable_config config;
-  uint32_t flags = 0u;
-  ApkAssetsCookie cookie = FindEntry(resid, 0u /* density_override */,
-                                     false /* stop_at_first_match */, &entry, &config, &flags);
+  FindEntryResult entry;
+  ApkAssetsCookie cookie =
+      FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
   if (cookie == kInvalidCookie) {
     return nullptr;
   }
@@ -503,13 +499,20 @@
         }
       }
       new_entry->cookie = cookie;
-      new_entry->value.copyFrom_dtoh(map_entry->value);
       new_entry->key = new_key;
       new_entry->key_pool = nullptr;
       new_entry->type_pool = nullptr;
+      new_entry->value.copyFrom_dtoh(map_entry->value);
+      status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+      if (err != NO_ERROR) {
+        LOG(ERROR) << base::StringPrintf(
+            "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
+            new_entry->value.data, new_key);
+        return nullptr;
+      }
       ++new_entry;
     }
-    new_bag->type_spec_flags = flags;
+    new_bag->type_spec_flags = entry.type_flags;
     new_bag->entry_count = static_cast<uint32_t>(entry_count);
     ResolvedBag* result = new_bag.get();
     cached_bags_[resid] = std::move(new_bag);
@@ -527,9 +530,6 @@
     return nullptr;
   }
 
-  // Combine flags from the parent and our own bag.
-  flags |= parent_bag->type_spec_flags;
-
   // Create the max possible entries we can make. Once we construct the bag,
   // we will realloc to fit to size.
   const size_t max_count = parent_bag->entry_count + dtohl(map->count);
@@ -554,10 +554,17 @@
       // Use the child key if it comes before the parent
       // or is equal to the parent (overrides).
       new_entry->cookie = cookie;
-      new_entry->value.copyFrom_dtoh(map_entry->value);
       new_entry->key = child_key;
       new_entry->key_pool = nullptr;
       new_entry->type_pool = nullptr;
+      new_entry->value.copyFrom_dtoh(map_entry->value);
+      status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+      if (err != NO_ERROR) {
+        LOG(ERROR) << base::StringPrintf(
+            "Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.", new_entry->value.dataType,
+            new_entry->value.data, child_key);
+        return nullptr;
+      }
       ++map_entry;
     } else {
       // Take the parent entry as-is.
@@ -582,10 +589,16 @@
       }
     }
     new_entry->cookie = cookie;
-    new_entry->value.copyFrom_dtoh(map_entry->value);
     new_entry->key = new_key;
     new_entry->key_pool = nullptr;
     new_entry->type_pool = nullptr;
+    new_entry->value.copyFrom_dtoh(map_entry->value);
+    status_t err = entry.dynamic_ref_table->lookupResourceValue(&new_entry->value);
+    if (err != NO_ERROR) {
+      LOG(ERROR) << base::StringPrintf("Failed to resolve value t=0x%02x d=0x%08x for key 0x%08x.",
+                                       new_entry->value.dataType, new_entry->value.data, new_key);
+      return nullptr;
+    }
     ++map_entry;
     ++new_entry;
   }
@@ -605,7 +618,8 @@
         new_bag.release(), sizeof(ResolvedBag) + (actual_count * sizeof(ResolvedBag::Entry)))));
   }
 
-  new_bag->type_spec_flags = flags;
+  // Combine flags from the parent and our own bag.
+  new_bag->type_spec_flags = entry.type_flags | parent_bag->type_spec_flags;
   new_bag->entry_count = static_cast<uint32_t>(actual_count);
   ResolvedBag* result = new_bag.get();
   cached_bags_[resid] = std::move(new_bag);
@@ -698,7 +712,37 @@
   }
 }
 
-std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); }
+std::unique_ptr<Theme> AssetManager2::NewTheme() {
+  return std::unique_ptr<Theme>(new Theme(this));
+}
+
+Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
+}
+
+Theme::~Theme() = default;
+
+namespace {
+
+struct ThemeEntry {
+  ApkAssetsCookie cookie;
+  uint32_t type_spec_flags;
+  Res_value value;
+};
+
+struct ThemeType {
+  int entry_count;
+  ThemeEntry entries[0];
+};
+
+constexpr size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
+
+}  // namespace
+
+struct Theme::Package {
+  // Each element of Type will be a dynamically sized object
+  // allocated to have the entries stored contiguously with the Type.
+  std::array<util::unique_cptr<ThemeType>, kTypeCount> types;
+};
 
 bool Theme::ApplyStyle(uint32_t resid, bool force) {
   ATRACE_CALL();
@@ -711,71 +755,69 @@
   // Merge the flags from this style.
   type_spec_flags_ |= bag->type_spec_flags;
 
-  // On the first iteration, verify the attribute IDs and
-  // update the entry count in each type.
-  const auto bag_iter_end = end(bag);
-  for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
+  int last_type_idx = -1;
+  int last_package_idx = -1;
+  Package* last_package = nullptr;
+  ThemeType* last_type = nullptr;
+
+  // Iterate backwards, because each bag is sorted in ascending key ID order, meaning we will only
+  // need to perform one resize per type.
+  using reverse_bag_iterator = std::reverse_iterator<const ResolvedBag::Entry*>;
+  const auto bag_iter_end = reverse_bag_iterator(begin(bag));
+  for (auto bag_iter = reverse_bag_iterator(end(bag)); bag_iter != bag_iter_end; ++bag_iter) {
     const uint32_t attr_resid = bag_iter->key;
 
-    // If the resource ID passed in is not a style, the key can be
-    // some other identifier that is not a resource ID.
+    // If the resource ID passed in is not a style, the key can be some other identifier that is not
+    // a resource ID. We should fail fast instead of operating with strange resource IDs.
     if (!is_valid_resid(attr_resid)) {
       return false;
     }
 
-    const uint32_t package_idx = get_package_id(attr_resid);
+    // We don't use the 0-based index for the type so that we can avoid doing ID validation
+    // upon lookup. Instead, we keep space for the type ID 0 in our data structures. Since
+    // the construction of this type is guarded with a resource ID check, it will never be
+    // populated, and querying type ID 0 will always fail.
+    const int package_idx = get_package_id(attr_resid);
+    const int type_idx = get_type_id(attr_resid);
+    const int entry_idx = get_entry_id(attr_resid);
 
-    // The type ID is 1-based, so subtract 1 to get an index.
-    const uint32_t type_idx = get_type_id(attr_resid) - 1;
-    const uint32_t entry_idx = get_entry_id(attr_resid);
-
-    std::unique_ptr<Package>& package = packages_[package_idx];
-    if (package == nullptr) {
-      package.reset(new Package());
-    }
-
-    util::unique_cptr<Type>& type = package->types[type_idx];
-    if (type == nullptr) {
-      // Set the initial capacity to take up a total amount of 1024 bytes.
-      constexpr uint32_t kInitialCapacity = (1024u - sizeof(Type)) / sizeof(Entry);
-      const uint32_t initial_capacity = std::max(entry_idx, kInitialCapacity);
-      type.reset(
-          reinterpret_cast<Type*>(calloc(sizeof(Type) + (initial_capacity * sizeof(Entry)), 1)));
-      type->entry_capacity = initial_capacity;
-    }
-
-    // Set the entry_count to include this entry. We will populate
-    // and resize the array as necessary in the next pass.
-    if (entry_idx + 1 > type->entry_count) {
-      // Increase the entry count to include this.
-      type->entry_count = entry_idx + 1;
-    }
-  }
-
-  // On the second pass, we will realloc to fit the entry counts
-  // and populate the structures.
-  for (auto bag_iter = begin(bag); bag_iter != bag_iter_end; ++bag_iter) {
-    const uint32_t attr_resid = bag_iter->key;
-    const uint32_t package_idx = get_package_id(attr_resid);
-    const uint32_t type_idx = get_type_id(attr_resid) - 1;
-    const uint32_t entry_idx = get_entry_id(attr_resid);
-    Package* package = packages_[package_idx].get();
-    util::unique_cptr<Type>& type = package->types[type_idx];
-    if (type->entry_count != type->entry_capacity) {
-      // Resize to fit the actual entries that will be included.
-      Type* type_ptr = type.release();
-      type.reset(reinterpret_cast<Type*>(
-          realloc(type_ptr, sizeof(Type) + (type_ptr->entry_count * sizeof(Entry)))));
-      if (type->entry_capacity < type->entry_count) {
-        // Clear the newly allocated memory (which does not get zero initialized).
-        // We need to do this because we |= type_spec_flags.
-        memset(type->entries + type->entry_capacity, 0,
-               sizeof(Entry) * (type->entry_count - type->entry_capacity));
+    if (last_package_idx != package_idx) {
+      std::unique_ptr<Package>& package = packages_[package_idx];
+      if (package == nullptr) {
+        package.reset(new Package());
       }
-      type->entry_capacity = type->entry_count;
+      last_package_idx = package_idx;
+      last_package = package.get();
+      last_type_idx = -1;
     }
-    Entry& entry = type->entries[entry_idx];
-    if (force || entry.value.dataType == Res_value::TYPE_NULL) {
+
+    if (last_type_idx != type_idx) {
+      util::unique_cptr<ThemeType>& type = last_package->types[type_idx];
+      if (type == nullptr) {
+        // Allocate enough memory to contain this entry_idx. Since we're iterating in reverse over
+        // a sorted list of attributes, this shouldn't be resized again during this method call.
+        type.reset(reinterpret_cast<ThemeType*>(
+            calloc(sizeof(ThemeType) + (entry_idx + 1) * sizeof(ThemeEntry), 1)));
+        type->entry_count = entry_idx + 1;
+      } else if (entry_idx >= type->entry_count) {
+        // Reallocate the memory to contain this entry_idx. Since we're iterating in reverse over
+        // a sorted list of attributes, this shouldn't be resized again during this method call.
+        const int new_count = entry_idx + 1;
+        type.reset(reinterpret_cast<ThemeType*>(
+            realloc(type.release(), sizeof(ThemeType) + (new_count * sizeof(ThemeEntry)))));
+
+        // Clear out the newly allocated space (which isn't zeroed).
+        memset(type->entries + type->entry_count, 0,
+               (new_count - type->entry_count) * sizeof(ThemeEntry));
+        type->entry_count = new_count;
+      }
+      last_type_idx = type_idx;
+      last_type = type.get();
+    }
+
+    ThemeEntry& entry = last_type->entries[entry_idx];
+    if (force || (entry.value.dataType == Res_value::TYPE_NULL &&
+                  entry.value.data != Res_value::DATA_NULL_EMPTY)) {
       entry.cookie = bag_iter->cookie;
       entry.type_spec_flags |= bag->type_spec_flags;
       entry.value = bag_iter->value;
@@ -786,88 +828,46 @@
 
 ApkAssetsCookie Theme::GetAttribute(uint32_t resid, Res_value* out_value,
                                     uint32_t* out_flags) const {
-  constexpr const int kMaxIterations = 20;
+  int cnt = 20;
 
   uint32_t type_spec_flags = 0u;
 
-  for (int iterations_left = kMaxIterations; iterations_left > 0; iterations_left--) {
-    if (!is_valid_resid(resid)) {
-      return kInvalidCookie;
-    }
-
-    const uint32_t package_idx = get_package_id(resid);
-
-    // Type ID is 1-based, subtract 1 to get the index.
-    const uint32_t type_idx = get_type_id(resid) - 1;
-    const uint32_t entry_idx = get_entry_id(resid);
-
+  do {
+    const int package_idx = get_package_id(resid);
     const Package* package = packages_[package_idx].get();
-    if (package == nullptr) {
-      return kInvalidCookie;
-    }
+    if (package != nullptr) {
+      // The themes are constructed with a 1-based type ID, so no need to decrement here.
+      const int type_idx = get_type_id(resid);
+      const ThemeType* type = package->types[type_idx].get();
+      if (type != nullptr) {
+        const int entry_idx = get_entry_id(resid);
+        if (entry_idx < type->entry_count) {
+          const ThemeEntry& entry = type->entries[entry_idx];
+          type_spec_flags |= entry.type_spec_flags;
 
-    const Type* type = package->types[type_idx].get();
-    if (type == nullptr) {
-      return kInvalidCookie;
-    }
+          if (entry.value.dataType == Res_value::TYPE_ATTRIBUTE) {
+            if (cnt > 0) {
+              cnt--;
+              resid = entry.value.data;
+              continue;
+            }
+            return kInvalidCookie;
+          }
 
-    if (entry_idx >= type->entry_count) {
-      return kInvalidCookie;
-    }
+          // @null is different than @empty.
+          if (entry.value.dataType == Res_value::TYPE_NULL &&
+              entry.value.data != Res_value::DATA_NULL_EMPTY) {
+            return kInvalidCookie;
+          }
 
-    const Entry& entry = type->entries[entry_idx];
-    type_spec_flags |= entry.type_spec_flags;
-
-    switch (entry.value.dataType) {
-      case Res_value::TYPE_NULL:
-        return kInvalidCookie;
-
-      case Res_value::TYPE_ATTRIBUTE:
-        resid = entry.value.data;
-        break;
-
-      case Res_value::TYPE_DYNAMIC_ATTRIBUTE: {
-        // Resolve the dynamic attribute to a normal attribute
-        // (with the right package ID).
-        resid = entry.value.data;
-        const DynamicRefTable* ref_table =
-            asset_manager_->GetDynamicRefTableForPackage(package_idx);
-        if (ref_table == nullptr || ref_table->lookupResourceId(&resid) != NO_ERROR) {
-          LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic attribute 0x%08x", resid);
-          return kInvalidCookie;
-        }
-      } break;
-
-      case Res_value::TYPE_DYNAMIC_REFERENCE: {
-        // Resolve the dynamic reference to a normal reference
-        // (with the right package ID).
-        out_value->dataType = Res_value::TYPE_REFERENCE;
-        out_value->data = entry.value.data;
-        const DynamicRefTable* ref_table =
-            asset_manager_->GetDynamicRefTableForPackage(package_idx);
-        if (ref_table == nullptr || ref_table->lookupResourceId(&out_value->data) != NO_ERROR) {
-          LOG(ERROR) << base::StringPrintf("Failed to resolve dynamic reference 0x%08x",
-                                           out_value->data);
-          return kInvalidCookie;
-        }
-
-        if (out_flags != nullptr) {
+          *out_value = entry.value;
           *out_flags = type_spec_flags;
+          return entry.cookie;
         }
-        return entry.cookie;
       }
-
-      default:
-        *out_value = entry.value;
-        if (out_flags != nullptr) {
-          *out_flags = type_spec_flags;
-        }
-        return entry.cookie;
     }
-  }
-
-  LOG(WARNING) << base::StringPrintf("Too many (%d) attribute references, stopped at: 0x%08x",
-                                     kMaxIterations, resid);
+    break;
+  } while (true);
   return kInvalidCookie;
 }
 
@@ -902,30 +902,36 @@
     return true;
   }
 
-  if (asset_manager_ != o.asset_manager_) {
-    return false;
-  }
-
   type_spec_flags_ = o.type_spec_flags_;
 
+  const bool copy_only_system = asset_manager_ != o.asset_manager_;
+
   for (size_t p = 0; p < packages_.size(); p++) {
     const Package* package = o.packages_[p].get();
-    if (package == nullptr) {
+    if (package == nullptr || (copy_only_system && p != 0x01)) {
+      // The other theme doesn't have this package, clear ours.
       packages_[p].reset();
       continue;
     }
 
+    if (packages_[p] == nullptr) {
+      // The other theme has this package, but we don't. Make one.
+      packages_[p].reset(new Package());
+    }
+
     for (size_t t = 0; t < package->types.size(); t++) {
-      const Type* type = package->types[t].get();
+      const ThemeType* type = package->types[t].get();
       if (type == nullptr) {
+        // The other theme doesn't have this type, clear ours.
         packages_[p]->types[t].reset();
         continue;
       }
 
-      const size_t type_alloc_size = sizeof(Type) + (type->entry_capacity * sizeof(Entry));
+      // Create a new type and update it to theirs.
+      const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry));
       void* copied_data = malloc(type_alloc_size);
       memcpy(copied_data, type, type_alloc_size);
-      packages_[p]->types[t].reset(reinterpret_cast<Type*>(copied_data));
+      packages_[p]->types[t].reset(reinterpret_cast<ThemeType*>(copied_data));
     }
   }
   return true;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
new file mode 100644
index 0000000..7c1ee5c
--- /dev/null
+++ b/libs/androidfw/Idmap.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 ATRACE_TAG ATRACE_TAG_RESOURCES
+
+#include "androidfw/Idmap.h"
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "utils/ByteOrder.h"
+#include "utils/Trace.h"
+
+#ifdef _WIN32
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
+#include "androidfw/ResourceTypes.h"
+
+using ::android::base::StringPrintf;
+
+namespace android {
+
+constexpr static inline bool is_valid_package_id(uint16_t id) {
+  return id != 0 && id <= 255;
+}
+
+constexpr static inline bool is_valid_type_id(uint16_t id) {
+  // Type IDs and package IDs have the same constraints in the IDMAP.
+  return is_valid_package_id(id);
+}
+
+bool LoadedIdmap::Lookup(const IdmapEntry_header* header, uint16_t input_entry_id,
+                         uint16_t* output_entry_id) {
+  if (input_entry_id < dtohs(header->entry_id_offset)) {
+    // After applying the offset, the entry is not present.
+    return false;
+  }
+
+  input_entry_id -= dtohs(header->entry_id_offset);
+  if (input_entry_id >= dtohs(header->entry_count)) {
+    // The entry is not present.
+    return false;
+  }
+
+  uint32_t result = dtohl(header->entries[input_entry_id]);
+  if (result == 0xffffffffu) {
+    return false;
+  }
+  *output_entry_id = static_cast<uint16_t>(result);
+  return true;
+}
+
+static bool is_word_aligned(const void* data) {
+  return (reinterpret_cast<uintptr_t>(data) & 0x03) == 0;
+}
+
+static bool IsValidIdmapHeader(const StringPiece& data) {
+  if (!is_word_aligned(data.data())) {
+    LOG(ERROR) << "Idmap header is not word aligned.";
+    return false;
+  }
+
+  if (data.size() < sizeof(Idmap_header)) {
+    LOG(ERROR) << "Idmap header is too small.";
+    return false;
+  }
+
+  const Idmap_header* header = reinterpret_cast<const Idmap_header*>(data.data());
+  if (dtohl(header->magic) != kIdmapMagic) {
+    LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
+                               dtohl(header->magic), kIdmapMagic);
+    return false;
+  }
+
+  if (dtohl(header->version) != kIdmapCurrentVersion) {
+    // We are strict about versions because files with this format are auto-generated and don't need
+    // backwards compatibility.
+    LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
+                               dtohl(header->version), kIdmapCurrentVersion);
+    return false;
+  }
+
+  if (!is_valid_package_id(dtohs(header->target_package_id))) {
+    LOG(ERROR) << StringPrintf("Target package ID in Idmap is invalid: 0x%02x",
+                               dtohs(header->target_package_id));
+    return false;
+  }
+
+  if (dtohs(header->type_count) > 255) {
+    LOG(ERROR) << StringPrintf("Idmap has too many type mappings (was %d, max 255)",
+                               (int)dtohs(header->type_count));
+    return false;
+  }
+  return true;
+}
+
+LoadedIdmap::LoadedIdmap(const Idmap_header* header) : header_(header) {
+  size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
+                          arraysize(header_->overlay_path));
+  overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length);
+}
+
+std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) {
+  ATRACE_CALL();
+  if (!IsValidIdmapHeader(idmap_data)) {
+    return {};
+  }
+
+  const Idmap_header* header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
+
+  // Can't use make_unique because LoadedImpl constructor is private.
+  std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>(new LoadedIdmap(header));
+
+  const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()) + sizeof(*header);
+  size_t data_size = idmap_data.size() - sizeof(*header);
+
+  size_t type_maps_encountered = 0u;
+  while (data_size >= sizeof(IdmapEntry_header)) {
+    if (!is_word_aligned(data_ptr)) {
+      LOG(ERROR) << "Type mapping in Idmap is not word aligned";
+      return {};
+    }
+
+    // Validate the type IDs.
+    const IdmapEntry_header* entry_header = reinterpret_cast<const IdmapEntry_header*>(data_ptr);
+    if (!is_valid_type_id(dtohs(entry_header->target_type_id)) || !is_valid_type_id(dtohs(entry_header->overlay_type_id))) {
+      LOG(ERROR) << StringPrintf("Invalid type map (0x%02x -> 0x%02x)",
+                                 dtohs(entry_header->target_type_id),
+                                 dtohs(entry_header->overlay_type_id));
+      return {};
+    }
+
+    // Make sure there is enough space for the entries declared in the header.
+    if ((data_size - sizeof(*entry_header)) / sizeof(uint32_t) <
+        static_cast<size_t>(dtohs(entry_header->entry_count))) {
+      LOG(ERROR) << StringPrintf("Idmap too small for the number of entries (%d)",
+                                 (int)dtohs(entry_header->entry_count));
+      return {};
+    }
+
+    // Only add a non-empty overlay.
+    if (dtohs(entry_header->entry_count != 0)) {
+      loaded_idmap->type_map_[static_cast<uint8_t>(dtohs(entry_header->overlay_type_id))] =
+          entry_header;
+    }
+
+    const size_t entry_size_bytes =
+        sizeof(*entry_header) + (dtohs(entry_header->entry_count) * sizeof(uint32_t));
+    data_ptr += entry_size_bytes;
+    data_size -= entry_size_bytes;
+    type_maps_encountered++;
+  }
+
+  // Verify that we parsed all the type maps.
+  if (type_maps_encountered != static_cast<size_t>(dtohs(header->type_count))) {
+    LOG(ERROR) << "Parsed " << type_maps_encountered << " type maps but expected "
+               << (int)dtohs(header->type_count);
+    return {};
+  }
+  return std::move(loaded_idmap);
+}
+
+uint8_t LoadedIdmap::TargetPackageId() const {
+  return static_cast<uint8_t>(dtohs(header_->target_package_id));
+}
+
+const IdmapEntry_header* LoadedIdmap::GetEntryMapForType(uint8_t type_id) const {
+  auto iter = type_map_.find(type_id);
+  if (iter != type_map_.end()) {
+    return iter->second;
+  }
+  return nullptr;
+}
+
+}  // namespace android
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index bd7b804..c361ea2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -37,7 +37,7 @@
 #include "androidfw/ResourceUtils.h"
 #include "androidfw/Util.h"
 
-using android::base::StringPrintf;
+using ::android::base::StringPrintf;
 
 namespace android {
 
@@ -61,6 +61,10 @@
   // and under which configurations it varies.
   const ResTable_typeSpec* type_spec;
 
+  // Pointer to the mmapped data where the IDMAP mappings for this type
+  // exist. May be nullptr if no IDMAP exists.
+  const IdmapEntry_header* idmap_entries;
+
   // The number of types that follow this struct.
   // There is a type for each configuration
   // that entries are defined for.
@@ -84,7 +88,10 @@
 // the Type structs.
 class TypeSpecPtrBuilder {
  public:
-  TypeSpecPtrBuilder(const ResTable_typeSpec* header) : header_(header) {}
+  explicit TypeSpecPtrBuilder(const ResTable_typeSpec* header,
+                              const IdmapEntry_header* idmap_header)
+      : header_(header), idmap_header_(idmap_header) {
+  }
 
   void AddType(const ResTable_type* type) {
     ResTable_config config;
@@ -99,6 +106,7 @@
     }
     TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
     type_spec->type_spec = header_;
+    type_spec->idmap_entries = idmap_header_;
     type_spec->type_count = types_.size();
     memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
     return TypeSpecPtr(type_spec);
@@ -108,50 +116,157 @@
   DISALLOW_COPY_AND_ASSIGN(TypeSpecPtrBuilder);
 
   const ResTable_typeSpec* header_;
+  const IdmapEntry_header* idmap_header_;
   std::vector<Type> types_;
 };
 
 }  // namespace
 
-bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
-                              LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                              uint32_t* out_flags) const {
-  ATRACE_CALL();
+LoadedPackage::LoadedPackage() = default;
+LoadedPackage::~LoadedPackage() = default;
 
-  // If the type IDs are offset in this package, we need to take that into account when searching
-  // for a type.
-  const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
-  if (ptr == nullptr) {
+// Precondition: The header passed in has already been verified, so reading any fields and trusting
+// the ResChunk_header is safe.
+static bool VerifyResTableType(const ResTable_type* header) {
+  const size_t entry_count = dtohl(header->entryCount);
+  if (entry_count > std::numeric_limits<uint16_t>::max()) {
+    LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
     return false;
   }
 
-  // Don't bother checking if the entry ID is larger than
-  // the number of entries.
-  if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
+  // Make sure that there is enough room for the entry offsets.
+  const size_t offsets_offset = dtohs(header->header.headerSize);
+  const size_t entries_offset = dtohl(header->entriesStart);
+  const size_t offsets_length = sizeof(uint32_t) * entry_count;
+
+  if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
+    LOG(ERROR) << "Entry offsets overlap actual entry data.";
     return false;
   }
 
+  if (entries_offset > dtohl(header->header.size)) {
+    LOG(ERROR) << "Entry offsets extend beyond chunk.";
+    return false;
+  }
+
+  if (entries_offset & 0x03) {
+    LOG(ERROR) << "Entries start at unaligned address.";
+    return false;
+  }
+  return true;
+}
+
+static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
+                                size_t entry_idx) {
+  // Check that the offset is aligned.
+  if (entry_offset & 0x03) {
+    LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned.";
+    return false;
+  }
+
+  // Check that the offset doesn't overflow.
+  if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) {
+    // Overflow in offset.
+    LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large.";
+    return false;
+  }
+
+  const size_t chunk_size = dtohl(type->header.size);
+
+  entry_offset += dtohl(type->entriesStart);
+  if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
+    LOG(ERROR) << "Entry offset at index " << entry_idx
+               << " is too large. No room for ResTable_entry.";
+    return false;
+  }
+
+  const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+      reinterpret_cast<const uint8_t*>(type) + entry_offset);
+
+  const size_t entry_size = dtohs(entry->size);
+  if (entry_size < sizeof(*entry)) {
+    LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+               << " is too small.";
+    return false;
+  }
+
+  if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
+    LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+               << " is too large.";
+    return false;
+  }
+
+  if (entry_size < sizeof(ResTable_map_entry)) {
+    // There needs to be room for one Res_value struct.
+    if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) {
+      LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx
+                 << " for type " << (int)type->id << ".";
+      return false;
+    }
+
+    const Res_value* value =
+        reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size);
+    const size_t value_size = dtohs(value->size);
+    if (value_size < sizeof(Res_value)) {
+      LOG(ERROR) << "Res_value at index " << entry_idx << " is too small.";
+      return false;
+    }
+
+    if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
+      LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx
+                 << " is too large.";
+      return false;
+    }
+  } else {
+    const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
+    const size_t map_entry_count = dtohl(map->count);
+    size_t map_entries_start = entry_offset + entry_size;
+    if (map_entries_start & 0x03) {
+      LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset.";
+      return false;
+    }
+
+    // Each entry is sizeof(ResTable_map) big.
+    if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) {
+      LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << ".";
+      return false;
+    }
+  }
+  return true;
+}
+
+template <bool Verified>
+bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
+                              const ResTable_config& config, FindEntryResult* out_entry) const {
   const ResTable_config* best_config = nullptr;
   const ResTable_type* best_type = nullptr;
   uint32_t best_offset = 0;
 
-  for (uint32_t i = 0; i < ptr->type_count; i++) {
-    const Type* type = &ptr->types[i];
+  for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
+    const Type* type = &type_spec_ptr->types[i];
 
     if (type->configuration.match(config) &&
         (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
       // The configuration matches and is better than the previous selection.
       // Find the entry value if it exists for this configuration.
-      size_t entry_count = dtohl(type->type->entryCount);
+      const size_t entry_count = dtohl(type->type->entryCount);
+      const size_t offsets_offset = dtohs(type->type->header.headerSize);
       if (entry_idx < entry_count) {
+        // If the package hasn't been verified, do bounds checking.
+        if (!Verified) {
+          if (!VerifyResTableType(type->type)) {
+            continue;
+          }
+        }
+
         const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
-            reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
+            reinterpret_cast<const uint8_t*>(type->type) + offsets_offset);
         const uint32_t offset = dtohl(entry_offsets[entry_idx]);
         if (offset != ResTable_type::NO_ENTRY) {
           // There is an entry for this resource, record it.
           best_config = &type->configuration;
           best_type = type->type;
-          best_offset = offset + dtohl(type->type->entriesStart);
+          best_offset = offset;
         }
       }
     }
@@ -161,84 +276,64 @@
     return false;
   }
 
-  const uint32_t* flags = reinterpret_cast<const uint32_t*>(ptr->type_spec + 1);
-  *out_flags = dtohl(flags[entry_idx]);
-  *out_selected_config = *best_config;
+  if (!Verified) {
+    if (!VerifyResTableEntry(best_type, best_offset, entry_idx)) {
+      return false;
+    }
+  }
 
   const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
-      reinterpret_cast<const uint8_t*>(best_type) + best_offset);
+      reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
+
+  const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
+  out_entry->type_flags = dtohl(flags[entry_idx]);
   out_entry->entry = best_entry;
+  out_entry->config = best_config;
   out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
   out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
   return true;
 }
 
-// The destructor gets generated into arbitrary translation units
-// if left implicit, which causes the compiler to complain about
-// forward declarations and incomplete types.
-LoadedArsc::~LoadedArsc() {}
-
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
-                           LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                           uint32_t* out_flags) const {
+bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
+                              FindEntryResult* out_entry) const {
   ATRACE_CALL();
-  const uint8_t package_id = get_package_id(resid);
-  const uint8_t type_id = get_type_id(resid);
-  const uint16_t entry_id = get_entry_id(resid);
 
-  if (type_id == 0) {
-    LOG(ERROR) << "Invalid ID 0x" << std::hex << resid << std::dec << ".";
+  // If the type IDs are offset in this package, we need to take that into account when searching
+  // for a type.
+  const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
+  if (ptr == nullptr) {
     return false;
   }
 
-  for (const auto& loaded_package : packages_) {
-    if (loaded_package->package_id_ == package_id) {
-      return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry,
-                                       out_selected_config, out_flags);
+  // If there is an IDMAP supplied with this package, translate the entry ID.
+  if (ptr->idmap_entries != nullptr) {
+    if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
+      // There is no mapping, so the resource is not meant to be in this overlay package.
+      return false;
     }
   }
-  return false;
-}
 
-const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
-  const uint8_t package_id = get_package_id(resid);
-  for (const auto& loaded_package : packages_) {
-    if (loaded_package->package_id_ == package_id) {
-      return loaded_package.get();
-    }
+  // Don't bother checking if the entry ID is larger than the number of entries.
+  if (entry_idx >= dtohl(ptr->type_spec->entryCount)) {
+    return false;
   }
-  return nullptr;
+
+  if (verified_) {
+    return FindEntry<true>(ptr, entry_idx, config, out_entry);
+  }
+  return FindEntry<false>(ptr, entry_idx, config, out_entry);
 }
 
 static bool VerifyType(const Chunk& chunk) {
   ATRACE_CALL();
   const ResTable_type* header = chunk.header<ResTable_type, kResTableTypeMinSize>();
 
+  if (!VerifyResTableType(header)) {
+    return false;
+  }
+
   const size_t entry_count = dtohl(header->entryCount);
-  if (entry_count > std::numeric_limits<uint16_t>::max()) {
-    LOG(ERROR) << "Too many entries in RES_TABLE_TYPE_TYPE.";
-    return false;
-  }
-
-  // Make sure that there is enough room for the entry offsets.
   const size_t offsets_offset = chunk.header_size();
-  const size_t entries_offset = dtohl(header->entriesStart);
-  const size_t offsets_length = sizeof(uint32_t) * entry_count;
-
-  if (offsets_offset + offsets_length > entries_offset) {
-    LOG(ERROR) << "Entry offsets overlap actual entry data.";
-    return false;
-  }
-
-  if (entries_offset > chunk.size()) {
-    LOG(ERROR) << "Entry offsets extend beyond chunk.";
-    return false;
-  }
-
-  if (entries_offset & 0x03) {
-    LOG(ERROR) << "Entries start at unaligned address.";
-    return false;
-  }
 
   // Check each entry offset.
   const uint32_t* offsets =
@@ -246,77 +341,9 @@
   for (size_t i = 0; i < entry_count; i++) {
     uint32_t offset = dtohl(offsets[i]);
     if (offset != ResTable_type::NO_ENTRY) {
-      // Check that the offset is aligned.
-      if (offset & 0x03) {
-        LOG(ERROR) << "Entry offset at index " << i << " is not 4-byte aligned.";
+      if (!VerifyResTableEntry(header, offset, i)) {
         return false;
       }
-
-      // Check that the offset doesn't overflow.
-      if (offset > std::numeric_limits<uint32_t>::max() - entries_offset) {
-        // Overflow in offset.
-        LOG(ERROR) << "Entry offset at index " << i << " is too large.";
-        return false;
-      }
-
-      offset += entries_offset;
-      if (offset > chunk.size() - sizeof(ResTable_entry)) {
-        LOG(ERROR) << "Entry offset at index " << i << " is too large. No room for ResTable_entry.";
-        return false;
-      }
-
-      const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
-          reinterpret_cast<const uint8_t*>(header) + offset);
-      const size_t entry_size = dtohs(entry->size);
-      if (entry_size < sizeof(*entry)) {
-        LOG(ERROR) << "ResTable_entry size " << entry_size << " is too small.";
-        return false;
-      }
-
-      // Check the declared entrySize.
-      if (entry_size > chunk.size() || offset > chunk.size() - entry_size) {
-        LOG(ERROR) << "ResTable_entry size " << entry_size << " is too large.";
-        return false;
-      }
-
-      // If this is a map entry, then keep validating.
-      if (entry_size >= sizeof(ResTable_map_entry)) {
-        const ResTable_map_entry* map = reinterpret_cast<const ResTable_map_entry*>(entry);
-        const size_t map_entry_count = dtohl(map->count);
-
-        size_t map_entries_start = offset + entry_size;
-        if (map_entries_start & 0x03) {
-          LOG(ERROR) << "Map entries start at unaligned offset.";
-          return false;
-        }
-
-        // Each entry is sizeof(ResTable_map) big.
-        if (map_entry_count > ((chunk.size() - map_entries_start) / sizeof(ResTable_map))) {
-          LOG(ERROR) << "Too many map entries in ResTable_map_entry.";
-          return false;
-        }
-
-        // Great, all the map entries fit!.
-      } else {
-        // There needs to be room for one Res_value struct.
-        if (offset + entry_size > chunk.size() - sizeof(Res_value)) {
-          LOG(ERROR) << "No room for Res_value after ResTable_entry.";
-          return false;
-        }
-
-        const Res_value* value = reinterpret_cast<const Res_value*>(
-            reinterpret_cast<const uint8_t*>(entry) + entry_size);
-        const size_t value_size = dtohs(value->size);
-        if (value_size < sizeof(Res_value)) {
-          LOG(ERROR) << "Res_value is too small.";
-          return false;
-        }
-
-        if (value_size > chunk.size() || offset + entry_size > chunk.size() - value_size) {
-          LOG(ERROR) << "Res_value size is too large.";
-          return false;
-        }
-      }
     }
   }
   return true;
@@ -412,10 +439,24 @@
   return 0u;
 }
 
-std::unique_ptr<LoadedPackage> LoadedPackage::Load(const Chunk& chunk) {
-  ATRACE_CALL();
-  std::unique_ptr<LoadedPackage> loaded_package{new LoadedPackage()};
+const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
+  const uint8_t package_id = get_package_id(resid);
+  for (const auto& loaded_package : packages_) {
+    if (loaded_package->GetPackageId() == package_id) {
+      return loaded_package.get();
+    }
+  }
+  return nullptr;
+}
 
+std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
+                                                         const LoadedIdmap* loaded_idmap,
+                                                         bool system, bool load_as_shared_library) {
+  ATRACE_CALL();
+  std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+
+  // typeIdOffset was added at some point, but we still must recognize apps built before this
+  // was added.
   constexpr size_t kMinPackageSize =
       sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
   const ResTable_package* header = chunk.header<ResTable_package, kMinPackageSize>();
@@ -424,12 +465,21 @@
     return {};
   }
 
+  loaded_package->system_ = system;
+
   loaded_package->package_id_ = dtohl(header->id);
-  if (loaded_package->package_id_ == 0) {
+  if (loaded_package->package_id_ == 0 ||
+      (loaded_package->package_id_ == kAppPackageId && load_as_shared_library)) {
     // Package ID of 0 means this is a shared library.
     loaded_package->dynamic_ = true;
   }
 
+  if (loaded_idmap != nullptr) {
+    // This is an overlay and so it needs to pretend to be the target package.
+    loaded_package->package_id_ = loaded_idmap->TargetPackageId();
+    loaded_package->overlay_ = true;
+  }
+
   if (header->header.headerSize >= sizeof(ResTable_package)) {
     uint32_t type_id_offset = dtohl(header->typeIdOffset);
     if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -490,7 +540,16 @@
             LOG(ERROR) << "Too many type configurations, overflow detected.";
             return {};
           }
-          loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+
+          // We only add the type to the package if there is no IDMAP, or if the type is
+          // overlaying something.
+          if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
+            // If this is an overlay, insert it at the target type ID.
+            if (type_spec_ptr->idmap_entries != nullptr) {
+              last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
+            }
+            loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+          }
 
           types_builder = {};
           last_type_idx = 0;
@@ -531,7 +590,15 @@
         }
 
         last_type_idx = type_spec->id - 1;
-        types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec);
+
+        // If this is an overlay, associate the mapping of this type to the target type
+        // from the IDMAP.
+        const IdmapEntry_header* idmap_entry_header = nullptr;
+        if (loaded_idmap != nullptr) {
+          idmap_entry_header = loaded_idmap->GetEntryMapForType(type_spec->id);
+        }
+
+        types_builder = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header);
       } break;
 
       case RES_TABLE_TYPE_TYPE: {
@@ -548,13 +615,16 @@
 
         // Type chunks must be preceded by their TypeSpec chunks.
         if (!types_builder || type->id - 1 != last_type_idx) {
-          LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without "
-                        "RES_TABLE_TYPE_SPEC_TYPE.";
+          LOG(ERROR) << "Found RES_TABLE_TYPE_TYPE chunk without RES_TABLE_TYPE_SPEC_TYPE.";
           return {};
         }
 
-        if (!VerifyType(child_chunk)) {
-          return {};
+        // Only verify the type if we haven't already failed verification.
+        if (loaded_package->verified_) {
+          if (!VerifyType(child_chunk)) {
+            LOG(WARNING) << "Package failed verification, resource retrieval may be slower";
+            loaded_package->verified_ = false;
+          }
         }
 
         types_builder->AddType(type);
@@ -608,17 +678,48 @@
       LOG(ERROR) << "Too many type configurations, overflow detected.";
       return {};
     }
-    loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+
+    // We only add the type to the package if there is no IDMAP, or if the type is
+    // overlaying something.
+    if (loaded_idmap == nullptr || type_spec_ptr->idmap_entries != nullptr) {
+      // If this is an overlay, insert it at the target type ID.
+      if (type_spec_ptr->idmap_entries != nullptr) {
+        last_type_idx = dtohs(type_spec_ptr->idmap_entries->target_type_id) - 1;
+      }
+      loaded_package->type_specs_.editItemAt(last_type_idx) = std::move(type_spec_ptr);
+    }
   }
 
   if (iter.HadError()) {
     LOG(ERROR) << iter.GetLastError();
     return {};
   }
-  return loaded_package;
+  return std::move(loaded_package);
 }
 
-bool LoadedArsc::LoadTable(const Chunk& chunk, bool load_as_shared_library) {
+bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
+                           FindEntryResult* out_entry) const {
+  ATRACE_CALL();
+
+  const uint8_t package_id = get_package_id(resid);
+  const uint8_t type_id = get_type_id(resid);
+  const uint16_t entry_id = get_entry_id(resid);
+
+  if (type_id == 0) {
+    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
+    return false;
+  }
+
+  for (const auto& loaded_package : packages_) {
+    if (loaded_package->GetPackageId() == package_id) {
+      return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry);
+    }
+  }
+  return false;
+}
+
+bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
+                           bool load_as_shared_library) {
   ATRACE_CALL();
   const ResTable_header* header = chunk.header<ResTable_header>();
   if (header == nullptr) {
@@ -652,22 +753,16 @@
       case RES_TABLE_PACKAGE_TYPE: {
         if (packages_seen + 1 > package_count) {
           LOG(ERROR) << "More package chunks were found than the " << package_count
-                     << " declared in the "
-                        "header.";
+                     << " declared in the header.";
           return false;
         }
         packages_seen++;
 
-        std::unique_ptr<LoadedPackage> loaded_package = LoadedPackage::Load(child_chunk);
+        std::unique_ptr<const LoadedPackage> loaded_package =
+            LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
         if (!loaded_package) {
           return false;
         }
-
-        // Mark the package as dynamic if we are forcefully loading the Apk as a shared library.
-        if (loaded_package->package_id_ == kAppPackageId) {
-          loaded_package->dynamic_ = load_as_shared_library;
-        }
-        loaded_package->system_ = system_;
         packages_.push_back(std::move(loaded_package));
       } break;
 
@@ -684,7 +779,8 @@
   return true;
 }
 
-std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const void* data, size_t len, bool system,
+std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
+                                                   const LoadedIdmap* loaded_idmap, bool system,
                                                    bool load_as_shared_library) {
   ATRACE_CALL();
 
@@ -692,12 +788,12 @@
   std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
   loaded_arsc->system_ = system;
 
-  ChunkIterator iter(data, len);
+  ChunkIterator iter(data.data(), data.size());
   while (iter.HasNext()) {
     const Chunk chunk = iter.Next();
     switch (chunk.type()) {
       case RES_TABLE_TYPE:
-        if (!loaded_arsc->LoadTable(chunk, load_as_shared_library)) {
+        if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
           return {};
         }
         break;
@@ -717,4 +813,8 @@
   return std::move(loaded_arsc);
 }
 
+std::unique_ptr<const LoadedArsc> LoadedArsc::CreateEmpty() {
+  return std::unique_ptr<LoadedArsc>(new LoadedArsc());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 7a0ef2b..17de8fa 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2847,14 +2847,111 @@
         }
         memcpy(str + charsWritten, localeVariant, sizeof(localeVariant));
     }
+
+    /* TODO: Add BCP47 extension. It requires RESTABLE_MAX_LOCALE_LEN
+     * increase from 28 to 42 bytes (-u-nu-xxxxxxxx) */
 }
 
-/* static */ inline bool assignLocaleComponent(ResTable_config* config,
-        const char* start, size_t size) {
+struct LocaleParserState {
+    enum State : uint8_t {
+        BASE, UNICODE_EXTENSION, IGNORE_THE_REST
+    } parserState;
+    enum UnicodeState : uint8_t {
+        /* Initial state after the Unicode singleton is detected. Either a keyword
+         * or an attribute is expected. */
+        NO_KEY,
+        /* Unicode extension key (but not attribute) is expected. Next states:
+         * NO_KEY, IGNORE_KEY or NUMBERING_SYSTEM. */
+        EXPECT_KEY,
+        /* A key is detected, however it is not supported for now. Ignore its
+         * value. Next states: IGNORE_KEY or NUMBERING_SYSTEM. */
+        IGNORE_KEY,
+        /* Numbering system key was detected. Store its value in the configuration
+         * localeNumberingSystem field. Next state: EXPECT_KEY */
+        NUMBERING_SYSTEM
+    } unicodeState;
+
+    LocaleParserState(): parserState(BASE), unicodeState(NO_KEY) {}
+};
+
+/* static */ inline LocaleParserState assignLocaleComponent(ResTable_config* config,
+        const char* start, size_t size, LocaleParserState state) {
+
+    /* It is assumed that this function is not invoked with state.parserState
+     * set to IGNORE_THE_REST. The condition is checked by setBcp47Locale
+     * function. */
+
+    if (state.parserState == LocaleParserState::UNICODE_EXTENSION) {
+        switch (size) {
+            case 1:
+                /* Other BCP 47 extensions are not supported at the moment */
+                state.parserState = LocaleParserState::IGNORE_THE_REST;
+                break;
+            case 2:
+                if (state.unicodeState == LocaleParserState::NO_KEY ||
+                    state.unicodeState == LocaleParserState::EXPECT_KEY) {
+                    /* Analyze Unicode extension key. Currently only 'nu'
+                     * (numbering system) is supported.*/
+                    if ((start[0] == 'n' || start[0] == 'N') &&
+                        (start[1] == 'u' || start[1] == 'U')) {
+                        state.unicodeState = LocaleParserState::NUMBERING_SYSTEM;
+                    } else {
+                        state.unicodeState = LocaleParserState::IGNORE_KEY;
+                    }
+                } else {
+                    /* Keys are not allowed in other state allowed, ignore the rest. */
+                    state.parserState = LocaleParserState::IGNORE_THE_REST;
+                }
+                break;
+            case 3:
+            case 4:
+            case 5:
+            case 6:
+            case 7:
+            case 8:
+                switch (state.unicodeState) {
+                    case LocaleParserState::NUMBERING_SYSTEM:
+                        /* Accept only the first occurrence of the numbering system. */
+                        if (config->localeNumberingSystem[0] == '\0') {
+                            for (size_t i = 0; i < size; ++i) {
+                               config->localeNumberingSystem[i] = tolower(start[i]);
+                            }
+                            state.unicodeState = LocaleParserState::EXPECT_KEY;
+                        } else {
+                            state.parserState = LocaleParserState::IGNORE_THE_REST;
+                        }
+                        break;
+                    case LocaleParserState::IGNORE_KEY:
+                        /* Unsupported Unicode keyword. Ignore. */
+                        state.unicodeState = LocaleParserState::EXPECT_KEY;
+                        break;
+                    case LocaleParserState::EXPECT_KEY:
+                        /* A keyword followed by an attribute is not allowed. */
+                        state.parserState = LocaleParserState::IGNORE_THE_REST;
+                        break;
+                    case LocaleParserState::NO_KEY:
+                        /* Extension attribute. Do nothing. */
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            default:
+                /* Unexpected field length - ignore the rest and treat as an error */
+                state.parserState = LocaleParserState::IGNORE_THE_REST;
+        }
+        return state;
+    }
 
   switch (size) {
        case 0:
-           return false;
+           state.parserState = LocaleParserState::IGNORE_THE_REST;
+           break;
+       case 1:
+           state.parserState = (start[0] == 'u' || start[0] == 'U')
+                   ? LocaleParserState::UNICODE_EXTENSION
+                   : LocaleParserState::IGNORE_THE_REST;
+           break;
        case 2:
        case 3:
            config->language[0] ? config->packRegion(start) : config->packLanguage(start);
@@ -2878,30 +2975,35 @@
            }
            break;
        default:
-           return false;
+           state.parserState = LocaleParserState::IGNORE_THE_REST;
   }
 
-  return true;
+  return state;
 }
 
 void ResTable_config::setBcp47Locale(const char* in) {
     locale = 0;
     memset(localeScript, 0, sizeof(localeScript));
     memset(localeVariant, 0, sizeof(localeVariant));
+    memset(localeNumberingSystem, 0, sizeof(localeNumberingSystem));
 
-    const char* separator = in;
     const char* start = in;
-    while ((separator = strchr(start, '-')) != NULL) {
+    LocaleParserState state;
+    while (const char* separator = strchr(start, '-')) {
         const size_t size = separator - start;
-        if (!assignLocaleComponent(this, start, size)) {
-            fprintf(stderr, "Invalid BCP-47 locale string: %s", in);
+        state = assignLocaleComponent(this, start, size, state);
+        if (state.parserState == LocaleParserState::IGNORE_THE_REST) {
+            fprintf(stderr, "Invalid BCP-47 locale string: %s\n", in);
+            break;
         }
-
         start = (separator + 1);
     }
 
-    const size_t size = in + strlen(in) - start;
-    assignLocaleComponent(this, start, size);
+    if (state.parserState != LocaleParserState::IGNORE_THE_REST) {
+        const size_t size = strlen(start);
+        assignLocaleComponent(this, start, size, state);
+    }
+
     localeScriptWasComputed = (localeScript[0] == '\0');
     if (localeScriptWasComputed) {
         computeScript();
@@ -6014,9 +6116,6 @@
 StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index)
     : mPool(pool), mIndex(index) {}
 
-StringPoolRef::StringPoolRef()
-    : mPool(NULL), mIndex(0) {}
-
 const char* StringPoolRef::string8(size_t* outLen) const {
     if (mPool != NULL) {
         return mPool->string8At(mIndex, outLen);
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 49fe8a2..6e2ca60c 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -55,7 +55,9 @@
 
 ZipFileRO::~ZipFileRO() {
     CloseArchive(mHandle);
-    free(mFileName);
+    if (mFileName != NULL) {
+        free(mFileName);
+    }
 }
 
 /*
@@ -76,6 +78,20 @@
 }
 
 
+/* static */ ZipFileRO* ZipFileRO::openFd(int fd, const char* debugFileName,
+        bool assume_ownership)
+{
+    ZipArchiveHandle handle;
+    const int32_t error = OpenArchiveFd(fd, debugFileName, &handle, assume_ownership);
+    if (error) {
+        ALOGW("Error opening archive fd %d %s: %s", fd, debugFileName, ErrorCodeString(error));
+        CloseArchive(handle);
+        return NULL;
+    }
+
+    return new ZipFileRO(handle, strdup(debugFileName));
+}
+
 ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
 {
     _ZipEntryRO* data = new _ZipEntryRO;
@@ -139,7 +155,8 @@
                                    prefix ? &pe : NULL,
                                    suffix ? &se : NULL);
     if (error) {
-        ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error));
+        ALOGW("Could not start iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
+                ErrorCodeString(error));
         delete ze;
         return false;
     }
@@ -154,7 +171,8 @@
     int32_t error = Next(ze->cookie, &(ze->entry), &(ze->name));
     if (error) {
         if (error != -1) {
-            ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error));
+            ALOGW("Error iteration over %s: %s", mFileName != NULL ? mFileName : "<null>",
+                    ErrorCodeString(error));
         }
         return NULL;
     }
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index b7e66fb..3a307fc 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -21,7 +21,6 @@
 #include <string>
 
 #include "android-base/macros.h"
-#include "ziparchive/zip_archive.h"
 
 #include "androidfw/Asset.h"
 #include "androidfw/LoadedArsc.h"
@@ -29,41 +28,63 @@
 
 namespace android {
 
+class LoadedIdmap;
+
 // Holds an APK.
 class ApkAssets {
  public:
+  // Creates an ApkAssets.
+  // If `system` is true, the package is marked as a system package, and allows some functions to
+  // filter out this package when computing what configurations/resources are available.
   static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
+
+  // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
+  // If `system` is true, the package is marked as a system package, and allows some functions to
+  // filter out this package when computing what configurations/resources are available.
   static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const std::string& path,
                                                               bool system = false);
 
+  // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
+  // data.
+  // If `system` is true, the package is marked as a system package, and allows some functions to
+  // filter out this package when computing what configurations/resources are available.
+  static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
+                                                      bool system = false);
+
   std::unique_ptr<Asset> Open(const std::string& path,
                               Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
 
   bool ForEachFile(const std::string& path,
                    const std::function<void(const StringPiece&, FileType)>& f) const;
 
-  inline const std::string& GetPath() const { return path_; }
+  inline const std::string& GetPath() const {
+    return path_;
+  }
 
-  inline const LoadedArsc* GetLoadedArsc() const { return loaded_arsc_.get(); }
+  // This is never nullptr.
+  inline const LoadedArsc* GetLoadedArsc() const {
+    return loaded_arsc_.get();
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
 
-  static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path, bool system,
-                                                   bool load_as_shared_library);
+  static std::unique_ptr<const ApkAssets> LoadImpl(const std::string& path,
+                                                   std::unique_ptr<Asset> idmap_asset,
+                                                   std::unique_ptr<const LoadedIdmap> loaded_idmap,
+                                                   bool system, bool load_as_shared_library);
 
-  ApkAssets() = default;
+  // Creates an Asset from any file on the file system.
+  static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
 
-  struct ZipArchivePtrCloser {
-    void operator()(::ZipArchiveHandle handle) { ::CloseArchive(handle); }
-  };
+  ApkAssets(void* unmanaged_handle, const std::string& path);
 
-  using ZipArchivePtr =
-      std::unique_ptr<typename std::remove_pointer<::ZipArchiveHandle>::type, ZipArchivePtrCloser>;
+  using ZipArchivePtr = std::unique_ptr<void, void(*)(void*)>;
 
   ZipArchivePtr zip_handle_;
-  std::string path_;
+  const std::string path_;
   std::unique_ptr<Asset> resources_asset_;
+  std::unique_ptr<Asset> idmap_asset_;
   std::unique_ptr<const LoadedArsc> loaded_arsc_;
 };
 
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index 0441b9d..4254614 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -92,6 +92,20 @@
     bool addOverlayPath(const String8& path, int32_t* cookie);
 
     /*
+     * Add a new source for assets from an already open file descriptor.
+     * This does not give full AssetManager functionality for these assets,
+     * since the origin of the file is not known for purposes of sharing,
+     * overlay resolution, and other features.  However it does allow you
+     * to do simple access to the contents of the given fd as an apk file.
+     *
+     * Returns "true" on success, "false" on failure.  If 'cookie' is non-NULL,
+     * then on success, *cookie is set to the value corresponding to the
+     * newly-added asset source.
+     */
+    bool addAssetFd(int fd, const String8& debugPathName, int32_t* cookie,
+        bool appAsLib=false, bool assume_ownership=true);
+
+    /*
      * Convenience for adding the standard system assets.  Uses the
      * ANDROID_ROOT environment variable to find them.
      */
@@ -195,15 +209,20 @@
         uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize);
 
 private:
+    class SharedZip;
+
     struct asset_path
     {
-        asset_path() : path(""), type(kFileTypeRegular), idmap(""),
-                       isSystemOverlay(false), isSystemAsset(false) {}
+        asset_path() : path(""), rawFd(-1), type(kFileTypeRegular), idmap(""),
+                       isSystemOverlay(false), isSystemAsset(false), assumeOwnership(false) {}
         String8 path;
+        int rawFd;
         FileType type;
         String8 idmap;
         bool isSystemOverlay;
         bool isSystemAsset;
+        bool assumeOwnership;
+        mutable sp<SharedZip> zip;
     };
 
     Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
@@ -238,6 +257,7 @@
     class SharedZip : public RefBase {
     public:
         static sp<SharedZip> get(const String8& path, bool createIfNotPresent = true);
+        static sp<SharedZip> create(int fd, const String8& path);
 
         ZipFileRO* getZip();
 
@@ -257,6 +277,7 @@
 
     private:
         SharedZip(const String8& path, time_t modWhen);
+        SharedZip(int fd, const String8& path);
         SharedZip(); // <-- not implemented
 
         String8 mPath;
@@ -290,6 +311,8 @@
          */
         ZipFileRO* getZip(const String8& path);
 
+        const sp<SharedZip> getSharedZip(const String8& path);
+
         Asset* getZipResourceTableAsset(const String8& path);
         Asset* setZipResourceTableAsset(const String8& path, Asset* asset);
 
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index d2bc6ee..a77c4b9 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -70,9 +70,8 @@
 };
 
 // AssetManager2 is the main entry point for accessing assets and resources.
-// AssetManager2 provides caching of resources retrieved via the underlying
-// ApkAssets.
-class AssetManager2 : public ::AAssetManager {
+// AssetManager2 provides caching of resources retrieved via the underlying ApkAssets.
+class AssetManager2 {
  public:
   struct ResourceName {
     const char* package = nullptr;
@@ -97,24 +96,29 @@
   // new resource IDs.
   bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true);
 
-  inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; }
+  inline const std::vector<const ApkAssets*> GetApkAssets() const {
+    return apk_assets_;
+  }
 
   // Returns the string pool for the given asset cookie.
-  // Use the string pool returned here with a valid Res_value object of
-  // type Res_value::TYPE_STRING.
+  // Use the string pool returned here with a valid Res_value object of type Res_value::TYPE_STRING.
   const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const;
 
   // Returns the DynamicRefTable for the given package ID.
+  // This may be nullptr if the APK represented by `cookie` has no resource table.
   const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
 
   // Returns the DynamicRefTable for the ApkAssets represented by the cookie.
+  // This may be nullptr if the APK represented by `cookie` has no resource table.
   const DynamicRefTable* GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
 
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
   void SetConfiguration(const ResTable_config& configuration);
 
-  inline const ResTable_config& GetConfiguration() const { return configuration_; }
+  inline const ResTable_config& GetConfiguration() const {
+    return configuration_;
+  }
 
   // Returns all configurations for which there are resources defined. This includes resource
   // configurations in all the ApkAssets set for this AssetManager.
@@ -228,26 +232,35 @@
   // Creates a new Theme from this AssetManager.
   std::unique_ptr<Theme> NewTheme();
 
+  template <typename Func>
+  void ForEachPackage(Func func) {
+    for (const PackageGroup& package_group : package_groups_) {
+      func(package_group.packages_.front()->GetPackageName(),
+           package_group.dynamic_ref_table.mAssignedPackageId);
+    }
+  }
+
   void DumpToLog() const;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(AssetManager2);
 
-  // Finds the best entry for `resid` amongst all the ApkAssets. The entry can be a simple
-  // Res_value, or a complex map/bag type.
+  // Finds the best entry for `resid` from the set of ApkAssets. The entry can be a simple
+  // Res_value, or a complex map/bag type. If successful, it is available in `out_entry`.
+  // Returns kInvalidCookie on failure. Otherwise, the return value is the cookie associated with
+  // the ApkAssets in which the entry was found.
   //
   // `density_override` overrides the density of the current configuration when doing a search.
   //
   // When `stop_at_first_match` is true, the first match found is selected and the search
   // terminates. This is useful for methods that just look up the name of a resource and don't
-  // care about the value. In this case, the value of `out_flags` is incomplete and should not
-  // be used.
+  // care about the value. In this case, the value of `FindEntryResult::type_flags` is incomplete
+  // and should not be used.
   //
-  // `out_flags` stores the resulting bitmask of configuration axis with which the resource
-  // value varies.
+  // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
+  // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
   ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
-                            LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                            uint32_t* out_flags);
+                            FindEntryResult* out_entry);
 
   // Assigns package IDs to all shared library ApkAssets.
   // Should be called whenever the ApkAssets are changed.
@@ -290,6 +303,8 @@
   friend class AssetManager2;
 
  public:
+  ~Theme();
+
   // Applies the style identified by `resid` to this theme. This can be called
   // multiple times with different styles. By default, any theme attributes that
   // are already defined before this call are not overridden. If `force` is set
@@ -304,27 +319,31 @@
 
   void Clear();
 
-  inline const AssetManager2* GetAssetManager() const { return asset_manager_; }
+  inline const AssetManager2* GetAssetManager() const {
+    return asset_manager_;
+  }
 
-  inline AssetManager2* GetAssetManager() { return asset_manager_; }
+  inline AssetManager2* GetAssetManager() {
+    return asset_manager_;
+  }
 
   // Returns a bit mask of configuration changes that will impact this
   // theme (and thus require completely reloading it).
-  inline uint32_t GetChangingConfigurations() const { return type_spec_flags_; }
+  inline uint32_t GetChangingConfigurations() const {
+    return type_spec_flags_;
+  }
 
-  // Retrieve a value in the theme. If the theme defines this value,
-  // returns an asset cookie indicating which ApkAssets it came from
-  // and populates `out_value` with the value. If `out_flags` is non-null,
-  // populates it with a bitmask of the configuration axis the resource
-  // varies with.
+  // Retrieve a value in the theme. If the theme defines this value, returns an asset cookie
+  // indicating which ApkAssets it came from and populates `out_value` with the value.
+  // `out_flags` is populated with a bitmask of the configuration axis with which the resource
+  // varies.
   //
   // If the attribute is not found, returns kInvalidCookie.
   //
-  // NOTE: This function does not do reference traversal. If you want
-  // to follow references to other resources to get the "real" value to
-  // use, you need to call ResolveReference() after this function.
-  ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value,
-                               uint32_t* out_flags = nullptr) const;
+  // NOTE: This function does not do reference traversal. If you want to follow references to other
+  // resources to get the "real" value to use, you need to call ResolveReference() after this
+  // function.
+  ApkAssetsCookie GetAttribute(uint32_t resid, Res_value* out_value, uint32_t* out_flags) const;
 
   // This is like AssetManager2::ResolveReference(), but also takes
   // care of resolving attribute references to the theme.
@@ -337,36 +356,21 @@
   DISALLOW_COPY_AND_ASSIGN(Theme);
 
   // Called by AssetManager2.
-  explicit inline Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {}
-
-  struct Entry {
-    ApkAssetsCookie cookie;
-    uint32_t type_spec_flags;
-    Res_value value;
-  };
-
-  struct Type {
-    // Use uint32_t for fewer cycles when loading from memory.
-    uint32_t entry_count;
-    uint32_t entry_capacity;
-    Entry entries[0];
-  };
-
-  static constexpr const size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
-  static constexpr const size_t kTypeCount = std::numeric_limits<uint8_t>::max() + 1;
-
-  struct Package {
-    // Each element of Type will be a dynamically sized object
-    // allocated to have the entries stored contiguously with the Type.
-    std::array<util::unique_cptr<Type>, kTypeCount> types;
-  };
+  explicit Theme(AssetManager2* asset_manager);
 
   AssetManager2* asset_manager_;
   uint32_t type_spec_flags_ = 0u;
+
+  // Defined in the cpp.
+  struct Package;
+
+  constexpr static size_t kPackageCount = std::numeric_limits<uint8_t>::max() + 1;
   std::array<std::unique_ptr<Package>, kPackageCount> packages_;
 };
 
-inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) { return bag->entries; }
+inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) {
+  return bag->entries;
+}
 
 inline const ResolvedBag::Entry* end(const ResolvedBag* bag) {
   return bag->entries + bag->entry_count;
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
new file mode 100644
index 0000000..fd02e6f
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -0,0 +1,76 @@
+/*
+ * 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 IDMAP_H_
+#define IDMAP_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+struct Idmap_header;
+struct IdmapEntry_header;
+
+// Represents a loaded/parsed IDMAP for a Runtime Resource Overlay (RRO).
+// An RRO and its target APK have different resource IDs assigned to their resources. Overlaying
+// a resource is done by resource name. An IDMAP is a generated mapping between the resource IDs
+// of the RRO and the target APK for each resource with the same name.
+// A LoadedIdmap can be set alongside the overlay's LoadedArsc to allow the overlay ApkAssets to
+// masquerade as the target ApkAssets resources.
+class LoadedIdmap {
+ public:
+  // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
+  static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data);
+
+  // Performs a lookup of the expected entry ID for the given IDMAP entry header.
+  // Returns true if the mapping exists and fills `output_entry_id` with the result.
+  static bool Lookup(const IdmapEntry_header* header, uint16_t input_entry_id,
+                     uint16_t* output_entry_id);
+
+  // Returns the package ID for which this overlay should apply.
+  uint8_t TargetPackageId() const;
+
+  // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
+  inline const std::string& OverlayApkPath() const {
+    return overlay_apk_path_;
+  }
+
+  // Returns the mapping of target entry ID to overlay entry ID for the given target type.
+  const IdmapEntry_header* GetEntryMapForType(uint8_t type_id) const;
+
+ protected:
+  // Exposed as protected so that tests can subclass and mock this class out.
+  LoadedIdmap() = default;
+
+  const Idmap_header* header_ = nullptr;
+  std::string overlay_apk_path_;
+  std::unordered_map<uint8_t, const IdmapEntry_header*> type_map_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
+
+  explicit LoadedIdmap(const Idmap_header* header);
+};
+
+}  // namespace android
+
+#endif  // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index f30b158..377735b 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -25,6 +25,7 @@
 
 #include "androidfw/ByteBucketArray.h"
 #include "androidfw/Chunk.h"
+#include "androidfw/Idmap.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 
@@ -40,12 +41,18 @@
   int package_id = 0;
 };
 
-struct LoadedArscEntry {
+struct FindEntryResult {
   // A pointer to the resource table entry for this resource.
   // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
   // a ResTable_map_entry and processed as a bag/map.
   const ResTable_entry* entry = nullptr;
 
+  // The configuration for which the resulting entry was defined.
+  const ResTable_config* config = nullptr;
+
+  // Stores the resulting bitmask of configuration axis with which the resource value varies.
+  uint32_t type_flags = 0u;
+
   // The dynamic package ID map for the package from which this resource came from.
   const DynamicRefTable* dynamic_ref_table = nullptr;
 
@@ -62,28 +69,60 @@
 class LoadedArsc;
 
 class LoadedPackage {
-  friend class LoadedArsc;
-
  public:
+  static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
+                                                   const LoadedIdmap* loaded_idmap, bool system,
+                                                   bool load_as_shared_library);
+
+  ~LoadedPackage();
+
   bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
-                 LoadedArscEntry* out_entry, ResTable_config* out_selected_config,
-                 uint32_t* out_flags) const;
+                 FindEntryResult* out_entry) const;
+
+  // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
+  // the underlying ResStringPool API expects this. For now this is acceptable, but since
+  // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
+  // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
+  // for patching the correct package ID to the resource ID.
+  uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
 
   // Returns the string pool where type names are stored.
-  inline const ResStringPool* GetTypeStringPool() const { return &type_string_pool_; }
+  inline const ResStringPool* GetTypeStringPool() const {
+    return &type_string_pool_;
+  }
 
   // Returns the string pool where the names of resource entries are stored.
-  inline const ResStringPool* GetKeyStringPool() const { return &key_string_pool_; }
+  inline const ResStringPool* GetKeyStringPool() const {
+    return &key_string_pool_;
+  }
 
-  inline const std::string& GetPackageName() const { return package_name_; }
+  inline const std::string& GetPackageName() const {
+    return package_name_;
+  }
 
-  inline int GetPackageId() const { return package_id_; }
+  inline int GetPackageId() const {
+    return package_id_;
+  }
 
   // Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
-  inline bool IsDynamic() const { return dynamic_; }
+  inline bool IsDynamic() const {
+    return dynamic_;
+  }
 
   // Returns true if this package originates from a system provided resource.
-  inline bool IsSystem() const { return system_; }
+  inline bool IsSystem() const {
+    return system_;
+  }
+
+  // Returns true if this package is from an overlay ApkAssets.
+  inline bool IsOverlay() const {
+    return overlay_;
+  }
+
+  // Returns true if this package is verified to be valid.
+  inline bool IsVerified() const {
+    return verified_;
+  }
 
   // Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
   // package could have been assigned a different package ID than what this LoadedPackage was
@@ -101,19 +140,14 @@
   // before being inserted into the set. This may cause some equivalent locales to de-dupe.
   void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
 
-  // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
-  // the underlying ResStringPool API expects this. For now this is acceptable, but since
-  // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
-  // Returns a partial resource ID, with the package ID left as 0x00. The caller is responsible
-  // for patching the correct package ID to the resource ID.
-  uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
-
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
-  static std::unique_ptr<LoadedPackage> Load(const Chunk& chunk);
+  LoadedPackage();
 
-  LoadedPackage() = default;
+  template <bool Verified>
+  bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
+                 const ResTable_config& config, FindEntryResult* out_entry) const;
 
   ResStringPool type_string_pool_;
   ResStringPool key_string_pool_;
@@ -122,6 +156,8 @@
   int type_id_offset_ = 0;
   bool dynamic_ = false;
   bool system_ = false;
+  bool overlay_ = false;
+  bool verified_ = true;
 
   ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
@@ -137,27 +173,33 @@
   // If `load_as_shared_library` is set to true, the application package (0x7f) is treated
   // as a shared library (0x00). When loaded into an AssetManager, the package will be assigned an
   // ID.
-  static std::unique_ptr<const LoadedArsc> Load(const void* data, size_t len, bool system = false,
+  static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
+                                                const LoadedIdmap* loaded_idmap = nullptr,
+                                                bool system = false,
                                                 bool load_as_shared_library = false);
 
-  ~LoadedArsc();
+  // Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
+  static std::unique_ptr<const LoadedArsc> CreateEmpty();
 
   // Returns the string pool where all string resource values
   // (Res_value::dataType == Res_value::TYPE_STRING) are indexed.
-  inline const ResStringPool* GetStringPool() const { return &global_string_pool_; }
+  inline const ResStringPool* GetStringPool() const {
+    return &global_string_pool_;
+  }
 
   // Finds the resource with ID `resid` with the best value for configuration `config`.
   // The parameter `out_entry` will be filled with the resulting resource entry.
   // The resource entry can be a simple entry (ResTable_entry) or a complex bag
   // (ResTable_entry_map).
-  bool FindEntry(uint32_t resid, const ResTable_config& config, LoadedArscEntry* out_entry,
-                 ResTable_config* selected_config, uint32_t* out_flags) const;
+  bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const;
 
   // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
   const LoadedPackage* GetPackageForId(uint32_t resid) const;
 
   // Returns true if this is a system provided resource.
-  inline bool IsSystem() const { return system_; }
+  inline bool IsSystem() const {
+    return system_;
+  }
 
   // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
   inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
@@ -168,7 +210,7 @@
   DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
 
   LoadedArsc() = default;
-  bool LoadTable(const Chunk& chunk, bool load_as_shared_library);
+  bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
 
   ResStringPool global_string_pool_;
   std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 66c66c2..20d0178 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -38,6 +38,9 @@
 
 namespace android {
 
+constexpr const static uint32_t kIdmapMagic = 0x504D4449u;
+constexpr const static uint32_t kIdmapCurrentVersion = 0x00000001u;
+
 /**
  * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
  * casting on raw data and expect char16_t to be exactly 16 bits.
@@ -543,15 +546,15 @@
  */
 class StringPoolRef {
 public:
-    StringPoolRef();
-    StringPoolRef(const ResStringPool* pool, uint32_t index);
+ StringPoolRef() = default;
+ StringPoolRef(const ResStringPool* pool, uint32_t index);
 
-    const char* string8(size_t* outLen) const;
-    const char16_t* string16(size_t* outLen) const;
+ const char* string8(size_t* outLen) const;
+ const char16_t* string16(size_t* outLen) const;
 
 private:
-    const ResStringPool*        mPool;
-    uint32_t                    mIndex;
+ const ResStringPool* mPool = nullptr;
+ uint32_t mIndex = 0u;
 };
 
 /** ********************************************************************
@@ -1179,6 +1182,10 @@
     // tried but could not compute a script.
     bool localeScriptWasComputed;
 
+    // The value of BCP 47 Unicode extension for key 'nu' (numbering system).
+    // Varies in length from 3 to 8 chars. Zero-filled value.
+    char localeNumberingSystem[8];
+
     void copyFromDeviceNoSwap(const ResTable_config& o);
     
     void copyFromDtoH(const ResTable_config& o);
@@ -1256,9 +1263,9 @@
     // variants, it will be a modified bcp47 tag: b+en+Latn+US.
     void appendDirLocale(String8& str) const;
 
-    // Sets the values of language, region, script and variant to the
-    // well formed BCP-47 locale contained in |in|. The input locale is
-    // assumed to be valid and no validation is performed.
+    // Sets the values of language, region, script, variant and numbering
+    // system to the well formed BCP 47 locale contained in |in|.
+    // The input locale is assumed to be valid and no validation is performed.
     void setBcp47Locale(const char* in);
 
     inline void clearLocale() {
@@ -1266,6 +1273,7 @@
         localeScriptWasComputed = false;
         memset(localeScript, 0, sizeof(localeScript));
         memset(localeVariant, 0, sizeof(localeVariant));
+        memset(localeNumberingSystem, 0, sizeof(localeNumberingSystem));
     }
 
     inline void computeScript() {
@@ -1583,6 +1591,30 @@
     uint16_t packageName[128];
 };
 
+struct alignas(uint32_t) Idmap_header {
+  // Always 0x504D4449 ('IDMP')
+  uint32_t magic;
+
+  uint32_t version;
+
+  uint32_t target_crc32;
+  uint32_t overlay_crc32;
+
+  uint8_t target_path[256];
+  uint8_t overlay_path[256];
+
+  uint16_t target_package_id;
+  uint16_t type_count;
+} __attribute__((packed));
+
+struct alignas(uint32_t) IdmapEntry_header {
+  uint16_t target_type_id;
+  uint16_t overlay_type_id;
+  uint16_t entry_count;
+  uint16_t entry_id_offset;
+  uint32_t entries[0];
+} __attribute__((packed));
+
 class AssetManager2;
 
 /**
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index 6bf7c24..c2eae85 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -40,7 +40,9 @@
   return static_cast<uint8_t>((resid >> 16) & 0x000000ffu);
 }
 
-inline uint16_t get_entry_id(uint32_t resid) { return static_cast<uint16_t>(resid & 0x0000ffffu); }
+inline uint16_t get_entry_id(uint32_t resid) {
+  return static_cast<uint16_t>(resid & 0x0000ffffu);
+}
 
 inline bool is_internal_resid(uint32_t resid) {
   return (resid & 0xffff0000u) != 0 && (resid & 0x00ff0000u) == 0;
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 7680342..03154d0 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -80,6 +80,12 @@
     static ZipFileRO* open(const char* zipFileName);
 
     /*
+     * Open an archive from an already open file descriptor.
+     */
+    static ZipFileRO* openFd(int fd, const char* debugFileName,
+        bool assume_ownership = true);
+
+    /*
      * Find an entry, by name.  Returns the entry identifier, or NULL if
      * not found.
      */
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
deleted file mode 100644
index 921fd14..0000000
--- a/libs/androidfw/tests/Android.mk
+++ /dev/null
@@ -1,126 +0,0 @@
-#
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# ==========================================================
-# Setup some common variables for the different build
-# targets here.
-# ==========================================================
-LOCAL_PATH:= $(call my-dir)
-
-testFiles := \
-    ApkAssets_test.cpp \
-    AppAsLib_test.cpp \
-    Asset_test.cpp \
-    AssetManager2_test.cpp \
-    AttributeFinder_test.cpp \
-    AttributeResolution_test.cpp \
-    ByteBucketArray_test.cpp \
-    Config_test.cpp \
-    ConfigLocale_test.cpp \
-    Idmap_test.cpp \
-    LoadedArsc_test.cpp \
-    ResourceUtils_test.cpp \
-    ResTable_test.cpp \
-    Split_test.cpp \
-    StringPiece_test.cpp \
-    TestHelpers.cpp \
-    TestMain.cpp \
-    Theme_test.cpp \
-    TypeWrappers_test.cpp \
-    ZipUtils_test.cpp
-
-benchmarkFiles := \
-    AssetManager2_bench.cpp \
-    BenchMain.cpp \
-    BenchmarkHelpers.cpp \
-    SparseEntry_bench.cpp \
-    TestHelpers.cpp \
-    Theme_bench.cpp
-
-androidfw_test_cflags := \
-    -Wall \
-    -Werror \
-    -Wunused \
-    -Wunreachable-code \
-    -Wno-missing-field-initializers
-
-# gtest is broken.
-androidfw_test_cflags += -Wno-unnamed-type-template-args
-
-# ==========================================================
-# Build the host tests: libandroidfw_tests
-# ==========================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libandroidfw_tests
-LOCAL_CFLAGS := $(androidfw_test_cflags)
-LOCAL_SRC_FILES := $(testFiles)
-LOCAL_STATIC_LIBRARIES := \
-    libandroidfw \
-    libbase \
-    libutils \
-    libcutils \
-    liblog \
-    libz \
-    libziparchive
-LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
-
-include $(BUILD_HOST_NATIVE_TEST)
-
-# ==========================================================
-# Build the device tests: libandroidfw_tests
-# ==========================================================
-ifneq ($(SDK_ONLY),true)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libandroidfw_tests
-LOCAL_CFLAGS := $(androidfw_test_cflags)
-LOCAL_SRC_FILES := $(testFiles) \
-    BackupData_test.cpp \
-    ObbFile_test.cpp \
-
-LOCAL_SHARED_LIBRARIES := \
-    libandroidfw \
-    libbase \
-    libcutils \
-    libutils \
-    libui \
-    libziparchive 
-LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
-
-include $(BUILD_NATIVE_TEST)
-
-# ==========================================================
-# Build the device benchmarks: libandroidfw_benchmarks
-# ==========================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libandroidfw_benchmarks
-LOCAL_CFLAGS := $(androidfw_test_cflags)
-LOCAL_SRC_FILES := $(benchmarkFiles)
-LOCAL_STATIC_LIBRARIES := \
-    libgoogle-benchmark
-LOCAL_SHARED_LIBRARIES := \
-    libandroidfw \
-    libbase \
-    libcutils \
-    libutils \
-    libziparchive
-LOCAL_PICKUP_FILES := $(LOCAL_PATH)/data
-
-include $(BUILD_NATIVE_TEST)
-endif # Not SDK_ONLY
-
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index c85b0b9..d65d93f 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -17,12 +17,14 @@
 #include "androidfw/ApkAssets.h"
 
 #include "android-base/file.h"
+#include "android-base/test_utils.h"
 #include "android-base/unique_fd.h"
+#include "androidfw/Util.h"
 
 #include "TestHelpers.h"
 #include "data/basic/R.h"
 
-using com::android::basic::R;
+using ::com::android::basic::R;
 
 namespace android {
 
@@ -30,7 +32,13 @@
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_NE(nullptr, loaded_apk);
-  EXPECT_NE(nullptr, loaded_apk->GetLoadedArsc());
+
+  const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
+  ASSERT_NE(nullptr, loaded_package);
+  EXPECT_TRUE(loaded_package->IsVerified());
 
   std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
   ASSERT_NE(nullptr, asset);
@@ -54,6 +62,51 @@
   EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
 }
 
+TEST(ApkAssetsTest, LoadApkWithIdmap) {
+  std::string contents;
+  ResTable target_table;
+  const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
+  ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
+  ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+
+  ResTable overlay_table;
+  const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
+  ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
+  ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+
+  util::unique_cptr<void> idmap_data;
+  void* temp_data;
+  size_t idmap_len;
+
+  ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
+                                               overlay_path.c_str(), &temp_data, &idmap_len));
+  idmap_data.reset(temp_data);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(base::WriteFully(tf.fd, idmap_data.get(), idmap_len));
+  close(tf.fd);
+
+  // Open something so that the destructor of TemporaryFile closes a valid fd.
+  tf.fd = open("/dev/null", O_WRONLY);
+
+  std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
+  ASSERT_NE(nullptr, loaded_overlay_apk);
+}
+
+TEST(ApkAssetsTest, LoadUnverifiableApk) {
+  std::unique_ptr<const ApkAssets> loaded_apk =
+      ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+  ASSERT_NE(nullptr, loaded_apk);
+
+  const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
+  ASSERT_NE(nullptr, loaded_package);
+
+  EXPECT_FALSE(loaded_package->IsVerified());
+}
+
 TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 67de741..739e733 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -23,14 +23,15 @@
 #include "androidfw/ResourceTypes.h"
 
 #include "BenchmarkHelpers.h"
-#include "TestHelpers.h"
 #include "data/basic/R.h"
 #include "data/libclient/R.h"
 #include "data/styles/R.h"
+#include "data/unverified/R.h"
 
 namespace app = com::android::app;
 namespace basic = com::android::basic;
 namespace libclient = com::android::libclient;
+namespace unverified = com::android::unverified;
 
 namespace android {
 
@@ -125,6 +126,12 @@
 }
 BENCHMARK(BM_AssetManagerGetResourceOld);
 
+static void BM_AssetManagerGetResourceUnverified(benchmark::State& state) {
+  GetResourceBenchmark({GetTestDataPath() + "/unverified/unverified.apk"}, nullptr /*config*/,
+                       unverified::R::integer::number1, state);
+}
+BENCHMARK(BM_AssetManagerGetResourceUnverified);
+
 static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
   GetResourceBenchmark(
       {GetTestDataPath() + "/lib_two/lib_two.apk", GetTestDataPath() + "/lib_one/lib_one.apk",
@@ -207,6 +214,30 @@
 }
 BENCHMARK(BM_AssetManagerGetBagOld);
 
+static void BM_AssetManagerGetBagUnverified(benchmark::State& state) {
+  std::unique_ptr<const ApkAssets> apk =
+      ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+  if (apk == nullptr) {
+    state.SkipWithError("Failed to load assets");
+    return;
+  }
+
+  AssetManager2 assets;
+  assets.SetApkAssets({apk.get()});
+
+  while (state.KeepRunning()) {
+    const ResolvedBag* bag = assets.GetBag(unverified::R::array::integerArray1);
+    const auto bag_end = end(bag);
+    for (auto iter = begin(bag); iter != bag_end; ++iter) {
+      uint32_t key = iter->key;
+      Res_value value = iter->value;
+      benchmark::DoNotOptimize(key);
+      benchmark::DoNotOptimize(value);
+    }
+  }
+}
+BENCHMARK(BM_AssetManagerGetBagUnverified);
+
 static void BM_AssetManagerGetResourceLocales(benchmark::State& state) {
   std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
   if (apk == nullptr) {
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index fcae53b..ab1a22e 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -28,6 +28,7 @@
 #include "data/libclient/R.h"
 #include "data/styles/R.h"
 #include "data/system/R.h"
+#include "data/unverified/R.h"
 
 namespace app = com::android::app;
 namespace appaslib = com::android::appaslib::app;
@@ -35,6 +36,7 @@
 namespace lib_one = com::android::lib_one;
 namespace lib_two = com::android::lib_two;
 namespace libclient = com::android::libclient;
+namespace unverified = com::android::unverified;
 
 namespace android {
 
@@ -431,4 +433,30 @@
 
 TEST_F(AssetManager2Test, OpensFileFromMultipleApkAssets) {}
 
+TEST_F(AssetManager2Test, OperateOnUnverifiedApkAssets) {
+  std::unique_ptr<const ApkAssets> unverified_assets =
+      ApkAssets::Load(GetTestDataPath() + "/unverified/unverified.apk");
+  ASSERT_NE(nullptr, unverified_assets);
+
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({unverified_assets.get()});
+
+  Res_value value;
+  ResTable_config config;
+  uint32_t flags;
+
+  EXPECT_EQ(kInvalidCookie,
+            assetmanager.GetResource(unverified::R::string::test1, false /*may_be_bag*/, 0u, &value,
+                                     &config, &flags));
+  EXPECT_EQ(kInvalidCookie,
+            assetmanager.GetResource(unverified::R::string::test2, false /*may_be_bag*/, 0u, &value,
+                                     &config, &flags));
+  EXPECT_NE(kInvalidCookie,
+            assetmanager.GetResource(unverified::R::integer::number1, false /*may_be_bag*/, 0u,
+                                     &value, &config, &flags));
+
+  EXPECT_EQ(nullptr, assetmanager.GetBag(unverified::R::style::Theme1));
+  EXPECT_NE(nullptr, assetmanager.GetBag(unverified::R::array::integerArray1));
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/BenchMain.cpp b/libs/androidfw/tests/BenchMain.cpp
index 105c5f9..58fc54a 100644
--- a/libs/androidfw/tests/BenchMain.cpp
+++ b/libs/androidfw/tests/BenchMain.cpp
@@ -18,7 +18,7 @@
 
 #include "benchmark/benchmark.h"
 
-#include "TestHelpers.h"
+#include "BenchmarkHelpers.h"
 
 int main(int argc, char** argv) {
   ::benchmark::Initialize(&argc, argv);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.h b/libs/androidfw/tests/BenchmarkHelpers.h
index fc36664..0bb96b5 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.h
+++ b/libs/androidfw/tests/BenchmarkHelpers.h
@@ -14,21 +14,22 @@
  * limitations under the License.
  */
 
-#ifndef TESTS_BENCHMARKHELPERS_H_
-#define TESTS_BENCHMARKHELPERS_H_
+#ifndef ANDROIDFW_TESTS_BENCHMARKHELPERS_H
+#define ANDROIDFW_TESTS_BENCHMARKHELPERS_H
 
 #include <string>
 #include <vector>
 
+#include "androidfw/ResourceTypes.h"
 #include "benchmark/benchmark.h"
 
-#include "androidfw/ResourceTypes.h"
+#include "CommonHelpers.h"
 
 namespace android {
 
 void GetResourceBenchmarkOld(const std::vector<std::string>& paths, const ResTable_config* config,
-                             uint32_t resid, benchmark::State& state);
+                             uint32_t resid, ::benchmark::State& state);
 
 }  // namespace android
 
-#endif /* TESTS_BENCHMARKHELPERS_H_ */
+#endif  // ANDROIDFW_TESTS_BENCHMARKHELPERS_H
diff --git a/libs/androidfw/tests/CommonHelpers.cpp b/libs/androidfw/tests/CommonHelpers.cpp
new file mode 100644
index 0000000..faa5350
--- /dev/null
+++ b/libs/androidfw/tests/CommonHelpers.cpp
@@ -0,0 +1,65 @@
+/*
+ * 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 "CommonHelpers.h"
+
+#include <iostream>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/strings.h"
+
+namespace android {
+
+static std::string sTestDataPath;
+
+void InitializeTest(int* argc, char** argv) {
+  // Set the default test data path to be the executable path directory + data.
+  SetTestDataPath(base::GetExecutableDirectory() + "/tests/data");
+
+  for (int i = 1; i < *argc; i++) {
+    const std::string arg = argv[i];
+    if (base::StartsWith(arg, "--testdata=")) {
+      SetTestDataPath(arg.substr(strlen("--testdata=")));
+      for (int j = i; j != *argc; j++) {
+        argv[j] = argv[j + 1];
+      }
+      --(*argc);
+      --i;
+    } else if (arg == "-h" || arg == "--help") {
+      std::cerr << "\nAdditional options specific to this test:\n"
+                   "  --testdata=[PATH]\n"
+                   "      Specify the location of test data used within the tests.\n";
+      exit(1);
+    }
+  }
+}
+
+void SetTestDataPath(const std::string& path) {
+  sTestDataPath = path;
+}
+
+const std::string& GetTestDataPath() {
+  CHECK(!sTestDataPath.empty()) << "no test data path set.";
+  return sTestDataPath;
+}
+
+std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) {
+  String8 str = pool->string8ObjectAt(idx);
+  return std::string(str.string(), str.length());
+}
+
+}  // namespace android
diff --git a/libs/androidfw/tests/CommonHelpers.h b/libs/androidfw/tests/CommonHelpers.h
new file mode 100644
index 0000000..c160fbb
--- /dev/null
+++ b/libs/androidfw/tests/CommonHelpers.h
@@ -0,0 +1,57 @@
+/*
+ * 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 ANDROIDFW_TEST_COMMON_HELPERS_H
+#define ANDROIDFW_TEST_COMMON_HELPERS_H
+
+#include <ostream>
+#include <string>
+
+#include "androidfw/ResourceTypes.h"
+#include "utils/String16.h"
+#include "utils/String8.h"
+
+namespace android {
+
+void InitializeTest(int* argc, char** argv);
+
+enum { MAY_NOT_BE_BAG = false };
+
+void SetTestDataPath(const std::string& path);
+
+const std::string& GetTestDataPath();
+
+std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx);
+
+static inline bool operator==(const ResTable_config& a, const ResTable_config& b) {
+  return a.compare(b) == 0;
+}
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const String8& str) {
+  return out << str.string();
+}
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const String16& str) {
+  return out << String8(str).string();
+}
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const ResTable_config& c) {
+  return out << c.toString();
+}
+
+}  // namespace android
+
+#endif  // ANDROIDFW_TEST_COMMON_HELPERS_H
diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
index 86a627e..35007c8 100644
--- a/libs/androidfw/tests/ConfigLocale_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -185,6 +185,7 @@
     EXPECT_TRUE(test.localeScriptWasComputed);
     EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
     EXPECT_EQ(0, test.localeVariant[0]);
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
 
     test.setBcp47Locale("eng-419");
     char out[4] = {1, 1, 1, 1};
@@ -198,6 +199,7 @@
     EXPECT_EQ('4', out[0]);
     EXPECT_EQ('1', out[1]);
     EXPECT_EQ('9', out[2]);
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
 
     test.setBcp47Locale("en-Latn-419");
     EXPECT_EQ('e', test.language[0]);
@@ -209,6 +211,7 @@
     EXPECT_EQ('4', out[0]);
     EXPECT_EQ('1', out[1]);
     EXPECT_EQ('9', out[2]);
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
 
     test.setBcp47Locale("de-1901");
     memset(out, 1, 4);
@@ -222,6 +225,7 @@
     test.unpackRegion(out);
     EXPECT_EQ('\0', out[0]);
     EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
 
     test.setBcp47Locale("de-Latn-1901");
     memset(out, 1, 4);
@@ -235,6 +239,44 @@
     test.unpackRegion(out);
     EXPECT_EQ('\0', out[0]);
     EXPECT_EQ(0, strcmp("1901", test.localeVariant));
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+    test.setBcp47Locale("ar-EG-u-nu-latn");
+    EXPECT_EQ('a', test.language[0]);
+    EXPECT_EQ('r', test.language[1]);
+    EXPECT_EQ('E', test.country[0]);
+    EXPECT_EQ('G', test.country[1]);
+    EXPECT_TRUE(test.localeScriptWasComputed);
+    EXPECT_EQ(0, memcmp("Arab", test.localeScript, 4));
+    EXPECT_EQ(0, test.localeVariant[0]);
+    EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+    test.setBcp47Locale("ar-EG-u");
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+    test.setBcp47Locale("ar-EG-u-nu");
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+    test.setBcp47Locale("ar-EG-u-attr-nu-latn");
+    EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+    test.setBcp47Locale("ar-EG-u-ca-gregory-nu-latn");
+    EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+    test.setBcp47Locale("ar-EG-u-nu-latn-ca-gregory");
+    EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+    test.setBcp47Locale("ar-EG-u-nu-toolongnumsys");
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+    test.setBcp47Locale("ar-EG-u-nu-latn-nu-arab");
+    EXPECT_EQ(0, memcmp("latn", test.localeNumberingSystem, 4));
+
+    test.setBcp47Locale("ar-EG-u-co-nu-latn");
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
+
+    test.setBcp47Locale("ar-u-co-abcd-attr-nu-latn");
+    EXPECT_EQ(0, test.localeNumberingSystem[0]);
 }
 
 TEST(ConfigLocaleTest, computeScript) {
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index d12be18..9eb4a13 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -22,7 +22,7 @@
 #include "TestHelpers.h"
 #include "data/basic/R.h"
 
-using com::android::basic::R;
+using ::com::android::basic::R;
 
 namespace android {
 
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 756869f..954a54d 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -32,8 +32,7 @@
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk", "resources.arsc",
                                       &contents));
 
-  std::unique_ptr<const LoadedArsc> loaded_arsc =
-      LoadedArsc::Load(contents.data(), contents.size());
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
   ASSERT_NE(nullptr, loaded_arsc);
 
   const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
@@ -45,12 +44,9 @@
   memset(&config, 0, sizeof(config));
   config.sdkVersion = 24;
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
+  FindEntryResult entry;
 
-  ASSERT_TRUE(
-      loaded_arsc->FindEntry(app::R::string::string_one, config, &entry, &selected_config, &flags));
+  ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
   ASSERT_NE(nullptr, entry.entry);
 }
 
@@ -59,8 +55,7 @@
   ASSERT_TRUE(
       ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
 
-  std::unique_ptr<const LoadedArsc> loaded_arsc =
-      LoadedArsc::Load(contents.data(), contents.size());
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
   ASSERT_NE(nullptr, loaded_arsc);
 
   ResTable_config desired_config;
@@ -68,12 +63,8 @@
   desired_config.language[0] = 'd';
   desired_config.language[1] = 'e';
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry,
-                                     &selected_config, &flags));
+  FindEntryResult entry;
+  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
   ASSERT_NE(nullptr, entry.entry);
 }
 
@@ -82,8 +73,7 @@
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk", "resources.arsc",
                                       &contents));
 
-  std::unique_ptr<const LoadedArsc> loaded_arsc =
-      LoadedArsc::Load(contents.data(), contents.size());
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
   ASSERT_NE(nullptr, loaded_arsc);
 
   const auto& packages = loaded_arsc->GetPackages();
@@ -104,8 +94,7 @@
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/libclient/libclient.apk",
                                       "resources.arsc", &contents));
 
-  std::unique_ptr<const LoadedArsc> loaded_arsc =
-      LoadedArsc::Load(contents.data(), contents.size());
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
   ASSERT_NE(nullptr, loaded_arsc);
 
   const auto& packages = loaded_arsc->GetPackages();
@@ -132,8 +121,9 @@
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/appaslib/appaslib.apk",
                                       "resources.arsc", &contents));
 
-  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(
-      contents.data(), contents.size(), false /*system*/, true /*load_as_shared_library*/);
+  std::unique_ptr<const LoadedArsc> loaded_arsc =
+      LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
+                       true /*load_as_shared_library*/);
   ASSERT_NE(nullptr, loaded_arsc);
 
   const auto& packages = loaded_arsc->GetPackages();
@@ -147,33 +137,68 @@
   std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
                                       &contents));
-  std::unique_ptr<const LoadedArsc> loaded_arsc =
-      LoadedArsc::Load(contents.data(), contents.size());
+  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
   ASSERT_NE(nullptr, loaded_arsc);
 
   ResTable_config desired_config;
   memset(&desired_config, 0, sizeof(desired_config));
 
-  LoadedArscEntry entry;
-  ResTable_config selected_config;
-  uint32_t flags;
-
-  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry,
-                                     &selected_config, &flags));
+  FindEntryResult entry;
+  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
 
   size_t len;
   const char16_t* type_name16 = entry.type_string_ref.string16(&len);
   ASSERT_NE(nullptr, type_name16);
   ASSERT_NE(0u, len);
 
-  size_t utf8_len = utf16_to_utf8_length(type_name16, len);
-  std::string type_name;
-  type_name.resize(utf8_len);
-  utf16_to_utf8(type_name16, len, &*type_name.begin(), utf8_len + 1);
-
+  std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
   EXPECT_EQ(std::string("string"), type_name);
 }
 
+class MockLoadedIdmap : public LoadedIdmap {
+ public:
+  MockLoadedIdmap() : LoadedIdmap() {
+    local_header_.magic = kIdmapMagic;
+    local_header_.version = kIdmapCurrentVersion;
+    local_header_.target_package_id = 0x08;
+    local_header_.type_count = 1;
+    header_ = &local_header_;
+
+    entry_header = util::unique_cptr<IdmapEntry_header>(
+        (IdmapEntry_header*)::malloc(sizeof(IdmapEntry_header) + sizeof(uint32_t)));
+    entry_header->target_type_id = 0x03;
+    entry_header->overlay_type_id = 0x02;
+    entry_header->entry_id_offset = 1;
+    entry_header->entry_count = 1;
+    entry_header->entries[0] = 0x00000000u;
+    type_map_[entry_header->overlay_type_id] = entry_header.get();
+  }
+
+ private:
+  Idmap_header local_header_;
+  util::unique_cptr<IdmapEntry_header> entry_header;
+};
+
+TEST(LoadedArscTest, LoadOverlay) {
+  std::string contents, overlay_contents;
+  ASSERT_TRUE(
+      ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
+                                      &overlay_contents));
+
+  MockLoadedIdmap loaded_idmap;
+
+  std::unique_ptr<const LoadedArsc> loaded_arsc =
+      LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
+  ASSERT_NE(nullptr, loaded_arsc);
+
+  ResTable_config desired_config;
+  memset(&desired_config, 0, sizeof(desired_config));
+
+  FindEntryResult entry;
+  ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
+}
+
 // structs with size fields (like Res_value, ResTable_entry) should be
 // backwards and forwards compatible (aka checking the size field against
 // sizeof(Res_value) might not be backwards compatible.
diff --git a/libs/androidfw/tests/SparseEntry_bench.cpp b/libs/androidfw/tests/SparseEntry_bench.cpp
index 1ebf7ce..d6dc07d 100644
--- a/libs/androidfw/tests/SparseEntry_bench.cpp
+++ b/libs/androidfw/tests/SparseEntry_bench.cpp
@@ -18,7 +18,6 @@
 #include "androidfw/ResourceTypes.h"
 
 #include "BenchmarkHelpers.h"
-#include "TestHelpers.h"
 #include "data/sparse/R.h"
 
 namespace sparse = com::android::sparse;
diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp
index 1e763a5..9e320a2 100644
--- a/libs/androidfw/tests/TestHelpers.cpp
+++ b/libs/androidfw/tests/TestHelpers.cpp
@@ -16,67 +16,22 @@
 
 #include "TestHelpers.h"
 
-#include <libgen.h>
-#include <unistd.h>
-
-#include <memory>
-#include <string>
-
-#include "android-base/file.h"
-#include "android-base/logging.h"
-#include "android-base/strings.h"
 #include "ziparchive/zip_archive.h"
 
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+
 namespace android {
 
-static std::string sTestDataPath;
-
-// Extract the directory of the current executable path.
-static std::string GetExecutableDir() {
-  const std::string path = base::GetExecutablePath();
-  std::unique_ptr<char, decltype(&std::free)> mutable_path = {strdup(path.c_str()), std::free};
-  std::string executable_dir = dirname(mutable_path.get());
-  return executable_dir;
-}
-
-void InitializeTest(int* argc, char** argv) {
-  // Set the default test data path to be the executable path directory.
-  SetTestDataPath(GetExecutableDir());
-
-  for (int i = 1; i < *argc; i++) {
-    const std::string arg = argv[i];
-    if (base::StartsWith(arg, "--testdata=")) {
-      SetTestDataPath(arg.substr(strlen("--testdata=")));
-      for (int j = i; j != *argc; j++) {
-        argv[j] = argv[j + 1];
-      }
-      --(*argc);
-      --i;
-    } else if (arg == "-h" || arg == "--help") {
-      std::cerr << "\nAdditional options specific to this test:\n"
-                   "  --testdata=[PATH]\n"
-                   "      Specify the location of test data used within the tests.\n";
-      exit(1);
-    }
-  }
-}
-
-void SetTestDataPath(const std::string& path) { sTestDataPath = path; }
-
-const std::string& GetTestDataPath() {
-  CHECK(!sTestDataPath.empty()) << "no test data path set.";
-  return sTestDataPath;
-}
-
-::testing::AssertionResult ReadFileFromZipToString(const std::string& zip_path,
-                                                   const std::string& file,
-                                                   std::string* out_contents) {
+AssertionResult ReadFileFromZipToString(const std::string& zip_path, const std::string& file,
+                                        std::string* out_contents) {
   out_contents->clear();
   ::ZipArchiveHandle handle;
   int32_t result = OpenArchive(zip_path.c_str(), &handle);
   if (result != 0) {
-    return ::testing::AssertionFailure() << "Failed to open zip '" << zip_path
-                                         << "': " << ::ErrorCodeString(result);
+    return AssertionFailure() << "Failed to open zip '" << zip_path
+                              << "': " << ::ErrorCodeString(result);
   }
 
   ::ZipString name(file.c_str());
@@ -84,8 +39,8 @@
   result = ::FindEntry(handle, name, &entry);
   if (result != 0) {
     ::CloseArchive(handle);
-    return ::testing::AssertionFailure() << "Could not find file '" << file << "' in zip '"
-                                         << zip_path << "' : " << ::ErrorCodeString(result);
+    return AssertionFailure() << "Could not find file '" << file << "' in zip '" << zip_path
+                              << "' : " << ::ErrorCodeString(result);
   }
 
   out_contents->resize(entry.uncompressed_length);
@@ -94,41 +49,36 @@
       out_contents->size());
   if (result != 0) {
     ::CloseArchive(handle);
-    return ::testing::AssertionFailure() << "Failed to extract file '" << file << "' from zip '"
-                                         << zip_path << "': " << ::ErrorCodeString(result);
+    return AssertionFailure() << "Failed to extract file '" << file << "' from zip '" << zip_path
+                              << "': " << ::ErrorCodeString(result);
   }
 
   ::CloseArchive(handle);
-  return ::testing::AssertionSuccess();
+  return AssertionSuccess();
 }
 
-::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
-                                         const char* expected_str) {
+AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
+                              const char* expected_str) {
   Res_value val;
   ssize_t block = table.getResource(resource_id, &val, MAY_NOT_BE_BAG);
   if (block < 0) {
-    return ::testing::AssertionFailure() << "could not find resource";
+    return AssertionFailure() << "could not find resource";
   }
 
   if (val.dataType != Res_value::TYPE_STRING) {
-    return ::testing::AssertionFailure() << "resource is not a string";
+    return AssertionFailure() << "resource is not a string";
   }
 
   const ResStringPool* pool = table.getTableStringBlock(block);
   if (pool == NULL) {
-    return ::testing::AssertionFailure() << "table has no string pool for block " << block;
+    return AssertionFailure() << "table has no string pool for block " << block;
   }
 
   const String8 actual_str = pool->string8ObjectAt(val.data);
   if (String8(expected_str) != actual_str) {
-    return ::testing::AssertionFailure() << actual_str.string();
+    return AssertionFailure() << actual_str.string();
   }
-  return ::testing::AssertionSuccess() << actual_str.string();
-}
-
-std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx) {
-  String8 str = pool->string8ObjectAt(idx);
-  return std::string(str.string(), str.length());
+  return AssertionSuccess() << actual_str.string();
 }
 
 }  // namespace android
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index ec78b2a..43a9955 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -14,53 +14,25 @@
  * limitations under the License.
  */
 
-#ifndef TEST_HELPERS_H_
-#define TEST_HELPERS_H_
+#ifndef ANDROIDFW_TEST_TESTHELPERS_H
+#define ANDROIDFW_TEST_TESTHELPERS_H
 
-#include <ostream>
 #include <string>
-#include <vector>
 
 #include "androidfw/ResourceTypes.h"
 #include "gtest/gtest.h"
-#include "utils/String16.h"
-#include "utils/String8.h"
 
-static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) {
-  return out << str.string();
-}
-
-static inline ::std::ostream& operator<<(::std::ostream& out, const android::String16& str) {
-  return out << android::String8(str).string();
-}
+#include "CommonHelpers.h"
 
 namespace android {
 
-void InitializeTest(int* argc, char** argv);
-
-enum { MAY_NOT_BE_BAG = false };
-
-void SetTestDataPath(const std::string& path);
-
-const std::string& GetTestDataPath();
-
 ::testing::AssertionResult ReadFileFromZipToString(const std::string& zip_path,
                                                    const std::string& file,
                                                    std::string* out_contents);
 
-static inline bool operator==(const ResTable_config& a, const ResTable_config& b) {
-  return a.compare(b) == 0;
-}
-
-static inline ::std::ostream& operator<<(::std::ostream& out, const ResTable_config& c) {
-  return out << c.toString().string();
-}
-
 ::testing::AssertionResult IsStringEqual(const ResTable& table, uint32_t resource_id,
                                          const char* expected_str);
 
-std::string GetStringFromPool(const ResStringPool* pool, uint32_t idx);
-
 }  // namespace android
 
-#endif  // TEST_HELPERS_H_
+#endif  // ANDROIDFW_TEST_TESTHELPERS_H
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index dfff9c0..55d53ed 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -23,6 +23,7 @@
 #include "data/lib_one/R.h"
 #include "data/libclient/R.h"
 #include "data/styles/R.h"
+#include "data/system/R.h"
 
 namespace app = com::android::app;
 namespace lib_one = com::android::lib_one;
@@ -33,6 +34,9 @@
 class ThemeTest : public ::testing::Test {
  public:
   void SetUp() override {
+    system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/);
+    ASSERT_NE(nullptr, system_assets_);
+
     style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
     ASSERT_NE(nullptr, style_assets_);
 
@@ -47,6 +51,7 @@
   }
 
  protected:
+  std::unique_ptr<const ApkAssets> system_assets_;
   std::unique_ptr<const ApkAssets> style_assets_;
   std::unique_ptr<const ApkAssets> libclient_assets_;
   std::unique_ptr<const ApkAssets> lib_one_assets_;
@@ -123,6 +128,18 @@
   EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
 }
 
+TEST_F(ThemeTest, TryToUseBadResourceId) {
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({style_assets_.get()});
+
+  std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+  ASSERT_TRUE(theme->ApplyStyle(app::R::style::StyleTwo));
+
+  Res_value value;
+  uint32_t flags;
+  ASSERT_EQ(kInvalidCookie, theme->GetAttribute(0x7f000001, &value, &flags));
+}
+
 TEST_F(ThemeTest, MultipleThemesOverlaidNotForce) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({style_assets_.get()});
@@ -262,20 +279,30 @@
   EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags);
 }
 
-TEST_F(ThemeTest, FailToCopyThemeWithDifferentAssetManager) {
+TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) {
   AssetManager2 assetmanager_one;
-  assetmanager_one.SetApkAssets({style_assets_.get()});
+  assetmanager_one.SetApkAssets({system_assets_.get(), style_assets_.get()});
 
   AssetManager2 assetmanager_two;
-  assetmanager_two.SetApkAssets({style_assets_.get()});
+  assetmanager_two.SetApkAssets({system_assets_.get(), style_assets_.get()});
 
   auto theme_one = assetmanager_one.NewTheme();
   ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne));
 
   auto theme_two = assetmanager_two.NewTheme();
+  ASSERT_TRUE(theme_two->ApplyStyle(R::style::Theme_One));
   ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo));
 
-  EXPECT_FALSE(theme_one->SetTo(*theme_two));
+  EXPECT_TRUE(theme_one->SetTo(*theme_two));
+
+  Res_value value;
+  uint32_t flags;
+
+  // No app resources.
+  EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags));
+
+  // Only system.
+  EXPECT_NE(kInvalidCookie, theme_one->GetAttribute(R::attr::foreground, &value, &flags));
 }
 
 }  // namespace android
diff --git a/libs/androidfw/tests/data/unverified/R.h b/libs/androidfw/tests/data/unverified/R.h
new file mode 100644
index 0000000..b734b49
--- /dev/null
+++ b/libs/androidfw/tests/data/unverified/R.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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 TESTS_DATA_UNVERIFIED_R_H_
+#define TESTS_DATA_UNVERIFIED_R_H_
+
+#include <cstdint>
+
+#include "tests/data/basic/R.h"
+
+namespace com {
+namespace android {
+
+namespace unverified = basic;
+
+}  // namespace android
+}  // namespace com
+
+#endif /* TESTS_DATA_UNVERIFIED_R_H_ */
diff --git a/libs/androidfw/tests/data/unverified/unverified.apk b/libs/androidfw/tests/data/unverified/unverified.apk
new file mode 100644
index 0000000..234b390
--- /dev/null
+++ b/libs/androidfw/tests/data/unverified/unverified.apk
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 770a57a..2644292 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -2,11 +2,13 @@
     name: "hwui_defaults",
     defaults: [
         "hwui_static_deps",
-
+        "skia_deps"
         //"hwui_bugreport_font_cache_usage",
         //"hwui_compile_for_perf",
     ],
 
+    cpp_std: "c++17",
+
     cflags: [
         "-DEGL_EGLEXT_PROTOTYPES",
         "-DGL_GLEXT_PROTOTYPES",
@@ -55,7 +57,6 @@
         "libEGL",
         "libGLESv2",
         "libvulkan",
-        "libskia",
         "libui",
         "libgui",
         "libprotobuf-cpp-lite",
@@ -114,6 +115,9 @@
 cc_defaults {
     name: "libhwui_defaults",
     defaults: ["hwui_defaults"],
+
+    whole_static_libs: ["libskia"],
+
     srcs: [
         "hwui/Bitmap.cpp",
         "font/CacheTexture.cpp",
@@ -248,7 +252,19 @@
         // If enabled, every GLES call is wrapped & error checked
         // Has moderate overhead
         "hwui_enable_opengl_validation",
-],
+    ],
+
+    // Build libhwui with PGO by default.
+    // Location of PGO profile data is defined in build/soong/cc/pgo.go
+    // and is separate from hwui.
+    // To turn it off, set ANDROID_PGO_NO_PROFILE_USE environment variable
+    // or set enable_profile_use property to false.
+    pgo: {
+        instrumentation: true,
+        profile_file: "hwui/hwui.profdata",
+        benchmarks: ["hwui"],
+        enable_profile_use: false,
+    },
 }
 
 // ------------------------
@@ -340,7 +356,8 @@
         "tests/unit/TestUtilsTests.cpp",
         "tests/unit/TextDropShadowCacheTests.cpp",
         "tests/unit/TextureCacheTests.cpp",
-	"tests/unit/TypefaceTests.cpp",
+        "tests/unit/ThreadBaseTests.cpp",
+        "tests/unit/TypefaceTests.cpp",
         "tests/unit/VectorDrawableTests.cpp",
         "tests/unit/VectorDrawableAtlasTests.cpp",
     ],
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
index f5bb821..69ead58 100644
--- a/libs/hwui/AnimatorManager.cpp
+++ b/libs/hwui/AnimatorManager.cpp
@@ -71,9 +71,11 @@
 
 void AnimatorManager::pushStaging() {
     if (mNewAnimators.size()) {
-        LOG_ALWAYS_FATAL_IF(!mAnimationHandle,
-                "Trying to start new animators on %p (%s) without an animation handle!",
-                &mParent, mParent.getName());
+        if (CC_UNLIKELY(!mAnimationHandle)) {
+            ALOGW("Trying to start new animators on %p (%s) without an animation handle!",
+                    &mParent, mParent.getName());
+            return;
+        }
 
         // Only add new animators that are not already in the mAnimators list
         for (auto& anim : mNewAnimators) {
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index 1e71cb0..115dcb6 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -31,6 +31,13 @@
 namespace uirenderer {
 
 Extensions::Extensions() {
+    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+        //Extensions class is used only by OpenGL and SkiaGL pipelines
+        //The code below will crash for SkiaVulkan, because OpenGL is not initialized
+        //TODO: instantiate Extensions class only for OpenGL pipeline
+        //TODO: remove the only usage of Extensions by SkiaGL in SkiaOpenGLReadback::copyImageInto
+        return;
+    }
     const char* version = (const char*) glGetString(GL_VERSION);
 
     // Section 6.1.5 of the OpenGL ES specification indicates the GL version
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index f9a1cc5..2687410 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -280,6 +280,11 @@
 
 bool OpenGLReadbackImpl::copyLayerInto(renderthread::RenderThread& renderThread,
         GlLayer& layer, SkBitmap* bitmap) {
+    if (!layer.isRenderable()) {
+        // layer has never been updated by DeferredLayerUpdater, abort copy
+        return false;
+    }
+
     return CopyResult::Success == copyTextureInto(Caches::getInstance(),
             renderThread.renderState(), layer.getTexture(), layer.getTexTransform(),
             Rect(), bitmap);
diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp
index cbf3eb3..70a77ed 100644
--- a/libs/hwui/ProfileDataContainer.cpp
+++ b/libs/hwui/ProfileDataContainer.cpp
@@ -16,6 +16,8 @@
 
 #include "ProfileDataContainer.h"
 
+#include <errno.h>
+
 #include <log/log.h>
 #include <cutils/ashmem.h>
 
@@ -75,4 +77,4 @@
 }
 
 } /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 3b59bb1..588c4de 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -58,6 +58,7 @@
 
 bool Properties::filterOutTestOverhead = false;
 bool Properties::disableVsync = false;
+bool Properties::skpCaptureEnabled = false;
 
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {'\0',};
@@ -128,6 +129,9 @@
 
     filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false);
 
+    skpCaptureEnabled = property_get_bool("ro.debuggable", false)
+            && property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false);
+
     return (prevDebugLayersUpdates != debugLayersUpdates)
             || (prevDebugOverdraw != debugOverdraw)
             || (prevDebugStencilClip != debugStencilClip);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 0fe4c77..077e7ae 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -165,6 +165,22 @@
  */
 #define PROPERTY_RENDERER "debug.hwui.renderer"
 
+/**
+ * Allows to collect a recording of Skia drawing commands.
+ */
+#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
+
+
+/**
+ * Defines how many frames in a sequence to capture.
+ */
+#define PROPERTY_CAPTURE_SKP_FRAMES  "debug.hwui.capture_skp_frames"
+
+/**
+ * File name and location, where a SKP recording will be saved.
+ */
+#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -254,6 +270,8 @@
     // created after changing this.
     static bool disableVsync;
 
+    static bool skpCaptureEnabled;
+
     // Used for testing only to change the render pipeline.
 #ifdef HWUI_GLES_WRAP_ENABLED
     static void overrideRenderPipelineType(RenderPipelineType);
diff --git a/libs/hwui/SwapBehavior.h b/libs/hwui/SwapBehavior.h
new file mode 100644
index 0000000..4091670c
--- /dev/null
+++ b/libs/hwui/SwapBehavior.h
@@ -0,0 +1,33 @@
+/*
+ * 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 HWUI_SWAPBEHAVIOR_H
+#define HWUI_SWAPBEHAVIOR_H
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+enum class SwapBehavior {
+    kSwap_default,
+    kSwap_discardBuffer,
+};
+
+} // namespace renderthread
+} //namespace uirenderer
+} // namespace android
+
+#endif //HWUI_SWAPBEHAVIOR_H
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index a087035..6f05799 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -158,7 +158,8 @@
 };
 
 void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount,
-        float x, float y, int bidiFlags, const Paint& origPaint, const Typeface* typeface) {
+        float x, float y, minikin::Bidi bidiFlags, const Paint& origPaint,
+        const Typeface* typeface) {
     // minikin may modify the original paint
     Paint paint(origPaint);
 
@@ -206,8 +207,9 @@
     const SkPath& path;
 };
 
-void Canvas::drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
-        float hOffset, float vOffset, const Paint& paint, const Typeface* typeface) {
+void Canvas::drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags,
+        const SkPath& path, float hOffset, float vOffset, const Paint& paint,
+        const Typeface* typeface) {
     Paint paintCopy(paint);
     minikin::Layout layout = MinikinUtils::doLayout(
             &paintCopy, bidiFlags, typeface, text, 0, count, count);
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index fbd8960..0a1bae7 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -33,6 +33,7 @@
 
 namespace minikin {
     class Layout;
+    enum class Bidi : uint8_t;
 }
 
 namespace android {
@@ -255,10 +256,12 @@
      * and delegating the final draw to virtual drawGlyphs method.
      */
     void drawText(const uint16_t* text, int start, int count, int contextCount,
-            float x, float y, int bidiFlags, const Paint& origPaint, const Typeface* typeface);
+            float x, float y, minikin::Bidi bidiFlags, const Paint& origPaint,
+            const Typeface* typeface);
 
-    void drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path,
-            float hOffset, float vOffset, const Paint& paint, const Typeface* typeface);
+    void drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags,
+            const SkPath& path, float hOffset, float vOffset, const Paint& paint,
+            const Typeface* typeface);
 
 protected:
     void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 2b29542..a5b4c42 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -104,14 +104,14 @@
 
 std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
         const std::vector<minikin::FontVariation>& variations) const {
-    SkFontMgr::FontParameters params;
+    SkFontArguments params;
 
     int ttcIndex;
-    SkStreamAsset* stream = mTypeface->openStream(&ttcIndex);
+    std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex));
     LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
 
     params.setCollectionIndex(ttcIndex);
-    std::vector<SkFontMgr::FontParameters::Axis> skAxes;
+    std::vector<SkFontArguments::Axis> skAxes;
     skAxes.resize(variations.size());
     for (size_t i = 0; i < variations.size(); i++) {
         skAxes[i].fTag = variations[i].axisTag;
@@ -119,7 +119,7 @@
     }
     params.setAxes(skAxes.data(), skAxes.size());
     sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> face(fm->createFromStream(stream, params));
+    sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), params));
 
     return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, ttcIndex,
             variations);
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 7da7f38..814d2cf 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -51,9 +51,8 @@
     return minikinStyle;
 }
 
-minikin::Layout MinikinUtils::doLayout(const Paint* paint, int bidiFlags,
-        const Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
-        size_t bufSize) {
+minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags,
+        const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize) {
     minikin::MinikinPaint minikinPaint;
     minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
     minikin::Layout layout;
@@ -62,8 +61,9 @@
     return layout;
 }
 
-float MinikinUtils::measureText(const Paint* paint, int bidiFlags, const Typeface* typeface,
-        const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) {
+float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
+        const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+        float *advances) {
     minikin::MinikinPaint minikinPaint;
     minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface);
     const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index bfd816f..2e8aa58 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -37,11 +37,11 @@
     ANDROID_API static minikin::FontStyle prepareMinikinPaint(minikin::MinikinPaint* minikinPaint,
             const Paint* paint, const Typeface* typeface);
 
-    ANDROID_API static minikin::Layout doLayout(const Paint* paint, int bidiFlags,
+    ANDROID_API static minikin::Layout doLayout(const Paint* paint, minikin::Bidi bidiFlags,
             const Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
             size_t bufSize);
 
-    ANDROID_API static float measureText(const Paint* paint, int bidiFlags,
+    ANDROID_API static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
               const Typeface* typeface, const uint16_t* buf, size_t start, size_t count,
               size_t bufSize, float *advances);
 
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 0a38874..7cc0871 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -34,12 +34,12 @@
 
 namespace android {
 
-static SkTypeface::Style computeSkiaStyle(int weight, bool italic) {
+static Typeface::Style computeAPIStyle(int weight, bool italic) {
     // This bold detection comes from SkTypeface.h
     if (weight >= SkFontStyle::kSemiBold_Weight) {
-        return italic ? SkTypeface::kBoldItalic : SkTypeface::kBold;
+        return italic ? Typeface::kBoldItalic : Typeface::kBold;
     } else {
-        return italic ? SkTypeface::kItalic : SkTypeface::kNormal;
+        return italic ? Typeface::kItalic : Typeface::kNormal;
     }
 }
 
@@ -50,12 +50,12 @@
 }
 
 // Resolve the relative weight from the baseWeight and target style.
-static minikin::FontStyle computeRelativeStyle(int baseWeight, SkTypeface::Style relativeStyle) {
+static minikin::FontStyle computeRelativeStyle(int baseWeight, Typeface::Style relativeStyle) {
     int weight = baseWeight;
-    if ((relativeStyle & SkTypeface::kBold) != 0) {
+    if ((relativeStyle & Typeface::kBold) != 0) {
         weight += 300;
     }
-    bool italic = (relativeStyle & SkTypeface::kItalic) != 0;
+    bool italic = (relativeStyle & Typeface::kItalic) != 0;
     return computeMinikinStyle(weight, italic);
 }
 
@@ -66,13 +66,13 @@
     return src == nullptr ? gDefaultTypeface : src;
 }
 
-Typeface* Typeface::createRelative(Typeface* src, SkTypeface::Style style) {
+Typeface* Typeface::createRelative(Typeface* src, Typeface::Style style) {
     const Typeface* resolvedFace = Typeface::resolveDefault(src);
     Typeface* result = new Typeface;
     if (result != nullptr) {
         result->fFontCollection = resolvedFace->fFontCollection;
         result->fBaseWeight = resolvedFace->fBaseWeight;
-        result->fSkiaStyle = style;
+        result->fAPIStyle = style;
         result->fStyle = computeRelativeStyle(result->fBaseWeight, style);
     }
     return result;
@@ -84,7 +84,7 @@
     if (result != nullptr) {
         result->fFontCollection = resolvedFace->fFontCollection;
         result->fBaseWeight = resolvedFace->fBaseWeight;
-        result->fSkiaStyle = computeSkiaStyle(weight, italic);
+        result->fAPIStyle = computeAPIStyle(weight, italic);
         result->fStyle = computeMinikinStyle(weight, italic);
     }
     return result;
@@ -105,7 +105,7 @@
         // Do not update styles.
         // TODO: We may want to update base weight if the 'wght' is specified.
         result->fBaseWeight = resolvedFace->fBaseWeight;
-        result->fSkiaStyle = resolvedFace->fSkiaStyle;
+        result->fAPIStyle = resolvedFace->fAPIStyle;
         result->fStyle = resolvedFace->fStyle;
     }
     return result;
@@ -117,8 +117,8 @@
     if (result != nullptr) {
         result->fFontCollection = resolvedFace->fFontCollection;
         result->fBaseWeight = weight;
-        result->fSkiaStyle = resolvedFace->fSkiaStyle;
-        result->fStyle = computeRelativeStyle(weight, result->fSkiaStyle);
+        result->fAPIStyle = resolvedFace->fAPIStyle;
+        result->fStyle = computeRelativeStyle(weight, result->fAPIStyle);
     }
     return result;
 }
@@ -161,7 +161,7 @@
     }
 
     result->fBaseWeight = weight;
-    result->fSkiaStyle = computeSkiaStyle(weight, italic);
+    result->fAPIStyle = computeAPIStyle(weight, italic);
     result->fStyle = computeMinikinStyle(weight, italic);
     return result;
 }
@@ -191,7 +191,7 @@
 
     Typeface* hwTypeface = new Typeface();
     hwTypeface->fFontCollection = collection;
-    hwTypeface->fSkiaStyle = SkTypeface::kNormal;
+    hwTypeface->fAPIStyle = Typeface::kNormal;
     hwTypeface->fBaseWeight = SkFontStyle::kNormal_Weight;
     hwTypeface->fStyle = minikin::FontStyle(4 /* weight */, false /* italic */);
 
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 38c6234..d90114d 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -38,8 +38,14 @@
     // resolved style actually used for rendering
     minikin::FontStyle fStyle;
 
-    // style used for constructing and querying Typeface objects
-    SkTypeface::Style fSkiaStyle;
+    // style used in the API
+    enum Style : uint8_t {
+        kNormal = 0,
+        kBold   = 0x01,
+        kItalic = 0x02,
+        kBoldItalic = 0x03
+    };
+    Style fAPIStyle;
 
     static const Typeface* resolveDefault(const Typeface* src);
 
@@ -68,7 +74,7 @@
     //
     //   Typeface* black = createAbsolute(base, 900, false);  // Rendered with a weight of 900.
     static Typeface* createWithDifferentBaseWeight(Typeface* src, int baseweight);
-    static Typeface* createRelative(Typeface* src, SkTypeface::Style desiredStyle);
+    static Typeface* createRelative(Typeface* src, Style desiredStyle);
     static Typeface* createAbsolute(Typeface* base, int weight, bool italic);
 
     static Typeface* createFromTypefaceWithVariation(Typeface* src,
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 17438e5..def04e1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -35,23 +35,23 @@
 }
 
 bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer) {
-    // transform the matrix based on the layer
-    int saveCount = -1;
-    if (!layer->getTransform().isIdentity()) {
-        saveCount = canvas->save();
-        SkMatrix transform;
-        layer->getTransform().copyTo(transform);
-        canvas->concat(transform);
+    if (context == nullptr) {
+        SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface"));
+        return false;
     }
-
+    // transform the matrix based on the layer
+    SkMatrix layerTransform;
+    layer->getTransform().copyTo(layerTransform);
     sk_sp<SkImage> layerImage;
+    int layerWidth = layer->getWidth();
+    int layerHeight = layer->getHeight();
     if (layer->getApi() == Layer::Api::OpenGL) {
         GlLayer* glLayer = static_cast<GlLayer*>(layer);
         GrGLTextureInfo externalTexture;
         externalTexture.fTarget = glLayer->getRenderTarget();
         externalTexture.fID = glLayer->getTextureId();
-        GrBackendTexture backendTexture(glLayer->getWidth(), glLayer->getHeight(),
-                kRGBA_8888_GrPixelConfig, externalTexture);
+        GrBackendTexture backendTexture(layerWidth, layerHeight, kRGBA_8888_GrPixelConfig,
+                externalTexture);
         layerImage = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin,
                 kPremul_SkAlphaType, nullptr);
     } else {
@@ -62,15 +62,37 @@
     }
 
     if (layerImage) {
+        SkMatrix textureMatrix;
+        layer->getTexTransform().copyTo(textureMatrix);
+        //TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+        // use bottom left origin and remove flipV and invert transformations.
+        SkMatrix flipV;
+        flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+        textureMatrix.preConcat(flipV);
+        textureMatrix.preScale(1.0f/layerWidth, 1.0f/layerHeight);
+        textureMatrix.postScale(layerWidth, layerHeight);
+        SkMatrix textureMatrixInv;
+        if (!textureMatrix.invert(&textureMatrixInv)) {
+            textureMatrixInv = textureMatrix;
+        }
+
+        SkMatrix matrix = SkMatrix::Concat(layerTransform, textureMatrixInv);
+
         SkPaint paint;
         paint.setAlpha(layer->getAlpha());
         paint.setBlendMode(layer->getMode());
         paint.setColorFilter(sk_ref_sp(layer->getColorFilter()));
-        canvas->drawImage(layerImage, 0, 0, &paint);
-    }
-    // restore the original matrix
-    if (saveCount >= 0) {
-        canvas->restoreToCount(saveCount);
+
+        const bool nonIdentityMatrix = !matrix.isIdentity();
+        if (nonIdentityMatrix) {
+            canvas->save();
+            canvas->concat(matrix);
+        }
+        canvas->drawImage(layerImage.get(), 0, 0, &paint);
+        // restore the original matrix
+        if (nonIdentityMatrix) {
+            canvas->restore();
+        }
     }
 
     return layerImage;
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 4ee47af..ca93925 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -19,6 +19,7 @@
 #include "SkiaDisplayList.h"
 #include "SkiaPipeline.h"
 #include "utils/TraceUtils.h"
+#include <SkPaintFilterCanvas.h>
 
 namespace android {
 namespace uirenderer {
@@ -100,7 +101,7 @@
 
 void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
     RenderNode* renderNode = mRenderNode.get();
-    if (SkiaPipeline::skpCaptureEnabled()) {
+    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
         SkRect dimensions = SkRect::MakeWH(renderNode->getWidth(), renderNode->getHeight());
         canvas->drawAnnotation(dimensions, renderNode->getName(), nullptr);
     }
@@ -151,6 +152,27 @@
     return false;
 }
 
+class AlphaFilterCanvas : public SkPaintFilterCanvas {
+public:
+    AlphaFilterCanvas(SkCanvas* canvas, float alpha) : SkPaintFilterCanvas(canvas), mAlpha(alpha) {}
+protected:
+    bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type t) const override {
+        SkTLazy<SkPaint> defaultPaint;
+        if (!*paint) {
+            paint->init(*defaultPaint.init());
+        }
+        paint->writable()->setAlpha((uint8_t)(*paint)->getAlpha()*mAlpha);
+        return true;
+    }
+    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
+        // We unroll the drawable using "this" canvas, so that draw calls contained inside will
+        // get their alpha applied. THe default SkPaintFilterCanvas::onDrawDrawable does not unroll.
+        drawable->draw(this, matrix);
+    }
+private:
+    float mAlpha;
+};
+
 void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
     RenderNode* renderNode = mRenderNode.get();
     float alphaMultiplier = 1.0f;
@@ -211,7 +233,14 @@
                 canvas->restore();
             }
         } else {
-            displayList->draw(canvas);
+            if (alphaMultiplier < 1.0f) {
+                // Non-layer draw for a view with getHasOverlappingRendering=false, will apply
+                // the alpha to the paint of each nested draw.
+                AlphaFilterCanvas alphaCanvas(canvas, alphaMultiplier);
+                displayList->draw(&alphaCanvas);
+            } else {
+                displayList->draw(canvas);
+            }
         }
     }
 }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index 9982a0c..608e694 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -84,57 +84,50 @@
     sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), backendTexture,
             kTopLeft_GrSurfaceOrigin));
     if (image) {
-        // Convert imgTransform matrix from right to left handed coordinate system.
-        // If we have a matrix transformation in right handed coordinate system
-        //|ScaleX, SkewX,  TransX| same transform in left handed is    |ScaleX, SkewX,   TransX  |
-        //|SkewY,  ScaleY, TransY|                                     |-SkewY, -ScaleY, 1-TransY|
-        //|0,      0,      1     |                                     |0,      0,       1       |
-        SkMatrix textureMatrix;
-        textureMatrix.setIdentity();
-        textureMatrix[SkMatrix::kMScaleX] = imgTransform[Matrix4::kScaleX];
-        textureMatrix[SkMatrix::kMScaleY] = -imgTransform[Matrix4::kScaleY];
-        textureMatrix[SkMatrix::kMSkewX] = imgTransform[Matrix4::kSkewX];
-        textureMatrix[SkMatrix::kMSkewY] = -imgTransform[Matrix4::kSkewY];
-        textureMatrix[SkMatrix::kMTransX] = imgTransform[Matrix4::kTranslateX];
-        textureMatrix[SkMatrix::kMTransY] = 1-imgTransform[Matrix4::kTranslateY];
-
-        // textureMatrix maps 2D texture coordinates of the form (s, t, 1) with s and t in the
-        // inclusive range [0, 1] to the texture (see GLConsumer::getTransformMatrix comments).
-        // Convert textureMatrix to translate in real texture dimensions. Texture width and
-        // height are affected by the orientation (width and height swapped for 90/270 rotation).
-        if (textureMatrix[SkMatrix::kMSkewX] >= 0.5f || textureMatrix[SkMatrix::kMSkewX] <= -0.5f) {
-            textureMatrix[SkMatrix::kMTransX] *= imgHeight;
-            textureMatrix[SkMatrix::kMTransY] *= imgWidth;
-        } else {
-            textureMatrix[SkMatrix::kMTransX] *= imgWidth;
-            textureMatrix[SkMatrix::kMTransY] *= imgHeight;
+        int displayedWidth = imgWidth, displayedHeight = imgHeight;
+        // If this is a 90 or 270 degree rotation we need to swap width/height to get the device
+        // size.
+        if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) {
+            std::swap(displayedWidth, displayedHeight);
         }
-
-        // convert to Skia data structures
-        SkRect skiaSrcRect = srcRect.toSkRect();
-        SkMatrix textureMatrixInv;
         SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height());
-        bool srcNotEmpty = false;
-        if (textureMatrix.invert(&textureMatrixInv)) {
-            if (skiaSrcRect.isEmpty()) {
-                skiaSrcRect = SkRect::MakeIWH(imgWidth, imgHeight);
-                srcNotEmpty = !skiaSrcRect.isEmpty();
-            } else {
-                // src and dest rectangles need to be converted into texture coordinates before the
-                // rotation matrix is applied (because drawImageRect preconcat its matrix).
-                textureMatrixInv.mapRect(&skiaSrcRect);
-                srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(imgWidth, imgHeight));
-            }
-            textureMatrixInv.mapRect(&skiaDestRect);
+        SkRect skiaSrcRect = srcRect.toSkRect();
+        if (skiaSrcRect.isEmpty()) {
+            skiaSrcRect = SkRect::MakeIWH(displayedWidth, displayedHeight);
         }
+        bool srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(displayedWidth, displayedHeight));
 
         if (srcNotEmpty) {
+            SkMatrix textureMatrixInv;
+            imgTransform.copyTo(textureMatrixInv);
+            //TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed
+            // use bottom left origin and remove flipV and invert transformations.
+            SkMatrix flipV;
+            flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1);
+            textureMatrixInv.preConcat(flipV);
+            textureMatrixInv.preScale(1.0f/displayedWidth, 1.0f/displayedHeight);
+            textureMatrixInv.postScale(imgWidth, imgHeight);
+            SkMatrix textureMatrix;
+            if (!textureMatrixInv.invert(&textureMatrix)) {
+                textureMatrix = textureMatrixInv;
+            }
+
+            textureMatrixInv.mapRect(&skiaSrcRect);
+            textureMatrixInv.mapRect(&skiaDestRect);
+
             // we render in an offscreen buffer to scale and to avoid an issue b/62262733
             // with reading incorrect data from EGLImage backed SkImage (likely a driver bug)
             sk_sp<SkSurface> scaledSurface = SkSurface::MakeRenderTarget(
                     grContext.get(), SkBudgeted::kYes, bitmap->info());
             SkPaint paint;
             paint.setBlendMode(SkBlendMode::kSrc);
+            // Apply a filter, which is matching OpenGL pipeline readback behaviour. Filter usage
+            // is codified by tests using golden images like DecodeAccuracyTest.
+            if (skiaSrcRect.width() != bitmap->width()
+                    || skiaSrcRect.height() != bitmap->height()) {
+                //TODO: apply filter always, but check if tests will be fine
+                paint.setFilterQuality(kLow_SkFilterQuality);
+            }
             scaledSurface->getCanvas()->concat(textureMatrix);
             scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect, skiaDestRect, &paint,
                     SkCanvas::kFast_SrcRectConstraint);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6f117de..a092cd5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -45,6 +45,10 @@
     mVectorDrawables.reserve(30);
 }
 
+SkiaPipeline::~SkiaPipeline() {
+    unpinImages();
+}
+
 TaskManager* SkiaPipeline::getTaskManager() {
     return mRenderThread.cacheManager().getTaskManager();
 }
@@ -71,6 +75,12 @@
     mPinnedImages.clear();
 }
 
+void SkiaPipeline::onPrepareTree() {
+    // The only time mVectorDrawables is not empty is if prepare tree was called 2 times without
+    // a renderFrame in the middle.
+    mVectorDrawables.clear();
+}
+
 void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
         LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
         const BakedOpRenderer::LightInfo& lightInfo) {
@@ -102,7 +112,7 @@
 
             const Rect& layerDamage = layers.entries()[i].damage;
 
-            SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
+            SkCanvas* layerCanvas = tryCapture(layerNode->getLayerSurface());
 
             int saveCount = layerCanvas->save();
             SkASSERT(saveCount == 1);
@@ -129,9 +139,11 @@
             layerCanvas->restoreToCount(saveCount);
             mLightCenter = savedLightCenter;
 
+            endCapture(layerNode->getLayerSurface());
+
             // cache the current context so that we can defer flushing it until
             // either all the layers have been rendered or the context changes
-            GrContext* currentContext = layerCanvas->getGrContext();
+            GrContext* currentContext = layerNode->getLayerSurface()->getCanvas()->getGrContext();
             if (cachedContext.get() != currentContext) {
                 if (cachedContext.get()) {
                     cachedContext->flush();
@@ -217,6 +229,91 @@
     }
 }
 
+class SkiaPipeline::SavePictureProcessor : public TaskProcessor<bool> {
+public:
+    explicit SavePictureProcessor(TaskManager* taskManager) : TaskProcessor<bool>(taskManager) {}
+
+    struct SavePictureTask : public Task<bool> {
+        sk_sp<SkData> data;
+        std::string filename;
+    };
+
+    void savePicture(const sk_sp<SkData>& data, const std::string& filename) {
+        sp<SavePictureTask> task(new SavePictureTask());
+        task->data = data;
+        task->filename = filename;
+        TaskProcessor<bool>::add(task);
+    }
+
+    virtual void onProcess(const sp<Task<bool> >& task) override {
+        SavePictureTask* t = static_cast<SavePictureTask*>(task.get());
+
+        if (0 == access(t->filename.c_str(), F_OK)) {
+            task->setResult(false);
+            return;
+        }
+
+        SkFILEWStream stream(t->filename.c_str());
+        if (stream.isValid()) {
+            stream.write(t->data->data(), t->data->size());
+            stream.flush();
+            SkDebugf("SKP Captured Drawing Output (%d bytes) for frame. %s",
+                    stream.bytesWritten(), t->filename.c_str());
+        }
+
+        task->setResult(true);
+    }
+};
+
+SkCanvas* SkiaPipeline::tryCapture(SkSurface* surface) {
+    if (CC_UNLIKELY(Properties::skpCaptureEnabled)) {
+        bool recordingPicture = mCaptureSequence > 0;
+        char prop[PROPERTY_VALUE_MAX] = {'\0'};
+        if (!recordingPicture) {
+            property_get(PROPERTY_CAPTURE_SKP_FILENAME, prop, "0");
+            recordingPicture = prop[0] != '0'
+                    && mCapturedFile != prop; //ensure we capture only once per filename
+            if (recordingPicture) {
+                mCapturedFile = prop;
+                mCaptureSequence = property_get_int32(PROPERTY_CAPTURE_SKP_FRAMES, 1);
+            }
+        }
+        if (recordingPicture) {
+            mRecorder.reset(new SkPictureRecorder());
+            return mRecorder->beginRecording(surface->width(), surface->height(), nullptr,
+                    SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
+        }
+    }
+    return surface->getCanvas();
+}
+
+void SkiaPipeline::endCapture(SkSurface* surface) {
+    if (CC_UNLIKELY(mRecorder.get())) {
+        sk_sp<SkPicture> picture = mRecorder->finishRecordingAsPicture();
+        surface->getCanvas()->drawPicture(picture);
+        if (picture->approximateOpCount() > 0) {
+            SkDynamicMemoryWStream stream;
+            PngPixelSerializer serializer;
+            picture->serialize(&stream, &serializer);
+
+            //offload saving to file in a different thread
+            if (!mSavePictureProcessor.get()) {
+                TaskManager* taskManager = getTaskManager();
+                mSavePictureProcessor = new SavePictureProcessor(
+                        taskManager->canRunTasks() ? taskManager : nullptr);
+            }
+            if (1 == mCaptureSequence) {
+                mSavePictureProcessor->savePicture(stream.detachAsData(), mCapturedFile);
+            } else {
+                mSavePictureProcessor->savePicture(stream.detachAsData(),
+                                        mCapturedFile + "_" + std::to_string(mCaptureSequence));
+            }
+            mCaptureSequence--;
+        }
+        mRecorder.reset();
+    }
+}
+
 void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
         const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut,
         const Rect &contentDrawBounds, sk_sp<SkSurface> surface) {
@@ -226,44 +323,21 @@
     // draw all layers up front
     renderLayersImpl(layers, opaque, wideColorGamut);
 
-    // initialize the canvas for the current frame
-    SkCanvas* canvas = surface->getCanvas();
-
+    // initialize the canvas for the current frame, that might be a recording canvas if SKP
+    // capture is enabled.
     std::unique_ptr<SkPictureRecorder> recorder;
-    bool recordingPicture = false;
-    char prop[PROPERTY_VALUE_MAX];
-    if (skpCaptureEnabled()) {
-        property_get("debug.hwui.capture_frame_as_skp", prop, "0");
-        recordingPicture = prop[0] != '0' && access(prop, F_OK) != 0;
-        if (recordingPicture) {
-            recorder.reset(new SkPictureRecorder());
-            canvas = recorder->beginRecording(surface->width(), surface->height(),
-                    nullptr, SkPictureRecorder::kPlaybackDrawPicture_RecordFlag);
-        }
-    }
+    SkCanvas* canvas = tryCapture(surface.get());
 
     renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas);
 
-    if (skpCaptureEnabled() && recordingPicture) {
-        sk_sp<SkPicture> picture = recorder->finishRecordingAsPicture();
-        if (picture->approximateOpCount() > 0) {
-            SkFILEWStream stream(prop);
-            if (stream.isValid()) {
-                PngPixelSerializer serializer;
-                picture->serialize(&stream, &serializer);
-                stream.flush();
-                SkDebugf("Captured Drawing Output (%d bytes) for frame. %s", stream.bytesWritten(), prop);
-            }
-        }
-        surface->getCanvas()->drawPicture(picture);
-    }
+    endCapture(surface.get());
 
     if (CC_UNLIKELY(Properties::debugOverdraw)) {
         renderOverdraw(layers, clip, nodes, contentDrawBounds, surface);
     }
 
     ATRACE_NAME("flush commands");
-    canvas->flush();
+    surface->getCanvas()->flush();
 }
 
 namespace {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 2b0c419..67b1878 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -21,6 +21,8 @@
 #include "renderthread/IRenderPipeline.h"
 #include <SkSurface.h>
 
+class SkPictureRecorder;
+
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -28,7 +30,7 @@
 class SkiaPipeline : public renderthread::IRenderPipeline {
 public:
     SkiaPipeline(renderthread::RenderThread& thread);
-    virtual ~SkiaPipeline() {}
+    virtual ~SkiaPipeline();
 
     TaskManager* getTaskManager() override;
 
@@ -37,6 +39,7 @@
     bool pinImages(std::vector<SkImage*>& mutableImages) override;
     bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
     void unpinImages() override;
+    void onPrepareTree() override;
 
     void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
             LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut,
@@ -55,9 +58,7 @@
 
     static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
 
-    static void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
-
-    static bool skpCaptureEnabled() { return false; }
+    void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut);
 
     static float getLightRadius() {
         if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) {
@@ -126,12 +127,38 @@
      */
     void renderVectorDrawableCache();
 
+    SkCanvas* tryCapture(SkSurface* surface);
+    void endCapture(SkSurface* surface);
+
     std::vector<sk_sp<SkImage>> mPinnedImages;
 
     /**
      *  populated by prepareTree with dirty VDs
      */
     std::vector<VectorDrawableRoot*> mVectorDrawables;
+
+
+    // Block of properties used only for debugging to record a SkPicture and save it in a file.
+    /**
+     * mCapturedFile is used to enforce we don't capture more than once for a given name (cause
+     * permissions don't allow to reset a property from render thread).
+     */
+    std::string mCapturedFile;
+    /**
+     *  mCaptureSequence counts how many frames are left to take in the sequence.
+     */
+    int mCaptureSequence = 0;
+    /**
+     *  mSavePictureProcessor is used to run the file saving code in a separate thread.
+     */
+    class SavePictureProcessor;
+    sp<SavePictureProcessor> mSavePictureProcessor;
+    /**
+     *  mRecorder holds the current picture recorder. We could store it on the stack to support
+     *  parallel tryCapture calls (not really needed).
+     */
+    std::unique_ptr<SkPictureRecorder> mRecorder;
+
     static float mLightRadius;
     static uint8_t mAmbientShadowAlpha;
     static uint8_t mSpotShadowAlpha;
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 5fc5cb2..0ceca33 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -105,8 +105,8 @@
 }
 
 void RenderState::onVkContextDestroyed() {
-    mLayerPool->clear();
     std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext);
+    destroyLayersInUpdater();
     GpuMemoryTracker::onGpuContextDestroyed();
 }
 
@@ -236,25 +236,11 @@
     std::for_each(mActiveLayerUpdaters.begin(), mActiveLayerUpdaters.end(), destroyLayerInUpdater);
 }
 
-class DecStrongTask : public renderthread::RenderTask {
-public:
-    explicit DecStrongTask(VirtualLightRefBase* object) : mObject(object) {}
-
-    virtual void run() override {
-        mObject->decStrong(nullptr);
-        mObject = nullptr;
-        delete this;
-    }
-
-private:
-    VirtualLightRefBase* mObject;
-};
-
 void RenderState::postDecStrong(VirtualLightRefBase* object) {
     if (pthread_equal(mThreadId, pthread_self())) {
         object->decStrong(nullptr);
     } else {
-        mRenderThread.queue(new DecStrongTask(object));
+        mRenderThread.queue().post([object]() { object->decStrong(nullptr); });
     }
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5d7f594..36a0da1 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -181,13 +181,13 @@
     mAnimationContext->destroy();
 }
 
-void CanvasContext::setSurface(Surface* surface) {
+void CanvasContext::setSurface(sp<Surface>&& surface) {
     ATRACE_CALL();
 
-    mNativeSurface = surface;
+    mNativeSurface = std::move(surface);
 
     ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::Srgb;
-    bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior, colorMode);
+    bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode);
 
     mFrameNumber = -1;
 
@@ -203,15 +203,7 @@
     mSwapBehavior = swapBehavior;
 }
 
-void CanvasContext::initialize(Surface* surface) {
-    setSurface(surface);
-}
-
-void CanvasContext::updateSurface(Surface* surface) {
-    setSurface(surface);
-}
-
-bool CanvasContext::pauseSurface(Surface* surface) {
+bool CanvasContext::pauseSurface() {
     return mRenderThread.removeFrameCallback(this);
 }
 
@@ -331,6 +323,7 @@
     info.layerUpdateQueue = &mLayerUpdateQueue;
 
     mAnimationContext->startFrame(info.mode);
+    mRenderPipeline->onPrepareTree();
     for (const sp<RenderNode>& node : mRenderNodes) {
         // Only the primary target node will be drawn full - all other nodes would get drawn in
         // real time mode. In case of a window, the primary node is the window content and the other
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 4a5b2c7..f8a8775 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -117,9 +117,8 @@
     // Won't take effect until next EGLSurface creation
     void setSwapBehavior(SwapBehavior swapBehavior);
 
-    void initialize(Surface* surface);
-    void updateSurface(Surface* surface);
-    bool pauseSurface(Surface* surface);
+    void setSurface(sp<Surface>&& surface);
+    bool pauseSurface();
     void setStopped(bool stopped);
     bool hasSurface() { return mNativeSurface.get(); }
 
@@ -205,8 +204,6 @@
     // lifecycle tracking
     friend class android::uirenderer::RenderState;
 
-    void setSurface(Surface* window);
-
     void freePrefetchedLayers();
 
     bool isSwapChainStuffed();
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index a097272..0a94678 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -78,7 +78,7 @@
 
 void DrawFrameTask::postAndWait() {
     AutoMutex _lock(mLock);
-    mRenderThread->queue(this);
+    mRenderThread->queue().post([this]() { run(); });
     mSignal.wait(mLock);
 }
 
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 83ecb98..4e4b6da 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -55,7 +55,7 @@
  * tracked across many frames not just a single frame.
  * It is the sync-state task, and will kick off the post-sync draw
  */
-class DrawFrameTask : public RenderTask {
+class DrawFrameTask {
 public:
     DrawFrameTask();
     virtual ~DrawFrameTask();
@@ -72,7 +72,7 @@
 
     int64_t* frameInfo() { return mFrameInfo; }
 
-    virtual void run() override;
+    void run();
 
 private:
     void postAndWait();
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index f9b6e38..cfc71cb 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include "FrameInfoVisualizer.h"
+#include "SwapBehavior.h"
 
 #include <SkRect.h>
 #include <utils/RefBase.h>
@@ -33,11 +34,6 @@
 
 namespace renderthread {
 
-enum class SwapBehavior {
-    kSwap_default,
-    kSwap_discardBuffer,
-};
-
 enum class MakeCurrentResult {
     AlreadyCurrent,
     Failed,
@@ -81,6 +77,7 @@
     virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
     virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
     virtual void unpinImages() = 0;
+    virtual void onPrepareTree() = 0;
 
     virtual ~IRenderPipeline() {}
 };
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
index 4ca19fb..1f467c1 100644
--- a/libs/hwui/renderthread/OpenGLPipeline.h
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -58,6 +58,7 @@
     bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
     bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override;
     void unpinImages() override;
+    void onPrepareTree() override {}
     static void destroyLayer(RenderNode* node);
     static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap);
     static void invokeFunctor(const RenderThread& thread, Functor* functor);
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a6aa301..2f406da 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -36,46 +36,12 @@
 namespace uirenderer {
 namespace renderthread {
 
-#define ARGS(method) method ## Args
-
-#define CREATE_BRIDGE0(name) CREATE_BRIDGE(name,,,,,,,,)
-#define CREATE_BRIDGE1(name, a1) CREATE_BRIDGE(name, a1,,,,,,,)
-#define CREATE_BRIDGE2(name, a1, a2) CREATE_BRIDGE(name, a1,a2,,,,,,)
-#define CREATE_BRIDGE3(name, a1, a2, a3) CREATE_BRIDGE(name, a1,a2,a3,,,,,)
-#define CREATE_BRIDGE4(name, a1, a2, a3, a4) CREATE_BRIDGE(name, a1,a2,a3,a4,,,,)
-#define CREATE_BRIDGE5(name, a1, a2, a3, a4, a5) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,,,)
-#define CREATE_BRIDGE6(name, a1, a2, a3, a4, a5, a6) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,,)
-#define CREATE_BRIDGE7(name, a1, a2, a3, a4, a5, a6, a7) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,a7,)
-#define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \
-    typedef struct { \
-        a1; a2; a3; a4; a5; a6; a7; a8; \
-    } ARGS(name); \
-    static_assert(std::is_trivially_destructible<ARGS(name)>::value, \
-            "Error, ARGS must be trivially destructible!"); \
-    static void* Bridge_ ## name(ARGS(name)* args)
-
-#define SETUP_TASK(method) \
-    LOG_ALWAYS_FATAL_IF( METHOD_INVOKE_PAYLOAD_SIZE < sizeof(ARGS(method)), \
-        "METHOD_INVOKE_PAYLOAD_SIZE %zu is smaller than sizeof(" #method "Args) %zu", \
-                METHOD_INVOKE_PAYLOAD_SIZE, sizeof(ARGS(method))); \
-    MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
-    ARGS(method) *args = (ARGS(method) *) task->payload()
-
-CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
-        RenderNode* rootRenderNode, IContextFactory* contextFactory) {
-    return CanvasContext::create(*args->thread, args->translucent,
-            args->rootRenderNode, args->contextFactory);
-}
-
 RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
         : mRenderThread(RenderThread::getInstance())
         , mContext(nullptr) {
-    SETUP_TASK(createContext);
-    args->translucent = translucent;
-    args->rootRenderNode = rootRenderNode;
-    args->thread = &mRenderThread;
-    args->contextFactory = contextFactory;
-    mContext = (CanvasContext*) postAndWait(task);
+    mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
+        return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
+    });
     mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
 }
 
@@ -83,162 +49,91 @@
     destroyContext();
 }
 
-CREATE_BRIDGE1(destroyContext, CanvasContext* context) {
-    delete args->context;
-    return nullptr;
-}
-
 void RenderProxy::destroyContext() {
     if (mContext) {
-        SETUP_TASK(destroyContext);
-        args->context = mContext;
-        mContext = nullptr;
         mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
         // This is also a fence as we need to be certain that there are no
         // outstanding mDrawFrame tasks posted before it is destroyed
-        postAndWait(task);
+        mRenderThread.queue().runSync([this]() {
+            delete mContext;
+        });
+        mContext = nullptr;
     }
 }
 
-CREATE_BRIDGE2(setSwapBehavior, CanvasContext* context, SwapBehavior swapBehavior) {
-    args->context->setSwapBehavior(args->swapBehavior);
-    return nullptr;
-}
-
 void RenderProxy::setSwapBehavior(SwapBehavior swapBehavior) {
-    SETUP_TASK(setSwapBehavior);
-    args->context = mContext;
-    args->swapBehavior = swapBehavior;
-    post(task);
-}
-
-CREATE_BRIDGE1(loadSystemProperties, CanvasContext* context) {
-    bool needsRedraw = false;
-    if (Caches::hasInstance()) {
-        needsRedraw = Properties::load();
-    }
-    if (args->context->profiler().consumeProperties()) {
-        needsRedraw = true;
-    }
-    return (void*) needsRedraw;
+    mRenderThread.queue().post([this, swapBehavior]() {
+        mContext->setSwapBehavior(swapBehavior);
+    });
 }
 
 bool RenderProxy::loadSystemProperties() {
-    SETUP_TASK(loadSystemProperties);
-    args->context = mContext;
-    return (bool) postAndWait(task);
-}
-
-CREATE_BRIDGE2(setName, CanvasContext* context, const char* name) {
-    args->context->setName(std::string(args->name));
-    return nullptr;
+    return mRenderThread.queue().runSync([this]() -> bool {
+        bool needsRedraw = false;
+        if (Caches::hasInstance()) {
+            needsRedraw = Properties::load();
+        }
+        if (mContext->profiler().consumeProperties()) {
+            needsRedraw = true;
+        }
+        return needsRedraw;
+    });
 }
 
 void RenderProxy::setName(const char* name) {
-    SETUP_TASK(setName);
-    args->context = mContext;
-    args->name = name;
-    postAndWait(task); // block since name/value pointers owned by caller
-}
-
-CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) {
-    args->context->initialize(args->surface);
-    return nullptr;
+    // block since name/value pointers owned by caller
+    // TODO: Support move arguments
+    mRenderThread.queue().runSync([this, name]() {
+        mContext->setName(std::string(name));
+    });
 }
 
 void RenderProxy::initialize(const sp<Surface>& surface) {
-    SETUP_TASK(initialize);
-    args->context = mContext;
-    args->surface = surface.get();
-    post(task);
-}
-
-CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) {
-    args->context->updateSurface(args->surface);
-    return nullptr;
+    mRenderThread.queue().post([this, surf = surface]() mutable {
+        mContext->setSurface(std::move(surf));
+    });
 }
 
 void RenderProxy::updateSurface(const sp<Surface>& surface) {
-    SETUP_TASK(updateSurface);
-    args->context = mContext;
-    args->surface = surface.get();
-    post(task);
-}
-
-CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) {
-    return (void*) args->context->pauseSurface(args->surface);
+    mRenderThread.queue().post([this, surf = surface]() mutable {
+        mContext->setSurface(std::move(surf));
+    });
 }
 
 bool RenderProxy::pauseSurface(const sp<Surface>& surface) {
-    SETUP_TASK(pauseSurface);
-    args->context = mContext;
-    args->surface = surface.get();
-    return (bool) postAndWait(task);
-}
-
-CREATE_BRIDGE2(setStopped, CanvasContext* context, bool stopped) {
-    args->context->setStopped(args->stopped);
-    return nullptr;
+    return mRenderThread.queue().runSync([this]() -> bool {
+        return mContext->pauseSurface();
+    });
 }
 
 void RenderProxy::setStopped(bool stopped) {
-    SETUP_TASK(setStopped);
-    args->context = mContext;
-    args->stopped = stopped;
-    postAndWait(task);
+    mRenderThread.queue().runSync([this, stopped]() {
+        mContext->setStopped(stopped);
+    });
 }
 
-CREATE_BRIDGE4(setup, CanvasContext* context,
-        float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
-    args->context->setup(args->lightRadius,
-            args->ambientShadowAlpha, args->spotShadowAlpha);
-    return nullptr;
-}
-
-void RenderProxy::setup(float lightRadius,
-        uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
-    SETUP_TASK(setup);
-    args->context = mContext;
-    args->lightRadius = lightRadius;
-    args->ambientShadowAlpha = ambientShadowAlpha;
-    args->spotShadowAlpha = spotShadowAlpha;
-    post(task);
-}
-
-CREATE_BRIDGE2(setLightCenter, CanvasContext* context, Vector3 lightCenter) {
-    args->context->setLightCenter(args->lightCenter);
-    return nullptr;
+void RenderProxy::setup(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+    mRenderThread.queue().post([=]() {
+        mContext->setup(lightRadius, ambientShadowAlpha, spotShadowAlpha);
+    });
 }
 
 void RenderProxy::setLightCenter(const Vector3& lightCenter) {
-    SETUP_TASK(setLightCenter);
-    args->context = mContext;
-    args->lightCenter = lightCenter;
-    post(task);
-}
-
-CREATE_BRIDGE2(setOpaque, CanvasContext* context, bool opaque) {
-    args->context->setOpaque(args->opaque);
-    return nullptr;
+    mRenderThread.queue().post([=]() {
+        mContext->setLightCenter(lightCenter);
+    });
 }
 
 void RenderProxy::setOpaque(bool opaque) {
-    SETUP_TASK(setOpaque);
-    args->context = mContext;
-    args->opaque = opaque;
-    post(task);
-}
-
-CREATE_BRIDGE2(setWideGamut, CanvasContext* context, bool wideGamut) {
-    args->context->setWideGamut(args->wideGamut);
-    return nullptr;
+    mRenderThread.queue().post([=]() {
+        mContext->setOpaque(opaque);
+    });
 }
 
 void RenderProxy::setWideGamut(bool wideGamut) {
-    SETUP_TASK(setWideGamut);
-    args->context = mContext;
-    args->wideGamut = wideGamut;
-    post(task);
+    mRenderThread.queue().post([=]() {
+        mContext->setWideGamut(wideGamut);
+    });
 }
 
 int64_t* RenderProxy::frameInfo() {
@@ -249,77 +144,45 @@
     return mDrawFrameTask.drawFrame();
 }
 
-CREATE_BRIDGE1(destroy, CanvasContext* context) {
-    args->context->destroy();
-    return nullptr;
-}
-
 void RenderProxy::destroy() {
-    SETUP_TASK(destroy);
-    args->context = mContext;
     // destroyCanvasAndSurface() needs a fence as when it returns the
     // underlying BufferQueue is going to be released from under
     // the render thread.
-    postAndWait(task);
-}
-
-CREATE_BRIDGE2(invokeFunctor, RenderThread* thread, Functor* functor) {
-    CanvasContext::invokeFunctor(*args->thread, args->functor);
-    return nullptr;
+    mRenderThread.queue().runSync([=]() {
+        mContext->destroy();
+    });
 }
 
 void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
     ATRACE_CALL();
     RenderThread& thread = RenderThread::getInstance();
-    SETUP_TASK(invokeFunctor);
-    args->thread = &thread;
-    args->functor = functor;
+    auto invoke = [&thread, functor]() { CanvasContext::invokeFunctor(thread, functor); };
     if (waitForCompletion) {
         // waitForCompletion = true is expected to be fairly rare and only
         // happen in destruction. Thus it should be fine to temporarily
         // create a Mutex
-        staticPostAndWait(task);
+        thread.queue().runSync(std::move(invoke));
     } else {
-        thread.queue(task);
+        thread.queue().post(std::move(invoke));
     }
 }
 
-CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) {
-    return args->context->createTextureLayer();
-}
-
 DeferredLayerUpdater* RenderProxy::createTextureLayer() {
-    SETUP_TASK(createTextureLayer);
-    args->context = mContext;
-    void* retval = postAndWait(task);
-    DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval);
-    return layer;
-}
-
-CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) {
-    args->context->buildLayer(args->node);
-    return nullptr;
+    return mRenderThread.queue().runSync([this]() -> auto {
+        return mContext->createTextureLayer();
+    });
 }
 
 void RenderProxy::buildLayer(RenderNode* node) {
-    SETUP_TASK(buildLayer);
-    args->context = mContext;
-    args->node = node;
-    postAndWait(task);
-}
-
-CREATE_BRIDGE3(copyLayerInto, CanvasContext* context, DeferredLayerUpdater* layer,
-        SkBitmap* bitmap) {
-    bool success = args->context->copyLayerInto(args->layer, args->bitmap);
-    return (void*) success;
+    mRenderThread.queue().runSync([&]() {
+        mContext->buildLayer(node);
+    });
 }
 
 bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) {
-    SETUP_TASK(copyLayerInto);
-    args->context = mContext;
-    args->layer = layer;
-    args->bitmap = &bitmap;
-    return (bool) postAndWait(task);
+    return mRenderThread.queue().runSync([&]() -> bool {
+        return mContext->copyLayerInto(layer, &bitmap);
+    });
 }
 
 void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -330,302 +193,154 @@
     mDrawFrameTask.removeLayerUpdate(layer);
 }
 
-CREATE_BRIDGE1(detachSurfaceTexture, DeferredLayerUpdater* layer) {
-    args->layer->detachSurfaceTexture();
-    return nullptr;
-}
-
 void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) {
-    SETUP_TASK(detachSurfaceTexture);
-    args->layer = layer;
-    postAndWait(task);
-}
-
-CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) {
-    args->context->destroyHardwareResources();
-    return nullptr;
+    return mRenderThread.queue().runSync([&]() {
+        layer->detachSurfaceTexture();
+    });
 }
 
 void RenderProxy::destroyHardwareResources() {
-    SETUP_TASK(destroyHardwareResources);
-    args->context = mContext;
-    postAndWait(task);
-}
-
-CREATE_BRIDGE2(trimMemory, RenderThread* thread, int level) {
-    CanvasContext::trimMemory(*args->thread, args->level);
-    return nullptr;
+    return mRenderThread.queue().runSync([&]() {
+        mContext->destroyHardwareResources();
+    });
 }
 
 void RenderProxy::trimMemory(int level) {
     // Avoid creating a RenderThread to do a trimMemory.
     if (RenderThread::hasInstance()) {
         RenderThread& thread = RenderThread::getInstance();
-        SETUP_TASK(trimMemory);
-        args->thread = &thread;
-        args->level = level;
-        thread.queue(task);
+        thread.queue().post([&thread, level]() {
+            CanvasContext::trimMemory(thread, level);
+        });
     }
 }
 
-CREATE_BRIDGE2(overrideProperty, const char* name, const char* value) {
-    Properties::overrideProperty(args->name, args->value);
-    return nullptr;
-}
-
 void RenderProxy::overrideProperty(const char* name, const char* value) {
-    SETUP_TASK(overrideProperty);
-    args->name = name;
-    args->value = value;
-    staticPostAndWait(task); // expensive, but block here since name/value pointers owned by caller
+    // expensive, but block here since name/value pointers owned by caller
+    RenderThread::getInstance().queue().runSync([&]() {
+        Properties::overrideProperty(name, value);
+    });
 }
 
-CREATE_BRIDGE0(fence) {
-    // Intentionally empty
-    return nullptr;
-}
-
-template <typename T>
-void UNUSED(T t) {}
-
 void RenderProxy::fence() {
-    SETUP_TASK(fence);
-    UNUSED(args);
-    postAndWait(task);
+    mRenderThread.queue().runSync([](){});
 }
 
 void RenderProxy::staticFence() {
-    SETUP_TASK(fence);
-    UNUSED(args);
-    staticPostAndWait(task);
-}
-
-CREATE_BRIDGE1(stopDrawing, CanvasContext* context) {
-    args->context->stopDrawing();
-    return nullptr;
+    RenderThread::getInstance().queue().runSync([](){});
 }
 
 void RenderProxy::stopDrawing() {
-    SETUP_TASK(stopDrawing);
-    args->context = mContext;
-    postAndWait(task);
-}
-
-CREATE_BRIDGE1(notifyFramePending, CanvasContext* context) {
-    args->context->notifyFramePending();
-    return nullptr;
+    mRenderThread.queue().runSync([this]() {
+        mContext->stopDrawing();
+    });
 }
 
 void RenderProxy::notifyFramePending() {
-    SETUP_TASK(notifyFramePending);
-    args->context = mContext;
-    mRenderThread.queueAtFront(task);
-}
-
-CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
-        int fd, int dumpFlags) {
-    args->context->profiler().dumpData(args->fd);
-    if (args->dumpFlags & DumpFlags::FrameStats) {
-        args->context->dumpFrames(args->fd);
-    }
-    if (args->dumpFlags & DumpFlags::JankStats) {
-        args->thread->globalProfileData()->dump(args->fd);
-    }
-    if (args->dumpFlags & DumpFlags::Reset) {
-        args->context->resetFrameStats();
-    }
-    return nullptr;
+    mRenderThread.queue().post([this]() {
+        mContext->notifyFramePending();
+    });
 }
 
 void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
-    SETUP_TASK(dumpProfileInfo);
-    args->context = mContext;
-    args->thread = &mRenderThread;
-    args->fd = fd;
-    args->dumpFlags = dumpFlags;
-    postAndWait(task);
-}
-
-CREATE_BRIDGE1(resetProfileInfo, CanvasContext* context) {
-    args->context->resetFrameStats();
-    return nullptr;
+    mRenderThread.queue().runSync([&]() {
+        mContext->profiler().dumpData(fd);
+        if (dumpFlags & DumpFlags::FrameStats) {
+            mContext->dumpFrames(fd);
+        }
+        if (dumpFlags & DumpFlags::JankStats) {
+            mRenderThread.globalProfileData()->dump(fd);
+        }
+        if (dumpFlags & DumpFlags::Reset) {
+            mContext->resetFrameStats();
+        }
+    });
 }
 
 void RenderProxy::resetProfileInfo() {
-    SETUP_TASK(resetProfileInfo);
-    args->context = mContext;
-    postAndWait(task);
+    mRenderThread.queue().runSync([=]() {
+        mContext->resetFrameStats();
+    });
 }
 
-CREATE_BRIDGE2(frameTimePercentile, RenderThread* thread, int percentile) {
-    return reinterpret_cast<void*>(static_cast<uintptr_t>(
-        args->thread->globalProfileData()->findPercentile(args->percentile)));
-}
-
-uint32_t RenderProxy::frameTimePercentile(int p) {
-    SETUP_TASK(frameTimePercentile);
-    args->thread = &mRenderThread;
-    args->percentile = p;
-    return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(
-        postAndWait(task)));
-}
-
-CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
-    args->thread->dumpGraphicsMemory(args->fd);
-    return nullptr;
+uint32_t RenderProxy::frameTimePercentile(int percentile) {
+    return mRenderThread.queue().runSync([&]() -> auto {
+        return mRenderThread.globalProfileData()->findPercentile(percentile);
+    });
 }
 
 void RenderProxy::dumpGraphicsMemory(int fd) {
-    if (!RenderThread::hasInstance()) return;
-    SETUP_TASK(dumpGraphicsMemory);
-    args->fd = fd;
-    args->thread = &RenderThread::getInstance();
-    staticPostAndWait(task);
-}
-
-CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) {
-    args->thread->globalProfileData().switchStorageToAshmem(args->fd);
-    close(args->fd);
-    return nullptr;
+    auto& thread = RenderThread::getInstance();
+    thread.queue().runSync([&]() {
+        thread.dumpGraphicsMemory(fd);
+    });
 }
 
 void RenderProxy::setProcessStatsBuffer(int fd) {
-    SETUP_TASK(setProcessStatsBuffer);
     auto& rt = RenderThread::getInstance();
-    args->thread = &rt;
-    args->fd = dup(fd);
-    rt.queue(task);
-}
-
-CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) {
-    args->thread->globalProfileData().rotateStorage();
-    return nullptr;
+    rt.queue().post([&rt, fd = dup(fd)]() {
+        rt.globalProfileData().switchStorageToAshmem(fd);
+        close(fd);
+    });
 }
 
 void RenderProxy::rotateProcessStatsBuffer() {
-    SETUP_TASK(rotateProcessStatsBuffer);
     auto& rt = RenderThread::getInstance();
-    args->thread = &rt;
-    rt.queue(task);
+    rt.queue().post([&rt]() {
+        rt.globalProfileData().rotateStorage();
+    });
 }
 
 int RenderProxy::getRenderThreadTid() {
     return mRenderThread.getTid();
 }
 
-CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
-    args->context->addRenderNode(args->node, args->placeFront);
-    return nullptr;
-}
-
 void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
-    SETUP_TASK(addRenderNode);
-    args->context = mContext;
-    args->node = node;
-    args->placeFront = placeFront;
-    post(task);
-}
-
-CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) {
-    args->context->removeRenderNode(args->node);
-    return nullptr;
+    mRenderThread.queue().post([=]() {
+        mContext->addRenderNode(node, placeFront);
+    });
 }
 
 void RenderProxy::removeRenderNode(RenderNode* node) {
-    SETUP_TASK(removeRenderNode);
-    args->context = mContext;
-    args->node = node;
-    post(task);
-}
-
-CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) {
-    args->context->prepareAndDraw(args->node);
-    return nullptr;
+    mRenderThread.queue().post([=]() {
+        mContext->removeRenderNode(node);
+    });
 }
 
 void RenderProxy::drawRenderNode(RenderNode* node) {
-    SETUP_TASK(drawRenderNode);
-    args->context = mContext;
-    args->node = node;
-    // Be pseudo-thread-safe and don't use any member variables
-    staticPostAndWait(task);
+    mRenderThread.queue().runSync([=]() {
+        mContext->prepareAndDraw(node);
+    });
 }
 
 void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
     mDrawFrameTask.setContentDrawBounds(left, top, right, bottom);
 }
 
-CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) {
-    args->context->serializeDisplayListTree();
-    return nullptr;
-}
-
 void RenderProxy::serializeDisplayListTree() {
-    SETUP_TASK(serializeDisplayListTree);
-    args->context = mContext;
-    post(task);
+    mRenderThread.queue().post([=]() {
+        mContext->serializeDisplayListTree();
+    });
 }
 
-CREATE_BRIDGE2(addFrameMetricsObserver, CanvasContext* context,
-        FrameMetricsObserver* frameStatsObserver) {
-   args->context->addFrameMetricsObserver(args->frameStatsObserver);
-   if (args->frameStatsObserver != nullptr) {
-       args->frameStatsObserver->decStrong(args->context);
-   }
-   return nullptr;
+void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observerPtr) {
+    mRenderThread.queue().post([this, observer = sp{observerPtr}]() {
+        mContext->addFrameMetricsObserver(observer.get());
+    });
 }
 
-void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observer) {
-    SETUP_TASK(addFrameMetricsObserver);
-    args->context = mContext;
-    args->frameStatsObserver = observer;
-    if (observer != nullptr) {
-        observer->incStrong(mContext);
-    }
-    post(task);
-}
-
-CREATE_BRIDGE2(removeFrameMetricsObserver, CanvasContext* context,
-        FrameMetricsObserver* frameStatsObserver) {
-   args->context->removeFrameMetricsObserver(args->frameStatsObserver);
-   if (args->frameStatsObserver != nullptr) {
-       args->frameStatsObserver->decStrong(args->context);
-   }
-   return nullptr;
-}
-
-void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
-    SETUP_TASK(removeFrameMetricsObserver);
-    args->context = mContext;
-    args->frameStatsObserver = observer;
-    if (observer != nullptr) {
-        observer->incStrong(mContext);
-    }
-    post(task);
-}
-
-CREATE_BRIDGE4(copySurfaceInto, RenderThread* thread,
-        Surface* surface, Rect srcRect, SkBitmap* bitmap) {
-    return (void*)args->thread->readback().copySurfaceInto(*args->surface,
-            args->srcRect, args->bitmap);
+void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr) {
+    mRenderThread.queue().post([this, observer = sp{observerPtr}]() {
+        mContext->removeFrameMetricsObserver(observer.get());
+    });
 }
 
 int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top,
         int right, int bottom,  SkBitmap* bitmap) {
-    SETUP_TASK(copySurfaceInto);
-    args->bitmap = bitmap;
-    args->surface = surface.get();
-    args->thread = &RenderThread::getInstance();
-    args->srcRect.set(left, top, right, bottom);
-    return static_cast<int>(
-            reinterpret_cast<intptr_t>( staticPostAndWait(task) ));
-}
-
-CREATE_BRIDGE2(prepareToDraw, RenderThread* thread, Bitmap* bitmap) {
-    CanvasContext::prepareToDraw(*args->thread, args->bitmap);
-    args->bitmap->unref();
-    args->bitmap = nullptr;
-    return nullptr;
+    auto& thread = RenderThread::getInstance();
+    return static_cast<int>(thread.queue().runSync([&]() -> auto {
+        return thread.readback().copySurfaceInto(*surface, Rect(left, top, right, bottom), bitmap);
+    }));
 }
 
 void RenderProxy::prepareToDraw(Bitmap& bitmap) {
@@ -635,10 +350,11 @@
     // window or not.
     if (!RenderThread::hasInstance()) return;
     RenderThread* renderThread = &RenderThread::getInstance();
-    SETUP_TASK(prepareToDraw);
-    args->thread = renderThread;
     bitmap.ref();
-    args->bitmap = &bitmap;
+    auto task = [renderThread, &bitmap]() {
+        CanvasContext::prepareToDraw(*renderThread, &bitmap);
+        bitmap.unref();
+    };
     nsecs_t lastVsync = renderThread->timeLord().latestVsync();
     nsecs_t estimatedNextVsync = lastVsync + renderThread->timeLord().frameIntervalNanos();
     nsecs_t timeToNextVsync = estimatedNextVsync - systemTime(CLOCK_MONOTONIC);
@@ -648,27 +364,17 @@
     // TODO: Make this concept a first-class supported thing? RT could use
     // knowledge of pending draws to better schedule this task
     if (timeToNextVsync > -6_ms && timeToNextVsync < 1_ms) {
-        renderThread->queueAt(task, estimatedNextVsync + 8_ms);
+        renderThread->queue().postAt(estimatedNextVsync + 8_ms, task);
     } else {
-        renderThread->queue(task);
+        renderThread->queue().post(task);
     }
 }
 
-CREATE_BRIDGE2(allocateHardwareBitmap, RenderThread* thread, SkBitmap* bitmap) {
-    sk_sp<Bitmap> hardwareBitmap = args->thread->allocateHardwareBitmap(*args->bitmap);
-    return hardwareBitmap.release();
-}
-
 sk_sp<Bitmap> RenderProxy::allocateHardwareBitmap(SkBitmap& bitmap) {
-    SETUP_TASK(allocateHardwareBitmap);
-    args->bitmap = &bitmap;
-    args->thread = &RenderThread::getInstance();
-    sk_sp<Bitmap> hardwareBitmap(reinterpret_cast<Bitmap*>(staticPostAndWait(task)));
-    return hardwareBitmap;
-}
-
-CREATE_BRIDGE3(copyGraphicBufferInto, RenderThread* thread, GraphicBuffer* buffer, SkBitmap* bitmap) {
-    return (void*) args->thread->readback().copyGraphicBufferInto(args->buffer, args->bitmap);
+    auto& thread = RenderThread::getInstance();
+    return thread.queue().runSync([&]() -> auto {
+        return thread.allocateHardwareBitmap(bitmap);
+    });
 }
 
 int RenderProxy::copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap) {
@@ -677,80 +383,36 @@
         //TODO: fix everything that hits this. We should never be triggering a readback ourselves.
         return (int) thread.readback().copyGraphicBufferInto(buffer, bitmap);
     } else {
-        SETUP_TASK(copyGraphicBufferInto);
-        args->thread = &thread;
-        args->bitmap = bitmap;
-        args->buffer = buffer;
-        return static_cast<int>(reinterpret_cast<intptr_t>(staticPostAndWait(task)));
+        return thread.queue().runSync([&]() -> int {
+            return (int) thread.readback().copyGraphicBufferInto(buffer, bitmap);
+        });
     }
 }
 
-CREATE_BRIDGE2(onBitmapDestroyed, RenderThread* thread, uint32_t pixelRefId) {
-    args->thread->renderState().onBitmapDestroyed(args->pixelRefId);
-    return nullptr;
-}
-
 void RenderProxy::onBitmapDestroyed(uint32_t pixelRefId) {
     if (!RenderThread::hasInstance()) return;
-    SETUP_TASK(onBitmapDestroyed);
     RenderThread& thread = RenderThread::getInstance();
-    args->thread = &thread;
-    args->pixelRefId = pixelRefId;
-    thread.queue(task);
+    thread.queue().post([&thread, pixelRefId]() {
+        thread.renderState().onBitmapDestroyed(pixelRefId);
+    });
 }
 
 void RenderProxy::disableVsync() {
     Properties::disableVsync = true;
 }
 
-void RenderProxy::post(RenderTask* task) {
-    mRenderThread.queue(task);
-}
-
-CREATE_BRIDGE1(repackVectorDrawableAtlas, RenderThread* thread) {
-    args->thread->cacheManager().acquireVectorDrawableAtlas()->repackIfNeeded(
-            args->thread->getGrContext());
-    return nullptr;
-}
-
 void RenderProxy::repackVectorDrawableAtlas() {
     RenderThread& thread = RenderThread::getInstance();
-    SETUP_TASK(repackVectorDrawableAtlas);
-    args->thread = &thread;
-    thread.queue(task);
-}
-
-CREATE_BRIDGE1(releaseVDAtlasEntries, RenderThread* thread) {
-    args->thread->cacheManager().acquireVectorDrawableAtlas()->delayedReleaseEntries();
-    return nullptr;
+    thread.queue().post([&thread]() {
+        thread.cacheManager().acquireVectorDrawableAtlas()->repackIfNeeded(thread.getGrContext());
+    });
 }
 
 void RenderProxy::releaseVDAtlasEntries() {
     RenderThread& thread = RenderThread::getInstance();
-    SETUP_TASK(releaseVDAtlasEntries);
-    args->thread = &thread;
-    thread.queue(task);
-}
-
-void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
-    void* retval;
-    task->setReturnPtr(&retval);
-    SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
-    AutoMutex _lock(mSyncMutex);
-    mRenderThread.queue(&syncTask);
-    while (!syncTask.hasRun()) {
-        mSyncCondition.wait(mSyncMutex);
-    }
-    return retval;
-}
-
-void* RenderProxy::staticPostAndWait(MethodInvokeRenderTask* task) {
-    RenderThread& thread = RenderThread::getInstance();
-    LOG_ALWAYS_FATAL_IF(gettid() == thread.getTid());
-    void* retval;
-    task->setReturnPtr(&retval);
-    thread.queueAndWait(task);
-    return retval;
+    thread.queue().post([&thread]() {
+        thread.cacheManager().acquireVectorDrawableAtlas()->delayedReleaseEntries();
+    });
 }
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 9440b15..b46d9cc 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -17,22 +17,16 @@
 #ifndef RENDERPROXY_H_
 #define RENDERPROXY_H_
 
-#include "RenderTask.h"
-
 #include <cutils/compiler.h>
-#include <EGL/egl.h>
 #include <SkBitmap.h>
-#include <utils/Condition.h>
 #include <utils/Functor.h>
-#include <utils/Mutex.h>
-#include <utils/Timers.h>
-#include <utils/StrongPointer.h>
+#include <gui/Surface.h>
 
-#include "../Caches.h"
 #include "../FrameMetricsObserver.h"
 #include "../IContextFactory.h"
-#include "CanvasContext.h"
+#include "hwui/Bitmap.h"
 #include "DrawFrameTask.h"
+#include "SwapBehavior.h"
 
 namespace android {
 class GraphicBuffer;
@@ -41,13 +35,11 @@
 
 class DeferredLayerUpdater;
 class RenderNode;
-class DisplayList;
-class Layer;
 class Rect;
 
 namespace renderthread {
 
-class ErrorChannel;
+class CanvasContext;
 class RenderThread;
 class RenderProxyBridge;
 
@@ -151,16 +143,8 @@
 
     DrawFrameTask mDrawFrameTask;
 
-    Mutex mSyncMutex;
-    Condition mSyncCondition;
-
     void destroyContext();
 
-    void post(RenderTask* task);
-    void* postAndWait(MethodInvokeRenderTask* task);
-
-    static void* staticPostAndWait(MethodInvokeRenderTask* task);
-
     // Friend class to help with bridging
     friend class RenderProxyBridge;
 };
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 51e9374..f3bb120 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -49,101 +49,6 @@
 // Slight delay to give the UI time to push us a new frame before we replay
 static const nsecs_t DISPATCH_FRAME_CALLBACKS_DELAY = milliseconds_to_nanoseconds(4);
 
-TaskQueue::TaskQueue() : mHead(nullptr), mTail(nullptr) {}
-
-RenderTask* TaskQueue::next() {
-    RenderTask* ret = mHead;
-    if (ret) {
-        mHead = ret->mNext;
-        if (!mHead) {
-            mTail = nullptr;
-        }
-        ret->mNext = nullptr;
-    }
-    return ret;
-}
-
-RenderTask* TaskQueue::peek() {
-    return mHead;
-}
-
-void TaskQueue::queue(RenderTask* task) {
-    // Since the RenderTask itself forms the linked list it is not allowed
-    // to have the same task queued twice
-    LOG_ALWAYS_FATAL_IF(task->mNext || mTail == task, "Task is already in the queue!");
-    if (mTail) {
-        // Fast path if we can just append
-        if (mTail->mRunAt <= task->mRunAt) {
-            mTail->mNext = task;
-            mTail = task;
-        } else {
-            // Need to find the proper insertion point
-            RenderTask* previous = nullptr;
-            RenderTask* next = mHead;
-            while (next && next->mRunAt <= task->mRunAt) {
-                previous = next;
-                next = next->mNext;
-            }
-            if (!previous) {
-                task->mNext = mHead;
-                mHead = task;
-            } else {
-                previous->mNext = task;
-                if (next) {
-                    task->mNext = next;
-                } else {
-                    mTail = task;
-                }
-            }
-        }
-    } else {
-        mTail = mHead = task;
-    }
-}
-
-void TaskQueue::queueAtFront(RenderTask* task) {
-    LOG_ALWAYS_FATAL_IF(task->mNext || mHead == task, "Task is already in the queue!");
-    if (mTail) {
-        task->mNext = mHead;
-        mHead = task;
-    } else {
-        mTail = mHead = task;
-    }
-}
-
-void TaskQueue::remove(RenderTask* task) {
-    // TaskQueue is strict here to enforce that users are keeping track of
-    // their RenderTasks due to how their memory is managed
-    LOG_ALWAYS_FATAL_IF(!task->mNext && mTail != task,
-            "Cannot remove a task that isn't in the queue!");
-
-    // If task is the head we can just call next() to pop it off
-    // Otherwise we need to scan through to find the task before it
-    if (peek() == task) {
-        next();
-    } else {
-        RenderTask* previous = mHead;
-        while (previous->mNext != task) {
-            previous = previous->mNext;
-        }
-        previous->mNext = task->mNext;
-        if (mTail == task) {
-            mTail = previous;
-        }
-    }
-}
-
-class DispatchFrameCallbacks : public RenderTask {
-private:
-    RenderThread* mRenderThread;
-public:
-    explicit DispatchFrameCallbacks(RenderThread* rt) : mRenderThread(rt) {}
-
-    virtual void run() override {
-        mRenderThread->dispatchFrameCallbacks();
-    }
-};
-
 static bool gHasRenderThreadInstance = false;
 
 bool RenderThread::hasInstance() {
@@ -159,19 +64,15 @@
     return *sInstance;
 }
 
-RenderThread::RenderThread() : Thread(true)
-        , mNextWakeup(LLONG_MAX)
+RenderThread::RenderThread() : ThreadBase()
         , mDisplayEventReceiver(nullptr)
         , mVsyncRequested(false)
         , mFrameCallbackTaskPending(false)
-        , mFrameCallbackTask(nullptr)
         , mRenderState(nullptr)
         , mEglManager(nullptr)
         , mVkManager(nullptr) {
     Properties::load();
-    mFrameCallbackTask = new DispatchFrameCallbacks(this);
-    mLooper = new Looper(false);
-    run("RenderThread");
+    start("RenderThread");
 }
 
 RenderThread::~RenderThread() {
@@ -321,7 +222,9 @@
             ATRACE_NAME("queue mFrameCallbackTask");
             mFrameCallbackTaskPending = true;
             nsecs_t runAt = (vsyncEvent + DISPATCH_FRAME_CALLBACKS_DELAY);
-            queueAt(mFrameCallbackTask, runAt);
+            queue().postAt(runAt, [this]() {
+                dispatchFrameCallbacks();
+            });
         }
     }
 }
@@ -356,35 +259,9 @@
     setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
     initThreadLocals();
 
-    int timeoutMillis = -1;
-    for (;;) {
-        int result = mLooper->pollOnce(timeoutMillis);
-        LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
-                "RenderThread Looper POLL_ERROR!");
-
-        nsecs_t nextWakeup;
-        {
-            FatVector<RenderTask*, 10> workQueue;
-            // Process our queue, if we have anything. By first acquiring
-            // all the pending events then processing them we avoid vsync
-            // starvation if more tasks are queued while we are processing tasks.
-            while (RenderTask* task = nextTask(&nextWakeup)) {
-                workQueue.push_back(task);
-            }
-            for (auto task : workQueue) {
-                task->run();
-                // task may have deleted itself, do not reference it again
-            }
-        }
-        if (nextWakeup == LLONG_MAX) {
-            timeoutMillis = -1;
-        } else {
-            nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
-            timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos);
-            if (timeoutMillis < 0) {
-                timeoutMillis = 0;
-            }
-        }
+    while (true) {
+        waitForWork();
+        processQueue();
 
         if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
             drainDisplayEventQueue();
@@ -406,46 +283,6 @@
     return false;
 }
 
-void RenderThread::queue(RenderTask* task) {
-    AutoMutex _lock(mLock);
-    mQueue.queue(task);
-    if (mNextWakeup && task->mRunAt < mNextWakeup) {
-        mNextWakeup = 0;
-        mLooper->wake();
-    }
-}
-
-void RenderThread::queueAndWait(RenderTask* task) {
-    // These need to be local to the thread to avoid the Condition
-    // signaling the wrong thread. The easiest way to achieve that is to just
-    // make this on the stack, although that has a slight cost to it
-    Mutex mutex;
-    Condition condition;
-    SignalingRenderTask syncTask(task, &mutex, &condition);
-
-    AutoMutex _lock(mutex);
-    queue(&syncTask);
-    while (!syncTask.hasRun()) {
-        condition.wait(mutex);
-    }
-}
-
-void RenderThread::queueAtFront(RenderTask* task) {
-    AutoMutex _lock(mLock);
-    mQueue.queueAtFront(task);
-    mLooper->wake();
-}
-
-void RenderThread::queueAt(RenderTask* task, nsecs_t runAtNs) {
-    task->mRunAt = runAtNs;
-    queue(task);
-}
-
-void RenderThread::remove(RenderTask* task) {
-    AutoMutex _lock(mLock);
-    mQueue.remove(task);
-}
-
 void RenderThread::postFrameCallback(IFrameCallback* callback) {
     mPendingRegistrationFrameCallbacks.insert(callback);
 }
@@ -463,26 +300,6 @@
     }
 }
 
-RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
-    AutoMutex _lock(mLock);
-    RenderTask* next = mQueue.peek();
-    if (!next) {
-        mNextWakeup = LLONG_MAX;
-    } else {
-        mNextWakeup = next->mRunAt;
-        // Most tasks won't be delayed, so avoid unnecessary systemTime() calls
-        if (next->mRunAt <= 0 || next->mRunAt <= systemTime(SYSTEM_TIME_MONOTONIC)) {
-            next = mQueue.next();
-        } else {
-            next = nullptr;
-        }
-    }
-    if (nextWakeup) {
-        *nextWakeup = mNextWakeup;
-    }
-    return next;
-}
-
 sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) {
     auto renderType = Properties::getRenderPipelineType();
     switch (renderType) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 30884b5..e1d61c5 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -22,6 +22,7 @@
 #include "../JankTracker.h"
 #include "CacheManager.h"
 #include "TimeLord.h"
+#include "thread/ThreadBase.h"
 
 #include <GrContext.h>
 #include <cutils/compiler.h>
@@ -31,7 +32,9 @@
 #include <utils/Thread.h>
 
 #include <memory>
+#include <mutex>
 #include <set>
+#include <thread/ThreadBase.h>
 
 namespace android {
 
@@ -47,26 +50,10 @@
 namespace renderthread {
 
 class CanvasContext;
-class DispatchFrameCallbacks;
 class EglManager;
 class RenderProxy;
 class VulkanManager;
 
-class TaskQueue {
-public:
-    TaskQueue();
-
-    RenderTask* next();
-    void queue(RenderTask* task);
-    void queueAtFront(RenderTask* task);
-    RenderTask* peek();
-    void remove(RenderTask* task);
-
-private:
-    RenderTask* mHead;
-    RenderTask* mTail;
-};
-
 // Mimics android.view.Choreographer.FrameCallback
 class IFrameCallback {
 public:
@@ -76,16 +63,11 @@
     ~IFrameCallback() {}
 };
 
-class ANDROID_API RenderThread : public Thread {
+class RenderThread : private ThreadBase {
     PREVENT_COPY_AND_ASSIGN(RenderThread);
 public:
-    // RenderThread takes complete ownership of tasks that are queued
-    // and will delete them after they are run
-    ANDROID_API void queue(RenderTask* task);
-    ANDROID_API void queueAndWait(RenderTask* task);
-    ANDROID_API void queueAtFront(RenderTask* task);
-    void queueAt(RenderTask* task, nsecs_t runAtNs);
-    void remove(RenderTask* task);
+
+    WorkQueue& queue() { return ThreadBase::queue(); }
 
     // Mimics android.view.Choreographer
     void postFrameCallback(IFrameCallback* callback);
@@ -140,17 +122,6 @@
     void dispatchFrameCallbacks();
     void requestVsync();
 
-    // Returns the next task to be run. If this returns NULL nextWakeup is set
-    // to the time to requery for the nextTask to run. mNextWakeup is also
-    // set to this time
-    RenderTask* nextTask(nsecs_t* nextWakeup);
-
-    sp<Looper> mLooper;
-    Mutex mLock;
-
-    nsecs_t mNextWakeup;
-    TaskQueue mQueue;
-
     DisplayInfo mDisplayInfo;
 
     DisplayEventReceiver* mDisplayEventReceiver;
@@ -162,7 +133,6 @@
     // the previous one
     std::set<IFrameCallback*> mPendingRegistrationFrameCallbacks;
     bool mFrameCallbackTaskPending;
-    DispatchFrameCallbacks* mFrameCallbackTask;
 
     TimeLord mTimeLord;
     RenderState* mRenderState;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index afb1193..f7a90b0 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -22,10 +22,11 @@
 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <log/log.h>
 
-#include <inttypes.h>
-#include <sys/types.h>
-#include <sys/stat.h>
+#include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
 #include <sys/mman.h>
 
@@ -366,4 +367,4 @@
 }
 
 } /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index c1ca1e7..1e30d23 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -81,10 +81,10 @@
     mSurfaceControl = mSurfaceComposerClient->createSurface(String8("HwuiTest"),
             gDisplay.w, gDisplay.h, PIXEL_FORMAT_RGBX_8888);
 
-    SurfaceComposerClient::openGlobalTransaction();
-    mSurfaceControl->setLayer(0x7FFFFFF);
-    mSurfaceControl->show();
-    SurfaceComposerClient::closeGlobalTransaction();
+    SurfaceComposerClient::Transaction t;
+    t.setLayer(mSurfaceControl, 0x7FFFFFF)
+            .show(mSurfaceControl)
+            .apply();
     mSurface = mSurfaceControl->getSurface();
 }
 
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 64ec58d..e613776 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,7 @@
 #include "hwui/Paint.h"
 #include "DeferredLayerUpdater.h"
 
+#include <minikin/Layout.h>
 #include <renderthread/EglManager.h>
 #include <renderthread/OpenGLPipeline.h>
 #include <pipeline/skia/SkiaOpenGLPipeline.h>
@@ -121,13 +122,15 @@
 void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
         const SkPaint& paint, float x, float y) {
     auto utf16 = asciiToUtf16(text);
-    canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, 0, paint, nullptr);
+    canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, paint,
+            nullptr);
 }
 
 void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text,
         const SkPaint& paint, const SkPath& path) {
     auto utf16 = asciiToUtf16(text);
-    canvas->drawTextOnPath(utf16.get(), strlen(text), 0, path, 0, 0, paint, nullptr);
+    canvas->drawTextOnPath(utf16.get(), strlen(text), minikin::Bidi::LTR, path, 0, 0, paint,
+            nullptr);
 }
 
 void TestUtils::TestTask::run() {
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index f293631..c383fcf 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -318,7 +318,9 @@
      */
     static void runOnRenderThread(RtCallback rtCallback) {
         TestTask task(rtCallback);
-        renderthread::RenderThread::getInstance().queueAndWait(&task);
+        renderthread::RenderThread::getInstance().queue().runSync([&]() {
+            task.run();
+        });
     }
 
     static bool isRenderThreadRunning() {
diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
index 5b685bb..21d8d75 100644
--- a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp
@@ -57,7 +57,7 @@
         for (int i = 0; i < 5; i++) {
             paint.setTextSize(10 + (frameNr % 20) + i * 20);
             canvas->drawText(text.get(), 0, textLength, textLength,
-                    0, 100 * (i + 2), minikin::kBidi_Force_LTR, paint, nullptr);
+                    0, 100 * (i + 2), minikin::Bidi::FORCE_LTR, paint, nullptr);
         }
 
         container->setStagingDisplayList(canvas->finishRecording());
diff --git a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
index ad0b1f1..d678af9 100644
--- a/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
+++ b/libs/hwui/tests/common/scenes/SaveLayer2Animation.cpp
@@ -55,7 +55,7 @@
             std::string offscreen = "offscreen line " + stri;
             std::unique_ptr<uint16_t[]> offtext = TestUtils::asciiToUtf16(offscreen.c_str());
             canvas.drawText(offtext.get(), 0, offscreen.length(), offscreen.length(),
-                    bounds.fLeft, top + padding, minikin::kBidi_Force_LTR, mBluePaint, nullptr);
+                    bounds.fLeft, top + padding, minikin::Bidi::FORCE_LTR, mBluePaint, nullptr);
             canvas.restore();
 
             canvas.drawRect(bounds.fLeft, top + padding, bounds.fRight,
@@ -63,7 +63,7 @@
             std::string onscreen = "onscreen line " + stri;
             std::unique_ptr<uint16_t[]> ontext = TestUtils::asciiToUtf16(onscreen.c_str());
             canvas.drawText(ontext.get(), 0, onscreen.length(), onscreen.length(), bounds.fLeft,
-                    top + smallRectHeight - padding, minikin::kBidi_Force_LTR, mGreenPaint,
+                    top + smallRectHeight - padding, minikin::Bidi::FORCE_LTR, mGreenPaint,
                     nullptr);
         }
     }
diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp
new file mode 100644
index 0000000..04fc2d4
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/TvApp.cpp
@@ -0,0 +1,274 @@
+/*
+ * 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 "TestSceneBase.h"
+#include "tests/common/BitmapAllocationTestUtils.h"
+#include "SkBlendMode.h"
+
+class TvApp;
+class TvAppNoRoundedCorner;
+class TvAppColorFilter;
+class TvAppNoRoundedCornerColorFilter;
+
+static bool _TvApp(
+        BitmapAllocationTestUtils::registerBitmapAllocationScene<TvApp>(
+                "tvapp", "A dense grid of cards:"
+                "with rounded corner, using overlay RenderNode for dimming."));
+
+static bool _TvAppNoRoundedCorner(
+        BitmapAllocationTestUtils::registerBitmapAllocationScene<TvAppNoRoundedCorner>(
+                "tvapp_norc", "A dense grid of cards:"
+                "no rounded corner, using overlay RenderNode for dimming"));
+
+static bool _TvAppColorFilter(
+        BitmapAllocationTestUtils::registerBitmapAllocationScene<TvAppColorFilter>(
+                "tvapp_cf", "A dense grid of cards:"
+                "with rounded corner, using ColorFilter for dimming"));
+
+static bool _TvAppNoRoundedCornerColorFilter(
+        BitmapAllocationTestUtils::registerBitmapAllocationScene<TvAppNoRoundedCornerColorFilter>(
+                "tvapp_norc_cf", "A dense grid of cards:"
+                "no rounded corner, using ColorFilter for dimming"));
+
+class TvApp : public TestScene {
+public:
+    TvApp(BitmapAllocationTestUtils::BitmapAllocator allocator)
+        : TestScene()
+        , mAllocator(allocator) { }
+
+    sp<RenderNode> mBg;
+    std::vector<sp<RenderNode>> mCards;
+    std::vector<sp<RenderNode>> mInfoAreas;
+    std::vector<sp<RenderNode>> mImages;
+    std::vector<sp<RenderNode>> mOverlays;
+    std::vector<sk_sp<Bitmap>> mCachedBitmaps;
+    BitmapAllocationTestUtils::BitmapAllocator mAllocator;
+    sk_sp<Bitmap> mSingleBitmap;
+    int mSeed = 0;
+    int mSeed2 = 0;
+
+    void createContent(int width, int height, Canvas& canvas) override {
+        mBg = createBitmapNode(canvas, 0xFF9C27B0, 0, 0, width, height);
+        canvas.drawRenderNode(mBg.get());
+
+        canvas.insertReorderBarrier(true);
+        mSingleBitmap = mAllocator(dp(160), dp(120), kRGBA_8888_SkColorType,
+                 [](SkBitmap& skBitmap) {
+             skBitmap.eraseColor(0xFF0000FF);
+        });
+
+        for (int y = dp(18) - dp(178); y < height - dp(18); y += dp(178)) {
+            bool isFirstCard = true;
+            for (int x = dp(18); x < width - dp(18); x += dp(178)) {
+                sp<RenderNode> card = createCard(x, y, dp(160), dp(160), isFirstCard);
+                isFirstCard = false;
+                canvas.drawRenderNode(card.get());
+                mCards.push_back(card);
+            }
+        }
+        canvas.insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        size_t numCards = mCards.size();
+        for (size_t ci = 0; ci < numCards; ci++) {
+            updateCard(ci, frameNr);
+        }
+    }
+
+private:
+    sp<RenderNode> createBitmapNode(Canvas& canvas, SkColor color, int left, int top,
+            int width, int height) {
+        return TestUtils::createNode(left, top, left + width , top + height,
+                [this, width, height, color](RenderProperties& props, Canvas& canvas) {
+            sk_sp<Bitmap> bitmap = mAllocator(width, height, kRGBA_8888_SkColorType,
+                    [color](SkBitmap& skBitmap) {
+                 skBitmap.eraseColor(color);
+            });
+            canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+        });
+    }
+
+    sp<RenderNode> createSharedBitmapNode(Canvas& canvas, int left, int top,
+            int width, int height, sk_sp<Bitmap>bitmap) {
+        return TestUtils::createNode(left, top, left + width , top + height,
+                [bitmap](RenderProperties& props, Canvas& canvas) {
+            canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+        });
+    }
+
+    sp<RenderNode> createInfoNode(Canvas& canvas, int left, int top,
+            int width, int height, const char* text, const char* text2) {
+        return TestUtils::createNode(left, top, left + width , top + height,
+                [text, text2](RenderProperties& props, Canvas& canvas) {
+            canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver);
+
+            SkPaint paint;
+            paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+            paint.setAntiAlias(true);
+            paint.setTextSize(24);
+
+            paint.setColor(Color::Black);
+            TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30);
+            paint.setTextSize(20);
+            TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54);
+
+        });
+    }
+
+    sp<RenderNode> createColorNode(Canvas& canvas, int left, int top,
+            int width, int height, SkColor color) {
+        return TestUtils::createNode(left, top, left + width , top + height,
+                [color](RenderProperties& props, Canvas& canvas) {
+            canvas.drawColor(color, SkBlendMode::kSrcOver);
+        });
+    }
+
+    virtual bool useSingleBitmap() {
+        return false;
+    }
+
+    virtual float roundedCornerRadius() {
+        return dp(2);
+    }
+
+    // when true, use overlay RenderNode for dimming, otherwise apply a ColorFilter to dim image
+    virtual bool useOverlay() {
+        return true;
+    }
+
+    sp<RenderNode> createCard(int x, int y, int width, int height, bool selected) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height, selected, this](RenderProperties& props, Canvas& canvas) {
+            if (selected) {
+                props.setElevation(dp(16));
+                props.setScaleX(1.2);
+                props.setScaleY(1.2);
+            }
+            props.mutableOutline().setRoundRect(0, 0, width, height, roundedCornerRadius(), 1);
+            props.mutableOutline().setShouldClip(true);
+
+            sk_sp<Bitmap> bitmap = useSingleBitmap() ? mSingleBitmap
+                 : mAllocator(width, dp(120), kRGBA_8888_SkColorType, [this](SkBitmap& skBitmap) {
+                       skBitmap.eraseColor(0xFF000000 | ((mSeed << 3) & 0xFF));
+                   });
+            sp<RenderNode> cardImage = createSharedBitmapNode(canvas, 0, 0, width, dp(120),
+                    bitmap);
+            canvas.drawRenderNode(cardImage.get());
+            mCachedBitmaps.push_back(bitmap);
+            mImages.push_back(cardImage);
+
+            char buffer[128];
+            sprintf(buffer, "Video %d-%d", mSeed, mSeed + 1);
+            mSeed++;
+            char buffer2[128];
+            sprintf(buffer2, "Studio %d", mSeed2++);
+            sp<RenderNode> infoArea = createInfoNode(canvas, 0, dp(120), width, height, buffer, buffer2);
+            canvas.drawRenderNode(infoArea.get());
+            mInfoAreas.push_back(infoArea);
+
+            if (useOverlay()) {
+                sp<RenderNode> overlayColor = createColorNode(canvas, 0, 0, width, height, 0x00000000);
+                canvas.drawRenderNode(overlayColor.get());
+                mOverlays.push_back(overlayColor);
+            }
+        });
+    }
+
+    void updateCard(int ci, int curFrame) {
+        // updating card's translation Y
+        sp<RenderNode> card = mCards[ci];
+        card->setPropertyFieldsDirty(RenderNode::Y);
+        card->mutateStagingProperties().setTranslationY(curFrame % 150);
+
+        // re-recording card's canvas, not necessary but to add some burden to CPU
+        std::unique_ptr<Canvas> cardcanvas(Canvas::create_recording_canvas(
+                card->stagingProperties().getWidth(),
+                card->stagingProperties().getHeight()));
+        sp<RenderNode> image = mImages[ci];
+        sp<RenderNode> infoArea = mInfoAreas[ci];
+        cardcanvas->drawRenderNode(infoArea.get());
+
+        if (useOverlay()) {
+             cardcanvas->drawRenderNode(image.get());
+            // re-recording card overlay's canvas, animating overlay color alpha
+            sp<RenderNode> overlay = mOverlays[ci];
+            std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(
+                    overlay->stagingProperties().getWidth(),
+                    overlay->stagingProperties().getHeight()));
+            canvas->drawColor((curFrame % 150) << 24, SkBlendMode::kSrcOver);
+            overlay->setStagingDisplayList(canvas->finishRecording());
+            cardcanvas->drawRenderNode(overlay.get());
+        } else {
+            // re-recording image node's canvas, animating ColorFilter
+            std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas(
+                    image->stagingProperties().getWidth(),
+                    image->stagingProperties().getHeight()));
+            SkPaint paint;
+            sk_sp<SkColorFilter> filter(SkColorFilter::MakeModeFilter((curFrame % 150) << 24,
+                    SkBlendMode::kSrcATop));
+            paint.setColorFilter(filter);
+            sk_sp<Bitmap> bitmap = mCachedBitmaps[ci];
+            canvas->drawBitmap(*bitmap, 0, 0, &paint);
+            image->setStagingDisplayList(canvas->finishRecording());
+            cardcanvas->drawRenderNode(image.get());
+        }
+
+        card->setStagingDisplayList(cardcanvas->finishRecording());
+    }
+};
+
+class TvAppNoRoundedCorner : public TvApp {
+public:
+    TvAppNoRoundedCorner(BitmapAllocationTestUtils::BitmapAllocator allocator)
+        : TvApp(allocator) { }
+
+private:
+
+    virtual float roundedCornerRadius() override {
+        return dp(0);
+    }
+};
+
+class TvAppColorFilter : public TvApp {
+public:
+    TvAppColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator)
+        : TvApp(allocator) { }
+
+private:
+
+    virtual bool useOverlay() override {
+        return false;
+    }
+};
+
+class TvAppNoRoundedCornerColorFilter : public TvApp {
+public:
+    TvAppNoRoundedCornerColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator)
+        : TvApp(allocator) { }
+
+private:
+
+    virtual float roundedCornerRadius() override {
+        return dp(0);
+    }
+
+    virtual bool useOverlay() override {
+        return false;
+    }
+};
+
+
diff --git a/libs/hwui/tests/microbench/TaskManagerBench.cpp b/libs/hwui/tests/microbench/TaskManagerBench.cpp
index cf47f273..67cb428 100644
--- a/libs/hwui/tests/microbench/TaskManagerBench.cpp
+++ b/libs/hwui/tests/microbench/TaskManagerBench.cpp
@@ -19,7 +19,9 @@
 #include "thread/Task.h"
 #include "thread/TaskManager.h"
 #include "thread/TaskProcessor.h"
+#include "thread/ThreadBase.h"
 
+#include <atomic>
 #include <vector>
 
 using namespace android;
@@ -38,6 +40,8 @@
     }
 };
 
+class TestThread : public ThreadBase, public virtual RefBase {};
+
 void BM_TaskManager_allocateTask(benchmark::State& state) {
     std::vector<sp<TrivialTask> > tasks;
     tasks.reserve(state.max_iterations);
@@ -86,3 +90,50 @@
     state.PauseTiming();
 }
 BENCHMARK(BM_TaskManager_enqueueRunDeleteTask);
+
+void BM_Thread_enqueueTask(benchmark::State& state) {
+    sp<TestThread> thread{new TestThread};
+    thread->start();
+
+    atomic_int counter(0);
+    int expected = 0;
+    while (state.KeepRunning()) {
+        expected++;
+        thread->queue().post([&counter](){
+            counter++;
+        });
+    }
+    thread->queue().runSync([](){});
+
+    thread->requestExit();
+    thread->join();
+    if (counter != expected) {
+        printf("Ran %d lambads, should have been %d\n", counter.load(), expected);
+    }
+}
+BENCHMARK(BM_Thread_enqueueTask);
+
+void BM_Thread_enqueueRunDeleteTask(benchmark::State& state) {
+    sp<TestThread> thread{new TestThread};
+    thread->start();
+    std::vector<std::future<int>> tasks;
+    tasks.reserve(state.max_iterations);
+
+    int expected = 0;
+    while (state.KeepRunning()) {
+        tasks.emplace_back(thread->queue().async([expected]() -> int {
+            return expected + 1;
+        }));
+        expected++;
+    }
+    state.ResumeTiming();
+    expected = 0;
+    for (auto& future : tasks) {
+        if (future.get() != ++expected) {
+            printf("Mismatch expected %d vs. observed %d\n", expected, future.get());
+        }
+    }
+    tasks.clear();
+    state.PauseTiming();
+}
+BENCHMARK(BM_Thread_enqueueRunDeleteTask);
\ No newline at end of file
diff --git a/libs/hwui/tests/scripts/skp-capture.sh b/libs/hwui/tests/scripts/skp-capture.sh
new file mode 100755
index 0000000..54fa229
--- /dev/null
+++ b/libs/hwui/tests/scripts/skp-capture.sh
@@ -0,0 +1,116 @@
+#!/bin/sh
+
+# Copyright 2015 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if [ -z "$1" ]; then
+    printf 'Usage:\n    skp-capture.sh PACKAGE_NAME OPTIONAL_FRAME_COUNT\n\n'
+    printf "Use \`adb shell 'pm list packages'\` to get a listing.\n\n"
+    exit 1
+fi
+if ! command -v adb > /dev/null 2>&1; then
+    if [ -x "${ANDROID_SDK_ROOT}/platform-tools/adb" ]; then
+        adb() {
+            "${ANDROID_SDK_ROOT}/platform-tools/adb" "$@"
+        }
+    else
+        echo 'adb missing'
+        exit 2
+    fi
+fi
+phase1_timeout_seconds=15
+phase2_timeout_seconds=60
+package="$1"
+filename="$(date '+%H%M%S').skp"
+remote_path="/data/data/${package}/cache/${filename}"
+local_path_prefix="$(date '+%Y-%m-%d_%H%M%S')_${package}"
+local_path="${local_path_prefix}.skp"
+enable_capture_key='debug.hwui.capture_skp_enabled'
+enable_capture_value=$(adb shell "getprop '${enable_capture_key}'")
+#printf 'captureflag=' "$enable_capture_value" '\n'
+if [ -z "$enable_capture_value" ]; then
+    printf 'Capture SKP property need to be enabled first. Please use\n'
+    printf "\"adb shell setprop debug.hwui.capture_skp_enabled true\" and then restart\n"
+    printf "the process.\n\n"
+    exit 1
+fi
+if [ ! -z "$2" ]; then
+    adb shell "setprop 'debug.hwui.capture_skp_frames' $2"
+fi
+filename_key='debug.hwui.skp_filename'
+adb shell "setprop '${filename_key}' '${remote_path}'"
+spin() {
+    case "$spin" in
+         1) printf '\b|';;
+         2) printf '\b\\';;
+         3) printf '\b-';;
+         *) printf '\b/';;
+    esac
+    spin=$(( ( ${spin:-0} + 1 ) % 4 ))
+    sleep $1
+}
+
+banner() {
+    printf '\n=====================\n'
+    printf '   %s' "$*"
+    printf '\n=====================\n'
+}
+banner '...WAITING...'
+adb_test_exist() {
+    test '0' = "$(adb shell "test -e \"$1\"; echo \$?")";
+}
+timeout=$(( $(date +%s) + $phase1_timeout_seconds))
+while ! adb_test_exist "$remote_path"; do
+    spin 0.05
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+#read -n1 -r -p "Press any key to continue..." key
+
+banner '...SAVING...'
+adb_test_file_nonzero() {
+    # grab first byte of `du` output
+    X="$(adb shell "du \"$1\" 2> /dev/null | dd bs=1 count=1 2> /dev/null")"
+    test "$X" && test "$X" -ne 0
+}
+#adb_filesize() {
+#    adb shell "wc -c \"$1\"" 2> /dev/null | awk '{print $1}'
+#}
+timeout=$(( $(date +%s) + $phase2_timeout_seconds))
+while ! adb_test_file_nonzero "$remote_path"; do
+    spin 0.05
+    if [ $(date +%s) -gt $timeout ] ; then
+        printf '\bTimed out.\n'
+        adb shell "setprop '${filename_key}' ''"
+        exit 3
+    fi
+done
+printf '\b'
+
+adb shell "setprop '${filename_key}' ''"
+
+i=0; while [ $i -lt 10 ]; do spin 0.10; i=$(($i + 1)); done; echo
+
+adb pull "$remote_path" "$local_path"
+if ! [ -f "$local_path" ] ; then
+    printf "something went wrong with `adb pull`."
+    exit 4
+fi
+adb shell rm "$remote_path"
+printf '\nSKP saved to file:\n    %s\n\n'  "$local_path"
+if [ ! -z "$2" ]; then
+    bridge="_"
+    adb shell "setprop 'debug.hwui.capture_skp_frames' ''"
+    for i in $(seq 2 $2); do
+        adb pull "${remote_path}_${i}" "${local_path_prefix}_${i}.skp"
+        adb shell rm "${remote_path}_${i}"
+    done
+fi
+
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index c4e4195..dab1f89 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -806,7 +806,7 @@
         paint.setTextSize(20);
         paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
-        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
+        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
     });
 
     int count = 0;
@@ -830,7 +830,7 @@
         paint.setTextSize(20);
         paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
         std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO");
-        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::kBidi_Force_LTR, paint, NULL);
+        canvas.drawText(dst.get(), 0, 5, 5, 25, 25, minikin::Bidi::FORCE_LTR, paint, NULL);
     });
     Properties::enableHighContrastText = false;
 
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 4c3e182..d182d78 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -22,6 +22,7 @@
 #include "IContextFactory.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #include "pipeline/skia/SkiaPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 #include "renderthread/CanvasContext.h"
 #include "tests/common/TestUtils.h"
@@ -335,7 +336,7 @@
     EXPECT_EQ(3, canvas.getIndex());
 }
 
-RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
     /* R is backward projected on B and C is a layer.
                 A
                / \
@@ -450,7 +451,8 @@
     LayerUpdateQueue layerUpdateQueue;
     layerUpdateQueue.enqueueLayerWithDamage(child.get(),
             android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
-    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true, false);
+    auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+    pipeline->renderLayersImpl(layerUpdateQueue, true, false);
     EXPECT_EQ(1, drawCounter);  //assert index 0 is drawn on the layer
 
     RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true);
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index b397b15..f430ce6 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -56,6 +56,40 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
+RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) {
+
+    auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
+        [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
+            redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
+        });
+
+    LayerUpdateQueue layerUpdateQueue;
+    SkRect dirty = SkRect::MakeLargest();
+    std::vector<sp<RenderNode>> renderNodes;
+    renderNodes.push_back(redNode);
+    bool opaque = true;
+    android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
+    auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
+    {
+        //add a pointer to a deleted vector drawable object in the pipeline
+        sp<VectorDrawableRoot> dirtyVD(new VectorDrawableRoot(new VectorDrawable::Group()));
+        dirtyVD->mutateProperties()->setScaledSize(5,5);
+        pipeline->getVectorDrawables()->push_back(dirtyVD.get());
+    }
+
+    //pipeline should clean list of dirty vector drawables before prepare tree
+    pipeline->onPrepareTree();
+
+    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
+    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+
+    //drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer
+    pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes,
+            opaque, false, contentDrawBounds, surface);
+    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+}
+
 RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
     auto halfGreenNode = TestUtils::createSkiaNode(0, 0, 2, 2,
         [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
diff --git a/libs/hwui/tests/unit/ThreadBaseTests.cpp b/libs/hwui/tests/unit/ThreadBaseTests.cpp
new file mode 100644
index 0000000..7aad348
--- /dev/null
+++ b/libs/hwui/tests/unit/ThreadBaseTests.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "thread/ThreadBase.h"
+#include "utils/TimeUtils.h"
+
+#include <chrono>
+#include "unistd.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+static ThreadBase& thread() {
+    class TestThread : public ThreadBase, public virtual RefBase {};
+    static sp<TestThread> thread = []() -> auto {
+        sp<TestThread> ret{new TestThread};
+        ret->start("TestThread");
+        return ret;
+    }();
+    return *thread;
+}
+
+static WorkQueue& queue() {
+    return thread().queue();
+}
+
+TEST(ThreadBase, post) {
+    std::atomic_bool ran(false);
+    queue().post([&ran]() {
+        ran = true;
+    });
+    for (int i = 0; !ran && i < 1000; i++) {
+        usleep(1);
+    }
+    ASSERT_TRUE(ran) << "Failed to flip atomic after 1 second";
+}
+
+TEST(ThreadBase, postDelay) {
+    using clock = WorkQueue::clock;
+
+    std::promise<nsecs_t> ranAtPromise;
+    auto queuedAt = clock::now();
+    queue().postDelayed(100_us, [&]() {
+        ranAtPromise.set_value(clock::now());
+    });
+    auto ranAt = ranAtPromise.get_future().get();
+    auto ranAfter = ranAt - queuedAt;
+    ASSERT_TRUE(ranAfter > 90_us) << "Ran after " << ns2us(ranAfter) << "us <= 90us";
+}
+
+TEST(ThreadBase, runSync) {
+    pid_t thisTid = gettid();
+    pid_t otherTid = thisTid;
+
+    auto result = queue().runSync([&otherTid]() -> auto {
+        otherTid = gettid();
+        return 42;
+    });
+
+    ASSERT_EQ(42, result);
+    ASSERT_NE(thisTid, otherTid);
+}
+
+TEST(ThreadBase, async) {
+    pid_t thisTid = gettid();
+    pid_t thisPid = getpid();
+
+    auto otherTid = queue().async([]() -> auto {
+        return gettid();
+    });
+    auto otherPid = queue().async([]() -> auto {
+        return getpid();
+    });
+    auto result = queue().async([]() -> auto {
+        return 42;
+    });
+
+    ASSERT_NE(thisTid, otherTid.get());
+    ASSERT_EQ(thisPid, otherPid.get());
+    ASSERT_EQ(42, result.get());
+}
+
+TEST(ThreadBase, lifecyclePerf) {
+    struct EventCount {
+        std::atomic_int construct{0};
+        std::atomic_int destruct{0};
+        std::atomic_int copy{0};
+        std::atomic_int move{0};
+    };
+
+    struct Counter {
+        Counter(EventCount* count) : mCount(count) {
+            mCount->construct++;
+        }
+
+        Counter(const Counter& other) : mCount(other.mCount) {
+            if (mCount) mCount->copy++;
+        }
+
+        Counter(Counter&& other) : mCount(other.mCount) {
+            other.mCount = nullptr;
+            if (mCount) mCount->move++;
+        }
+
+        Counter& operator=(const Counter& other) {
+            mCount = other.mCount;
+            if (mCount) mCount->copy++;
+            return *this;
+        }
+
+        Counter& operator=(Counter&& other) {
+            mCount = other.mCount;
+            other.mCount = nullptr;
+            if (mCount) mCount->move++;
+            return *this;
+        }
+
+        ~Counter() {
+            if (mCount) mCount->destruct++;
+        }
+
+        EventCount* mCount;
+    };
+
+    EventCount count;
+    {
+        Counter counter{&count};
+        queue().runSync([c = std::move(counter)](){});
+    }
+    ASSERT_EQ(1, count.construct.load());
+    ASSERT_EQ(1, count.destruct.load());
+    ASSERT_EQ(0, count.copy.load());
+    ASSERT_LE(1, count.move.load());
+}
+
+int lifecycleTestHelper(const sp<VirtualLightRefBase>& test) {
+    return queue().runSync([t = test]() -> int {
+        return t->getStrongCount();
+    });
+}
+
+TEST(ThreadBase, lifecycle) {
+    sp<VirtualLightRefBase> dummyObject{new VirtualLightRefBase};
+    ASSERT_EQ(1, dummyObject->getStrongCount());
+    ASSERT_EQ(2, queue().runSync([dummyObject]() -> int {
+        return dummyObject->getStrongCount();
+    }));
+    ASSERT_EQ(1, dummyObject->getStrongCount());
+    ASSERT_EQ(2, lifecycleTestHelper(dummyObject));
+    ASSERT_EQ(1, dummyObject->getStrongCount());
+}
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 345cfd6..1f3eaef 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -52,7 +52,7 @@
             SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
     sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
-    sk_sp<SkTypeface> typeface(fm->createFromStream(fontData.release()));
+    sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
     std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
             std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
@@ -84,159 +84,159 @@
     std::unique_ptr<Typeface> bold(Typeface::createWithDifferentBaseWeight(nullptr, 700));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, bold->fAPIStyle);
 
     std::unique_ptr<Typeface> light(Typeface::createWithDifferentBaseWeight(nullptr, 300));
     EXPECT_EQ(3, light->fStyle.getWeight());
     EXPECT_FALSE(light->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, light->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, light->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromRegular) {
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.NORMAL);
-    std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, SkTypeface::kNormal));
+    std::unique_ptr<Typeface> normal(Typeface::createRelative(nullptr, Typeface::kNormal));
     EXPECT_EQ(4, normal->fStyle.getWeight());
     EXPECT_FALSE(normal->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
-    std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, SkTypeface::kBold));
+    std::unique_ptr<Typeface> bold(Typeface::createRelative(nullptr, Typeface::kBold));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.ITALIC);
-    std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, SkTypeface::kItalic));
+    std::unique_ptr<Typeface> italic(Typeface::createRelative(nullptr, Typeface::kItalic));
     EXPECT_EQ(4, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.DEFAULT, Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface> boldItalic(
-            Typeface::createRelative(nullptr, SkTypeface::kBoldItalic));
+            Typeface::createRelative(nullptr, Typeface::kBoldItalic));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_BoldBase) {
     std::unique_ptr<Typeface> base(Typeface::createWithDifferentBaseWeight(nullptr, 700));
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.NORMAL);
-    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
     EXPECT_EQ(7, normal->fStyle.getWeight());
     EXPECT_FALSE(normal->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.BOLD);
-    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
     EXPECT_EQ(10, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.ITALIC);
-    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
     EXPECT_EQ(7, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-bold"), Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface>
-            boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+            boldItalic(Typeface::createRelative(base.get(), Typeface::kBoldItalic));
     EXPECT_EQ(10, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_LightBase) {
     std::unique_ptr<Typeface> base(Typeface::createWithDifferentBaseWeight(nullptr, 300));
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.NORMAL);
-    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
     EXPECT_EQ(3, normal->fStyle.getWeight());
     EXPECT_FALSE(normal->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.BOLD);
-    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
     EXPECT_EQ(6, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.ITLIC);
-    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
     EXPECT_EQ(3, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create("sans-serif-light"), Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface>
-            boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+            boldItalic(Typeface::createRelative(base.get(), Typeface::kBoldItalic));
     EXPECT_EQ(6, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromBoldStyled) {
-    std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, SkTypeface::kBold));
+    std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, Typeface::kBold));
 
     // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.NORMAL);
-    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
     EXPECT_EQ(4, normal->fStyle.getWeight());
     EXPECT_FALSE(normal->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.BOLD);
-    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.ITALIC);
-    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
     EXPECT_EQ(4, normal->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.BOLD), Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface>
-            boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+            boldItalic(Typeface::createRelative(base.get(), Typeface::kBoldItalic));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromItalicStyled) {
-    std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, SkTypeface::kItalic));
+    std::unique_ptr<Typeface> base(Typeface::createRelative(nullptr, Typeface::kItalic));
 
     // In Java,
     // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.NORMAL);
-    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
     EXPECT_EQ(4, normal->fStyle.getWeight());
     EXPECT_FALSE(normal->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java, Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.BOLD);
-    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
     // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.ITALIC);
-    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
     EXPECT_EQ(4, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // Typeface.create(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC), Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface>
-            boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+            boldItalic(Typeface::createRelative(base.get(), Typeface::kBoldItalic));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createRelativeTest_fromSpecifiedStyled) {
@@ -246,38 +246,38 @@
     // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.NORMAL);
-    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), SkTypeface::kNormal));
+    std::unique_ptr<Typeface> normal(Typeface::createRelative(base.get(), Typeface::kNormal));
     EXPECT_EQ(4, normal->fStyle.getWeight());
     EXPECT_FALSE(normal->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, normal->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, normal->fAPIStyle);
 
     // In Java,
     // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.BOLD);
-    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), SkTypeface::kBold));
+    std::unique_ptr<Typeface> bold(Typeface::createRelative(base.get(), Typeface::kBold));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
     // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.ITALIC);
-    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), SkTypeface::kItalic));
+    std::unique_ptr<Typeface> italic(Typeface::createRelative(base.get(), Typeface::kItalic));
     EXPECT_EQ(4, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // Typeface typeface = new Typeface.Builder(invalid).setFallback("sans-serif")
     //     .setWeight(700).setItalic(false).build();
     // Typeface.create(typeface, Typeface.BOLD_ITALIC);
     std::unique_ptr<Typeface>
-            boldItalic(Typeface::createRelative(base.get(), SkTypeface::kBoldItalic));
+            boldItalic(Typeface::createRelative(base.get(), Typeface::kBoldItalic));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createAbsolute) {
@@ -287,7 +287,7 @@
     std::unique_ptr<Typeface> regular(Typeface::createAbsolute(nullptr, 400, false));
     EXPECT_EQ(4, regular->fStyle.getWeight());
     EXPECT_FALSE(regular->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, regular->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java,
     // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(false)
@@ -295,7 +295,7 @@
     std::unique_ptr<Typeface> bold(Typeface::createAbsolute(nullptr, 700, false));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java,
     // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(400).setItalic(true)
@@ -303,7 +303,7 @@
     std::unique_ptr<Typeface> italic(Typeface::createAbsolute(nullptr, 400, true));
     EXPECT_EQ(4, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(700).setItalic(true)
@@ -311,7 +311,7 @@
     std::unique_ptr<Typeface> boldItalic(Typeface::createAbsolute(nullptr, 700, true));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBoldItalic, boldItalic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBoldItalic, boldItalic->fAPIStyle);
 
     // In Java,
     // new Typeface.Builder(invalid).setFallback("sans-serif").setWeight(1100).setItalic(true)
@@ -319,7 +319,7 @@
     std::unique_ptr<Typeface> over1000(Typeface::createAbsolute(nullptr, 1100, false));
     EXPECT_EQ(10, over1000->fStyle.getWeight());
     EXPECT_FALSE(over1000->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, over1000->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
 TEST(TypefaceTest, createFromFamilies_Single) {
@@ -328,21 +328,21 @@
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoRegular), 400, false));
     EXPECT_EQ(4, regular->fStyle.getWeight());
     EXPECT_FALSE(regular->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, regular->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Bold.ttf").setWeight(700).setItalic(false).build();
     std::unique_ptr<Typeface> bold(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 700, false));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Italic.ttf").setWeight(400).setItalic(true).build();
     std::unique_ptr<Typeface> italic(
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoItalic), 400, true));
     EXPECT_EQ(4, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // new Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(700).setItalic(true).build();
@@ -350,7 +350,7 @@
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBoldItalic), 700, true));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java,
     // new Typeface.Builder("Roboto-BoldItalic.ttf").setWeight(1100).setItalic(false).build();
@@ -358,7 +358,7 @@
             Typeface::createFromFamilies(makeSingleFamlyVector(kRobotoBold), 1100, false));
     EXPECT_EQ(10, over1000->fStyle.getWeight());
     EXPECT_FALSE(over1000->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, over1000->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, over1000->fAPIStyle);
 }
 
 TEST(TypefaceTest, createFromFamilies_Single_resolveByTable) {
@@ -368,7 +368,7 @@
                     RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(4, regular->fStyle.getWeight());
     EXPECT_FALSE(regular->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kNormal, regular->fSkiaStyle);
+    EXPECT_EQ(Typeface::kNormal, regular->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Bold.ttf").build();
     std::unique_ptr<Typeface> bold(
@@ -376,7 +376,7 @@
                     RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(7, bold->fStyle.getWeight());
     EXPECT_FALSE(bold->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kBold, bold->fSkiaStyle);
+    EXPECT_EQ(Typeface::kBold, bold->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-Italic.ttf").build();
     std::unique_ptr<Typeface> italic(
@@ -384,7 +384,7 @@
                     RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(4, italic->fStyle.getWeight());
     EXPECT_TRUE(italic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 
     // In Java, new Typeface.Builder("Roboto-BoldItalic.ttf").build();
     std::unique_ptr<Typeface> boldItalic(
@@ -392,7 +392,7 @@
                     RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
     EXPECT_EQ(7, boldItalic->fStyle.getWeight());
     EXPECT_TRUE(boldItalic->fStyle.getItalic());
-    EXPECT_EQ(SkTypeface::kItalic, italic->fSkiaStyle);
+    EXPECT_EQ(Typeface::kItalic, italic->fAPIStyle);
 }
 
 TEST(TypefaceTest, createFromFamilies_Family) {
diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h
new file mode 100644
index 0000000..402fd1e
--- /dev/null
+++ b/libs/hwui/thread/ThreadBase.h
@@ -0,0 +1,93 @@
+/*
+ * 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 HWUI_THREADBASE_H
+#define HWUI_THREADBASE_H
+
+#include "WorkQueue.h"
+#include "utils/Macros.h"
+
+#include <utils/Looper.h>
+#include <utils/Thread.h>
+
+#include <algorithm>
+
+namespace android::uirenderer {
+
+class ThreadBase : protected Thread {
+    PREVENT_COPY_AND_ASSIGN(ThreadBase);
+
+public:
+    ThreadBase()
+            : mLooper(new Looper(false))
+            , mQueue([this](){ mLooper->wake(); }, mLock)
+    {}
+
+    WorkQueue& queue() { return mQueue; }
+
+    void requestExit() {
+        Thread::requestExit();
+        mLooper->wake();
+    }
+
+    void start(const char* name = "ThreadBase") {
+        Thread::run(name);
+    }
+
+    void join() {
+        Thread::join();
+    }
+
+protected:
+    void waitForWork() {
+        nsecs_t nextWakeup;
+        {
+            std::unique_lock lock{mLock};
+            nextWakeup = mQueue.nextWakeup(lock);
+        }
+        int timeout = -1;
+        if (nextWakeup < std::numeric_limits<nsecs_t>::max()) {
+            timeout = ns2ms(nextWakeup - WorkQueue::clock::now());
+            if (timeout < 0) timeout = 0;
+        }
+        int result = mLooper->pollOnce(timeout);
+        LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
+                "RenderThread Looper POLL_ERROR!");
+    }
+
+    void processQueue() {
+        mQueue.process();
+    }
+
+    virtual bool threadLoop() override {
+        while (!exitPending()) {
+            waitForWork();
+            processQueue();
+        }
+        return false;
+    }
+
+    sp<Looper> mLooper;
+
+private:
+    WorkQueue mQueue;
+    std::mutex mLock;
+};
+
+} // namespace android::uirenderer
+
+
+#endif //HWUI_THREADBASE_H
diff --git a/libs/hwui/thread/WorkQueue.h b/libs/hwui/thread/WorkQueue.h
new file mode 100644
index 0000000..fbb24bb
--- /dev/null
+++ b/libs/hwui/thread/WorkQueue.h
@@ -0,0 +1,148 @@
+/*
+ * 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 HWUI_WORKQUEUE_H
+#define HWUI_WORKQUEUE_H
+
+#include "utils/Macros.h"
+
+#include <log/log.h>
+#include <utils/Timers.h>
+
+#include <condition_variable>
+#include <functional>
+#include <future>
+#include <mutex>
+#include <variant>
+#include <vector>
+
+namespace android::uirenderer {
+
+struct MonotonicClock {
+    static nsecs_t now() { return systemTime(CLOCK_MONOTONIC); }
+};
+
+class WorkQueue {
+    PREVENT_COPY_AND_ASSIGN(WorkQueue);
+public:
+    using clock = MonotonicClock;
+
+private:
+    struct WorkItem {
+        WorkItem() = delete;
+        WorkItem(const WorkItem& other) = delete;
+        WorkItem& operator=(const WorkItem& other) = delete;
+        WorkItem(WorkItem&& other) = default;
+        WorkItem& operator=(WorkItem&& other) = default;
+
+        WorkItem(nsecs_t runAt, std::function<void()>&& work)
+                : runAt(runAt), work(std::move(work)) {}
+
+        nsecs_t runAt;
+        std::function<void()> work;
+    };
+
+public:
+    WorkQueue(std::function<void()>&& wakeFunc, std::mutex& lock)
+            : mWakeFunc(move(wakeFunc))
+            , mLock(lock) {}
+
+    void process() {
+        auto now = clock::now();
+        std::vector<WorkItem> toProcess;
+        {
+            std::unique_lock _lock{mLock};
+            if (mWorkQueue.empty()) return;
+            toProcess = std::move(mWorkQueue);
+            auto moveBack = find_if(std::begin(toProcess), std::end(toProcess),
+                    [&now](WorkItem& item) {
+                        return item.runAt > now;
+                    });
+            if (moveBack != std::end(toProcess)) {
+                mWorkQueue.reserve(std::distance(moveBack, std::end(toProcess)) + 5);
+                std::move(moveBack, std::end(toProcess), std::back_inserter(mWorkQueue));
+                toProcess.erase(moveBack, std::end(toProcess));
+            }
+        }
+        for (auto& item : toProcess) {
+            item.work();
+        }
+    }
+
+    template<class F>
+    void postAt(nsecs_t time, F&& func) {
+        enqueue(WorkItem{time, std::function<void()>(std::forward<F>(func))});
+    }
+
+    template<class F>
+    void postDelayed(nsecs_t delay, F&& func) {
+        enqueue(WorkItem{clock::now() + delay, std::function<void()>(std::forward<F>(func))});
+    }
+
+    template<class F>
+    void post(F&& func) {
+        postAt(0, std::forward<F>(func));
+    }
+
+    template<class F>
+    auto async(F&& func) -> std::future<decltype(func())> {
+        typedef std::packaged_task<decltype(func())()> task_t;
+        auto task = std::make_shared<task_t>(std::forward<F>(func));
+        post([task]() { std::invoke(*task); });
+        return task->get_future();
+    }
+
+    template<class F>
+    auto runSync(F&& func) -> decltype(func()) {
+        std::packaged_task<decltype(func())()> task{std::forward<F>(func)};
+        post([&task]() { std::invoke(task); });
+        return task.get_future().get();
+    };
+
+    nsecs_t nextWakeup(std::unique_lock<std::mutex> &lock) {
+        if (mWorkQueue.empty()) {
+            return std::numeric_limits<nsecs_t>::max();
+        } else {
+            return std::begin(mWorkQueue)->runAt;
+        }
+    }
+
+private:
+    void enqueue(WorkItem&& item) {
+        bool needsWakeup;
+        {
+            std::unique_lock _lock{mLock};
+            auto insertAt = std::find_if(std::begin(mWorkQueue), std::end(mWorkQueue),
+                    [time = item.runAt](WorkItem& item) {
+                        return item.runAt > time;
+                    });
+            needsWakeup = std::begin(mWorkQueue) == insertAt;
+            mWorkQueue.emplace(insertAt, std::move(item));
+        }
+        if (needsWakeup) {
+            mWakeFunc();
+        }
+    }
+
+    std::function<void()> mWakeFunc;
+
+    std::mutex& mLock;
+    std::vector<WorkItem> mWorkQueue;
+};
+
+} // namespace android::uirenderer
+
+#endif //HWUI_WORKQUEUE_H
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 4310706..67682a0 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -24,7 +24,7 @@
         "libcutils",
         "liblog",
         "libutils",
-        "libskia",
+        "libhwui",
         "libgui",
         "libui",
         "libinput",
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index ed31b12..173cd50 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -148,8 +148,9 @@
         }
     }
 
-    // Resize sprites if needed, inside a global transaction.
-    bool haveGlobalTransaction = false;
+    // Resize sprites if needed.
+    SurfaceComposerClient::Transaction t;
+    bool needApplyTransaction = false;
     for (size_t i = 0; i < numSprites; i++) {
         SpriteUpdate& update = updates.editItemAt(i);
 
@@ -158,36 +159,24 @@
             int32_t desiredHeight = update.state.icon.bitmap.height();
             if (update.state.surfaceWidth < desiredWidth
                     || update.state.surfaceHeight < desiredHeight) {
-                if (!haveGlobalTransaction) {
-                    SurfaceComposerClient::openGlobalTransaction();
-                    haveGlobalTransaction = true;
-                }
+                needApplyTransaction = true;
 
-                status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight);
-                if (status) {
-                    ALOGE("Error %d resizing sprite surface from %dx%d to %dx%d",
-                            status, update.state.surfaceWidth, update.state.surfaceHeight,
-                            desiredWidth, desiredHeight);
-                } else {
-                    update.state.surfaceWidth = desiredWidth;
-                    update.state.surfaceHeight = desiredHeight;
-                    update.state.surfaceDrawn = false;
-                    update.surfaceChanged = surfaceChanged = true;
+                t.setSize(update.state.surfaceControl,
+                        desiredWidth, desiredHeight);
+                update.state.surfaceWidth = desiredWidth;
+                update.state.surfaceHeight = desiredHeight;
+                update.state.surfaceDrawn = false;
+                update.surfaceChanged = surfaceChanged = true;
 
-                    if (update.state.surfaceVisible) {
-                        status = update.state.surfaceControl->hide();
-                        if (status) {
-                            ALOGE("Error %d hiding sprite surface after resize.", status);
-                        } else {
-                            update.state.surfaceVisible = false;
-                        }
-                    }
+                if (update.state.surfaceVisible) {
+                    t.hide(update.state.surfaceControl);
+                    update.state.surfaceVisible = false;
                 }
             }
         }
     }
-    if (haveGlobalTransaction) {
-        SurfaceComposerClient::closeGlobalTransaction();
+    if (needApplyTransaction) {
+        t.apply();
     }
 
     // Redraw sprites if needed.
@@ -240,8 +229,7 @@
         }
     }
 
-    // Set sprite surface properties and make them visible.
-    bool haveTransaction = false;
+    needApplyTransaction = false;
     for (size_t i = 0; i < numSprites; i++) {
         SpriteUpdate& update = updates.editItemAt(i);
 
@@ -253,75 +241,59 @@
                 || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
                         | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
                         | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
-            status_t status;
-            if (!haveTransaction) {
-                SurfaceComposerClient::openGlobalTransaction();
-                haveTransaction = true;
-            }
+            needApplyTransaction = true;
 
             if (wantSurfaceVisibleAndDrawn
                     && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
-                status = update.state.surfaceControl->setAlpha(update.state.alpha);
-                if (status) {
-                    ALOGE("Error %d setting sprite surface alpha.", status);
-                }
+                t.setAlpha(update.state.surfaceControl,
+                        update.state.alpha);
             }
 
             if (wantSurfaceVisibleAndDrawn
                     && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
                             | DIRTY_HOTSPOT)))) {
-                status = update.state.surfaceControl->setPosition(
+                t.setPosition(
+                        update.state.surfaceControl,
                         update.state.positionX - update.state.icon.hotSpotX,
                         update.state.positionY - update.state.icon.hotSpotY);
-                if (status) {
-                    ALOGE("Error %d setting sprite surface position.", status);
-                }
             }
 
             if (wantSurfaceVisibleAndDrawn
                     && (becomingVisible
                             || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
-                status = update.state.surfaceControl->setMatrix(
+                t.setMatrix(
+                        update.state.surfaceControl,
                         update.state.transformationMatrix.dsdx,
                         update.state.transformationMatrix.dtdx,
                         update.state.transformationMatrix.dsdy,
                         update.state.transformationMatrix.dtdy);
-                if (status) {
-                    ALOGE("Error %d setting sprite surface transformation matrix.", status);
-                }
             }
 
             int32_t surfaceLayer = mOverlayLayer + update.state.layer;
             if (wantSurfaceVisibleAndDrawn
                     && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
-                status = update.state.surfaceControl->setLayer(surfaceLayer);
-                if (status) {
-                    ALOGE("Error %d setting sprite surface layer.", status);
-                }
+                t.setLayer(update.state.surfaceControl, surfaceLayer);
             }
 
             if (becomingVisible) {
-                status = update.state.surfaceControl->show();
-                if (status) {
-                    ALOGE("Error %d showing sprite surface.", status);
-                } else {
-                    update.state.surfaceVisible = true;
-                    update.surfaceChanged = surfaceChanged = true;
-                }
+                t.show(update.state.surfaceControl);
+
+                update.state.surfaceVisible = true;
+                update.surfaceChanged = surfaceChanged = true;
             } else if (becomingHidden) {
-                status = update.state.surfaceControl->hide();
-                if (status) {
-                    ALOGE("Error %d hiding sprite surface.", status);
-                } else {
-                    update.state.surfaceVisible = false;
-                    update.surfaceChanged = surfaceChanged = true;
-                }
+                t.hide(update.state.surfaceControl);
+
+                update.state.surfaceVisible = false;
+                update.surfaceChanged = surfaceChanged = true;
             }
         }
     }
 
-    if (haveTransaction) {
-        SurfaceComposerClient::closeGlobalTransaction();
+    if (needApplyTransaction) {
+        status_t status = t.apply();
+        if (status) {
+            ALOGE("Error applying Surface transaction");
+        }
     }
 
     // If any surfaces were changed, write back the new surface properties to the sprites.
diff --git a/libs/protoutil/Android.mk b/libs/protoutil/Android.mk
new file mode 100644
index 0000000..2a2b087
--- /dev/null
+++ b/libs/protoutil/Android.mk
@@ -0,0 +1,39 @@
+#
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libprotoutil
+
+LOCAL_CFLAGS := \
+        -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+
+LOCAL_SHARED_LIBRARIES := \
+        libcutils \
+        liblog \
+
+LOCAL_C_INCLUDES := \
+        $(LOCAL_PATH)/include
+
+LOCAL_SRC_FILES := \
+        src/EncodedBuffer.cpp \
+        src/ProtoOutputStream.cpp \
+        src/protobuf.cpp \
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
new file mode 100644
index 0000000..e568e4c
--- /dev/null
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -0,0 +1,227 @@
+/*
+ * 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 ANDROID_UTIL_ENCODED_BUFFER_H
+#define ANDROID_UTIL_ENCODED_BUFFER_H
+
+#include <stdint.h>
+#include <vector>
+
+namespace android {
+namespace util {
+
+using namespace std;
+
+/**
+ * A stream of bytes containing a read pointer and a write pointer,
+ * backed by a set of fixed-size buffers.  There are write functions for the
+ * primitive types stored by protocol buffers, but none of the logic
+ * for tags, inner objects, or any of that.
+ *
+ * Terminology:
+ *      *Pos:       Position in the whole data set (as if it were a single buffer).
+ *      *Index:     Index of a buffer within the mBuffers list.
+ *      *Offset:    Position within a buffer.
+ */
+class EncodedBuffer
+{
+public:
+    EncodedBuffer();
+    EncodedBuffer(size_t chunkSize);
+    ~EncodedBuffer();
+
+    class Pointer {
+    public:
+        Pointer();
+        Pointer(size_t chunkSize);
+
+        size_t pos() const;
+        size_t index() const;
+        size_t offset() const;
+
+        Pointer* move(size_t amt);
+        inline Pointer* move() { return move(1); };
+        Pointer* rewind();
+
+        Pointer copy() const;
+
+    private:
+        size_t mChunkSize;
+        size_t mIndex;
+        size_t mOffset;
+    };
+
+    /******************************** Write APIs ************************************************/
+
+    /**
+     * Returns the number of bytes written in the buffer
+     */
+    size_t size() const;
+
+    /**
+     * Returns the write pointer.
+     */
+    Pointer* wp();
+
+    /**
+     * Returns the current position of write pointer, if the write buffer is full, it will automatically
+     * rotate to a new buffer with given chunkSize. If NULL is returned, it means NO_MEMORY
+     */
+    uint8_t* writeBuffer();
+
+    /**
+     * Returns the writeable size in the current write buffer .
+     */
+    size_t currentToWrite();
+
+    /**
+     * Write a single byte to the buffer.
+     */
+    void writeRawByte(uint8_t val);
+
+    /**
+     * Write a varint32 into the buffer. Return the size of the varint.
+     */
+    size_t writeRawVarint32(uint32_t val);
+
+    /**
+     * Write a varint64 into the buffer. Return the size of the varint.
+     */
+    size_t writeRawVarint64(uint64_t val);
+
+    /**
+     * Write Fixed32 into the buffer.
+     */
+    void writeRawFixed32(uint32_t val);
+
+    /**
+     * Write Fixed64 into the buffer.
+     */
+    void writeRawFixed64(uint64_t val);
+
+    /**
+     * Write a protobuf header. Return the size of the header.
+     */
+    size_t writeHeader(uint32_t fieldId, uint8_t wireType);
+
+    /********************************* Edit APIs ************************************************/
+    /**
+     * Returns the edit pointer.
+     */
+    Pointer* ep();
+
+    /**
+     * Read a single byte at ep, and move ep to next byte;
+     */
+    uint8_t readRawByte();
+
+    /**
+     * Read varint starting at ep, ep will move to pos of next byte.
+     */
+    uint64_t readRawVarint();
+
+    /**
+     * Read 4 bytes starting at ep, ep will move to pos of next byte.
+     */
+    uint32_t readRawFixed32();
+
+    /**
+     * Read 8 bytes starting at ep, ep will move to pos of next byte.
+     */
+    uint64_t readRawFixed64();
+
+    /**
+     * Edit 4 bytes starting at pos.
+     */
+    void editRawFixed32(size_t pos, uint32_t val);
+
+    /**
+     * Copy _size_ bytes of data starting at __srcPos__ to wp.
+     */
+    void copy(size_t srcPos, size_t size);
+
+    /********************************* Read APIs ************************************************/
+    class iterator;
+    friend class iterator;
+    class iterator {
+    public:
+        iterator(const EncodedBuffer& buffer);
+
+        /**
+         * Returns the number of bytes written in the buffer
+         */
+        size_t size() const;
+
+        /**
+         * Returns the size of total bytes read.
+         */
+        size_t bytesRead() const;
+
+        /**
+         * Returns the read pointer.
+         */
+        Pointer* rp();
+
+        /**
+         * Returns the current position of read pointer, if NULL is returned, it reaches end of buffer.
+         */
+        uint8_t const* readBuffer();
+
+        /**
+         * Returns the readable size in the current read buffer.
+         */
+        size_t currentToRead();
+
+        /**
+         * Returns true if next bytes is available for read.
+         */
+        bool hasNext();
+
+        /**
+         * Reads the current byte and moves pointer 1 bit.
+         */
+        uint8_t next();
+
+        /**
+         * Read varint from iterator, the iterator will point to next available byte.
+         */
+        uint64_t readRawVarint();
+
+    private:
+        const EncodedBuffer& mData;
+        Pointer mRp;
+    };
+
+    /**
+     * Returns the iterator of EncodedBuffer so it guarantees consumers won't be able to modified the buffer.
+     */
+    iterator begin() const;
+
+private:
+    size_t mChunkSize;
+    vector<uint8_t*> mBuffers;
+
+    Pointer mWp;
+    Pointer mEp;
+
+    inline uint8_t* at(const Pointer& p) const; // helper function to get value
+};
+
+} // util
+} // android
+
+#endif // ANDROID_UTIL_ENCODED_BUFFER_H
+
diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h
new file mode 100644
index 0000000..b8415b2
--- /dev/null
+++ b/libs/protoutil/include/android/util/ProtoOutputStream.h
@@ -0,0 +1,107 @@
+/*
+ * 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 ANDROID_UTIL_PROTOOUTPUT_STREAM_H
+#define ANDROID_UTIL_PROTOOUTPUT_STREAM_H
+
+#include <android/util/EncodedBuffer.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace android {
+namespace util {
+
+/**
+ * Class to write to a protobuf stream.
+ *
+ * Each write method takes an ID code from the protoc generated classes
+ * and the value to write.  To make a nested object, call start
+ * and then end when you are done.
+ *
+ * See the java version implementation (ProtoOutputStream.java) for more infos.
+ */
+class ProtoOutputStream
+{
+public:
+    ProtoOutputStream();
+    ~ProtoOutputStream();
+
+    /**
+     * Write APIs for dumping protobuf data. Returns true if the write succeeds.
+     */
+    bool write(uint64_t fieldId, double val);
+    bool write(uint64_t fieldId, float val);
+    bool write(uint64_t fieldId, int val);
+    bool write(uint64_t fieldId, long long val);
+    bool write(uint64_t fieldId, bool val);
+    bool write(uint64_t fieldId, std::string val);
+    bool write(uint64_t fieldId, const char* val, size_t size);
+
+    /**
+     * Starts a sub-message write session.
+     * Returns a token of this write session.
+     * Must call end(token) when finish write this sub-message.
+     */
+    long long start(uint64_t fieldId);
+    void end(long long token);
+
+    /**
+     * Flushes the protobuf data out to given fd.
+     */
+    size_t size();
+    EncodedBuffer::iterator data();
+    bool flush(int fd);
+
+    // Please don't use the following functions to dump protos unless you are familiar with protobuf encoding.
+    void writeRawVarint(uint64_t varint);
+    void writeLengthDelimitedHeader(uint32_t id, size_t size);
+    void writeRawByte(uint8_t byte);
+
+private:
+    EncodedBuffer mBuffer;
+    size_t mCopyBegin;
+    bool mCompact;
+    int mDepth;
+    int mObjectId;
+    long long mExpectedObjectToken;
+
+    inline void writeDoubleImpl(uint32_t id, double val);
+    inline void writeFloatImpl(uint32_t id, float val);
+    inline void writeInt64Impl(uint32_t id, long long val);
+    inline void writeInt32Impl(uint32_t id, int val);
+    inline void writeUint64Impl(uint32_t id, uint64_t val);
+    inline void writeUint32Impl(uint32_t id, uint32_t val);
+    inline void writeFixed64Impl(uint32_t id, uint64_t val);
+    inline void writeFixed32Impl(uint32_t id, uint32_t val);
+    inline void writeSFixed64Impl(uint32_t id, long long val);
+    inline void writeSFixed32Impl(uint32_t id, int val);
+    inline void writeZigzagInt64Impl(uint32_t id, long long val);
+    inline void writeZigzagInt32Impl(uint32_t id, int val);
+    inline void writeEnumImpl(uint32_t id, int val);
+    inline void writeBoolImpl(uint32_t id, bool val);
+    inline void writeUtf8StringImpl(uint32_t id, const char* val, size_t size);
+    inline void writeMessageBytesImpl(uint32_t id, const char* val, size_t size);
+
+    bool compact();
+    size_t editEncodedSize(size_t rawSize);
+    bool compactSize(size_t rawSize);
+};
+
+}
+}
+
+#endif // ANDROID_UTIL_PROTOOUTPUT_STREAM_H
diff --git a/libs/protoutil/include/android/util/protobuf.h b/libs/protoutil/include/android/util/protobuf.h
new file mode 100644
index 0000000..ca45e26
--- /dev/null
+++ b/libs/protoutil/include/android/util/protobuf.h
@@ -0,0 +1,65 @@
+/*
+ * 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 ANDROID_UTIL_PROTOBUF_H
+#define ANDROID_UTIL_PROTOBUF_H
+
+#include <stdint.h>
+
+namespace android {
+namespace util {
+
+using namespace std;
+
+const int FIELD_ID_SHIFT = 3;
+const uint8_t WIRE_TYPE_MASK = (1 << FIELD_ID_SHIFT) - 1;
+
+const uint8_t WIRE_TYPE_VARINT = 0;
+const uint8_t WIRE_TYPE_FIXED64 = 1;
+const uint8_t WIRE_TYPE_LENGTH_DELIMITED = 2;
+const uint8_t WIRE_TYPE_FIXED32 = 5;
+
+/**
+ * Read the wire type from varint, it is the smallest 3 bits.
+ */
+uint8_t read_wire_type(uint32_t varint);
+
+/**
+ * Read field id from varint, it is varint >> 3;
+ */
+uint32_t read_field_id(uint32_t varint);
+
+/**
+ * Get the size of a varint.
+ */
+size_t get_varint_size(uint64_t varint);
+
+/**
+ * Write a varint into the buffer. Return the next position to write at.
+ * There must be 10 bytes in the buffer.
+ */
+uint8_t* write_raw_varint(uint8_t* buf, uint64_t val);
+
+/**
+ * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position
+ * to write at. There must be 20 bytes in the buffer.
+ */
+uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size);
+
+} // util
+} // android
+
+#endif  // ANDROID_UTIL_PROTOUBUF_H
diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp
new file mode 100644
index 0000000..435ae88
--- /dev/null
+++ b/libs/protoutil/src/EncodedBuffer.cpp
@@ -0,0 +1,349 @@
+/*
+ * 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 <android/util/EncodedBuffer.h>
+#include <android/util/protobuf.h>
+
+#include <stdlib.h>
+
+namespace android {
+namespace util {
+
+const size_t BUFFER_SIZE = 8 * 1024; // 8 KB
+
+EncodedBuffer::Pointer::Pointer() : Pointer(BUFFER_SIZE)
+{
+}
+
+EncodedBuffer::Pointer::Pointer(size_t chunkSize)
+        :mIndex(0),
+         mOffset(0)
+{
+    mChunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize;
+}
+
+size_t
+EncodedBuffer::Pointer::pos() const
+{
+    return mIndex * mChunkSize + mOffset;
+}
+
+size_t
+EncodedBuffer::Pointer::index() const
+{
+    return mIndex;
+}
+
+size_t
+EncodedBuffer::Pointer::offset() const
+{
+    return mOffset;
+}
+
+EncodedBuffer::Pointer*
+EncodedBuffer::Pointer::move(size_t amt)
+{
+    size_t newOffset = mOffset + amt;
+    mIndex += newOffset / mChunkSize;
+    mOffset = newOffset % mChunkSize;
+    return this;
+}
+
+EncodedBuffer::Pointer*
+EncodedBuffer::Pointer::rewind()
+{
+    mIndex = 0;
+    mOffset = 0;
+    return this;
+}
+
+EncodedBuffer::Pointer
+EncodedBuffer::Pointer::copy() const
+{
+    Pointer p = Pointer(mChunkSize);
+    p.mIndex = mIndex;
+    p.mOffset = mOffset;
+    return p;
+}
+
+// ===========================================================
+EncodedBuffer::EncodedBuffer() : EncodedBuffer(0)
+{
+}
+
+EncodedBuffer::EncodedBuffer(size_t chunkSize)
+        :mBuffers()
+{
+    mChunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize;
+    mWp = Pointer(mChunkSize);
+    mEp = Pointer(mChunkSize);
+}
+
+EncodedBuffer::~EncodedBuffer()
+{
+    for (size_t i=0; i<mBuffers.size(); i++) {
+        uint8_t* buf = mBuffers[i];
+        free(buf);
+    }
+}
+
+inline uint8_t*
+EncodedBuffer::at(const Pointer& p) const
+{
+    return mBuffers[p.index()] + p.offset();
+}
+
+/******************************** Write APIs ************************************************/
+size_t
+EncodedBuffer::size() const
+{
+    return mWp.pos();
+}
+
+EncodedBuffer::Pointer*
+EncodedBuffer::wp()
+{
+    return &mWp;
+}
+
+uint8_t*
+EncodedBuffer::writeBuffer()
+{
+    // This prevents write pointer move too fast than allocating the buffer.
+    if (mWp.index() > mBuffers.size()) return NULL;
+    uint8_t* buf = NULL;
+    if (mWp.index() == mBuffers.size()) {
+        buf = (uint8_t*)malloc(mChunkSize);
+
+        if (buf == NULL) return NULL; // This indicates NO_MEMORY
+
+        mBuffers.push_back(buf);
+    }
+    return at(mWp);
+}
+
+size_t
+EncodedBuffer::currentToWrite()
+{
+    return mChunkSize - mWp.offset();
+}
+
+void
+EncodedBuffer::writeRawByte(uint8_t val)
+{
+    *writeBuffer() = val;
+    mWp.move();
+}
+
+size_t
+EncodedBuffer::writeRawVarint64(uint64_t val)
+{
+    size_t size = 0;
+    while (true) {
+        size++;
+        if ((val & ~0x7F) == 0) {
+            writeRawByte((uint8_t) val);
+            return size;
+        } else {
+            writeRawByte((uint8_t)((val & 0x7F) | 0x80));
+            val >>= 7;
+        }
+    }
+}
+
+size_t
+EncodedBuffer::writeRawVarint32(uint32_t val)
+{
+    uint64_t v =(uint64_t)val;
+    return writeRawVarint64(v);
+}
+
+void
+EncodedBuffer::writeRawFixed32(uint32_t val)
+{
+    writeRawByte((uint8_t) val);
+    writeRawByte((uint8_t) (val>>8));
+    writeRawByte((uint8_t) (val>>16));
+    writeRawByte((uint8_t) (val>>24));
+}
+
+void
+EncodedBuffer::writeRawFixed64(uint64_t val)
+{
+    writeRawByte((uint8_t) val);
+    writeRawByte((uint8_t) (val>>8));
+    writeRawByte((uint8_t) (val>>16));
+    writeRawByte((uint8_t) (val>>24));
+    writeRawByte((uint8_t) (val>>32));
+    writeRawByte((uint8_t) (val>>40));
+    writeRawByte((uint8_t) (val>>48));
+    writeRawByte((uint8_t) (val>>56));
+}
+
+size_t
+EncodedBuffer::writeHeader(uint32_t fieldId, uint8_t wireType)
+{
+    return writeRawVarint32((fieldId << FIELD_ID_SHIFT) | wireType);
+}
+
+/******************************** Edit APIs ************************************************/
+EncodedBuffer::Pointer*
+EncodedBuffer::ep()
+{
+    return &mEp;
+}
+
+uint8_t
+EncodedBuffer::readRawByte()
+{
+    uint8_t val = *at(mEp);
+    mEp.move();
+    return val;
+}
+
+uint64_t
+EncodedBuffer::readRawVarint()
+{
+    uint64_t val = 0, shift = 0;
+    size_t start = mEp.pos();
+    while (true) {
+        uint8_t byte = readRawByte();
+        val += (byte & 0x7F) << shift;
+        if ((byte & 0x80) == 0) break;
+        shift += 7;
+    }
+    return val;
+}
+
+uint32_t
+EncodedBuffer::readRawFixed32()
+{
+    uint32_t val = 0;
+    for (auto i=0; i<32; i+=8) {
+        val += (uint32_t)readRawByte() << i;
+    }
+    return val;
+}
+
+uint64_t
+EncodedBuffer::readRawFixed64()
+{
+    uint64_t val = 0;
+    for (auto i=0; i<64; i+=8) {
+        val += (uint64_t)readRawByte() << i;
+    }
+    return val;
+}
+
+void
+EncodedBuffer::editRawFixed32(size_t pos, uint32_t val)
+{
+    size_t oldPos = mEp.pos();
+    mEp.rewind()->move(pos);
+    for (auto i=0; i<32; i+=8) {
+        *at(mEp) = (uint8_t) (val >> i);
+        mEp.move();
+    }
+    mEp.rewind()->move(oldPos);
+}
+
+void
+EncodedBuffer::copy(size_t srcPos, size_t size)
+{
+    if (size == 0) return;
+    Pointer cp(mChunkSize);
+    cp.move(srcPos);
+
+    while (cp.pos() < srcPos + size) {
+        writeRawByte(*at(cp));
+        cp.move();
+    }
+}
+
+/********************************* Read APIs ************************************************/
+EncodedBuffer::iterator
+EncodedBuffer::begin() const
+{
+    return EncodedBuffer::iterator(*this);
+}
+
+EncodedBuffer::iterator::iterator(const EncodedBuffer& buffer)
+        :mData(buffer),
+         mRp(buffer.mChunkSize)
+{
+}
+
+size_t
+EncodedBuffer::iterator::size() const
+{
+    return mData.size();
+}
+
+size_t
+EncodedBuffer::iterator::bytesRead() const
+{
+    return mRp.pos();
+}
+
+EncodedBuffer::Pointer*
+EncodedBuffer::iterator::rp()
+{
+    return &mRp;
+}
+
+uint8_t const*
+EncodedBuffer::iterator::readBuffer()
+{
+    return hasNext() ? const_cast<uint8_t const*>(mData.at(mRp)) : NULL;
+}
+
+size_t
+EncodedBuffer::iterator::currentToRead()
+{
+    return (mData.mWp.index() > mRp.index()) ?
+            mData.mChunkSize - mRp.offset() :
+            mData.mWp.offset() - mRp.offset();
+}
+
+bool
+EncodedBuffer::iterator::hasNext()
+{
+    return mRp.pos() < mData.mWp.pos();
+}
+
+uint8_t
+EncodedBuffer::iterator::next()
+{
+    uint8_t res = *(mData.at(mRp));
+    mRp.move();
+    return res;
+}
+
+uint64_t
+EncodedBuffer::iterator::readRawVarint()
+{
+    uint64_t val = 0, shift = 0;
+    while (true) {
+        uint8_t byte = next();
+        val += (byte & 0x7F) << shift;
+        if ((byte & 0x80) == 0) break;
+        shift += 7;
+    }
+    return val;
+}
+
+} // util
+} // android
diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp
new file mode 100644
index 0000000..b91e3db
--- /dev/null
+++ b/libs/protoutil/src/ProtoOutputStream.cpp
@@ -0,0 +1,697 @@
+/*
+ * 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 "libprotoutil"
+
+#include <android/util/protobuf.h>
+#include <android/util/ProtoOutputStream.h>
+#include <cutils/log.h>
+#include <cstring>
+
+namespace android {
+namespace util {
+
+/**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+/**
+ * Mask for the field types stored in a fieldId.  Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+const uint64_t FIELD_TYPE_MASK = 0x0ffULL << FIELD_TYPE_SHIFT;
+
+const uint64_t FIELD_TYPE_UNKNOWN  = 0;
+const uint64_t TYPE_DOUBLE         = 1ULL << FIELD_TYPE_SHIFT;   // double, exactly eight bytes on the wire.
+const uint64_t TYPE_FLOAT          = 2ULL << FIELD_TYPE_SHIFT;   // float, exactly four bytes on the wire.
+const uint64_t TYPE_INT64          = 3ULL << FIELD_TYPE_SHIFT;   // int64, varint on the wire.  Negative numbers
+                                                                 // take 10 bytes.  Use TYPE_SINT64 if negative
+                                                                 // values are likely.
+const uint64_t TYPE_UINT64         = 4ULL << FIELD_TYPE_SHIFT;   // uint64, varint on the wire.
+const uint64_t TYPE_INT32          = 5ULL << FIELD_TYPE_SHIFT;   // int32, varint on the wire.  Negative numbers
+                                                                 // take 10 bytes.  Use TYPE_SINT32 if negative
+                                                                 // values are likely.
+const uint64_t TYPE_FIXED64        = 6ULL << FIELD_TYPE_SHIFT;   // uint64, exactly eight bytes on the wire.
+const uint64_t TYPE_FIXED32        = 7ULL << FIELD_TYPE_SHIFT;   // uint32, exactly four bytes on the wire.
+const uint64_t TYPE_BOOL           = 8ULL << FIELD_TYPE_SHIFT;   // bool, varint on the wire.
+const uint64_t TYPE_STRING         = 9ULL << FIELD_TYPE_SHIFT;   // UTF-8 text.
+const uint64_t TYPE_GROUP          = 10ULL << FIELD_TYPE_SHIFT;  // Tag-delimited message.  Deprecated.
+const uint64_t TYPE_MESSAGE        = 11ULL << FIELD_TYPE_SHIFT;  // Length-delimited message.
+
+const uint64_t TYPE_BYTES          = 12ULL << FIELD_TYPE_SHIFT;  // Arbitrary byte array.
+const uint64_t TYPE_UINT32         = 13ULL << FIELD_TYPE_SHIFT;  // uint32, varint on the wire
+const uint64_t TYPE_ENUM           = 14ULL << FIELD_TYPE_SHIFT;  // Enum, varint on the wire
+const uint64_t TYPE_SFIXED32       = 15ULL << FIELD_TYPE_SHIFT;  // int32, exactly four bytes on the wire
+const uint64_t TYPE_SFIXED64       = 16ULL << FIELD_TYPE_SHIFT;  // int64, exactly eight bytes on the wire
+const uint64_t TYPE_SINT32         = 17ULL << FIELD_TYPE_SHIFT;  // int32, ZigZag-encoded varint on the wire
+const uint64_t TYPE_SINT64         = 18ULL << FIELD_TYPE_SHIFT;  // int64, ZigZag-encoded varint on the wire
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
+
+ProtoOutputStream::ProtoOutputStream()
+        :mBuffer(),
+         mCopyBegin(0),
+         mCompact(false),
+         mDepth(0),
+         mObjectId(0),
+         mExpectedObjectToken(0LL)
+{
+}
+
+ProtoOutputStream::~ProtoOutputStream()
+{
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, double val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        default:
+            ALOGW("Field type %d is not supported when writing double val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+    return true;
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, float val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        default:
+            ALOGW("Field type %d is not supported when writing float val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+    return true;
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, int val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        default:
+            ALOGW("Field type %d is not supported when writing int val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+    return true;
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, long long val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_DOUBLE:   writeDoubleImpl(id, (double)val);           break;
+        case TYPE_FLOAT:    writeFloatImpl(id, (float)val);             break;
+        case TYPE_INT64:    writeInt64Impl(id, (long long)val);         break;
+        case TYPE_UINT64:   writeUint64Impl(id, (uint64_t)val);         break;
+        case TYPE_INT32:    writeInt32Impl(id, (int)val);               break;
+        case TYPE_FIXED64:  writeFixed64Impl(id, (uint64_t)val);        break;
+        case TYPE_FIXED32:  writeFixed32Impl(id, (uint32_t)val);        break;
+        case TYPE_UINT32:   writeUint32Impl(id, (uint32_t)val);         break;
+        case TYPE_SFIXED32: writeSFixed32Impl(id, (int)val);            break;
+        case TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val);      break;
+        case TYPE_SINT32:   writeZigzagInt32Impl(id, (int)val);         break;
+        case TYPE_SINT64:   writeZigzagInt64Impl(id, (long long)val);   break;
+        case TYPE_ENUM:     writeEnumImpl(id, (int)val);                break;
+        case TYPE_BOOL:     writeBoolImpl(id, val != 0);                break;
+        default:
+            ALOGW("Field type %d is not supported when writing long long val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+    return true;
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, bool val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_BOOL:
+            writeBoolImpl(id, val);
+            return true;
+        default:
+            ALOGW("Field type %d is not supported when writing bool val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, string val)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_STRING:
+            writeUtf8StringImpl(id, val.c_str(), val.size());
+            return true;
+        default:
+            ALOGW("Field type %d is not supported when writing string val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+}
+
+bool
+ProtoOutputStream::write(uint64_t fieldId, const char* val, size_t size)
+{
+    if (mCompact) return false;
+    const uint32_t id = (uint32_t)fieldId;
+    switch (fieldId & FIELD_TYPE_MASK) {
+        case TYPE_STRING:
+        case TYPE_BYTES:
+            writeUtf8StringImpl(id, val, size);
+            return true;
+        case TYPE_MESSAGE:
+            // can directly write valid format of message bytes into ProtoOutputStream without calling start/end
+            writeMessageBytesImpl(id, val, size);
+            return true;
+        default:
+            ALOGW("Field type %d is not supported when writing char[] val.",
+                    (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT));
+            return false;
+    }
+}
+
+/**
+ * Make a token.
+ *  Bits 61-63 - tag size (So we can go backwards later if the object had not data)
+ *                - 3 bits, max value 7, max value needed 5
+ *  Bit  60    - true if the object is repeated
+ *  Bits 59-51 - depth (For error checking)
+ *                - 9 bits, max value 512, when checking, value is masked (if we really
+ *                  are more than 512 levels deep)
+ *  Bits 32-50 - objectId (For error checking)
+ *                - 19 bits, max value 524,288. that's a lot of objects. IDs will wrap
+ *                  because of the overflow, and only the tokens are compared.
+ *  Bits  0-31 - offset of the first size field in the buffer.
+ */
+long long
+makeToken(int tagSize, bool repeated, int depth, int objectId, int sizePos) {
+    return ((0x07L & (long long)tagSize) << 61)
+            | (repeated ? (1LL << 60) : 0)
+            | (0x01ffL & (long long)depth) << 51
+            | (0x07ffffL & (long long)objectId) << 32
+            | (0x0ffffffffL & (long long)sizePos);
+}
+
+/**
+ * Get the encoded tag size from the token.
+ */
+static int getTagSizeFromToken(long long token) {
+    return (int)(0x7 & (token >> 61));
+}
+
+/**
+ * Get the nesting depth of startObject calls from the token.
+ */
+static int getDepthFromToken(long long token) {
+    return (int)(0x01ff & (token >> 51));
+}
+
+/**
+ * Get the location of the childRawSize (the first 32 bit size field) in this object.
+ */
+static int getSizePosFromToken(long long token) {
+    return (int)token;
+}
+
+long long
+ProtoOutputStream::start(uint64_t fieldId)
+{
+    if ((fieldId & FIELD_TYPE_MASK) != TYPE_MESSAGE) {
+        ALOGE("Can't call start for non-message type field: 0x%llx", (long long)fieldId);
+        return 0;
+    }
+
+    uint32_t id = (uint32_t)fieldId;
+    mBuffer.writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED);
+
+    size_t sizePos = mBuffer.wp()->pos();
+
+    mDepth++;
+    mObjectId++;
+    mBuffer.writeRawFixed64(mExpectedObjectToken); // push previous token into stack.
+
+    mExpectedObjectToken = makeToken(get_varint_size(id),
+        (bool)(fieldId & FIELD_COUNT_REPEATED), mDepth, mObjectId, sizePos);
+    return mExpectedObjectToken;
+}
+
+void
+ProtoOutputStream::end(long long token)
+{
+    if (token != mExpectedObjectToken) {
+        ALOGE("Unexpected token: 0x%llx, should be 0x%llx", token, mExpectedObjectToken);
+        return;
+    }
+
+    int depth = getDepthFromToken(token);
+    if (depth != (mDepth & 0x01ff)) {
+        ALOGE("Unexpected depth: %d, should be %d", depth, mDepth);
+        return;
+    }
+    mDepth--;
+
+    int sizePos = getSizePosFromToken(token);
+    // number of bytes written in this start-end session.
+    int childRawSize = mBuffer.wp()->pos() - sizePos - 8;
+
+    // retrieve the old token from stack.
+    mBuffer.ep()->rewind()->move(sizePos);
+    mExpectedObjectToken = mBuffer.readRawFixed64();
+
+    // If raw size is larger than 0, write the negative value here to indicate a compact is needed.
+    if (childRawSize > 0) {
+        mBuffer.editRawFixed32(sizePos, -childRawSize);
+        mBuffer.editRawFixed32(sizePos+4, -1);
+    } else {
+        // reset wp which erase the header tag of the message when its size is 0.
+        mBuffer.wp()->rewind()->move(sizePos - getTagSizeFromToken(token));
+    }
+}
+
+bool
+ProtoOutputStream::compact() {
+    if (mCompact) return true;
+    if (mDepth != 0) {
+        ALOGE("Can't compact when depth(%d) is not zero. Missing calls to end.", mDepth);
+        return false;
+    }
+    // record the size of the original buffer.
+    size_t rawBufferSize = mBuffer.size();
+    if (rawBufferSize == 0) return true; // nothing to do if the buffer is empty;
+
+    // reset edit pointer and recursively compute encoded size of messages.
+    mBuffer.ep()->rewind();
+    if (editEncodedSize(rawBufferSize) == 0) {
+        ALOGE("Failed to editEncodedSize.");
+        return false;
+    }
+
+    // reset both edit pointer and write pointer, and compact recursively.
+    mBuffer.ep()->rewind();
+    mBuffer.wp()->rewind();
+    if (!compactSize(rawBufferSize)) {
+        ALOGE("Failed to compactSize.");
+        return false;
+    }
+    // copy the reset to the buffer.
+    if (mCopyBegin < rawBufferSize) {
+        mBuffer.copy(mCopyBegin, rawBufferSize - mCopyBegin);
+    }
+
+    // mark true means it is not legal to write to this ProtoOutputStream anymore
+    mCompact = true;
+    return true;
+}
+
+/**
+ * First compaction pass.  Iterate through the data, and fill in the
+ * nested object sizes so the next pass can compact them.
+ */
+size_t
+ProtoOutputStream::editEncodedSize(size_t rawSize)
+{
+    size_t objectStart = mBuffer.ep()->pos();
+    size_t objectEnd = objectStart + rawSize;
+    size_t encodedSize = 0;
+    int childRawSize, childEncodedSize;
+    size_t childEncodedSizePos;
+
+    while (mBuffer.ep()->pos() < objectEnd) {
+        uint32_t tag = (uint32_t)mBuffer.readRawVarint();
+        encodedSize += get_varint_size(tag);
+        switch (read_wire_type(tag)) {
+            case WIRE_TYPE_VARINT:
+                do {
+                    encodedSize++;
+                } while ((mBuffer.readRawByte() & 0x80) != 0);
+                break;
+            case WIRE_TYPE_FIXED64:
+                encodedSize += 8;
+                mBuffer.ep()->move(8);
+                break;
+            case WIRE_TYPE_LENGTH_DELIMITED:
+                childRawSize = (int)mBuffer.readRawFixed32();
+                childEncodedSizePos = mBuffer.ep()->pos();
+                childEncodedSize = (int)mBuffer.readRawFixed32();
+                if (childRawSize >= 0 && childRawSize == childEncodedSize) {
+                    mBuffer.ep()->move(childRawSize);
+                } else if (childRawSize < 0 && childEncodedSize == -1){
+                    childEncodedSize = editEncodedSize(-childRawSize);
+                    mBuffer.editRawFixed32(childEncodedSizePos, childEncodedSize);
+                } else {
+                    ALOGE("Bad raw or encoded values: raw=%d, encoded=%d at %zu",
+                            childRawSize, childEncodedSize, childEncodedSizePos);
+                    return 0;
+                }
+                encodedSize += get_varint_size(childEncodedSize) + childEncodedSize;
+                break;
+            case WIRE_TYPE_FIXED32:
+                encodedSize += 4;
+                mBuffer.ep()->move(4);
+                break;
+            default:
+                ALOGE("Unexpected wire type %d in editEncodedSize at [%zu, %zu]",
+                        read_wire_type(tag), objectStart, objectEnd);
+                return 0;
+        }
+    }
+    return encodedSize;
+}
+
+/**
+ * Second compaction pass.  Iterate through the data, and copy the data
+ * forward in the buffer, converting the pairs of uint32s into a single
+ * unsigned varint of the size.
+ */
+bool
+ProtoOutputStream::compactSize(size_t rawSize)
+{
+    size_t objectStart = mBuffer.ep()->pos();
+    size_t objectEnd = objectStart + rawSize;
+    int childRawSize, childEncodedSize;
+
+    while (mBuffer.ep()->pos() < objectEnd) {
+        uint32_t tag = (uint32_t)mBuffer.readRawVarint();
+        switch (read_wire_type(tag)) {
+            case WIRE_TYPE_VARINT:
+                while ((mBuffer.readRawByte() & 0x80) != 0) {}
+                break;
+            case WIRE_TYPE_FIXED64:
+                mBuffer.ep()->move(8);
+                break;
+            case WIRE_TYPE_LENGTH_DELIMITED:
+                mBuffer.copy(mCopyBegin, mBuffer.ep()->pos() - mCopyBegin);
+
+                childRawSize = (int)mBuffer.readRawFixed32();
+                childEncodedSize = (int)mBuffer.readRawFixed32();
+                mCopyBegin = mBuffer.ep()->pos();
+
+                // write encoded size to buffer.
+                mBuffer.writeRawVarint32(childEncodedSize);
+                if (childRawSize >= 0 && childRawSize == childEncodedSize) {
+                    mBuffer.ep()->move(childEncodedSize);
+                } else if (childRawSize < 0){
+                    if (!compactSize(-childRawSize)) return false;
+                } else {
+                    ALOGE("Bad raw or encoded values: raw=%d, encoded=%d",
+                            childRawSize, childEncodedSize);
+                    return false;
+                }
+                break;
+            case WIRE_TYPE_FIXED32:
+                mBuffer.ep()->move(4);
+                break;
+            default:
+                ALOGE("Unexpected wire type %d in compactSize at [%zu, %zu]",
+                        read_wire_type(tag), objectStart, objectEnd);
+                return false;
+        }
+    }
+    return true;
+}
+
+size_t
+ProtoOutputStream::size()
+{
+    compact();
+    return mBuffer.size();
+}
+
+static bool write_all(int fd, uint8_t const* buf, size_t size)
+{
+    while (size > 0) {
+        ssize_t amt = ::write(fd, buf, size);
+        if (amt < 0) {
+            return false;
+        }
+        size -= amt;
+        buf += amt;
+    }
+    return true;
+}
+
+bool
+ProtoOutputStream::flush(int fd)
+{
+    if (fd < 0) return false;
+    if (!compact()) return false;
+
+    EncodedBuffer::iterator it = mBuffer.begin();
+    while (it.readBuffer() != NULL) {
+        if (!write_all(fd, it.readBuffer(), it.currentToRead())) return false;
+        it.rp()->move(it.currentToRead());
+    }
+    return true;
+}
+
+EncodedBuffer::iterator
+ProtoOutputStream::data()
+{
+    compact();
+    return mBuffer.begin();
+}
+
+void
+ProtoOutputStream::writeRawVarint(uint64_t varint)
+{
+    mBuffer.writeRawVarint64(varint);
+}
+
+void
+ProtoOutputStream::writeLengthDelimitedHeader(uint32_t id, size_t size)
+{
+    mBuffer.writeHeader(id, WIRE_TYPE_LENGTH_DELIMITED);
+    // reserves 64 bits for length delimited fields, if first field is negative, compact it.
+    mBuffer.writeRawFixed32(size);
+    mBuffer.writeRawFixed32(size);
+}
+
+void
+ProtoOutputStream::writeRawByte(uint8_t byte)
+{
+    mBuffer.writeRawByte(byte);
+}
+
+
+// =========================================================================
+// Private functions
+
+/**
+ * bit_cast
+ */
+template <class From, class To>
+inline To bit_cast(From const &from) {
+    To to;
+    memcpy(&to, &from, sizeof(to));
+    return to;
+}
+
+inline void
+ProtoOutputStream::writeDoubleImpl(uint32_t id, double val)
+{
+    if (val == 0.0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
+    mBuffer.writeRawFixed64(bit_cast<double, uint64_t>(val));
+}
+
+inline void
+ProtoOutputStream::writeFloatImpl(uint32_t id, float val)
+{
+    if (val == 0.0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
+    mBuffer.writeRawFixed32(bit_cast<float, uint32_t>(val));
+}
+
+inline void
+ProtoOutputStream::writeInt64Impl(uint32_t id, long long val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint64((uint64_t)val);
+}
+
+inline void
+ProtoOutputStream::writeInt32Impl(uint32_t id, int val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint32((uint32_t)val);
+}
+
+inline void
+ProtoOutputStream::writeUint64Impl(uint32_t id, uint64_t val)
+{
+   if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint64(val);
+}
+
+inline void
+ProtoOutputStream::writeUint32Impl(uint32_t id, uint32_t val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint32(val);
+}
+
+inline void
+ProtoOutputStream::writeFixed64Impl(uint32_t id, uint64_t val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
+    mBuffer.writeRawFixed64(val);
+}
+
+inline void
+ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
+    mBuffer.writeRawFixed32(val);
+}
+
+inline void
+ProtoOutputStream::writeSFixed64Impl(uint32_t id, long long val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_FIXED64);
+    mBuffer.writeRawFixed64((uint64_t)val);
+}
+
+inline void
+ProtoOutputStream::writeSFixed32Impl(uint32_t id, int val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_FIXED32);
+    mBuffer.writeRawFixed32((uint32_t)val);
+}
+
+inline void
+ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, long long val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint64((val << 1) ^ (val >> 63));
+}
+
+inline void
+ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int val)
+{
+    if (val == 0) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint32((val << 1) ^ (val >> 31));
+}
+
+inline void
+ProtoOutputStream::writeEnumImpl(uint32_t id, int val)
+{
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint32((uint32_t) val);
+}
+
+inline void
+ProtoOutputStream::writeBoolImpl(uint32_t id, bool val)
+{
+    if (!val) return;
+    mBuffer.writeHeader(id, WIRE_TYPE_VARINT);
+    mBuffer.writeRawVarint32(val ? 1 : 0);
+}
+
+inline void
+ProtoOutputStream::writeUtf8StringImpl(uint32_t id, const char* val, size_t size)
+{
+    if (val == NULL || size == 0) return;
+    writeLengthDelimitedHeader(id, size);
+    for (size_t i=0; i<size; i++) {
+        mBuffer.writeRawByte((uint8_t)val[i]);
+    }
+}
+
+inline void
+ProtoOutputStream::writeMessageBytesImpl(uint32_t id, const char* val, size_t size)
+{
+    if (val == NULL) return;
+    writeLengthDelimitedHeader(id, size);
+    for (size_t i=0; i<size; i++) {
+        mBuffer.writeRawByte(val[i]);
+    }
+}
+
+} // util
+} // android
+
diff --git a/libs/protoutil/src/protobuf.cpp b/libs/protoutil/src/protobuf.cpp
new file mode 100644
index 0000000..1c7eef9
--- /dev/null
+++ b/libs/protoutil/src/protobuf.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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 <android/util/protobuf.h>
+
+namespace android {
+namespace util {
+
+uint8_t
+read_wire_type(uint32_t varint)
+{
+    return (uint8_t) (varint & WIRE_TYPE_MASK);
+}
+
+uint32_t
+read_field_id(uint32_t varint)
+{
+    return varint >> FIELD_ID_SHIFT;
+}
+
+size_t
+get_varint_size(uint64_t varint)
+{
+    size_t size = 1;
+    while ((varint & ~0x7F)) {
+        size++;
+        varint >>= 7;
+    }
+    return size;
+}
+
+uint8_t*
+write_raw_varint(uint8_t* buf, uint64_t val)
+{
+    uint8_t* p = buf;
+    while (true) {
+        if ((val & ~0x7F) == 0) {
+            *p++ = (uint8_t)val;
+            return p;
+        } else {
+            *p++ = (uint8_t)((val & 0x7F) | 0x80);
+            val >>= 7;
+        }
+    }
+}
+
+uint8_t*
+write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
+{
+    buf = write_raw_varint(buf, (fieldId << FIELD_ID_SHIFT) | WIRE_TYPE_LENGTH_DELIMITED);
+    buf = write_raw_varint(buf, size);
+    return buf;
+}
+
+} // util
+} // android
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
index cbfd4b3..d72059a 100644
--- a/libs/services/Android.mk
+++ b/libs/services/Android.mk
@@ -21,7 +21,8 @@
 LOCAL_MODULE := libservices
 LOCAL_SRC_FILES := \
     ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
-    src/os/DropBoxManager.cpp
+    src/os/DropBoxManager.cpp \
+    src/os/StatsLogEventWrapper.cpp
 
 LOCAL_AIDL_INCLUDES := \
     $(LOCAL_PATH)/../../core/java
diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h
new file mode 100644
index 0000000..255619c
--- /dev/null
+++ b/libs/services/include/android/os/StatsLogEventWrapper.h
@@ -0,0 +1,47 @@
+/*
+ * 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 STATS_LOG_EVENT_WRAPPER_H
+#define STATS_LOG_EVENT_WRAPPER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+namespace android {
+namespace os {
+
+// Represents a parcelable object. Only used to send data from Android OS to statsd.
+class StatsLogEventWrapper : public android::Parcelable {
+ public:
+  StatsLogEventWrapper();
+
+  StatsLogEventWrapper(StatsLogEventWrapper&& in) = default;
+
+  android::status_t writeToParcel(android::Parcel* out) const;
+
+  android::status_t readFromParcel(const android::Parcel* in);
+
+  // These are public for ease of conversion.
+  std::vector<uint8_t> bytes;
+};
+} // Namespace os
+} // Namespace android
+
+
+#endif  // STATS_LOG_EVENT_WRAPPER_H
+
diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
new file mode 100644
index 0000000..8b3aa9a
--- /dev/null
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 <android/os/StatsLogEventWrapper.h>
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+using android::Parcel;
+using android::Parcelable;
+using android::status_t;
+using std::vector;
+
+namespace android {
+namespace os {
+
+StatsLogEventWrapper::StatsLogEventWrapper(){};
+
+status_t StatsLogEventWrapper::writeToParcel(Parcel* out) const {
+    out->writeByteVector(bytes);
+    return ::android::NO_ERROR;
+};
+
+status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) {
+    in->readByteVector(&bytes);
+    return ::android::NO_ERROR;
+};
+
+} // Namespace os
+} // Namespace android
diff --git a/location/java/android/location/GnssClock.java b/location/java/android/location/GnssClock.java
index 25d247d..671c57c 100644
--- a/location/java/android/location/GnssClock.java
+++ b/location/java/android/location/GnssClock.java
@@ -332,6 +332,9 @@
     /**
      * Gets the clock's Drift in nanoseconds per second.
      *
+     * <p>This value is the instantaneous time-derivative of the value provided by
+     * {@link #getBiasNanos()}.
+     *
      * <p>A positive value indicates that the frequency is higher than the nominal (e.g. GPS master
      * clock) frequency. The error estimate for this reported drift is
      * {@link #getDriftUncertaintyNanosPerSecond()}.
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index d24a477..412cc29 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -612,6 +612,16 @@
      *
      * <pre>
      *          accumulated delta range = -k * carrier phase    (where k is a constant)</pre>
+     *
+     * <p>Similar to the concept of an RTCM "Phaserange", when the accumulated delta range is
+     * initially chosen, and whenever it is reset, it will retain the integer nature
+     * of the relative carrier phase offset between satellites observed by this receiver, such that
+     * the double difference of this value between receivers and satellites may be used, together
+     * with integer ambiguity resolution, to determine highly precise relative location between
+     * receivers.
+     *
+     * <p>This includes ensuring that all half-cycle ambiguities are resolved before this value is
+     * reported as {@link #ADR_STATE_VALID}.
      */
     public double getAccumulatedDeltaRangeMeters() {
         return mAccumulatedDeltaRangeMeters;
@@ -861,7 +871,7 @@
     }
 
     /**
-     * Gets the Signal-to-Noise ratio (SNR) in dB.
+     * Gets the (post-correlation & integration) Signal-to-Noise ratio (SNR) in dB.
      *
      * <p>The value is only available if {@link #hasSnrInDb()} is {@code true}.
      */
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index e8eaa59..e7f903e 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -813,15 +813,16 @@
     /**
      * Get the estimated vertical accuracy of this location, in meters.
      *
-     * <p>We define vertical accuracy as the radius of 68% confidence. In other
-     * words, if you draw a circle centered at this location's altitude, and with a radius
-     * equal to the vertical accuracy, then there is a 68% probability that the true altitude is
-     * inside the circle.
+     * <p>We define vertical accuracy at 68% confidence.  Specifically, as 1-side of the
+     * 2-sided range above and below the estimated altitude reported by {@link #getAltitude()},
+     * within which there is a 68% probability of finding the true altitude.
      *
-     * <p>In statistical terms, it is assumed that location errors
-     * are random with a normal distribution, so the 68% confidence circle
-     * represents one standard deviation. Note that in practice, location
-     * errors do not always follow such a simple distribution.
+     * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be
+     * considered 1 standard deviation.
+     *
+     * <p>For example, if {@link #getAltitude()} returns 150, and
+     * {@link #getVerticalAccuracyMeters()} ()} returns 20 then there is a 68% probability
+     * of the true altitude being between 130 and 170 meters.
      *
      * <p>If this location does not have a vertical accuracy, then 0.0 is returned.
      */
@@ -866,14 +867,16 @@
     /**
      * Get the estimated speed accuracy of this location, in meters per second.
      *
-     * <p>We define speed accuracy as a 1-standard-deviation value, i.e. as 1-side of the
-     * 2-sided range above and below the estimated
-     * speed reported by {@link #getSpeed()}, within which there is a 68% probability of
-     * finding the true speed.
+     * <p>We define speed accuracy at 68% confidence.  Specifically, as 1-side of the
+     * 2-sided range above and below the estimated speed reported by {@link #getSpeed()},
+     * within which there is a 68% probability of finding the true speed.
      *
-     * <p>For example, if {@link #getSpeed()} returns 5.0, and
-     * {@link #getSpeedAccuracyMetersPerSecond()} returns 1.0, then there is a 68% probably of the
-     * true speed being between 4.0 and 6.0 meters per second.
+     * <p>In the case where the underlying
+     * distribution is assumed Gaussian normal, this would be considered 1 standard deviation.
+     *
+     * <p>For example, if {@link #getSpeed()} returns 5, and
+     * {@link #getSpeedAccuracyMetersPerSecond()} returns 1, then there is a 68% probability of
+     * the true speed being between 4 and 6 meters per second.
      *
      * <p>Note that the speed and speed accuracy is often better than would be obtained simply from
      * differencing sequential positions, such as when the Doppler measurements from GNSS satellites
@@ -922,13 +925,16 @@
     /**
      * Get the estimated bearing accuracy of this location, in degrees.
      *
-     * <p>We define bearing accuracy as a 1-standard-deviation value, i.e. as 1-side of the
+     * <p>We define bearing accuracy at 68% confidence.  Specifically, as 1-side of the
      * 2-sided range on each side of the estimated bearing reported by {@link #getBearing()},
      * within which there is a 68% probability of finding the true bearing.
      *
-     * <p>For example, if {@link #getBearing()} returns 60., and
-     * {@link #getBearingAccuracyDegrees()} ()} returns 10., then there is a 68% probably of the
-     * true bearing being between 50. and 70. degrees.
+     * <p>In the case where the underlying distribution is assumed Gaussian normal, this would be
+     * considered 1 standard deviation.
+     *
+     * <p>For example, if {@link #getBearing()} returns 60, and
+     * {@link #getBearingAccuracyDegrees()} ()} returns 10, then there is a 68% probability of the
+     * true bearing being between 50 and 70 degrees.
      *
      * <p>If this location does not have a bearing accuracy, then 0.0 is returned.
      */
diff --git a/media/java/android/media/AmrInputStream.java b/media/java/android/media/AmrInputStream.java
index fb91bbb..efaf224 100644
--- a/media/java/android/media/AmrInputStream.java
+++ b/media/java/android/media/AmrInputStream.java
@@ -25,12 +25,12 @@
 
 
 /**
- * AmrInputStream
+ * DO NOT USE
  * @hide
  */
 public final class AmrInputStream extends InputStream {
     private final static String TAG = "AmrInputStream";
-    
+
     // frame is 20 msec at 8.000 khz
     private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;
 
@@ -51,10 +51,10 @@
     private byte[] mOneByte = new byte[1];
 
     /**
-     * Create a new AmrInputStream, which converts 16 bit PCM to AMR
-     * @param inputStream InputStream containing 16 bit PCM.
+     * DO NOT USE - use MediaCodec instead
      */
     public AmrInputStream(InputStream inputStream) {
+        Log.w(TAG, "@@@@ AmrInputStream is not a public API @@@@");
         mInputStream = inputStream;
 
         MediaFormat format  = new MediaFormat();
@@ -83,17 +83,26 @@
         mInfo = new BufferInfo();
     }
 
+    /**
+     * DO NOT USE
+     */
     @Override
     public int read() throws IOException {
         int rtn = read(mOneByte, 0, 1);
         return rtn == 1 ? (0xff & mOneByte[0]) : -1;
     }
 
+    /**
+     * DO NOT USE
+     */
     @Override
     public int read(byte[] b) throws IOException {
         return read(b, 0, b.length);
     }
 
+    /**
+     * DO NOT USE
+     */
     @Override
     public int read(byte[] b, int offset, int length) throws IOException {
         if (mCodec == null) {
@@ -131,19 +140,15 @@
                 }
             }
 
-            // now read encoded data from the encoder (blocking, since we just filled up the
-            // encoder's input with data it should be able to output at least one buffer)
-            while (true) {
-                int index = mCodec.dequeueOutputBuffer(mInfo, -1);
-                if (index >= 0) {
-                    mBufIn = mInfo.size;
-                    ByteBuffer out = mCodec.getOutputBuffer(index);
-                    out.get(mBuf, 0 /* offset */, mBufIn /* length */);
-                    mCodec.releaseOutputBuffer(index,  false /* render */);
-                    if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        mSawOutputEOS = true;
-                    }
-                    break;
+            // now read encoded data from the encoder
+            int index = mCodec.dequeueOutputBuffer(mInfo, 0);
+            if (index >= 0) {
+                mBufIn = mInfo.size;
+                ByteBuffer out = mCodec.getOutputBuffer(index);
+                out.get(mBuf, 0 /* offset */, mBufIn /* length */);
+                mCodec.releaseOutputBuffer(index,  false /* render */);
+                if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    mSawOutputEOS = true;
                 }
             }
         }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 3b9a5de..20405d3 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -19,12 +19,14 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.media.AudioAttributesProto;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseIntArray;
+import android.util.proto.ProtoOutputStream;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -177,7 +179,7 @@
 
     /**
      * IMPORTANT: when adding new usage types, add them to SDK_USAGES and update SUPPRESSIBLE_USAGES
-     *            if applicable.
+     *            if applicable, as well as audioattributes.proto.
      */
 
     /**
@@ -200,6 +202,22 @@
      * @see #SUPPRESSIBLE_USAGES
      */
     public final static int SUPPRESSIBLE_NEVER = 3;
+    /**
+     * @hide
+     * Denotes a usage for alarms,
+     * will be muted when the Zen mode doesn't allow alarms
+     * @see #SUPPRESSIBLE_USAGES
+     */
+    public final static int SUPPRESSIBLE_ALARM = 4;
+    /**
+     * @hide
+     * Denotes a usage for all other sounds not caught in SUPPRESSIBLE_NOTIFICATION,
+     * SUPPRESSIBLE_CALL,SUPPRESSIBLE_NEVER or SUPPRESSIBLE_ALARM.
+     * This includes media, system, game, navigation, the assistant, and more.
+     * These will be muted when the Zen mode doesn't allow media/system/other.
+     * @see #SUPPRESSIBLE_USAGES
+     */
+    public final static int SUPPRESSIBLE_MEDIA_SYSTEM_OTHER = 5;
 
     /**
      * @hide
@@ -219,6 +237,13 @@
         SUPPRESSIBLE_USAGES.put(USAGE_NOTIFICATION_EVENT,                SUPPRESSIBLE_NOTIFICATION);
         SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_ACCESSIBILITY,          SUPPRESSIBLE_NEVER);
         SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION,               SUPPRESSIBLE_NEVER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ALARM,                             SUPPRESSIBLE_ALARM);
+        SUPPRESSIBLE_USAGES.put(USAGE_MEDIA,                             SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_SONIFICATION,           SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_GAME,                              SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING,    SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+        SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT,                         SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
     }
 
     /**
@@ -850,6 +875,21 @@
     }
 
     /** @hide */
+    public void toProto(ProtoOutputStream proto) {
+        proto.write(AudioAttributesProto.USAGE, mUsage);
+        proto.write(AudioAttributesProto.CONTENT_TYPE, mContentType);
+        proto.write(AudioAttributesProto.FLAGS, mFlags);
+        // mFormattedTags is never null due to assignment in Builder or unmarshalling.
+        for (String t : mFormattedTags.split(";")) {
+            t = t.trim();
+            if (t != "") {
+                proto.write(AudioAttributesProto.TAGS, t);
+            }
+        }
+        // TODO: is the data in mBundle useful for debugging?
+    }
+
+    /** @hide */
     public String usageToString() {
         return usageToString(mUsage);
     }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 186b265..dab7632a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4119,7 +4119,15 @@
                         Log.w(TAG, "updateAudioPortCache: listAudioPatches failed");
                         return status;
                     }
-                } while (patchGeneration[0] != portGeneration[0]);
+                    // Loop until patch generation is the same as port generation unless audio ports
+                    // and audio patches are not null.
+                } while (patchGeneration[0] != portGeneration[0]
+                        && (ports == null || patches == null));
+                // If the patch generation doesn't equal port generation, return ERROR here in case
+                // of mismatch between audio ports and audio patches.
+                if (patchGeneration[0] != portGeneration[0]) {
+                    return ERROR;
+                }
 
                 for (int i = 0; i < newPatches.size(); i++) {
                     for (int j = 0; j < newPatches.get(i).sources().length; j++) {
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index c152245..ac3904a 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import java.util.ArrayList;
@@ -30,6 +31,7 @@
 
 class AudioPortEventHandler {
     private Handler mHandler;
+    private HandlerThread mHandlerThread;
     private final ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners =
             new ArrayList<AudioManager.OnAudioPortUpdateListener>();
 
@@ -40,6 +42,8 @@
     private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
     private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
 
+    private static final long RESCHEDULE_MESSAGE_DELAY_MS = 100;
+
     /**
      * Accessed by native methods: JNI Callback context.
      */
@@ -51,11 +55,12 @@
             if (mHandler != null) {
                 return;
             }
-            // find the looper for our new event handler
-            Looper looper = Looper.getMainLooper();
+            // create a new thread for our new event handler
+            mHandlerThread = new HandlerThread(TAG);
+            mHandlerThread.start();
 
-            if (looper != null) {
-                mHandler = new Handler(looper) {
+            if (mHandlerThread.getLooper() != null) {
+                mHandler = new Handler(mHandlerThread.getLooper()) {
                     @Override
                     public void handleMessage(Message msg) {
                         ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
@@ -86,6 +91,12 @@
                         if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
                             int status = AudioManager.updateAudioPortCache(ports, patches, null);
                             if (status != AudioManager.SUCCESS) {
+                                // Since audio ports and audio patches are not null, the return
+                                // value could be ERROR due to inconsistency between port generation
+                                // and patch generation. In this case, we need to reschedule the
+                                // message to make sure the native callback is done.
+                                sendMessageDelayed(obtainMessage(msg.what, msg.obj),
+                                        RESCHEDULE_MESSAGE_DELAY_MS);
                                 return;
                             }
                         }
@@ -132,6 +143,9 @@
     @Override
     protected void finalize() {
         native_finalize();
+        if (mHandlerThread.isAlive()) {
+            mHandlerThread.quit();
+        }
     }
     private native void native_finalize();
 
@@ -168,6 +182,10 @@
             Handler handler = eventHandler.handler();
             if (handler != null) {
                 Message m = handler.obtainMessage(what, arg1, arg2, obj);
+                if (what != AUDIOPORT_EVENT_NEW_LISTENER) {
+                    // Except AUDIOPORT_EVENT_NEW_LISTENER, we can only respect the last message.
+                    handler.removeMessages(what);
+                }
                 handler.sendMessage(m);
             }
         }
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 1f5edfa..ba41a7b 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2584,22 +2584,21 @@
                             ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
                 }
 
-                // Note that the rotation angle from MediaMetadataRetriever for heif images
-                // are CCW, while rotation in ExifInterface orientations are CW.
                 String rotation = retriever.extractMetadata(
                         MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
                 if (rotation != null) {
                     int orientation = ExifInterface.ORIENTATION_NORMAL;
 
+                    // all rotation angles in CW
                     switch (Integer.parseInt(rotation)) {
                         case 90:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
                             break;
                         case 180:
                             orientation = ExifInterface.ORIENTATION_ROTATE_180;
                             break;
                         case 270:
-                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
                             break;
                     }
 
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index dc7fa8c..3308fc9 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -28,7 +28,6 @@
 
     MediaRouterClientState getState(IMediaRouterClient client);
     boolean isPlaybackActive(IMediaRouterClient client);
-    boolean isGlobalBluetoothA2doOn();
 
     void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan);
     void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit);
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index ed5f7d8..c475e12 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -96,6 +96,19 @@
  * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
  * <tr><td>{@link #KEY_LANGUAGE}</td><td>String</td><td>The language of the content.</td></tr>
  * </table>
+ *
+ * Image formats have the following keys:
+ * <table>
+ * <tr><td>{@link #KEY_MIME}</td><td>String</td><td>The type of the format.</td></tr>
+ * <tr><td>{@link #KEY_WIDTH}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_HEIGHT}</td><td>Integer</td><td></td></tr>
+ * <tr><td>{@link #KEY_COLOR_FORMAT}</td><td>Integer</td><td>set by the user
+ *         for encoders, readable in the output format of decoders</b></td></tr>
+ * <tr><td>{@link #KEY_GRID_WIDTH}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_HEIGHT}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_ROWS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * <tr><td>{@link #KEY_GRID_COLS}</td><td>Integer</td><td>required if the image has grid</td></tr>
+ * </table>
  */
 public final class MediaFormat {
     public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
@@ -126,6 +139,35 @@
     public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
 
     /**
+     * MIME type for HEIF still image data encoded in HEVC.
+     *
+     * To decode such an image, {@link MediaCodec} decoder for
+     * {@ #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
+     * the correct {@link #MediaFormat} based on additional information in
+     * the track format, and send it to {@link MediaCodec#configure}.
+     *
+     * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+     * {@link #KEY_HEIGHT} keys, which describes the width and height
+     * of the image. If the image doesn't contain grid (i.e. none of
+     * {@link #KEY_GRID_WIDTH}, {@link #KEY_GRID_HEIGHT},
+     * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLS} are present}), the
+     * track will contain a single sample of coded data for the entire image,
+     * and the image width and height should be used to set up the decoder.
+     *
+     * If the image does come with grid, each sample from the track will
+     * contain one tile in the grid, of which the size is described by
+     * {@link #KEY_GRID_WIDTH} and {@link #KEY_GRID_HEIGHT}. This size
+     * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+     * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+     * by {@link #KEY_GRID_COLS} samples in row-major, top-row first,
+     * left-to-right order. The output image should be reconstructed by
+     * first tiling the decoding results of the tiles in the correct order,
+     * then trimming (before rotation is applied) on the bottom and right
+     * side, if the tiled area is larger than the image width and height.
+     */
+    public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+
+    /**
      * MIME type for WebVTT subtitle data.
      */
     public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -232,6 +274,54 @@
     public static final String KEY_FRAME_RATE = "frame-rate";
 
     /**
+     * A key describing the grid width of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+     * track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_HEIGHT
+     * @see #KEY_GRID_ROWS
+     * @see #KEY_GRID_COLS
+     */
+    public static final String KEY_GRID_WIDTH = "grid-width";
+
+    /**
+     * A key describing the grid height of the content in a {@link #MIMETYPE_IMAGE_ANDROID_HEIC}
+     * track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_WIDTH
+     * @see #KEY_GRID_ROWS
+     * @see #KEY_GRID_COLS
+     */
+    public static final String KEY_GRID_HEIGHT = "grid-height";
+
+    /**
+     * A key describing the number of grid rows in the content in a
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_WIDTH
+     * @see #KEY_GRID_HEIGHT
+     * @see #KEY_GRID_COLS
+     */
+    public static final String KEY_GRID_ROWS = "grid-rows";
+
+    /**
+     * A key describing the number of grid columns in the content in a
+     * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+     *
+     * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+     *
+     * @see #KEY_GRID_WIDTH
+     * @see #KEY_GRID_HEIGHT
+     * @see #KEY_GRID_ROWS
+     */
+    public static final String KEY_GRID_COLS = "grid-cols";
+
+    /**
      * A key describing the raw audio sample encoding/format.
      *
      * <p>The associated value is an integer, using one of the
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 4ea4e38..0b86401 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -47,7 +47,7 @@
     // The field below is accessed by native methods
     @SuppressWarnings("unused")
     private long mNativeContext;
- 
+
     private static final int EMBEDDED_PICTURE_TYPE_ANY = 0xFFFF;
 
     public MediaMetadataRetriever() {
@@ -58,7 +58,7 @@
      * Sets the data source (file pathname) to use. Call this
      * method before the rest of the methods in this class. This method may be
      * time-consuming.
-     * 
+     *
      * @param path The path of the input media file.
      * @throws IllegalArgumentException If the path is invalid.
      */
@@ -113,7 +113,7 @@
      * responsibility to close the file descriptor. It is safe to do so as soon
      * as this call returns. Call this method before the rest of the methods in
      * this class. This method may be time-consuming.
-     * 
+     *
      * @param fd the FileDescriptor for the file you want to play
      * @param offset the offset into the file where the data to be played starts,
      * in bytes. It must be non-negative
@@ -123,13 +123,13 @@
      */
     public native void setDataSource(FileDescriptor fd, long offset, long length)
             throws IllegalArgumentException;
-    
+
     /**
      * Sets the data source (FileDescriptor) to use. It is the caller's
      * responsibility to close the file descriptor. It is safe to do so as soon
      * as this call returns. Call this method before the rest of the methods in
      * this class. This method may be time-consuming.
-     * 
+     *
      * @param fd the FileDescriptor for the file you want to play
      * @throws IllegalArgumentException if the FileDescriptor is invalid
      */
@@ -138,11 +138,11 @@
         // intentionally less than LONG_MAX
         setDataSource(fd, 0, 0x7ffffffffffffffL);
     }
-    
+
     /**
-     * Sets the data source as a content Uri. Call this method before 
+     * Sets the data source as a content Uri. Call this method before
      * the rest of the methods in this class. This method may be time-consuming.
-     * 
+     *
      * @param context the Context to use when resolving the Uri
      * @param uri the Content URI of the data you want to play
      * @throws IllegalArgumentException if the Uri is invalid
@@ -154,7 +154,7 @@
         if (uri == null) {
             throw new IllegalArgumentException();
         }
-        
+
         String scheme = uri.getScheme();
         if(scheme == null || scheme.equals("file")) {
             setDataSource(uri.getPath());
@@ -213,12 +213,12 @@
     /**
      * Call this method after setDataSource(). This method retrieves the
      * meta data value associated with the keyCode.
-     * 
+     *
      * The keyCode currently supported is listed below as METADATA_XXX
      * constants. With any other value, it returns a null pointer.
-     * 
+     *
      * @param keyCode One of the constants listed below at the end of the class.
-     * @return The meta data value associate with the given keyCode on success; 
+     * @return The meta data value associate with the given keyCode on success;
      * null on failure.
      */
     public native String extractMetadata(int keyCode);
@@ -357,6 +357,109 @@
     private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
 
     /**
+     * This method retrieves a video frame by its index. It should only be called
+     * after {@link #setDataSource}.
+     *
+     * @param frameIndex 0-based index of the video frame. The frame index must be that of
+     *        a valid frame. The total number of frames available for retrieval can be queried
+     *        via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+     *
+     * @throws IllegalStateException if the container doesn't contain video or image sequences.
+     * @throws IllegalArgumentException if the requested frame index does not exist.
+     *
+     * @return A Bitmap containing the requested video frame, or null if the retrieval fails.
+     *
+     * @see #getFramesAtIndex(int, int)
+     */
+    public Bitmap getFrameAtIndex(int frameIndex) {
+        Bitmap[] bitmaps = getFramesAtIndex(frameIndex, 1);
+        if (bitmaps == null || bitmaps.length < 1) {
+            return null;
+        }
+        return bitmaps[0];
+    }
+
+    /**
+     * This method retrieves a consecutive set of video frames starting at the
+     * specified index. It should only be called after {@link #setDataSource}.
+     *
+     * If the caller intends to retrieve more than one consecutive video frames,
+     * this method is preferred over {@link #getFrameAtIndex(int)} for efficiency.
+     *
+     * @param frameIndex 0-based index of the first video frame to retrieve. The frame index
+     *        must be that of a valid frame. The total number of frames available for retrieval
+     *        can be queried via the {@link #METADATA_KEY_VIDEO_FRAME_COUNT} key.
+     * @param numFrames number of consecutive video frames to retrieve. Must be a positive
+     *        value. The stream must contain at least numFrames frames starting at frameIndex.
+     *
+     * @throws IllegalStateException if the container doesn't contain video or image sequences.
+     * @throws IllegalArgumentException if the frameIndex or numFrames is invalid, or the
+     *         stream doesn't contain at least numFrames starting at frameIndex.
+
+     * @return An array of Bitmaps containing the requested video frames. The returned
+     *         array could contain less frames than requested if the retrieval fails.
+     *
+     * @see #getFrameAtIndex(int)
+     */
+    public Bitmap[] getFramesAtIndex(int frameIndex, int numFrames) {
+        if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO))) {
+            throw new IllegalStateException("Does not contail video or image sequences");
+        }
+        int frameCount = Integer.parseInt(
+                extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+        if (frameIndex < 0 || numFrames < 1
+                || frameIndex >= frameCount
+                || frameIndex > frameCount - numFrames) {
+            throw new IllegalArgumentException("Invalid frameIndex or numFrames: "
+                + frameIndex + ", " + numFrames);
+        }
+        return _getFrameAtIndex(frameIndex, numFrames);
+    }
+    private native Bitmap[] _getFrameAtIndex(int frameIndex, int numFrames);
+
+    /**
+     * This method retrieves a still image by its index. It should only be called
+     * after {@link #setDataSource}.
+     *
+     * @param imageIndex 0-based index of the image, with negative value indicating
+     *        the primary image.
+     * @throws IllegalStateException if the container doesn't contain still images.
+     * @throws IllegalArgumentException if the requested image does not exist.
+     *
+     * @return the requested still image, or null if the image cannot be retrieved.
+     *
+     * @see #getPrimaryImage
+     */
+    public Bitmap getImageAtIndex(int imageIndex) {
+        if (!"yes".equals(extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE))) {
+            throw new IllegalStateException("Does not contail still images");
+        }
+
+        String imageCount = extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT);
+        if (imageIndex >= Integer.parseInt(imageCount)) {
+            throw new IllegalArgumentException("Invalid image index: " + imageCount);
+        }
+
+        return _getImageAtIndex(imageIndex);
+    }
+
+    /**
+     * This method retrieves the primary image of the media content. It should only
+     * be called after {@link #setDataSource}.
+     *
+     * @return the primary image, or null if it cannot be retrieved.
+     *
+     * @throws IllegalStateException if the container doesn't contain still images.
+     *
+     * @see #getImageAtIndex(int)
+     */
+    public Bitmap getPrimaryImage() {
+        return getImageAtIndex(-1);
+    }
+
+    private native Bitmap _getImageAtIndex(int imageIndex);
+
+    /**
      * Call this method after setDataSource(). This method finds the optional
      * graphic or album/cover art associated associated with the data source. If
      * there are more than one pictures, (any) one of them is returned.
@@ -395,7 +498,7 @@
      * @see #getFrameAtTime(long, int)
      */
     /* Do not change these option values without updating their counterparts
-     * in include/media/stagefright/MediaSource.h!
+     * in include/media/MediaSource.h!
      */
     /**
      * This option is used with {@link #getFrameAtTime(long, int)} to retrieve
@@ -572,5 +675,40 @@
      * number.
      */
     public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
+    /**
+     * If this key exists the media contains still image content.
+     */
+    public static final int METADATA_KEY_HAS_IMAGE       = 26;
+    /**
+     * If the media contains still images, this key retrieves the number
+     * of still images.
+     */
+    public static final int METADATA_KEY_IMAGE_COUNT     = 27;
+    /**
+     * If the media contains still images, this key retrieves the image
+     * index of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_PRIMARY   = 28;
+    /**
+     * If the media contains still images, this key retrieves the width
+     * of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_WIDTH     = 29;
+    /**
+     * If the media contains still images, this key retrieves the height
+     * of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_HEIGHT    = 30;
+    /**
+     * If the media contains still images, this key retrieves the rotation
+     * of the primary image.
+     */
+    public static final int METADATA_KEY_IMAGE_ROTATION  = 31;
+    /**
+     * If the media contains video and this key exists, it retrieves the
+     * total number of frames in the video sequence.
+     */
+    public static final int METADATA_KEY_VIDEO_FRAME_COUNT = 32;
+
     // Add more here...
 }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 7787d4b..62757e2 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -39,6 +39,7 @@
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.system.OsConstants;
 import android.util.Log;
 import android.util.Pair;
@@ -60,7 +61,6 @@
 import com.android.internal.util.Preconditions;
 
 import libcore.io.IoBridge;
-import libcore.io.Libcore;
 import libcore.io.Streams;
 
 import java.io.ByteArrayOutputStream;
@@ -2843,7 +2843,7 @@
 
         final FileDescriptor dupedFd;
         try {
-            dupedFd = Libcore.os.dup(fd);
+            dupedFd = Os.dup(fd);
         } catch (ErrnoException ex) {
             Log.e(TAG, ex.getMessage(), ex);
             throw new RuntimeException(ex);
@@ -2881,7 +2881,7 @@
             private int addTrack() {
                 final ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 try {
-                    Libcore.os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
+                    Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
                     byte[] buffer = new byte[4096];
                     for (long total = 0; total < length2;) {
                         int bytesToRead = (int) Math.min(buffer.length, length2 - total);
@@ -2905,7 +2905,7 @@
                     return MEDIA_INFO_TIMED_TEXT_ERROR;
                 } finally {
                     try {
-                        Libcore.os.close(dupedFd);
+                        Os.close(dupedFd);
                     } catch (ErrnoException e) {
                         Log.e(TAG, e.getMessage(), e);
                     }
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 59a124f..7678490 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -917,7 +917,7 @@
      */
     public void setNextOutputFile(File file) throws IOException
     {
-        RandomAccessFile f = new RandomAccessFile(file, "rws");
+        RandomAccessFile f = new RandomAccessFile(file, "rw");
         try {
             _setNextOutputFile(f.getFD());
         } finally {
@@ -942,7 +942,7 @@
     public void prepare() throws IllegalStateException, IOException
     {
         if (mPath != null) {
-            RandomAccessFile file = new RandomAccessFile(mPath, "rws");
+            RandomAccessFile file = new RandomAccessFile(mPath, "rw");
             try {
                 _setOutputFile(file.getFD());
             } finally {
@@ -951,7 +951,7 @@
         } else if (mFd != null) {
             _setOutputFile(mFd);
         } else if (mFile != null) {
-            RandomAccessFile file = new RandomAccessFile(mFile, "rws");
+            RandomAccessFile file = new RandomAccessFile(mFile, "rw");
             try {
                 _setOutputFile(file.getFD());
             } finally {
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 8707ad0..70ab863 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -184,13 +184,15 @@
 
         void updateAudioRoutes(AudioRoutesInfo newRoutes) {
             boolean audioRoutesChanged = false;
+            boolean forceUseDefaultRoute = false;
+
             if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
                 mCurAudioRoutesInfo.mainType = newRoutes.mainType;
                 int name;
-                if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
-                        || (newRoutes.mainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
+                if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
+                        || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_headphones;
-                } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+                } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
                 } else if ((newRoutes.mainType&AudioRoutesInfo.MAIN_HDMI) != 0) {
                     name = com.android.internal.R.string.default_audio_route_name_hdmi;
@@ -201,10 +203,16 @@
                 }
                 mDefaultAudioVideo.mNameResId = name;
                 dispatchRouteChanged(mDefaultAudioVideo);
+
+                if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
+                        | AudioRoutesInfo.MAIN_HEADPHONES | AudioRoutesInfo.MAIN_USB)) != 0) {
+                    forceUseDefaultRoute = true;
+                }
                 audioRoutesChanged = true;
             }
 
             if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+                forceUseDefaultRoute = false;
                 mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
                 if (mCurAudioRoutesInfo.bluetoothName != null) {
                     if (mBluetoothA2dpRoute == null) {
@@ -233,30 +241,18 @@
                 Log.v(TAG, "Audio routes updated: " + newRoutes + ", a2dp=" + isBluetoothA2dpOn());
                 if (mSelectedRoute == null || mSelectedRoute == mDefaultAudioVideo
                         || mSelectedRoute == mBluetoothA2dpRoute) {
-                    selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, getDefaultSystemAudioRoute(), false);
+                    if (forceUseDefaultRoute || mBluetoothA2dpRoute == null) {
+                        selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false);
+                    } else {
+                        selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false);
+                    }
                 }
             }
         }
 
-        RouteInfo getDefaultSystemAudioRoute() {
-            boolean globalBluetoothA2doOn = false;
-            try {
-                globalBluetoothA2doOn = mMediaRouterService.isGlobalBluetoothA2doOn();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Unable to call isSystemBluetoothA2doOn.", ex);
-            }
-            return (globalBluetoothA2doOn && mBluetoothA2dpRoute != null)
-                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
-        }
-
-        RouteInfo getCurrentSystemAudioRoute() {
-            return (isBluetoothA2dpOn() && mBluetoothA2dpRoute != null)
-                    ? mBluetoothA2dpRoute : mDefaultAudioVideo;
-        }
-
         boolean isBluetoothA2dpOn() {
             try {
-                return mAudioService.isBluetoothA2dpOn();
+                return mBluetoothA2dpRoute != null && mAudioService.isBluetoothA2dpOn();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error querying Bluetooth A2DP state", e);
                 return false;
@@ -604,13 +600,20 @@
 
             @Override
             public void onRestoreRoute() {
-                // Skip restoring route if the selected route is not a system audio route, or
-                // MediaRouter is initializing.
-                if ((mSelectedRoute != mDefaultAudioVideo && mSelectedRoute != mBluetoothA2dpRoute)
-                        || mSelectedRoute == null) {
-                    return;
-                }
-                mSelectedRoute.select();
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Skip restoring route if the selected route is not a system audio route,
+                        // MediaRouter is initializing, or mClient was changed.
+                        if (Client.this != mClient || mSelectedRoute == null
+                                || (mSelectedRoute != mDefaultAudioVideo
+                                        && mSelectedRoute != mBluetoothA2dpRoute)) {
+                            return;
+                        }
+                        Log.v(TAG, "onRestoreRoute() : route=" + mSelectedRoute);
+                        mSelectedRoute.select();
+                    }
+                });
             }
         }
     }
@@ -942,10 +945,12 @@
         Log.v(TAG, "Selecting route: " + route);
         assert(route != null);
         final RouteInfo oldRoute = sStatic.mSelectedRoute;
+        final RouteInfo currentSystemRoute = sStatic.isBluetoothA2dpOn()
+                ? sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo;
         boolean wasDefaultOrBluetoothRoute = (oldRoute == sStatic.mDefaultAudioVideo
                 || oldRoute == sStatic.mBluetoothA2dpRoute);
         if (oldRoute == route
-                && (!wasDefaultOrBluetoothRoute || route == sStatic.getCurrentSystemAudioRoute())) {
+                && (!wasDefaultOrBluetoothRoute || route == currentSystemRoute)) {
             return;
         }
         if (!route.matchesTypes(types)) {
@@ -1016,8 +1021,7 @@
 
     static void selectDefaultRouteStatic() {
         // TODO: Be smarter about the route types here; this selects for all valid.
-        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute
-                && sStatic.mBluetoothA2dpRoute != null && sStatic.isBluetoothA2dpOn()) {
+        if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute && sStatic.isBluetoothA2dpOn()) {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false);
         } else {
             selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false);
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 4808d7a..09449a1 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -127,8 +127,9 @@
             Log.e(TAG, "Error talking to audio service, STARTED state will not be tracked", e);
         }
         synchronized (mLock) {
+            boolean attributesChanged = (mAttributes != attr);
             mAttributes = attr;
-            updateAppOpsPlayAudio_sync();
+            updateAppOpsPlayAudio_sync(attributesChanged);
         }
     }
 
@@ -200,16 +201,13 @@
     }
 
     void baseSetVolume(float leftVolume, float rightVolume) {
-        final boolean hasAppOpsPlayAudio;
+        final boolean isRestricted;
         synchronized (mLock) {
             mLeftVolume = leftVolume;
             mRightVolume = rightVolume;
-            hasAppOpsPlayAudio = mHasAppOpsPlayAudio;
-            if (isRestricted_sync()) {
-                return;
-            }
+            isRestricted = isRestricted_sync();
         }
-        playerSetVolume(!hasAppOpsPlayAudio/*muting*/,
+        playerSetVolume(isRestricted/*muting*/,
                 leftVolume * mPanMultiplierL, rightVolume * mPanMultiplierR);
     }
 
@@ -250,7 +248,7 @@
 
     private void updateAppOpsPlayAudio() {
         synchronized (mLock) {
-            updateAppOpsPlayAudio_sync();
+            updateAppOpsPlayAudio_sync(false);
         }
     }
 
@@ -258,7 +256,7 @@
      * To be called whenever a condition that might affect audibility of this player is updated.
      * Must be called synchronized on mLock.
      */
-    void updateAppOpsPlayAudio_sync() {
+    void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
         try {
             int mode = AppOpsManager.MODE_IGNORED;
@@ -275,9 +273,10 @@
         // AppsOps alters a player's volume; when the restriction changes, reflect it on the actual
         // volume used by the player
         try {
-            if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio) {
+            if (oldHasAppOpsPlayAudio != mHasAppOpsPlayAudio ||
+                    attributesChanged) {
                 getService().playerHasOpPlayAudio(mPlayerIId, mHasAppOpsPlayAudio);
-                if (mHasAppOpsPlayAudio) {
+                if (!isRestricted_sync()) {
                     if (DEBUG_APP_OPS) {
                         Log.v(TAG, "updateAppOpsPlayAudio: unmuting player, vol=" + mLeftVolume
                                 + "/" + mRightVolume);
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index 8c1010d..33c5490 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -138,9 +138,10 @@
 </pre>
 
 
-<p>Note that &ldquo;input&rdquo; and &ldquo;output&rdquo; are from the standpoint of the device. So a
-synthesizer will have an &ldquo;input&rdquo; port that receives messages. A keyboard will
-have an &ldquo;output&rdquo; port that sends messages.</p>
+<p>Note that &ldquo;input&rdquo; and &ldquo;output&rdquo; directions reflect the point of view
+of the MIDI device itself, not your app.
+For example, to send MIDI notes to a synthesizer, open the synth's INPUT port.
+To receive notes from a keyboard, open the keyboard's OUTPUT port.</p>
 
 <p>The MidiDeviceInfo has a bundle of properties.</p>
 
@@ -359,8 +360,10 @@
 <p>MIDI devices can be connected to Android using Bluetooth LE.</p>
 
 <p>Before using the device, the app must scan for available BTLE devices and then allow
-the user to connect. An example program
-will be provided so look for it on the Android developer website.</p>
+the user to connect.
+See the Android developer website for an
+<a href="https://source.android.com/devices/audio/midi_test#apps" target="_blank">example
+program</a>.</p>
 
 <h2 id=btle_location_permissions>Request Location Permission for BTLE</h2>
 
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9f2c08e..aa0d0cc 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -20,8 +20,10 @@
 import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.media.projection.IMediaProjection;
 import android.os.Handler;
 import android.os.IBinder;
@@ -71,8 +73,11 @@
      */
     public Intent createScreenCaptureIntent() {
         Intent i = new Intent();
-        i.setClassName("com.android.systemui",
-                "com.android.systemui.media.MediaProjectionPermissionActivity");
+        final ComponentName mediaProjectionPermissionDialogComponent =
+                ComponentName.unflattenFromString(mContext.getResources().getString(
+                        com.android.internal.R.string
+                        .config_mediaProjectionPermissionDialogComponent));
+        i.setComponent(mediaProjectionPermissionDialogComponent);
         return i;
     }
 
diff --git a/media/java/android/media/tv/ITvInputHardware.aidl b/media/java/android/media/tv/ITvInputHardware.aidl
index 96223ba..94c1013 100644
--- a/media/java/android/media/tv/ITvInputHardware.aidl
+++ b/media/java/android/media/tv/ITvInputHardware.aidl
@@ -40,12 +40,6 @@
     void setStreamVolume(float volume);
 
     /**
-     * Dispatch key event to HDMI service. The events would be automatically converted to
-     * HDMI CEC commands. If the hardware is not representing an HDMI port, this method will fail.
-     */
-    boolean dispatchKeyEventToHdmi(in KeyEvent event);
-
-    /**
      * Override default audio sink from audio policy. When override is on, it is
      * TvInputService's responsibility to adjust to audio configuration change
      * (for example, when the audio sink becomes unavailable or more desirable
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index d7a9ede..fd1f2cf 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2590,12 +2590,9 @@
             }
         }
 
+        /** @removed */
         public boolean dispatchKeyEventToHdmi(KeyEvent event) {
-            try {
-                return mInterface.dispatchKeyEventToHdmi(event);
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
-            }
+            return false;
         }
 
         public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index 80fd5c0..aaf18e7 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -471,10 +471,14 @@
                     if (parent == 0xFFFFFFFF) {
                         // all objects in root of store
                         parent = 0;
+                        where = STORAGE_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(storageID),
+                                Integer.toString(parent)};
+                    }  else {
+                        // If a parent is specified, the storage is redundant
+                        where = PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(parent)};
                     }
-                    where = STORAGE_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID),
-                                               Integer.toString(parent) };
                 }
             } else {
                 // query specific format
@@ -487,11 +491,16 @@
                     if (parent == 0xFFFFFFFF) {
                         // all objects in root of store
                         parent = 0;
+                        where = STORAGE_FORMAT_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(storageID),
+                                Integer.toString(format),
+                                Integer.toString(parent)};
+                    } else {
+                        // If a parent is specified, the storage is redundant
+                        where = FORMAT_PARENT_WHERE;
+                        whereArgs = new String[]{Integer.toString(format),
+                                Integer.toString(parent)};
                     }
-                    where = STORAGE_FORMAT_PARENT_WHERE;
-                    whereArgs = new String[] { Integer.toString(storageID),
-                                               Integer.toString(format),
-                                               Integer.toString(parent) };
                 }
             }
         }
@@ -845,6 +854,34 @@
         return MtpConstants.RESPONSE_OK;
     }
 
+    private int moveObject(int handle, int newParent, int newStorage, String newPath) {
+        String[] whereArgs = new String[] {  Integer.toString(handle) };
+
+        // do not allow renaming any of the special subdirectories
+        if (isStorageSubDirectory(newPath)) {
+            return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED;
+        }
+
+        // update database
+        ContentValues values = new ContentValues();
+        values.put(Files.FileColumns.DATA, newPath);
+        values.put(Files.FileColumns.PARENT, newParent);
+        values.put(Files.FileColumns.STORAGE_ID, newStorage);
+        int updated = 0;
+        try {
+            // note - we are relying on a special case in MediaProvider.update() to update
+            // the paths for all children in the case where this is a directory.
+            updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in mMediaProvider.update", e);
+        }
+        if (updated == 0) {
+            Log.e(TAG, "Unable to update path for " + handle + " to " + newPath);
+            return MtpConstants.RESPONSE_GENERAL_ERROR;
+        }
+        return MtpConstants.RESPONSE_OK;
+    }
+
     private int setObjectProperty(int handle, int property,
                             long intValue, String stringValue) {
         switch (property) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 02667ca..1d85c97 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -38,7 +38,7 @@
         "libmediametrics",
         "libmediadrm",
         "libmidi",
-        "libskia",
+        "libhwui",
         "libui",
         "liblog",
         "libcutils",
diff --git a/media/jni/android_media_MediaDescrambler.cpp b/media/jni/android_media_MediaDescrambler.cpp
index bee5218..e77e855 100644
--- a/media/jni/android_media_MediaDescrambler.cpp
+++ b/media/jni/android_media_MediaDescrambler.cpp
@@ -21,7 +21,7 @@
 #include "android_media_MediaDescrambler.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_os_HwRemoteBinder.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android/hardware/cas/native/1.0/BpHwDescrambler.h>
 #include <android/hardware/cas/native/1.0/BnHwDescrambler.h>
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index c9657b1..5c90d00 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -27,7 +27,7 @@
 #include "android_runtime/Log.h"
 #include "android_util_Binder.h"
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <android/hardware/cas/1.0/BpHwCas.h>
 #include <android/hardware/cas/1.0/BnHwCas.h>
@@ -37,7 +37,8 @@
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/DataSource.h>
+#include <media/DataSource.h>
+#include <media/stagefright/InterfaceUtils.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MetaData.h>
 #include <media/stagefright/NuMediaExtractor.h>
@@ -744,7 +745,7 @@
     }
 
     sp<DataSource> bridge =
-        DataSource::CreateFromIDataSource(new JMediaDataSource(env, callbackObj));
+        CreateDataSourceFromIDataSource(new JMediaDataSource(env, callbackObj));
     status_t err = extractor->setDataSource(bridge);
 
     if (err != OK) {
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index 94d36f2..a4638ac 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -18,8 +18,8 @@
 #define _ANDROID_MEDIA_MEDIAEXTRACTOR_H_
 
 #include <media/stagefright/foundation/ABase.h>
-#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/DataSource.h>
+#include <media/MediaSource.h>
+#include <media/DataSource.h>
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 4659ae1..42ebf6a 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -244,29 +244,9 @@
     }
 }
 
-static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
-        JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
-{
-    ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
-            (long long)timeUs, option, dst_width, dst_height);
-    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
-    if (retriever == 0) {
-        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
-        return NULL;
-    }
-
-    // Call native method to retrieve a video frame
-    VideoFrame *videoFrame = NULL;
-    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
-    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
-        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
-    }
-    if (videoFrame == NULL) {
-        ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
-        return NULL;
-    }
-
-    ALOGV("Dimension = %dx%d and bytes = %d",
+static jobject getBitmapFromVideoFrame(
+        JNIEnv *env, VideoFrame *videoFrame, jint dst_width, jint dst_height) {
+    ALOGV("getBitmapFromVideoFrame: dimension = %dx%d and bytes = %d",
             videoFrame->mDisplayWidth,
             videoFrame->mDisplayHeight,
             videoFrame->mSize);
@@ -301,7 +281,7 @@
         if (env->ExceptionCheck()) {
             env->ExceptionClear();
         }
-        ALOGE("getFrameAtTime: create Bitmap failed!");
+        ALOGE("getBitmapFromVideoFrame: create Bitmap failed!");
         return NULL;
     }
 
@@ -340,6 +320,93 @@
     return jBitmap;
 }
 
+static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
+        JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
+{
+    ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
+            (long long)timeUs, option, dst_width, dst_height);
+    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    if (retriever == 0) {
+        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
+        return NULL;
+    }
+
+    // Call native method to retrieve a video frame
+    VideoFrame *videoFrame = NULL;
+    sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
+    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
+        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+    }
+    if (videoFrame == NULL) {
+        ALOGE("getFrameAtTime: videoFrame is a NULL pointer");
+        return NULL;
+    }
+
+    return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height);
+}
+
+static jobject android_media_MediaMetadataRetriever_getImageAtIndex(
+        JNIEnv *env, jobject thiz, jint index)
+{
+    ALOGV("getImageAtIndex: index %d", index);
+    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    if (retriever == 0) {
+        jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
+        return NULL;
+    }
+
+    // Call native method to retrieve an image
+    VideoFrame *videoFrame = NULL;
+    sp<IMemory> frameMemory = retriever->getImageAtIndex(index);
+    if (frameMemory != 0) {  // cast the shared structure to a VideoFrame object
+        videoFrame = static_cast<VideoFrame *>(frameMemory->pointer());
+    }
+    if (videoFrame == NULL) {
+        ALOGE("getImageAtIndex: videoFrame is a NULL pointer");
+        return NULL;
+    }
+
+    return getBitmapFromVideoFrame(env, videoFrame, -1, -1);
+}
+
+static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex(
+        JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames)
+{
+    ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames);
+    MediaMetadataRetriever* retriever = getRetriever(env, thiz);
+    if (retriever == 0) {
+        jniThrowException(env,
+                "java/lang/IllegalStateException", "No retriever available");
+        return NULL;
+    }
+
+    std::vector<sp<IMemory> > frames;
+    status_t err = retriever->getFrameAtIndex(&frames, frameIndex, numFrames);
+    if (err != OK || frames.size() == 0) {
+        ALOGE("failed to get frames from retriever, err=%d, size=%zu",
+                err, frames.size());
+        return NULL;
+    }
+
+    jobjectArray bitmapArrayObj = env->NewObjectArray(
+            frames.size(), fields.bitmapClazz, NULL);
+    if (bitmapArrayObj == NULL) {
+        ALOGE("can't create bitmap array object");
+        return NULL;
+    }
+
+    for (size_t i = 0; i < frames.size(); i++) {
+        if (frames[i] == NULL || frames[i]->pointer() == NULL) {
+            ALOGE("video frame at index %zu is a NULL pointer", frameIndex + i);
+            continue;
+        }
+        VideoFrame *videoFrame = static_cast<VideoFrame *>(frames[i]->pointer());
+        jobject bitmapObj = getBitmapFromVideoFrame(env, videoFrame, -1, -1);
+        env->SetObjectArrayElement(bitmapArrayObj, i, bitmapObj);
+    }
+    return bitmapArrayObj;
+}
+
 static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture(
         JNIEnv *env, jobject thiz, jint pictureType)
 {
@@ -485,6 +552,8 @@
         {"setDataSource",   "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaMetadataRetriever_setDataSourceFD},
         {"_setDataSource",   "(Landroid/media/MediaDataSource;)V", (void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
         {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtTime},
+        {"_getImageAtIndex", "(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getImageAtIndex},
+        {"_getFrameAtIndex", "(II)[Landroid/graphics/Bitmap;", (void *)android_media_MediaMetadataRetriever_getFrameAtIndex},
         {"extractMetadata", "(I)Ljava/lang/String;", (void *)android_media_MediaMetadataRetriever_extractMetadata},
         {"getEmbeddedPicture", "(I)[B", (void *)android_media_MediaMetadataRetriever_getEmbeddedPicture},
         {"release",         "()V", (void *)android_media_MediaMetadataRetriever_release},
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 8979cec..38f7a7e 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -16,7 +16,7 @@
 
 #include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 
 #include "android_media_MediaMetricsJNI.h"
 #include <media/MediaAnalyticsItem.h>
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/media/jni/android_media_MediaMetricsJNI.h
index d174212..16081b4 100644
--- a/media/jni/android_media_MediaMetricsJNI.h
+++ b/media/jni/android_media_MediaMetricsJNI.h
@@ -19,7 +19,7 @@
 
 #include <android_runtime/AndroidRuntime.h>
 #include <jni.h>
-#include <JNIHelp.h>
+#include <nativehelper/JNIHelp.h>
 #include <media/MediaAnalyticsItem.h>
 
 namespace android {
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 7f6980d..28827e6 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -24,7 +24,7 @@
 #include <media/IMediaHTTPService.h>
 #include <media/MediaPlayerInterface.h>
 #include <media/MediaAnalyticsItem.h>
-#include <media/stagefright/Utils.h>            // for FOURCC definition
+#include <media/stagefright/foundation/ByteUtils.h>  // for FOURCC definition
 #include <stdio.h>
 #include <assert.h>
 #include <limits.h>
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index ceab478..497684c 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -35,7 +35,7 @@
 #include <nativehelper/ScopedUtfChars.h>
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "android_media_MediaMetricsJNI.h"
 #include "android_runtime/AndroidRuntime.h"
 
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index 5b874cd..fe2a939 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -68,6 +68,7 @@
 static jmethodID method_getObjectInfo;
 static jmethodID method_getObjectFilePath;
 static jmethodID method_deleteFile;
+static jmethodID method_moveObject;
 static jmethodID method_getObjectReferences;
 static jmethodID method_setObjectReferences;
 static jmethodID method_sessionStarted;
@@ -178,6 +179,9 @@
 
     virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property);
 
+    virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+                                            MtpStorageID newStorage, MtpString& newPath);
+
     virtual void                    sessionStarted();
 
     virtual void                    sessionEnded();
@@ -951,6 +955,7 @@
                         outThumbSize = image_data.thumbnail.length;
                     } else {
                         free(result);
+                        result = NULL;
                     }
                 }
                 break;
@@ -995,6 +1000,18 @@
     return result;
 }
 
+MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
+        MtpStorageID newStorage, MtpString &newPath) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jstring stringValue = env->NewStringUTF((const char *) newPath);
+    MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject,
+                (jint)handle, (jint)newParent, (jint) newStorage, stringValue);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+    env->DeleteLocalRef(stringValue);
+    return result;
+}
+
 struct PropertyTableEntry {
     MtpObjectProperty   property;
     int                 type;
@@ -1360,6 +1377,11 @@
         ALOGE("Can't find deleteFile");
         return -1;
     }
+    method_moveObject = env->GetMethodID(clazz, "moveObject", "(IIILjava/lang/String;)I");
+    if (method_moveObject == NULL) {
+        ALOGE("Can't find moveObject");
+        return -1;
+    }
     method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I");
     if (method_getObjectReferences == NULL) {
         ALOGE("Can't find getObjectReferences");
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index e9e9309..6ce104d 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -72,7 +72,7 @@
     const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);
     const char *deviceInfoSerialNumberStr = env->GetStringUTFChars(deviceInfoSerialNumber, NULL);
     MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
-            usePtp, AID_MEDIA_RW, 0664, 0775,
+            usePtp,
             MtpString((deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : ""),
             MtpString((deviceInfoModelStr != NULL) ? deviceInfoModelStr : ""),
             MtpString((deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : ""),
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 5f1cf16..ab0da07 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -26,7 +26,6 @@
 #include <media/AudioTrack.h>
 #include <media/IMediaHTTPService.h>
 #include <media/mediaplayer.h>
-#include <media/stagefright/MediaExtractor.h>
 #include "SoundPool.h"
 #include "SoundPoolThread.h"
 #include <media/AudioPolicyHelper.h>
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index d456950..0704e35 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -12,6 +12,32 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+cc_library_shared {
+    name: "libjnigraphics",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+
+    // our source files
+    //
+    srcs: ["bitmap.cpp"],
+
+    shared_libs: [
+        "libandroid_runtime",
+    ],
+
+    arch: {
+        arm: {
+            // TODO: This is to work around b/24465209. Remove after root cause is fixed
+            ldflags: ["-Wl,--hash-style=both"],
+        },
+    },
+}
+
 // The headers module is in frameworks/native/Android.bp.
 ndk_library {
     name: "libjnigraphics",
diff --git a/native/graphics/jni/Android.mk b/native/graphics/jni/Android.mk
deleted file mode 100644
index ec4b35a..0000000
--- a/native/graphics/jni/Android.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-BASE_PATH := $(call my-dir)
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# setup for skia optimizations
-#
-ifneq ($(ARCH_ARM_HAVE_VFP),true)
-    LOCAL_CFLAGS += -DSK_SOFTWARE_FLOAT
-endif
-
-ifeq ($(ARCH_ARM_HAVE_NEON),true)
-    LOCAL_CFLAGS += -D__ARM_HAVE_NEON
-endif
-
-# our source files
-#
-LOCAL_SRC_FILES:= \
-    bitmap.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    libandroid_runtime \
-    libskia \
-    libui \
-    libandroidfw
-
-LOCAL_C_INCLUDES += \
-    frameworks/base/native/include \
-    frameworks/base/core/jni/android/graphics \
-    frameworks/base/libs/hwui
-
-LOCAL_MODULE:= libjnigraphics
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-# TODO: This is to work around b/24465209. Remove after root cause is fixed
-LOCAL_LDFLAGS_arm := -Wl,--hash-style=both
-
-include $(BUILD_SHARED_LIBRARY)
-
diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp
index bf5cabb..ff14832 100644
--- a/native/graphics/jni/bitmap.cpp
+++ b/native/graphics/jni/bitmap.cpp
@@ -15,7 +15,7 @@
  */
 
 #include <android/bitmap.h>
-#include <Bitmap.h>
+#include <android/graphics/Bitmap.h>
 
 int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                           AndroidBitmapInfo* info) {
@@ -56,4 +56,3 @@
     }
     return ANDROID_BITMAP_RESULT_SUCCESS;
 }
-
diff --git a/native/webview/loader/loader.cpp b/native/webview/loader/loader.cpp
index 376dbb8..adb371d 100644
--- a/native/webview/loader/loader.cpp
+++ b/native/webview/loader/loader.cpp
@@ -143,17 +143,7 @@
   return DoReserveAddressSpace(size);
 }
 
-jboolean CreateRelroFile(JNIEnv* env, jclass, jstring lib32, jstring lib64,
-                         jstring relro32, jstring relro64) {
-#ifdef __LP64__
-  jstring lib = lib64;
-  jstring relro = relro64;
-  (void)lib32; (void)relro32;
-#else
-  jstring lib = lib32;
-  jstring relro = relro32;
-  (void)lib64; (void)relro64;
-#endif
+jboolean CreateRelroFile(JNIEnv* env, jclass, jstring lib, jstring relro) {
   jboolean ret = JNI_FALSE;
   const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
   if (lib_utf8 != NULL) {
@@ -167,15 +157,8 @@
   return ret;
 }
 
-jint LoadWithRelroFile(JNIEnv* env, jclass, jstring lib, jstring relro32,
-                       jstring relro64, jobject clazzLoader) {
-#ifdef __LP64__
-  jstring relro = relro64;
-  (void)relro32;
-#else
-  jstring relro = relro32;
-  (void)relro64;
-#endif
+jint LoadWithRelroFile(JNIEnv* env, jclass, jstring lib, jstring relro,
+                       jobject clazzLoader) {
   jint ret = LIBLOAD_FAILED_JNI_CALL;
   const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
   if (lib_utf8 != NULL) {
@@ -196,10 +179,10 @@
   { "nativeReserveAddressSpace", "(J)Z",
       reinterpret_cast<void*>(ReserveAddressSpace) },
   { "nativeCreateRelroFile",
-      "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+      "(Ljava/lang/String;Ljava/lang/String;)Z",
       reinterpret_cast<void*>(CreateRelroFile) },
   { "nativeLoadWithRelroFile",
-      "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)I",
+      "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)I",
       reinterpret_cast<void*>(LoadWithRelroFile) },
 };
 
diff --git a/packages/BackupRestoreConfirmation/res/values-hy/strings.xml b/packages/BackupRestoreConfirmation/res/values-hy/strings.xml
index 285c15d..ca9834e 100644
--- a/packages/BackupRestoreConfirmation/res/values-hy/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values-hy/strings.xml
@@ -32,7 +32,7 @@
     <string name="backup_enc_password_required" msgid="7889652203371654149">"Քանի որ ձեր սարքը գաղտնագրված է, դուք պետք է գաղտնագրեք նաև ձեր պահուստը: Խնդրում ենք ստորև սահմանել գաղտնաբառը՝"</string>
     <string name="restore_enc_password_text" msgid="6140898525580710823">"Եթե ​​վերականգնվող տվյալները գաղտնագրված են, խնդրում ենք մուտքագրել գաղտնաբառը ստորև`"</string>
     <string name="toast_backup_started" msgid="550354281452756121">"Պահուստավորումը սկսվում է..."</string>
-    <string name="toast_backup_ended" msgid="3818080769548726424">"Պահուստավորումն ավարտվեց"</string>
+    <string name="toast_backup_ended" msgid="3818080769548726424">"Պահուստավորումն ավարտված է"</string>
     <string name="toast_restore_started" msgid="7881679218971277385">"Վերականգնումը մեկնարկեց..."</string>
     <string name="toast_restore_ended" msgid="1764041639199696132">"Վերականգնումն ավարտվեց"</string>
     <string name="toast_timeout" msgid="5276598587087626877">"Գործողության ժամանակը սպառվեց"</string>
diff --git a/packages/CaptivePortalLogin/res/values-in/strings.xml b/packages/CaptivePortalLogin/res/values-in/strings.xml
index 7fa3a0a..f9f6481 100644
--- a/packages/CaptivePortalLogin/res/values-in/strings.xml
+++ b/packages/CaptivePortalLogin/res/values-in/strings.xml
@@ -7,6 +7,6 @@
     <string name="action_bar_label" msgid="917235635415966620">"Masuk ke jaringan"</string>
     <string name="action_bar_title" msgid="5645564790486983117">"Login ke %1$s"</string>
     <string name="ssl_error_warning" msgid="6653188881418638872">"Jaringan yang ingin Anda masuki mengalami masalah keamanan."</string>
-    <string name="ssl_error_example" msgid="647898534624078900">"Misalnya, laman masuk mungkin bukan milik organisasi yang ditampilkan."</string>
+    <string name="ssl_error_example" msgid="647898534624078900">"Misalnya, halaman masuk mungkin bukan milik organisasi yang ditampilkan."</string>
     <string name="ssl_error_continue" msgid="6492718244923937110">"Tetap lanjutkan melalui browser"</string>
 </resources>
diff --git a/packages/CtsShim/Android.mk b/packages/CtsShim/Android.mk
index fa6423e..88b85e0 100644
--- a/packages/CtsShim/Android.mk
+++ b/packages/CtsShim/Android.mk
@@ -30,8 +30,11 @@
 # Make sure the build system doesn't try to resign the APK
 LOCAL_CERTIFICATE := PRESIGNED
 LOCAL_DEX_PREOPT := false
+LOCAL_MODULE_TARGET_ARCH := arm arm64 x86 x86_64
 
-LOCAL_SRC_FILES := CtsShimPriv.apk
+my_archs := arm x86
+my_src_arch := $(call get-prebuilt-src-arch, $(my_archs))
+LOCAL_SRC_FILES := apk/$(my_src_arch)/CtsShimPriv.apk
 
 include $(BUILD_PREBUILT)
 
@@ -48,8 +51,11 @@
 # Make sure the build system doesn't try to resign the APK
 LOCAL_CERTIFICATE := PRESIGNED
 LOCAL_DEX_PREOPT := false
+LOCAL_MODULE_TARGET_ARCH := arm arm64 x86 x86_64
 
-LOCAL_SRC_FILES := CtsShim.apk
+my_archs := arm x86
+my_src_arch := $(call get-prebuilt-src-arch, $(my_archs))
+LOCAL_SRC_FILES := apk/$(my_src_arch)/CtsShim.apk
 
 include $(BUILD_PREBUILT)
 
diff --git a/packages/CtsShim/CtsShim.apk b/packages/CtsShim/CtsShim.apk
deleted file mode 100644
index 2728903..0000000
--- a/packages/CtsShim/CtsShim.apk
+++ /dev/null
Binary files differ
diff --git a/packages/CtsShim/CtsShimPriv.apk b/packages/CtsShim/CtsShimPriv.apk
deleted file mode 100644
index 9a8e75c..0000000
--- a/packages/CtsShim/CtsShimPriv.apk
+++ /dev/null
Binary files differ
diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk
new file mode 100644
index 0000000..a911603
--- /dev/null
+++ b/packages/CtsShim/apk/arm/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk
new file mode 100644
index 0000000..845d781
--- /dev/null
+++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk
new file mode 100644
index 0000000..a911603
--- /dev/null
+++ b/packages/CtsShim/apk/x86/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk
new file mode 100644
index 0000000..2fc9a94
--- /dev/null
+++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk
Binary files differ
diff --git a/packages/CtsShim/build/Android.mk b/packages/CtsShim/build/Android.mk
index 21f0afe..ec14d50 100644
--- a/packages/CtsShim/build/Android.mk
+++ b/packages/CtsShim/build/Android.mk
@@ -32,6 +32,9 @@
 
 LOCAL_MANIFEST_FILE := shim_priv_upgrade/AndroidManifest.xml
 
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := libshim_jni
+
 include $(BUILD_PACKAGE)
 my_shim_priv_upgrade_apk := $(LOCAL_BUILT_MODULE)
 
@@ -60,6 +63,9 @@
 
 LOCAL_FULL_MANIFEST_FILE := $(gen)
 
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := libshim_jni
+
 include $(BUILD_PACKAGE)
 
 ###########################################################
@@ -80,6 +86,9 @@
 
 LOCAL_MANIFEST_FILE := shim_priv_upgrade/AndroidManifest.xml
 
+LOCAL_MULTILIB := both
+LOCAL_JNI_SHARED_LIBRARIES := libshim_jni
+
 include $(BUILD_PACKAGE)
 
 
@@ -99,3 +108,5 @@
 
 include $(BUILD_PACKAGE)
 
+###########################################################
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/CtsShim/build/README b/packages/CtsShim/build/README
index 9869377..59af068 100644
--- a/packages/CtsShim/build/README
+++ b/packages/CtsShim/build/README
@@ -6,19 +6,34 @@
 NOTE: The need to include a binary on the system image may be deprecated if a
 solution involving a temporarily writable /system partition is implemented.
 
-build:
-    $ tapas CtsShim CtsShimPriv CtsShimPrivUpgrade CtsShimPrivUpgradeWrongSHA
+For local testing, build the apk and put them in the following folders.
+This is for arm:
+    $ tapas CtsShim CtsShimPriv CtsShimPrivUpgrade CtsShimPrivUpgradeWrongSHA arm64
     $ m
-
-local testing:
     $ cp $OUT/system/priv-app/CtsShimPrivUpgrade/CtsShimPrivUpgrade.apk \
-        cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp
+        cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/apk/arm
+    $ cp $OUT/system/priv-app/CtsShimPrivUpgrade/CtsShimPrivUpgrade.apk \
+        vendor/xts/gts-tests/hostsidetests/packagemanager/app/apk/arm/GtsShimPrivUpgrade.apk
     $ cp $OUT/system/priv-app/CtsShimPrivUpgradeWrongSHA/CtsShimPrivUpgradeWrongSHA.apk \
-        cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp
+        cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/apk/arm
     $ cp $OUT/system/priv-app/CtsShimPriv/CtsShimPriv.apk \
-        frameworks/base/packages/CtsShim
+        frameworks/base/packages/CtsShim/apk/arm
     $ cp $OUT/system/app/CtsShim/CtsShim.apk \
-        frameworks/base/packages/CtsShim
+        frameworks/base/packages/CtsShim/apk/arm
+
+This is for x86:
+    $ tapas CtsShim CtsShimPriv CtsShimPrivUpgrade CtsShimPrivUpgradeWrongSHA x86_64
+    $ m
+    $ cp $OUT/system/priv-app/CtsShimPrivUpgrade/CtsShimPrivUpgrade.apk \
+        cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/apk/x86
+    $ cp $OUT/system/priv-app/CtsShimPrivUpgrade/CtsShimPrivUpgrade.apk \
+        vendor/xts/gts-tests/hostsidetests/packagemanager/app/apk/x86/GtsShimPrivUpgrade.apk
+    $ cp $OUT/system/priv-app/CtsShimPrivUpgradeWrongSHA/CtsShimPrivUpgradeWrongSHA.apk \
+        cts/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/apk/x86
+    $ cp $OUT/system/priv-app/CtsShimPriv/CtsShimPriv.apk \
+        frameworks/base/packages/CtsShim/apk/x86
+    $ cp $OUT/system/app/CtsShim/CtsShim.apk \
+        frameworks/base/packages/CtsShim/apk/x86
 
 For final submission, the APKs should be downloaded from the build server, then
 submitted to the cts/ and frameworks/base/ repos.
diff --git a/packages/CtsShim/build/jni/Android.mk b/packages/CtsShim/build/jni/Android.mk
new file mode 100644
index 0000000..968fc0b
--- /dev/null
+++ b/packages/CtsShim/build/jni/Android.mk
@@ -0,0 +1,27 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libshim_jni
+
+LOCAL_SRC_FILES := Shim.c
+
+LOCAL_SDK_VERSION := 24
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/packages/CtsShim/build/jni/Shim.c b/packages/CtsShim/build/jni/Shim.c
new file mode 100644
index 0000000..44eb316
--- /dev/null
+++ b/packages/CtsShim/build/jni/Shim.c
@@ -0,0 +1,17 @@
+/*
+ * 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 <jni.h>
\ No newline at end of file
diff --git a/packages/CtsShim/build/shim_priv/AndroidManifest.xml b/packages/CtsShim/build/shim_priv/AndroidManifest.xml
index 5195ef7..9bf454c 100644
--- a/packages/CtsShim/build/shim_priv/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim_priv/AndroidManifest.xml
@@ -27,6 +27,7 @@
 
     <application
         android:hasCode="false"
+        android:multiArch="true"
         tools:ignore="AllowBackup,MissingApplicationIcon" >
 
         <!-- These activities don't actually exist; define them just to test the filters !-->
diff --git a/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml b/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml
index b938e3e..023e93e 100644
--- a/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml
+++ b/packages/CtsShim/build/shim_priv_upgrade/AndroidManifest.xml
@@ -24,6 +24,7 @@
 
     <application
         android:hasCode="false"
+        android:multiArch="true"
         tools:ignore="AllowBackup,MissingApplicationIcon" >
 
         <!-- These activities don't actually exist; define them just to test the filters !-->
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 3800e6f..4a771eb 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -16,8 +16,6 @@
 
 package com.android.defcontainer;
 
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
-
 import android.app.IntentService;
 import android.content.Context;
 import android.content.Intent;
@@ -31,21 +29,15 @@
 import android.content.res.ObbInfo;
 import android.content.res.ObbScanner;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.Environment.UserEnvironment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructStatVfs;
 import android.util.Slog;
 
 import com.android.internal.app.IMediaContainerService;
-import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.IParcelFileDescriptorFactory;
 import com.android.internal.util.ArrayUtils;
@@ -72,51 +64,6 @@
 
     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
         /**
-         * Creates a new container and copies package there.
-         *
-         * @param packagePath absolute path to the package to be copied. Can be
-         *            a single monolithic APK file or a cluster directory
-         *            containing one or more APKs.
-         * @param containerId the id of the secure container that should be used
-         *            for creating a secure container into which the resource
-         *            will be copied.
-         * @param key Refers to key used for encrypting the secure container
-         * @return Returns the new cache path where the resource has been copied
-         *         into
-         */
-        @Override
-        public String copyPackageToContainer(String packagePath, String containerId, String key,
-                boolean isExternal, boolean isForwardLocked, String abiOverride) {
-            if (packagePath == null || containerId == null) {
-                return null;
-            }
-
-            if (isExternal) {
-                // Make sure the sdcard is mounted.
-                String status = Environment.getExternalStorageState();
-                if (!status.equals(Environment.MEDIA_MOUNTED)) {
-                    Slog.w(TAG, "Make sure sdcard is mounted.");
-                    return null;
-                }
-            }
-
-            PackageLite pkg = null;
-            NativeLibraryHelper.Handle handle = null;
-            try {
-                final File packageFile = new File(packagePath);
-                pkg = PackageParser.parsePackageLite(packageFile, 0);
-                handle = NativeLibraryHelper.Handle.create(pkg);
-                return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
-                        isForwardLocked, abiOverride);
-            } catch (PackageParserException | IOException e) {
-                Slog.w(TAG, "Failed to copy package at " + packagePath, e);
-                return null;
-            } finally {
-                IoUtils.closeQuietly(handle);
-            }
-        }
-
-        /**
          * Copy package to the target location.
          *
          * @param packagePath absolute path to the package to be copied. Can be
@@ -153,7 +100,6 @@
         public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
                 String abiOverride) {
             final Context context = DefaultContainerService.this;
-            final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
             PackageInfoLite ret = new PackageInfoLite();
             if (packagePath == null) {
@@ -167,7 +113,7 @@
             final long sizeBytes;
             try {
                 pkg = PackageParser.parsePackageLite(packageFile, 0);
-                sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+                sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
             } catch (PackageParserException | IOException e) {
                 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
 
@@ -230,13 +176,13 @@
          *            containing one or more APKs.
          */
         @Override
-        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
-                String abiOverride) throws RemoteException {
+        public long calculateInstalledSize(String packagePath, String abiOverride)
+                throws RemoteException {
             final File packageFile = new File(packagePath);
             final PackageParser.PackageLite pkg;
             try {
                 pkg = PackageParser.parsePackageLite(packageFile, 0);
-                return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+                return PackageHelper.calculateInstalledSize(pkg, abiOverride);
             } catch (PackageParserException | IOException e) {
                 Slog.w(TAG, "Failed to calculate installed size: " + e);
                 return Long.MAX_VALUE;
@@ -292,60 +238,6 @@
         return mBinder;
     }
 
-    private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
-            String newCid, String key, boolean isExternal, boolean isForwardLocked,
-            String abiOverride) throws IOException {
-
-        // Calculate container size, rounding up to nearest MB and adding an
-        // extra MB for filesystem overhead
-        final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
-                isForwardLocked, abiOverride);
-
-        // Create new container
-        final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
-                Process.myUid(), isExternal);
-        if (newMountPath == null) {
-            throw new IOException("Failed to create container " + newCid);
-        }
-        final File targetDir = new File(newMountPath);
-
-        try {
-            // Copy all APKs
-            copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
-            if (!ArrayUtils.isEmpty(pkg.splitNames)) {
-                for (int i = 0; i < pkg.splitNames.length; i++) {
-                    copyFile(pkg.splitCodePaths[i], targetDir,
-                            "split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
-                }
-            }
-
-            // Extract native code
-            final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
-            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                    abiOverride);
-            if (res != PackageManager.INSTALL_SUCCEEDED) {
-                throw new IOException("Failed to extract native code, res=" + res);
-            }
-
-            if (!PackageHelper.finalizeSdDir(newCid)) {
-                throw new IOException("Failed to finalize " + newCid);
-            }
-
-            if (PackageHelper.isContainerMounted(newCid)) {
-                PackageHelper.unMountSdDir(newCid);
-            }
-
-        } catch (ErrnoException e) {
-            PackageHelper.destroySdDir(newCid);
-            throw e.rethrowAsIOException();
-        } catch (IOException e) {
-            PackageHelper.destroySdDir(newCid);
-            throw e;
-        }
-
-        return newMountPath;
-    }
-
     private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
             throws IOException, RemoteException {
         copyFile(pkg.baseCodePath, target, "base.apk");
@@ -373,28 +265,4 @@
             IoUtils.closeQuietly(in);
         }
     }
-
-    private void copyFile(String sourcePath, File targetDir, String targetName,
-            boolean isForwardLocked) throws IOException, ErrnoException {
-        final File sourceFile = new File(sourcePath);
-        final File targetFile = new File(targetDir, targetName);
-
-        Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
-        if (!FileUtils.copyFile(sourceFile, targetFile)) {
-            throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
-        }
-
-        if (isForwardLocked) {
-            final String publicTargetName = PackageHelper.replaceEnd(targetName,
-                    ".apk", ".zip");
-            final File publicTargetFile = new File(targetDir, publicTargetName);
-
-            PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
-
-            Os.chmod(targetFile.getAbsolutePath(), 0640);
-            Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
-        } else {
-            Os.chmod(targetFile.getAbsolutePath(), 0644);
-        }
-    }
 }
diff --git a/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java b/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java
index bbbdf80..8a06cc6 100644
--- a/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java
+++ b/packages/EasterEgg/src/com/android/egg/octo/Ocquarium.java
@@ -26,6 +26,7 @@
 
 public class Ocquarium extends Activity {
     ImageView mImageView;
+    private OctopusDrawable mOcto;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -43,10 +44,9 @@
         bg.addView(mImageView, new FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
 
-        final OctopusDrawable octo = new OctopusDrawable(getApplicationContext());
-        octo.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
-        mImageView.setImageDrawable(octo);
-        octo.startDrift();
+        mOcto = new OctopusDrawable(getApplicationContext());
+        mOcto.setSizePx((int) (OctopusDrawable.randfrange(40f,180f) * dp));
+        mImageView.setImageDrawable(mOcto);
 
         mImageView.setOnTouchListener(new View.OnTouchListener() {
             boolean touching;
@@ -54,24 +54,36 @@
             public boolean onTouch(View view, MotionEvent motionEvent) {
                 switch (motionEvent.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN:
-                        if (octo.hitTest(motionEvent.getX(), motionEvent.getY())) {
+                        if (mOcto.hitTest(motionEvent.getX(), motionEvent.getY())) {
                             touching = true;
-                            octo.stopDrift();
+                            mOcto.stopDrift();
                         }
                         break;
                     case MotionEvent.ACTION_MOVE:
                         if (touching) {
-                            octo.moveTo(motionEvent.getX(), motionEvent.getY());
+                            mOcto.moveTo(motionEvent.getX(), motionEvent.getY());
                         }
                         break;
                     case MotionEvent.ACTION_UP:
                     case MotionEvent.ACTION_CANCEL:
                         touching = false;
-                        octo.startDrift();
+                        mOcto.startDrift();
                         break;
                 }
                 return true;
             }
         });
     }
+
+    @Override
+    protected void onPause() {
+        mOcto.stopDrift();
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mOcto.startDrift();
+    }
 }
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index f54b6fb..291009e 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -42,6 +42,15 @@
             </intent-filter>
         </service>
 
+        <service android:name=".notification.Assistant"
+                 android:label="@string/notification_assistant"
+                 android:permission="android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationAssistantService" />
+            </intent-filter>
+        </service>
+
         <library android:name="android.ext.services"/>
     </application>
 
diff --git a/packages/ExtServices/res/values/strings.xml b/packages/ExtServices/res/values/strings.xml
index 531e517..a2e65bc 100644
--- a/packages/ExtServices/res/values/strings.xml
+++ b/packages/ExtServices/res/values/strings.xml
@@ -16,4 +16,7 @@
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name">Android Services Library</string>
+
+    <string name="notification_assistant">Notification Assistant</string>
+    <string name="prompt_block_reason">Too many dismissals:views</string>
 </resources>
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
new file mode 100644
index 0000000..f535368
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -0,0 +1,165 @@
+/**
+ * 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.ext.services.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEGATIVE;
+
+import android.app.INotificationManager;
+import android.content.Context;
+import android.ext.services.R;
+import android.os.Bundle;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationAssistantService;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Notification assistant that provides guidance on notification channel blocking
+ */
+public class Assistant extends NotificationAssistantService {
+    private static final String TAG = "ExtAssistant";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final ArrayList<Integer> DISMISS_WITH_PREJUDICE = new ArrayList<>();
+    static {
+        DISMISS_WITH_PREJUDICE.add(REASON_CANCEL);
+        DISMISS_WITH_PREJUDICE.add(REASON_LISTENER_CANCEL);
+    }
+
+    // key : impressions tracker
+    // TODO: persist across reboots
+    ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>();
+    // SBN key : channel id
+    ArrayMap<String, String> mLiveNotifications = new ArrayMap<>();
+
+    private Ranking mFakeRanking = null;
+
+    @Override
+    public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
+        if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
+        return null;
+    }
+
+    @Override
+    public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+        if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
+        try {
+            Ranking ranking = getRanking(sbn.getKey(), rankingMap);
+            if (ranking != null && ranking.getChannel() != null) {
+                String key = getKey(
+                        sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId());
+                ChannelImpressions ci = mkeyToImpressions.getOrDefault(key,
+                        new ChannelImpressions());
+                if (ranking.getImportance() > IMPORTANCE_MIN && ci.shouldTriggerBlock()) {
+                    adjustNotification(createNegativeAdjustment(
+                            sbn.getPackageName(), sbn.getKey(), sbn.getUserId()));
+                }
+                mkeyToImpressions.put(key, ci);
+                mLiveNotifications.put(sbn.getKey(), ranking.getChannel().getId());
+            }
+        } catch (Throwable e) {
+            Log.e(TAG, "Error occurred processing post", e);
+        }
+    }
+
+    @Override
+    public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+            NotificationStats stats, int reason) {
+        try {
+            String channelId = mLiveNotifications.remove(sbn.getKey());
+            String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
+            ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, new ChannelImpressions());
+            if (stats.hasSeen()) {
+                ci.incrementViews();
+            }
+            if (DISMISS_WITH_PREJUDICE.contains(reason)
+                    && !sbn.isAppGroup()
+                    && !sbn.getNotification().isGroupChild()
+                    && !stats.hasInteracted()
+                    && stats.getDismissalSurface() != NotificationStats.DISMISSAL_AOD
+                    && stats.getDismissalSurface() != NotificationStats.DISMISSAL_PEEK
+                    && stats.getDismissalSurface() != NotificationStats.DISMISSAL_OTHER) {
+               if (DEBUG) Log.i(TAG, "increment dismissals");
+                ci.incrementDismissals();
+            } else {
+                if (DEBUG) Slog.i(TAG, "reset streak");
+                ci.resetStreak();
+            }
+            mkeyToImpressions.put(key, ci);
+        } catch (Throwable e) {
+            Slog.e(TAG, "Error occurred processing removal", e);
+        }
+    }
+
+    @Override
+    public void onNotificationSnoozedUntilContext(StatusBarNotification sbn,
+            String snoozeCriterionId) {
+    }
+
+    @Override
+    public void onListenerConnected() {
+        if (DEBUG) Log.i(TAG, "CONNECTED");
+        try {
+            for (StatusBarNotification sbn : getActiveNotifications()) {
+                onNotificationPosted(sbn);
+            }
+        } catch (Throwable e) {
+            Log.e(TAG, "Error occurred on connection", e);
+        }
+    }
+
+    private String getKey(String pkg, int userId, String channelId) {
+        return pkg + "|" + userId + "|" + channelId;
+    }
+
+    private Ranking getRanking(String key, RankingMap rankingMap) {
+        if (mFakeRanking != null) {
+            return mFakeRanking;
+        }
+        Ranking ranking = new Ranking();
+        rankingMap.getRanking(key, ranking);
+        return ranking;
+    }
+
+    private Adjustment createNegativeAdjustment(String packageName, String key, int user) {
+        if (DEBUG) Log.d(TAG, "User probably doesn't want " + key);
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
+        return new Adjustment(packageName, key,  signals,
+                getContext().getString(R.string.prompt_block_reason), user);
+    }
+
+    // for testing
+    protected void setFakeRanking(Ranking ranking) {
+        mFakeRanking = ranking;
+    }
+
+    protected void setNoMan(INotificationManager noMan) {
+        mNoMan = noMan;
+    }
+
+    protected void setContext(Context context) {
+        mSystemContext = context;
+    }
+}
\ No newline at end of file
diff --git a/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java b/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
new file mode 100644
index 0000000..30567cc
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/ChannelImpressions.java
@@ -0,0 +1,137 @@
+/**
+ * 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.ext.services.notification;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+public final class ChannelImpressions implements Parcelable {
+    private static final String TAG = "ExtAssistant.CI";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    static final double DISMISS_TO_VIEW_RATIO_LIMIT = .8;
+    static final int STREAK_LIMIT = 2;
+
+    private int mDismissals = 0;
+    private int mViews = 0;
+    private int mStreak = 0;
+
+    public ChannelImpressions() {
+    }
+
+    public ChannelImpressions(int dismissals, int views) {
+        mDismissals = dismissals;
+        mViews = views;
+    }
+
+    protected ChannelImpressions(Parcel in) {
+        mDismissals = in.readInt();
+        mViews = in.readInt();
+        mStreak = in.readInt();
+    }
+
+    public int getStreak() {
+        return mStreak;
+    }
+
+    public int getDismissals() {
+        return mDismissals;
+    }
+
+    public int getViews() {
+        return mViews;
+    }
+
+    public void incrementDismissals() {
+        mDismissals++;
+        mStreak++;
+    }
+
+    public void incrementViews() {
+        mViews++;
+    }
+
+    public void resetStreak() {
+        mStreak = 0;
+    }
+
+    public boolean shouldTriggerBlock() {
+        if (getViews() == 0) {
+            return false;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "should trigger? " + getDismissals() + " " + getViews() + " " + getStreak());
+        }
+        return ((double) getDismissals() / getViews()) > DISMISS_TO_VIEW_RATIO_LIMIT
+                && getStreak() > STREAK_LIMIT;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mDismissals);
+        dest.writeInt(mViews);
+        dest.writeInt(mStreak);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ChannelImpressions> CREATOR = new Creator<ChannelImpressions>() {
+        @Override
+        public ChannelImpressions createFromParcel(Parcel in) {
+            return new ChannelImpressions(in);
+        }
+
+        @Override
+        public ChannelImpressions[] newArray(int size) {
+            return new ChannelImpressions[size];
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ChannelImpressions that = (ChannelImpressions) o;
+
+        if (mDismissals != that.mDismissals) return false;
+        if (mViews != that.mViews) return false;
+        return mStreak == that.mStreak;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mDismissals;
+        result = 31 * result + mViews;
+        result = 31 * result + mStreak;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("ChannelImpressions{");
+        sb.append("mDismissals=").append(mDismissals);
+        sb.append(", mViews=").append(mViews);
+        sb.append(", mStreak=").append(mStreak);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/packages/ExtServices/tests/Android.mk b/packages/ExtServices/tests/Android.mk
index cb3c352..92afbeb 100644
--- a/packages/ExtServices/tests/Android.mk
+++ b/packages/ExtServices/tests/Android.mk
@@ -12,7 +12,8 @@
     mockito-target-minus-junit4 \
     espresso-core \
     truth-prebuilt \
-    legacy-android-test
+    legacy-android-test \
+    testables
 
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
new file mode 100644
index 0000000..4e5e9f9
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/AssistantTest.java
@@ -0,0 +1,268 @@
+/**
+ * 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.ext.services.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.content.Intent;
+import android.ext.services.R;
+import android.os.UserHandle;
+import android.service.notification.Adjustment;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+import android.support.test.InstrumentationRegistry;
+import android.test.ServiceTestCase;
+import android.testing.TestableContext;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AssistantTest extends ServiceTestCase<Assistant> {
+
+    private static final String PKG1 = "pkg1";
+    private static final int UID1 = 1;
+    private static final NotificationChannel P1C1 =
+            new NotificationChannel("one", "", IMPORTANCE_LOW);
+    private static final NotificationChannel P1C2 =
+            new NotificationChannel("p1c2", "", IMPORTANCE_DEFAULT);
+    private static final NotificationChannel P1C3 =
+            new NotificationChannel("p1c3", "", IMPORTANCE_MIN);
+    private static final String PKG2 = "pkg2";
+
+    private static final int UID2 = 2;
+    private static final NotificationChannel P2C1 =
+            new NotificationChannel("one", "", IMPORTANCE_LOW);
+
+    @Mock INotificationManager mNoMan;
+
+    Assistant mAssistant;
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getContext(), null);
+
+    public AssistantTest() {
+        super(Assistant.class);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Intent startIntent =
+                new Intent("android.service.notification.NotificationAssistantService");
+        startIntent.setPackage("android.ext.services");
+        bindService(startIntent);
+        mAssistant = getService();
+        mAssistant.setNoMan(mNoMan);
+    }
+
+    private StatusBarNotification generateSbn(String pkg, int uid, NotificationChannel channel,
+            String tag, String groupKey) {
+        Notification n = new Notification.Builder(mContext, channel.getId())
+                .setContentTitle("foo")
+                .setGroup(groupKey)
+                .build();
+
+        StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 0, tag, uid, uid, n,
+                UserHandle.SYSTEM, null, 0);
+
+        return sbn;
+    }
+
+    private Ranking generateRanking(StatusBarNotification sbn, NotificationChannel channel) {
+        Ranking mockRanking = mock(Ranking.class);
+        when(mockRanking.getChannel()).thenReturn(channel);
+        when(mockRanking.getImportance()).thenReturn(channel.getImportance());
+        when(mockRanking.getKey()).thenReturn(sbn.getKey());
+        when(mockRanking.getOverrideGroupKey()).thenReturn(null);
+        return mockRanking;
+    }
+
+    private void almostBlockChannel(String pkg, int uid, NotificationChannel channel) {
+        for (int i = 0; i < ChannelImpressions.STREAK_LIMIT; i++) {
+            dismissBadNotification(pkg, uid, channel, String.valueOf(i));
+        }
+    }
+
+    private void dismissBadNotification(String pkg, int uid, NotificationChannel channel,
+            String tag) {
+        StatusBarNotification sbn = generateSbn(pkg, uid, channel, tag, null);
+        mAssistant.setFakeRanking(generateRanking(sbn, channel));
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+        mAssistant.setFakeRanking(mock(Ranking.class));
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
+        stats.setSeen();
+        mAssistant.onNotificationRemoved(
+                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
+    }
+
+    @Test
+    public void testNoAdjustmentForInitialPost() throws Exception {
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, null, null);
+
+        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testTriggerAdjustment() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+        dismissBadNotification(PKG1, UID1, P1C1, "trigger!");
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
+        mAssistant.setFakeRanking(generateRanking(sbn, P1C1));
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        ArgumentCaptor<Adjustment> captor = ArgumentCaptor.forClass(Adjustment.class);
+        verify(mNoMan, times(1)).applyAdjustmentFromAssistant(any(), captor.capture());
+        assertEquals(sbn.getKey(), captor.getValue().getKey());
+        assertEquals(Ranking.USER_SENTIMENT_NEGATIVE,
+                captor.getValue().getSignals().getInt(Adjustment.KEY_USER_SENTIMENT));
+    }
+
+    @Test
+    public void testMinCannotTriggerAdjustment() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C3);
+        dismissBadNotification(PKG1, UID1, P1C3, "trigger!");
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C3, "new one!", null);
+        mAssistant.setFakeRanking(generateRanking(sbn, P1C3));
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testGroupCannotTriggerAdjustment() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", "I HAVE A GROUP");
+        mAssistant.setFakeRanking(mock(Ranking.class));
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
+        stats.setSeen();
+        mAssistant.onNotificationRemoved(
+                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
+
+        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testAodCannotTriggerAdjustment() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
+        mAssistant.setFakeRanking(mock(Ranking.class));
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_AOD);
+        stats.setSeen();
+        mAssistant.onNotificationRemoved(
+                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
+
+        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testInteractedCannotTriggerAdjustment() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
+        mAssistant.setFakeRanking(mock(Ranking.class));
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
+        stats.setSeen();
+        stats.setExpanded();
+        mAssistant.onNotificationRemoved(
+                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_CANCEL);
+
+        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testAppDismissedCannotTriggerAdjustment() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C1, "no", null);
+        mAssistant.setFakeRanking(mock(Ranking.class));
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_SHADE);
+        stats.setSeen();
+        mAssistant.onNotificationRemoved(
+                sbn, mock(RankingMap.class), stats, NotificationListenerService.REASON_APP_CANCEL);
+
+        sbn = generateSbn(PKG1, UID1, P1C1, "new one!", null);
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testAppSeparation() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+        dismissBadNotification(PKG1, UID1, P1C1, "trigger!");
+
+        StatusBarNotification sbn = generateSbn(PKG2, UID2, P2C1, "new app!", null);
+        mAssistant.setFakeRanking(generateRanking(sbn, P2C1));
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+
+    @Test
+    public void testChannelSeparation() throws Exception {
+        almostBlockChannel(PKG1, UID1, P1C1);
+        dismissBadNotification(PKG1, UID1, P1C1, "trigger!");
+
+        StatusBarNotification sbn = generateSbn(PKG1, UID1, P1C2, "new app!", null);
+        mAssistant.setFakeRanking(generateRanking(sbn, P1C2));
+        mAssistant.onNotificationPosted(sbn, mock(RankingMap.class));
+
+        verify(mNoMan, never()).applyAdjustmentFromAssistant(any(), any());
+    }
+}
diff --git a/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java b/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
new file mode 100644
index 0000000..a8c9fa3
--- /dev/null
+++ b/packages/ExtServices/tests/src/android/ext/services/notification/ChannelImpressionsTest.java
@@ -0,0 +1,85 @@
+/**
+ * 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.ext.services.notification;
+
+import static android.ext.services.notification.ChannelImpressions.STREAK_LIMIT;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class ChannelImpressionsTest {
+
+    @Test
+    public void testNoResultNoBlock() {
+        ChannelImpressions ci = new ChannelImpressions();
+        assertFalse(ci.shouldTriggerBlock());
+    }
+
+    @Test
+    public void testNoStreakNoBlock() {
+        ChannelImpressions ci = new ChannelImpressions();
+
+        for (int i = 0; i < STREAK_LIMIT - 1; i++) {
+            ci.incrementViews();
+            ci.incrementDismissals();
+        }
+
+        assertFalse(ci.shouldTriggerBlock());
+    }
+
+    @Test
+    public void testNoStreakNoBlock_breakStreak() {
+        ChannelImpressions ci = new ChannelImpressions();
+
+        for (int i = 0; i < STREAK_LIMIT; i++) {
+            ci.incrementViews();
+            ci.incrementDismissals();
+            if (i == STREAK_LIMIT - 1) {
+                ci.resetStreak();
+            }
+        }
+
+        assertFalse(ci.shouldTriggerBlock());
+    }
+
+    @Test
+    public void testStreakBlock() {
+        ChannelImpressions ci = new ChannelImpressions();
+
+        for (int i = 0; i <= STREAK_LIMIT; i++) {
+            ci.incrementViews();
+            ci.incrementDismissals();
+        }
+
+        assertTrue(ci.shouldTriggerBlock());
+    }
+
+    @Test
+    public void testRatio_NoBlockEvenWithStreak() {
+        ChannelImpressions ci = new ChannelImpressions();
+
+        for (int i = 0; i < STREAK_LIMIT; i++) {
+            ci.incrementViews();
+            ci.incrementDismissals();
+            ci.incrementViews();
+        }
+
+        assertFalse(ci.shouldTriggerBlock());
+    }
+}
diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml
index 334952c..08ff4ca 100644
--- a/packages/InputDevices/res/values-da/strings.xml
+++ b/packages/InputDevices/res/values-da/strings.xml
@@ -3,7 +3,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="8016145283189546017">"Inputenheder"</string>
     <string name="keyboard_layouts_label" msgid="6688773268302087545">"Android-tastatur"</string>
-    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelsk (UK)"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Engelsk (Storbritannien)"</string>
     <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"engelsk (USA)"</string>
     <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Engelsk (USA), international stil"</string>
     <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Engelsk (USA), Colemak-stil"</string>
diff --git a/packages/PrintSpooler/res/values-vi/strings.xml b/packages/PrintSpooler/res/values-vi/strings.xml
index a181424..6e8c7a8 100644
--- a/packages/PrintSpooler/res/values-vi/strings.xml
+++ b/packages/PrintSpooler/res/values-vi/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4469836075319831821">"Print Spooler"</string>
-    <string name="more_options_button" msgid="2243228396432556771">"Thêm tùy chọn"</string>
+    <string name="more_options_button" msgid="2243228396432556771">"Tùy chọn khác"</string>
     <string name="label_destination" msgid="9132510997381599275">"Đích"</string>
     <string name="label_copies" msgid="3634531042822968308">"Bản sao"</string>
     <string name="label_copies_summary" msgid="3861966063536529540">"Bản sao:"</string>
diff --git a/packages/PrintSpooler/tests/outofprocess/Android.mk b/packages/PrintSpooler/tests/outofprocess/Android.mk
index 3c02453..149be74 100644
--- a/packages/PrintSpooler/tests/outofprocess/Android.mk
+++ b/packages/PrintSpooler/tests/outofprocess/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4 print-test-util-lib
 
 LOCAL_PACKAGE_NAME := PrintSpoolerOutOfProcessTests
 LOCAL_COMPATIBILITY_SUITE := device-tests
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml
index 4a05f6f..307cc93 100644
--- a/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidManifest.xml
@@ -21,10 +21,12 @@
     <application>
         <uses-library android:name="android.test.runner" />
 
-        <activity android:name=".PrintTestActivity"/>
+        <activity
+            android:name="android.print.test.PrintDocumentActivity"
+            android:theme="@style/NoAnimation" />
 
         <service
-                android:name=".mockservice.MockPrintService"
+                android:name="android.print.test.services.FirstPrintService"
                 android:permission="android.permission.BIND_PRINT_SERVICE">
 
             <intent-filter>
@@ -37,13 +39,15 @@
         </service>
 
         <activity
-                android:name=".mockservice.SettingsActivity"
+                android:name="android.print.test.services.SettingsActivity"
                 android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"
+                android:theme="@style/NoAnimation"
                 android:exported="true">
         </activity>
 
         <activity
-                android:name=".mockservice.AddPrintersActivity"
+                android:name="android.print.test.services.AddPrintersActivity"
+                android:theme="@style/NoAnimation"
                 android:exported="true">
         </activity>
 
diff --git a/packages/PrintSpooler/tests/outofprocess/res/values/themes.xml b/packages/PrintSpooler/tests/outofprocess/res/values/themes.xml
new file mode 100644
index 0000000..49eb257
--- /dev/null
+++ b/packages/PrintSpooler/tests/outofprocess/res/values/themes.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+    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.
+  -->
+<resources>
+    <style name="NoAnimation" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
+</resources>
diff --git a/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml b/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml
index 9eecf45..a6282b1 100644
--- a/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml
+++ b/packages/PrintSpooler/tests/outofprocess/res/xml/printservice.xml
@@ -17,4 +17,4 @@
   -->
 
 <print-service  xmlns:android="http://schemas.android.com/apk/res/android"
-        android:addPrintersActivity="com.android.printspooler.outofprocess.tests.mockservice.AddPrintersActivity" />
+        android:addPrintersActivity="android.print.test.services.AddPrintersActivity" />
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/BasePrintTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/BasePrintTest.java
deleted file mode 100644
index 9a7f362..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/BasePrintTest.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests;
-
-import static android.content.pm.PackageManager.GET_META_DATA;
-import static android.content.pm.PackageManager.GET_SERVICES;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.CancellationSignal;
-import android.os.ParcelFileDescriptor;
-import android.print.PrintAttributes;
-import android.print.PrintDocumentAdapter;
-import android.print.PrintManager;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.uiautomator.UiDevice;
-
-import com.android.printspooler.outofprocess.tests.mockservice.PrintServiceCallbacks;
-import com.android.printspooler.outofprocess.tests.mockservice.PrinterDiscoverySessionCallbacks;
-import com.android.printspooler.outofprocess.tests.mockservice.StubbablePrinterDiscoverySession;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.mockito.stubbing.Answer;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * This is the base class for print tests.
- */
-abstract class BasePrintTest {
-    protected static final long OPERATION_TIMEOUT = 30000;
-    private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
-    private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
-    private static String sDisabledPrintServicesBefore;
-
-    private android.print.PrintJob mPrintJob;
-
-    private static Instrumentation sInstrumentation;
-    private static UiDevice sUiDevice;
-
-    @Rule
-    public ActivityTestRule<PrintTestActivity> mActivityRule =
-            new ActivityTestRule<>(PrintTestActivity.class, false, true);
-
-    /**
-     * Return the UI device
-     *
-     * @return the UI device
-     */
-    public UiDevice getUiDevice() {
-        return sUiDevice;
-    }
-
-    protected static Instrumentation getInstrumentation() {
-        return sInstrumentation;
-    }
-
-    @BeforeClass
-    public static void setUpClass() throws Exception {
-        sInstrumentation = InstrumentationRegistry.getInstrumentation();
-        assumeTrue(sInstrumentation.getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_PRINTING));
-
-        sUiDevice = UiDevice.getInstance(sInstrumentation);
-
-        // Make sure we start with a clean slate.
-        clearPrintSpoolerData();
-
-        disablePrintServices(sInstrumentation.getTargetContext().getPackageName());
-
-        // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
-        // Dexmaker is used by mockito.
-        System.setProperty("dexmaker.dexcache", getInstrumentation()
-                .getTargetContext().getCacheDir().getPath());
-    }
-
-    @AfterClass
-    public static void tearDownClass() throws Exception {
-        enablePrintServices();
-    }
-
-    @Before
-    public void unlockScreen() throws Exception {
-        // Unlock screen.
-        runShellCommand("input keyevent KEYCODE_WAKEUP");
-        runShellCommand("wm dismiss-keyguard");
-    }
-
-    @After
-    public void exitActivities() throws Exception {
-        // Exit print spooler
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
-    }
-
-    protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
-            final PrintAttributes attributes) {
-        // Initiate printing as if coming from the app.
-        getInstrumentation().runOnMainSync(() -> {
-            PrintManager printManager = (PrintManager) getActivity()
-                    .getSystemService(Context.PRINT_SERVICE);
-            mPrintJob = printManager.print("Print job", adapter, attributes);
-        });
-
-        return mPrintJob;
-    }
-
-    protected PrintTestActivity getActivity() {
-        return mActivityRule.getActivity();
-    }
-
-    public static String runShellCommand(String cmd)
-            throws IOException {
-        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(cmd);
-        byte[] buf = new byte[512];
-        int bytesRead;
-        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-        StringBuilder stdout = new StringBuilder();
-        while ((bytesRead = fis.read(buf)) != -1) {
-            stdout.append(new String(buf, 0, bytesRead));
-        }
-        fis.close();
-        return stdout.toString();
-    }
-
-    protected static void clearPrintSpoolerData() throws Exception {
-        assertTrue("failed to clear print spooler data", runShellCommand(
-                String.format("pm clear --user %d %s", CURRENT_USER_ID,
-                        PrintManager.PRINT_SPOOLER_PACKAGE_NAME)).contains(
-                PM_CLEAR_SUCCESS_OUTPUT));
-    }
-
-    /**
-     * Disable all print services beside the ones we want to leave enabled.
-     *
-     * @param packageToLeaveEnabled The package of the services to leave enabled.
-     */
-    private static void disablePrintServices(String packageToLeaveEnabled) throws IOException {
-        Instrumentation instrumentation = getInstrumentation();
-
-        sDisabledPrintServicesBefore = runShellCommand(
-                "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
-
-        Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
-        List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager()
-                .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA);
-
-        StringBuilder builder = new StringBuilder();
-        for (ResolveInfo service : installedServices) {
-            if (packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
-                continue;
-            }
-            if (builder.length() > 0) {
-                builder.append(":");
-            }
-            builder.append(new ComponentName(service.serviceInfo.packageName,
-                    service.serviceInfo.name).flattenToString());
-        }
-
-        runShellCommand(
-                "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
-    }
-
-    /**
-     * Revert {@link #disablePrintServices(String)}
-     */
-    private static  void enablePrintServices() throws IOException {
-        runShellCommand("settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
-                        + sDisabledPrintServicesBefore);
-    }
-
-    @SuppressWarnings("unchecked")
-    protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
-            Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
-            Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
-            Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
-            Answer<Void> onDestroy) {
-        PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
-
-        doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
-        when(callbacks.getSession()).thenCallRealMethod();
-
-        if (onStartPrinterDiscovery != null) {
-            doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
-                    any(List.class));
-        }
-        if (onStopPrinterDiscovery != null) {
-            doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
-        }
-        if (onValidatePrinters != null) {
-            doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
-                    any(List.class));
-        }
-        if (onStartPrinterStateTracking != null) {
-            doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
-                    any(PrinterId.class));
-        }
-        if (onRequestCustomPrinterIcon != null) {
-            doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
-                    any(PrinterId.class), any(CancellationSignal.class),
-                    any(CustomPrinterIconCallback.class));
-        }
-        if (onStopPrinterStateTracking != null) {
-            doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
-                    any(PrinterId.class));
-        }
-        if (onDestroy != null) {
-            doAnswer(onDestroy).when(callbacks).onDestroy();
-        }
-
-        return callbacks;
-    }
-
-    protected PrintServiceCallbacks createMockPrintServiceCallbacks(
-            Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
-            Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
-        final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
-
-        doCallRealMethod().when(service).setService(any(PrintService.class));
-        when(service.getService()).thenCallRealMethod();
-
-        if (onCreatePrinterDiscoverySessionCallbacks != null) {
-            doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
-                    .onCreatePrinterDiscoverySessionCallbacks();
-        }
-        if (onPrintJobQueued != null) {
-            doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
-        }
-        if (onRequestCancelPrintJob != null) {
-            doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
-                    any(PrintJob.class));
-        }
-
-        return service;
-    }
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/PrintTestActivity.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/PrintTestActivity.java
deleted file mode 100644
index 4905a0b..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/PrintTestActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class PrintTestActivity extends Activity {
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-    }
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
index 78a0cac..7ebf93d 100644
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
@@ -30,6 +30,11 @@
 import android.print.PrinterId;
 import android.print.PrinterInfo;
 import android.print.pdf.PrintedPdfDocument;
+import android.print.test.BasePrintTest;
+import android.print.test.services.AddPrintersActivity;
+import android.print.test.services.FirstPrintService;
+import android.print.test.services.PrinterDiscoverySessionCallbacks;
+import android.print.test.services.StubbablePrinterDiscoverySession;
 import android.support.test.filters.LargeTest;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiObject;
@@ -38,11 +43,6 @@
 import android.support.test.uiautomator.Until;
 import android.util.Log;
 
-import com.android.printspooler.outofprocess.tests.mockservice.AddPrintersActivity;
-import com.android.printspooler.outofprocess.tests.mockservice.MockPrintService;
-import com.android.printspooler.outofprocess.tests.mockservice.PrinterDiscoverySessionCallbacks;
-import com.android.printspooler.outofprocess.tests.mockservice.StubbablePrinterDiscoverySession;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -62,10 +62,6 @@
 public class WorkflowTest extends BasePrintTest {
     private static final String LOG_TAG = WorkflowTest.class.getSimpleName();
 
-    private static float sWindowAnimationScaleBefore;
-    private static float sTransitionAnimationScaleBefore;
-    private static float sAnimatiorDurationScaleBefore;
-
     private PrintAttributes.MediaSize mFirst;
     private boolean mSelectPrinter;
     private PrintAttributes.MediaSize mSecond;
@@ -91,7 +87,7 @@
             throws TimeoutException, InterruptedException {
         long startTime = System.currentTimeMillis();
         while (condition.get()) {
-            long timeLeft = OPERATION_TIMEOUT - (System.currentTimeMillis() - startTime);
+            long timeLeft = OPERATION_TIMEOUT_MILLIS - (System.currentTimeMillis() - startTime);
             if (timeLeft < 0) {
                 throw new TimeoutException();
             }
@@ -156,7 +152,7 @@
      */
     private void setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef,
             ArrayList<String> trackedPrinters, PrintAttributes.MediaSize mediaSize) {
-        MockPrintService.setCallbacks(createMockPrintServiceCallbacks(
+        FirstPrintService.setCallbacks(createMockPrintServiceCallbacks(
                 inv -> createMockPrinterDiscoverySessionCallbacks(inv2 -> {
                             synchronized (sessionRef) {
                                 sessionRef[0] = ((PrinterDiscoverySessionCallbacks) inv2.getMock())
@@ -243,7 +239,7 @@
                     callback.onWriteFailed(e.getMessage());
                 }
             }
-        }, null);
+        }, (PrintAttributes) null);
     }
 
     @Parameterized.Parameters
@@ -303,7 +299,7 @@
         } else {
             Log.i(LOG_TAG, "Waiting for error message");
             assertNotNull(getUiDevice().wait(Until.findObject(
-                    By.text("This printer isn't available right now.")), OPERATION_TIMEOUT));
+                    By.text("This printer isn't available right now.")), OPERATION_TIMEOUT_MILLIS));
         }
 
         setPrinter("All printers\u2026");
@@ -316,7 +312,7 @@
                 () -> addPrinter(session[0], "2nd printer", mSecond));
 
         // This executes the observer registered above
-        clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName())
+        clickOn(new UiSelector().text(FirstPrintService.class.getCanonicalName())
                 .resourceId("com.android.printspooler:id/title"));
 
         getUiDevice().pressBack();
@@ -342,7 +338,8 @@
             } else {
                 Log.i(LOG_TAG, "Waiting for error message");
                 assertNotNull(getUiDevice().wait(Until.findObject(
-                        By.text("This printer isn't available right now.")), OPERATION_TIMEOUT));
+                        By.text("This printer isn't available right now.")),
+                        OPERATION_TIMEOUT_MILLIS));
             }
 
             Log.i(LOG_TAG, "Waiting for 1st printer to be not tracked");
@@ -370,7 +367,8 @@
             } else {
                 Log.i(LOG_TAG, "Waiting for error message");
                 assertNotNull(getUiDevice().wait(Until.findObject(
-                        By.text("This printer isn't available right now.")), OPERATION_TIMEOUT));
+                        By.text("This printer isn't available right now.")),
+                        OPERATION_TIMEOUT_MILLIS));
             }
 
             Log.i(LOG_TAG, "Waiting for 1st printer to be tracked");
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/AddPrintersActivity.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/AddPrintersActivity.java
deleted file mode 100644
index 2ea4e7d..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/AddPrintersActivity.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests.mockservice;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-
-import java.util.ArrayList;
-
-public class AddPrintersActivity extends Activity {
-    private static final ArrayList<Runnable> sObservers = new ArrayList<>();
-
-    public static void addObserver(@NonNull Runnable observer) {
-        synchronized (sObservers) {
-            sObservers.add(observer);
-        }
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        synchronized (sObservers) {
-            for (Runnable sObserver : sObservers) {
-                sObserver.run();
-            }
-        }
-
-        finish();
-    }
-
-    public static void clearObservers() {
-        synchronized (sObservers) {
-            sObservers.clear();
-        }
-    }
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/MockPrintService.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/MockPrintService.java
deleted file mode 100644
index 3a23113..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/MockPrintService.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests.mockservice;
-
-public class MockPrintService extends StubbablePrintService {
-
-    private static final Object sLock = new Object();
-
-    private static PrintServiceCallbacks sCallbacks;
-
-    public static void setCallbacks(PrintServiceCallbacks callbacks) {
-        synchronized (sLock) {
-            sCallbacks = callbacks;
-        }
-    }
-
-    @Override
-    protected PrintServiceCallbacks getCallbacks() {
-        synchronized (sLock) {
-            if (sCallbacks != null) {
-                sCallbacks.setService(this);
-            }
-            return sCallbacks;
-        }
-    }
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrintServiceCallbacks.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrintServiceCallbacks.java
deleted file mode 100644
index 07baa0f..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrintServiceCallbacks.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests.mockservice;
-
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-
-public abstract class PrintServiceCallbacks {
-
-    private PrintService mService;
-
-    public PrintService getService() {
-        return mService;
-    }
-
-    public void setService(PrintService service) {
-        mService = service;
-    }
-
-    public abstract PrinterDiscoverySessionCallbacks onCreatePrinterDiscoverySessionCallbacks();
-
-    public abstract void onRequestCancelPrintJob(PrintJob printJob);
-
-    public abstract void onPrintJobQueued(PrintJob printJob);
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrinterDiscoverySessionCallbacks.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrinterDiscoverySessionCallbacks.java
deleted file mode 100644
index 5c1260c..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/PrinterDiscoverySessionCallbacks.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests.mockservice;
-
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-
-import java.util.List;
-
-public abstract class PrinterDiscoverySessionCallbacks {
-
-    private StubbablePrinterDiscoverySession mSession;
-
-    public void setSession(StubbablePrinterDiscoverySession session) {
-        mSession = session;
-    }
-
-    public StubbablePrinterDiscoverySession getSession() {
-        return mSession;
-    }
-
-    public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
-
-    public abstract void onStopPrinterDiscovery();
-
-    public abstract void onValidatePrinters(List<PrinterId> printerIds);
-
-    public abstract void onStartPrinterStateTracking(PrinterId printerId);
-
-    public abstract void onRequestCustomPrinterIcon(PrinterId printerId,
-            CancellationSignal cancellationSignal, CustomPrinterIconCallback callback);
-
-    public abstract void onStopPrinterStateTracking(PrinterId printerId);
-
-    public abstract void onDestroy();
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrintService.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrintService.java
deleted file mode 100644
index be9d19b..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrintService.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests.mockservice;
-
-import android.printservice.PrintJob;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-
-public abstract class StubbablePrintService extends PrintService {
-
-    @Override
-    public PrinterDiscoverySession onCreatePrinterDiscoverySession() {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            return new StubbablePrinterDiscoverySession(this,
-                    getCallbacks().onCreatePrinterDiscoverySessionCallbacks());
-        }
-        return null;
-    }
-
-    @Override
-    public void onRequestCancelPrintJob(PrintJob printJob) {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            callbacks.onRequestCancelPrintJob(printJob);
-        }
-    }
-
-    @Override
-    public void onPrintJobQueued(PrintJob printJob) {
-        PrintServiceCallbacks callbacks = getCallbacks();
-        if (callbacks != null) {
-            callbacks.onPrintJobQueued(printJob);
-        }
-    }
-
-    protected abstract PrintServiceCallbacks getCallbacks();
-}
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrinterDiscoverySession.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrinterDiscoverySession.java
deleted file mode 100644
index a828cd6..0000000
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/mockservice/StubbablePrinterDiscoverySession.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2016 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.printspooler.outofprocess.tests.mockservice;
-
-import android.os.CancellationSignal;
-import android.print.PrinterId;
-import android.printservice.CustomPrinterIconCallback;
-import android.printservice.PrintService;
-import android.printservice.PrinterDiscoverySession;
-import android.support.annotation.NonNull;
-
-import java.util.List;
-
-public class StubbablePrinterDiscoverySession extends PrinterDiscoverySession {
-    private final PrintService mService;
-    private final PrinterDiscoverySessionCallbacks mCallbacks;
-
-    public StubbablePrinterDiscoverySession(PrintService service,
-            PrinterDiscoverySessionCallbacks callbacks) {
-        mService = service;
-        mCallbacks = callbacks;
-        if (mCallbacks != null) {
-            mCallbacks.setSession(this);
-        }
-    }
-
-    public PrintService getService() {
-        return mService;
-    }
-
-    @Override
-    public void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
-        if (mCallbacks != null) {
-            mCallbacks.onStartPrinterDiscovery(priorityList);
-        }
-    }
-
-    @Override
-    public void onStopPrinterDiscovery() {
-        if (mCallbacks != null) {
-            mCallbacks.onStopPrinterDiscovery();
-        }
-    }
-
-    @Override
-    public void onValidatePrinters(@NonNull List<PrinterId> printerIds) {
-        if (mCallbacks != null) {
-            mCallbacks.onValidatePrinters(printerIds);
-        }
-    }
-
-    @Override
-    public void onStartPrinterStateTracking(@NonNull PrinterId printerId) {
-        if (mCallbacks != null) {
-            mCallbacks.onStartPrinterStateTracking(printerId);
-        }
-    }
-
-    @Override
-    public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull CustomPrinterIconCallback callback) {
-        if (mCallbacks != null) {
-            mCallbacks.onRequestCustomPrinterIcon(printerId, cancellationSignal, callback);
-        }
-    }
-
-    @Override
-    public void onStopPrinterStateTracking(@NonNull PrinterId printerId) {
-        if (mCallbacks != null) {
-            mCallbacks.onStopPrinterStateTracking(printerId);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (mCallbacks != null) {
-            mCallbacks.onDestroy();
-        }
-    }
-}
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
new file mode 100644
index 0000000..4211c27
--- /dev/null
+++ b/packages/SettingsLib/OWNERS
@@ -0,0 +1,21 @@
+# People who can approve changes for submission
+asapperstein@google.com
+asargent@google.com
+dehboxturtle@google.com
+dhnishi@google.com
+dling@google.com
+dsandler@google.com
+evanlaird@google.com
+jackqdyulei@google.com
+jmonk@google.com
+mfritze@google.com
+nicoya@google.com
+rogerxue@google.com
+virgild@google.com
+zhfan@google.com
+
+# Emergency approvers in case the above are not available
+miket@google.com
+
+# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
+per-file *.xml=*
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_bt_laptop.xml b/packages/SettingsLib/res/drawable/ic_bt_laptop.xml
new file mode 100644
index 0000000..029e4d9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_bt_laptop.xml
@@ -0,0 +1,26 @@
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4c-1.1,0
+            -2,0.9 -2,2v10c0,1.1 0.9,2 2,2H0v2h24v-2h-4zM4,6h16v10H4V6z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml b/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml
new file mode 100644
index 0000000..6e32e1a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_settings_bluetooth.xml
@@ -0,0 +1,25 @@
+<!--
+    Copyright (C) 2016 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M13.5,12l3.8,-3.7c0.4,-0.4 0.4,-1.1 0,-1.5l-4.5,-4.5c-0.4,-0.4 -1.1,-0.4 -1.5,0.1C11.1,2.5 11,2.8 11,3v6.4L6.9,5.4C6.5,5 5.9,5 5.5,5.4s-0.4,1.1 0,1.5l5.1,5.1l-5.1,5.1c-0.4,0.4 -0.4,1.1 0,1.5s1.1,0.4 1.5,0l4.1,-4V21c0,0.6 0.5,1 1,1c0.3,0 0.5,-0.1 0.7,-0.3l0.1,0l4.5,-4.5c0.4,-0.4 0.4,-1.1 0,-1.5L13.5,12zM13,9.7V5.4l2.1,2.2L13,9.7zM13,18.6v-4.3l2.1,2.2L13,18.6z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_settings_print.xml b/packages/SettingsLib/res/drawable/ic_settings_print.xml
new file mode 100644
index 0000000..0eab402
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_settings_print.xml
@@ -0,0 +1,28 @@
+<!--
+    Copyright (C) 2016 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19,8H5c-1.66,0-3,1.34-3,3v5c0,0.55,0.45,1,1,1h3v3c0,0.55,0.45,1,1,1h10c0.55,0,1-0.45,1-1v-3h3c0.55,0,1-0.45,1-1v-5
+C22,9.34,20.66,8,19,8z M16,19H8v-5h8V19z
+M19,12c-0.55,0-1-0.45-1-1s0.45-1,1-1s1,0.45,1,1S19.55,12,19,12z M17,3H7
+C6.45,3,6,3.45,6,4v3h12V4C18,3.45,17.55,3,17,3z"/>
+</vector>
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 0e0d9ae..145060b 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-oudio-LDAC-kodek: Speelgehalte"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Kies Bluetooth-oudio-LDAC-kodek:\nSpeelgehalte"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Stroming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS oor TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Indien dit geaktiveer is, probeer DNS oor TLS op poort 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Wys opsies vir draadlose skermsertifisering"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Verhoog Wi-Fi-aantekeningvlak, wys per SSID RSSI in Wi‑Fi-kieser"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Wanneer dit geaktiveer is, sal Wi-Fi die dataverbinding aggressiewer na mobiel oordra wanneer die Wi-Fi-sein swak is"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Niks"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Wag vir ontfouter"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Ontfoutde program wag vir ontfouter om te heg voordat dit uitgevoer word"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefonie-monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telefonie-monitor sal loglêers insamel wanneer dit \'n probleem met telefonie-/modemfunksionaliteit bespeur en \'n kennisgewing aan die gebruiker stuur om \'n fout in te dien"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Invoer"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Skets"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardeware-versnelde lewering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Kon nie instellings vir <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> oopmaak nie"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Die invoermetode kan dalk alle teks wat jy invoer, versamel, insluitend persoonlike data soos wagwoorde en kredietkaartnommers. Dit kom van die program <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Wil jy dié invoermetode gebruik?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Let wel: Ná \'n herselflaai kan hierdie program nie begin voordat jy jou foon ontsluit het nie"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-registrasiestaat"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Geregistreer"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nie geregistreer nie"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Onbeskikbaar"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 017722f..0abf21b 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"የብሉቱዝ ኦዲዮ LDAC ኮዴክ ይምረጡ፦ የመልሶ ማጫወት ጥራት"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"የብሉቱዝ ኦዲዮ LDAC ኮዴክ ይምረጡ፦\nየመልሶ ማጫወት ጥራት"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ዥረት፦ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS በTLS ላይ"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ከነቃ በወደብ 853 ላይ DNS በTLS ላይ ይሞክሩት።"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"የገመድ አልባ ማሳያ እውቅና ማረጋገጫ አማራጮችን አሳይ"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"የWi‑Fi ምዝግብ ማስታወሻ አያያዝ ደረጃ ጨምር፣ በWi‑Fi መምረጫ ውስጥ በአንድ SSID RSSI አሳይ"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ሲነቃ የWi‑Fi ምልክት ዝቅተኛ ሲሆን Wi‑Fi የውሂብ ግንኙነት ለሞባይል ማስረከብ ላይ ይበልጥ አስገዳጅ ይሆናል"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ምንም"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ስህተት ማስወገጃውን ጠብቅ"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ስህተቱ የተወገደለት መተግበሪያ ከመፈጸሙ በፊት የስህተት ማስወገጃው እስኪያያዝ ድረስ እየጠበቀው ነው"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"የቴሌፎኒ መከታተያ"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor በቴሌፎኒ/ሞደም ተግባር ላይ ችግር እንዳለ ሲያገኝ የምዝግብ ማስታወሻዎችን ይሰበስብና ተጠቃሚው ሳንካ እንዲያስመዘግቡ በማሳወቂያ ይጠይቃቸዋል"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ግብዓት"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ስዕል"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"የተፋጠነ የሃርድዌር አሰጣጥ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"የ<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> ቅንብሮች መክፈት አልተሳካም"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ይህ ግቤት ስልት የሚትተይበውን ፅሁፍ ሁሉ፣  እንደይለፍ ቃል እና የብድር ካርድ ጨምሮ የግል ውሂብ ምናልባት መሰብሰብ ይችላል። ከትግበራው ይመጣል። <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ይህን ግቤት ስልትይጠቀም?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ማስታወሻ፦ እንደገና ከማስነሳት በኋላ ይህ መተግበሪያ ስልክዎን እስከሚከፍቱት ድረስ ሊጀምር አይችልም"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"የIMS ምዝገባ ቀን"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"የተመዘገበ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"አልተመዘገበም"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"አይገኝም"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index 2c39566..2dc8dc9 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"‏برنامج ترميز LDAC لصوت البلوتوث: جودة التشغيل"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"‏اختيار برنامج ترميز LDAC لصوت البلوتوث:\nجودة التشغيل"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"البث: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"نظام أسماء النطاقات عبر طبقة النقل الآمنة"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"عند تمكينه، يمكن استخدام نظام أسماء النطاقات عبر طبقة النقل الآمنة على المنفذ 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"عرض خيارات شهادة عرض شاشة لاسلكي"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"‏زيادة مستوى تسجيل Wi-Fi، وعرض لكل SSID RSSI في منتقي Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"‏عند تمكينه، سيكون Wi-Fi أكثر حدة في تسليم اتصال البيانات إلى الجوّال، وذلك عندما تكون إشارة WiFi منخفضة"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"لا شيء"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"انتظار برنامج التصحيح"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ينتظر التطبيق قيد التصحيح انضمام برنامج التصحيح قبل التنفيذ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"خدمة مراقبة الاتصالات الهاتفية"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"ستجمع خدمة مراقبة الاتصالات الهاتفية سجلات عند اكتشاف مشكلة متعلقة بوظائف الاتصالات الهاتفية أو المودم، وإرسال إشعار إلى المستخدم لإرسال تقرير بالخطأ"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"الإدخال"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"رسم"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"عرض تسارع الأجهزة"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"تعذّر فتح الإعدادات لـ <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"يمكن أن يكون أسلوب الإدخال هذا قادرًا على جمع كل النصوص التي تكتبها، بما في ذلك البيانات الشخصية مثل كلمات المرور وأرقام بطاقات الائتمان. يتم الحصول على هذا الأسلوب من التطبيق <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. هل تريد استخدام أسلوب الإدخال هذا؟"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ملاحظة: بعد إعادة التشغيل، يتعذر بدء هذا التطبيق إلى أن تلغي قفل هاتفك."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"‏حالة تسجيل IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"مُسجَّل"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"غير مُسجَّل"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"غير متاح"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 33e9e85..0ae9025 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Kodeki:Oxutma Keyfiyyəti"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Audio LDAC Kodek:\nOxutma Keyfiyyəti Seçin"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Canlı yayım: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS ilə DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Aktiv edilərsə, 853 nömrəli portda TLS ilə DNS-i sınayın."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Simsiz displey sertifikatlaşması üçün seçimləri göstərir"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi giriş səviyyəsini qaldırın, Wi‑Fi seçəndə hər SSID RSSI üzrə göstərin"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Aktiv edildikdə, Wi-Fi siqnalı zəif olan zaman, data bağlantısını mobilə ötürərəkən Wi-Fi daha aqressiv olacaq"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Heç nə"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Sazlamanı gözləyin"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Sazlanmış tətbiq icradan əvvəl qoşulmaq üçün sazlayıcı gözləyir"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefoniya Monitoru"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telefoniya Monitoru telefoniya/modem funksionallığı ilə bağlı problem aşkar etdikdə qeydiyyatları toplayır və baq haqqında istifadəçiyə bildiriş göndərir"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Daxiletmə"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Təsvir"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Avadanlıq qaldırma renderi"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> üçün ayarları açmaq alınmadı"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Bu daxiletmə metodu yazdığınız bütün mətni toplaya bilər. Buna kredit kart kimi şəxsi məlumat aid ola bilər. Kökü <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> tətbiqindədir. Bu daxiletmə metodu istifadə olunsun?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Qeyd: Yenidən yüklənmədən sonra, bu cihazın kilidini açmamış tətbiq başlaya bilməz"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS qeydiyyat statusu"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Qeydiyyatlı"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Qeydiyyatsız"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Əlçatmazdır"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 48e8e6d..ef4e48e 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audio kodek LDAC: kvalitet reprodukcije"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Izaberite Bluetooth audio kodek LDAC:\nkvalitet reprodukcije"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strimovanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS preko TLS-a"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ako je omogućeno, probajte DNS preko TLS-a na portu 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Prikaz opcija za sertifikaciju bežičnog ekrana"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećava nivo evidentiranja za Wi‑Fi. Prikaz po SSID RSSI-u u biraču Wi‑Fi mreže"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kad se omogući, Wi‑Fi će biti agresivniji pri prebacivanju mreže za prenos podataka na mobilnu ako je Wi‑Fi signal slab"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nijedna"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Sačekaj program za otklanjanje grešaka"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikacija čeka program za otklanjanje grešaka da priloži pre izvršavanja"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"TelephonyMonitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor će prikupljati evidenciju kada otkrije problem sa funkcionisanjem telefonije/modema i zatražiće od korisnika da prijavi grešku"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Unos"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Crtanje"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardverski ubrzano prikazivanje"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Otvaranje podešavanja za aplikaciju <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> nije uspelo"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ovaj metod unosa možda može da prikuplja sav tekst koji unosite, uključujući lične podatke, kao što su lozinke i brojevi kreditnih kartica. Potiče od aplikacije <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Želite li da koristite ovaj metod unosa?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Napomena: Posle restartovanja ova aplikacija ne može da se pokrene dok ne otključate telefon"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Status IMS registracije"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrovan je"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nije registrovan"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nedostupno"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 551a86d..e855f9b 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Аўдыякодэк Bluetooth LDAC: якасць прайгравання"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Выбраць аўдыякодэк Bluetooth LDAC:\nякасць прайгравання"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Перадача плынню: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS праз TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Калі магчыма, паспрабаваць DNS праз TLS на порце 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Паказаць опцыі сертыфікацыі бесправаднога дысплея"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Падвыс. узровень дэтал-цыі журнала Wi‑Fi у залежн. ад SSID RSSI у Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Калі гэта функцыя ўключана, Wi-Fi будзе больш інтэнсіўна імкнуцца перайсці на падключ. маб. перад. даных пры слабым сігнале Wi‑Fi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Нічога"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Пачакайце адладчык"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Адладжанае прыкладанне чакае далучэння да iнструмента для адладкi перад працай"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Сродак адсочвання тэлефаніі"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Праграма TelephonyMonitor будзе заносіць у журналы выпадкі выяўлення праблем, што датычацца фукнцый тэлефаніі/мадэма, а таксама паказваць карыстальніку адпаведнае апавяшчэнне з прапановай адпраўкі паведамлення пра памылку"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Увод"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Чарцёж"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Апаратнае паскарэнне рэндэрынгу"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Не атрымалася адкрыць параметры <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Гэты метад уводу можа збіраць увесь тэкст, які ўводзіцца, у тым лiку такiя персанальныя дадзеныя, як паролі і нумары крэдытных карт. Ён выкарыстоўваецца прыкладаннем <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Выкарыстоўваць гэты метад уводу?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Заўвага. Пасля перазагрузкі гэта праграма не зможа запусціцца, пакуль вы не разблакіруеце тэлефон"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Стан рэгістрацыі IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Зарэгістраваны"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Не зарэгістраваны"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Адсутнічае"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 802e66e..d8b650a 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек за звука през Bluetooth с технологията LDAC: Качество на възпроизвеждане"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изберете кодек за звука през Bluetooth с технологията LDAC:\nКачество на възпроизвеждане"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Поточно предаване: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS през TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ако опцията е активирана, изпробвайте DNS през TLS на порт 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показване на опциите за сертифициране на безжичния дисплей"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"По-подробно регистр. на Wi‑Fi – данни за RSSI на SSID в инстр. за избор на Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"При активиране предаването на връзката за данни от Wi-Fi към мобилната мрежа ще е по-агресивно, когато сигналът за Wi-Fi е слаб"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Няма"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Изчакване на инструмента за отстраняване на грешки"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Прил. изчаква инстр. за отстраняване на грешки да се прикачи преди изпълнение"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Наблюдение на телефонията"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Функцията за наблюдение на телефонията ще събира регистрационни файлове, когато установи проблем с функционалността на телефонията/модема, и ще изпрати на потребителя известие с подкана да подаде сигнал за програмна грешка"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Въвеждане"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Начертаване"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Хардуерно ускорено изобразяване"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Отварянето на настройките за <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> не бе успешно"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Този метод за въвеждане може да събира целия въведен от вас текст, включително лични данни като пароли и номера на кредитни карти. Той произлиза от приложението <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Искате ли да го използвате?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Забележка: След рестартиране това приложение не може да се стартира, докато не отключите телефона си"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Състояние на регистрацията за незабавни съобщения"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Регистрирано"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Не е регистрирано"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Няма данни"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 9e2ef59..24a9084 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ব্লুটুথ অডিও LDAC কোডেক: প্লেব্যাক গুণমান"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ব্লুটুথ অডিও LDAC কোডেক বেছে নিন:\nপ্লেব্যাক গুণমান"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"স্ট্রিমিং: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS এ ডিএনএস"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"যদি সক্ষম করা থাকে তাহলে পোর্ট ৮৫৩ তে TLS এ ডিএনএস এর চেষ্টা করুন।"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ওয়্যারলেস প্রদর্শন সার্টিফিকেশন জন্য বিকল্পগুলি দেখান"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ওয়াই-ফাই লগিং স্তর বাড়ান, ওয়াই-ফাই চয়নকারীতে SSID RSSI অনুযায়ী দেখান"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"সক্ষম করা থাকলে, ওয়াই ফাই সিগন্যালের মান খারাপ হলে ডেটা সংযোগ মোবাইলের কাছে হস্তান্তর করার জন্য ওয়াই ফাই আরো বেশি তৎপর হবে।"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"কিছুই না"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ডিবাগারের জন্য অপেক্ষা করুন"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"চালানোর আগে সংযুক্ত করতে জন্য ডিবাগ করা অ্যাপ্লিকেশনটি ডিবাগারের জন্য অপেক্ষা করছে"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"টেলিফোনি মনিটর"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"টেলিফোনি মনিটর টেলিফোনি/মোডেমের কার্যকারিতায় কোনও সমস্যা শনাক্ত করলে সমস্যাটি লগ করবে এবং সমস্যাটি জানাতে একটি বাগ ফাইল করার জন্য ব্যবহারকারিকে বিজ্ঞপ্তি পাঠাবে"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ইনপুট"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"অঙ্কন"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"হার্ডওয়্যার দ্বারা চালিত রেন্ডারিং"</string>
@@ -289,7 +289,7 @@
     <string name="transition_animation_scale_title" msgid="387527540523595875">"ট্র্যানজিশন অ্যানিমেশন স্কেল"</string>
     <string name="animator_duration_scale_title" msgid="3406722410819934083">"অ্যানিমেটর সময়কাল স্কেল"</string>
     <string name="overlay_display_devices_title" msgid="5364176287998398539">"গৌণ প্রদর্শনগুলি নকল করুন"</string>
-    <string name="debug_applications_category" msgid="4206913653849771549">"অ্যাপ্লিকেশানগুলি"</string>
+    <string name="debug_applications_category" msgid="4206913653849771549">"অ্যাপ"</string>
     <string name="immediately_destroy_activities" msgid="1579659389568133959">"কার্যকলাপ রাখবেন না"</string>
     <string name="immediately_destroy_activities_summary" msgid="3592221124808773368">"ব্যবহারকারী এটি ছেড়ে যাওয়ার পরে যত তাড়াতাড়ি সম্ভব প্রতিটি কার্যকলাপ ধ্বংস করুন"</string>
     <string name="app_process_limit_title" msgid="4280600650253107163">"পশ্চাদপট প্রক্রিয়ার সীমা"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> জন্য সেটিংস খুলতে ব্যর্থ হয়েছে"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"এই ইনপুট পদ্ধতিটি হয়তো পাসওয়ার্ড এবং ক্রেডিট কার্ড নম্বর সহ আপনার টাইপ করা সমস্ত টেক্সট সংগ্রহ করতে সক্ষম হতে পারে। এটি <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> অ্যাপ থেকে এসেছে। এই ইনপুট পদ্ধতিটি ব্যবহার করবেন?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"দ্রষ্টব্য: পুনরায় চালু করার পরে, আপনি আপনার ফোন আনলক না করা পর্যন্ত এই অ্যাপটিকে চালু করতে পারবেন না"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS রেজিস্ট্রেশনের স্থিতি"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"রেজিস্টার করা"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"রেজিস্টার করা নয়"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"অনুপলব্ধ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index d16ec3c..ca610f7 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -71,7 +71,7 @@
     <string name="bluetooth_profile_pbap" msgid="5372051906968576809">"Dijeljenje kontakta"</string>
     <string name="bluetooth_profile_pbap_summary" msgid="6605229608108852198">"Koristi za dijeljenje kontakta"</string>
     <string name="bluetooth_profile_pan_nap" msgid="8429049285027482959">"Dijeljenje internet veze"</string>
-    <string name="bluetooth_profile_map" msgid="1019763341565580450">"Tekstualne poruke"</string>
+    <string name="bluetooth_profile_map" msgid="1019763341565580450">"SMS-ovi"</string>
     <string name="bluetooth_profile_sap" msgid="5764222021851283125">"Pristup SIM-u"</string>
     <string name="bluetooth_profile_a2dp_high_quality" msgid="5444517801472820055">"HD audio: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
     <string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="8510588052415438887">"HD audio"</string>
@@ -110,7 +110,7 @@
     <string name="process_kernel_label" msgid="3916858646836739323">"Android OS"</string>
     <string name="data_usage_uninstalled_apps" msgid="614263770923231598">"Uklonjene aplikacije"</string>
     <string name="data_usage_uninstalled_apps_users" msgid="7986294489899813194">"Uklonjene aplikacije i korisnici"</string>
-    <string name="tether_settings_title_usb" msgid="6688416425801386511">"USB dijeljenje veze"</string>
+    <string name="tether_settings_title_usb" msgid="6688416425801386511">"Povezivanje mobitela USB-om"</string>
     <string name="tether_settings_title_wifi" msgid="3277144155960302049">"Prijenosna pristupna tačka"</string>
     <string name="tether_settings_title_bluetooth" msgid="355855408317564420">"Dijeljenje Bluetooth veze"</string>
     <string name="tether_settings_title_usb_bluetooth" msgid="5355828977109785001">"Dijeljenje veze"</string>
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC kodek: Kvalitet reprodukcije"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Odaberite Bluetooth Audio LDAC kodek:\nKvalitet reprodukcije"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Prijenos: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS preko TLS-a"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ako je opcija omogućena, pokušaj DNS preko TLS-a na priključku 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži opcije za certifikaciju Bežičnog prikaza"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećajte nivo Wi-Fi zapisivanja, pokazati po SSID RSSI Wi-Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kada je omogućeno, Wi-Fi veza će u slučaju slabog signala agresivnije predavati vezu za prijenos podataka na mobilnu vezu"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ništa"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Pričekajte na program za otklanjanje grešaka"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikacija u kojoj se otklanjaju greške čeka da se priloži program za otklanjanje grešaka prije izvršavanja"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Nadzor telefonije"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Nadzor telefonije će prikupiti zapisnike kada otkrije problem u funkcioniranju telefona/modema i obavijestiti korisnika da prijavi grešku"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ulaz"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Crtanje"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Prikaz s hardverskom akceleracijom"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nije uspjelo otvaranje postavki za <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ovaj način unosa može prikupiti sav tekst koji otkucate, uključujući lične podatke kao što su lozinke i brojevi kreditnih kartica. Način omogućava aplikacija <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Da li želite koristiti ovaj način unosa?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Napomena: Nakon ponovnog pokretanja, ova aplikacija se neće moći pokrenuti dok ne otključate telefon"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stanje IMS registracije"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrirano"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nije registrirano"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nije dostupno"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index ded9352..0ee111a 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Còdec LDAC d\'àudio per Bluetooth: qualitat de reproducció"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecciona el còdec LDAC d\'àudio per Bluetooth:\nQualitat de reproducció"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"S\'està reproduint en temps real: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS per TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Si s\'ha activat, prova d\'utilitzar DNS per TLS al port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra les opcions de certificació de pantalla sense fil"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Augmenta nivell de registre Wi‑Fi i mostra\'l per SSID RSSI al Selector de Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Quan s\'activa, la Wi-Fi és més agressiva en transferir la connexió de dades al mòbil quan el senyal de la Wi-Fi sigui dèbil"</string>
@@ -234,8 +236,8 @@
     <string name="verify_apps_over_usb_title" msgid="4177086489869041953">"Verifica aplicacions per USB"</string>
     <string name="verify_apps_over_usb_summary" msgid="9164096969924529200">"Comprova les aplicacions instal·lades mitjançant ADB/ADT per detectar possibles comportaments perillosos"</string>
     <string name="bluetooth_show_devices_without_names_summary" msgid="2351196058115755520">"Es mostraran els dispositius Bluetooth sense el nom (només l\'adreça MAC)"</string>
-    <string name="bluetooth_disable_absolute_volume_summary" msgid="6031284410786545957">"Desactiva la funció de volum absolut de Bluetooth en cas que es produeixin problemes de volum amb dispositius remots, com ara un volum massa alt o una manca de control."</string>
-    <string name="bluetooth_enable_inband_ringing_summary" msgid="2787866074741784975">"Permet que els sons de trucada del telèfon es reprodueixin en auriculars amb Bluetooth"</string>
+    <string name="bluetooth_disable_absolute_volume_summary" msgid="6031284410786545957">"Desactiva la funció de volum absolut del Bluetooth en cas que es produeixin problemes de volum amb dispositius remots, com ara un volum massa alt o una manca de control."</string>
+    <string name="bluetooth_enable_inband_ringing_summary" msgid="2787866074741784975">"Permet que els sons de trucada del telèfon es reprodueixin en auriculars Bluetooth"</string>
     <string name="enable_terminal_title" msgid="95572094356054120">"Terminal local"</string>
     <string name="enable_terminal_summary" msgid="67667852659359206">"Activa l\'aplicació de terminal que ofereix accés al shell local"</string>
     <string name="hdcp_checking_title" msgid="8605478913544273282">"Comprovació HDCP"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Cap"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Espera el depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Abans d\'executar-se, l\'aplicació de depuració espera que es connecti el depurador"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor de telefonia"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Quan el monitor de telefonia detecta un problema amb la funció de telefonia/mòdem, recopila registres i mostra una notificació a l\'usuari perquè informi de l\'error"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Dibuix"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Renderització accelerada per maquinari"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"No s\'ha pogut obrir la configuració de: <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Pot ser que aquest mètode d\'introducció pugui recopilar tot el que escriviu, incloses dades personals, com ara contrasenyes i números de targetes de crèdit. Ve de l\'aplicació <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Voleu utilitzar aquest mètode d\'introducció?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: després de reiniciar, l\'aplicació no s\'iniciarà fins que no desbloquegis el telèfon"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estat del registre d\'IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrat"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Sense registrar"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"No disponible"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 5afa00c..60bbbf3 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek Bluetooth Audio LDAC: Kvalita přehrávání"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vyberte kodek Bluetooth Audio LDAC:\nKvalita přehrávání"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamování: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS přes TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Pokud tuto možnost aktivujete, zařízení se bude připojovat k serverům DNS pomocí protokolu TLS na portu 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Zobrazit možnosti certifikace bezdrátového displeje"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zvýšit úroveň protokolování Wi‑Fi zobrazenou v SSID a RSSI při výběru sítě Wi‑Fi."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Pokud je tato možnost zapnuta, bude síť Wi-Fi při předávání datového připojení mobilní síti při slabém signálu Wi-Fi agresivnější."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nic"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Počkat na ladicí program"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikace čeká na připojení ladicího programu"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Služba Telephony Monitor bude zaznamenávat protokoly problémů s funkcemi telefonu či modemu a zobrazí uživateli oznámení, že je třeba vyplnit hlášení o chybě"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Vstup"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Vykreslování"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardwarově urychlené vykreslování"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nastavení aplikace <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> se nepodařilo otevřít"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Prostřednictvím této vstupní metody zadávání dat lze shromažďovat zadaný text včetně osobních údajů, jako jsou hesla a čísla platebních karet. Metoda je poskytována aplikací <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Chcete tuto metodu zadávání dat použít?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Poznámka: Po restartování se tato aplikace nespustí, dokud telefon neodemknete."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stav registrace IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrováno"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Neregistrováno"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Není k dispozici"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 275822c..089c43c 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-codec for Bluetooth-lyd: Afspilningskvalitet"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vælg LDAC-codec for Bluetooth-lyd:\nAfspilningskvalitet"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamer: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS via TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Prøv DNS via TLS (hvis muligheden er aktiveret) på port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vis valgmuligheder for certificering af trådløs skærm"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Øg mængden af Wi‑Fi-logføring. Vis opdelt efter SSID RSSI i Wi‑Fi-vælgeren"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Når dette er aktiveret, gennemtvinges en overdragelse af dataforbindelsen fra Wi-Fi til mobilnetværk, når Wi-Fi-signalet er svagt"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ingen"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Vent på fejlfinder"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Fejlrettet app venter på tilknytning af fejlfinder før udførelse"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Overvågning af telefoni"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Overvågning af telefoni indsamler logfiler, når der registreres et problem med telefoni-/modemfunktioner, og sender brugeren en underretning, der beder vedkommende om at indsende en fejlrapport"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Tegning"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardware-accelereret gengivelse"</string>
@@ -331,7 +331,7 @@
     <string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"Konvertér…"</string>
     <string name="convert_to_file_encryption_done" msgid="7859766358000523953">"Allerede filkrypteret"</string>
     <string name="title_convert_fbe" msgid="1263622876196444453">"Konverterer til filbaseret kryptering"</string>
-    <string name="convert_to_fbe_warning" msgid="6139067817148865527">"Konvertér datapartitionen til filbaseret kryptering.\nAdvarsel! Alle dine data vil blive slettet.\n Dette er en alfafunktion, og den fungerer muligvis ikke korrekt.\n Tryk på \"Ryd og konvertér…\" for at fortsætte."</string>
+    <string name="convert_to_fbe_warning" msgid="6139067817148865527">"Konvertér datapartitionen til filbaseret kryptering.\nAdvarsel! Alle dine data vil blive fuldstændigt slettet.\n Dette er en alfafunktion, og den fungerer muligvis ikke korrekt.\n Tryk på \"Ryd og konvertér…\" for at fortsætte."</string>
     <string name="button_convert_fbe" msgid="5152671181309826405">"Ryd og konvertér…"</string>
     <string name="picture_color_mode" msgid="4560755008730283695">"Farvetilstand for billeder"</string>
     <string name="picture_color_mode_desc" msgid="1141891467675548590">"Brug sRGB"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Indstillingerne for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> kunne ikke åbnes"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Denne inputmetode kan muligvis indsamle al indtastet tekst, f.eks. personlige data såsom adgangskoder og kreditkortnumre. Den kommer fra appen <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Vil du anvende denne inputmetode?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Bemærk! Efter en genstart kan denne app ikke starte, før du låser din telefon op"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Status for IMS-registrering"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registreret"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ikke registreret"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Utilgængelig"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 5f11b51..2635e66 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-Audio-LDAC-Codec: Wiedergabequalität"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth-Audio-LDAC-Codec auswählen:\nWiedergabequalität"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS-over-TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Wenn diese Option aktiviert ist, versuche für Port 853 DNS-over-TLS anzuwenden."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Optionen zur Zertifizierung für kabellose Übertragung anzeigen"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Level für WLAN-Protokollierung erhöhen, in WiFi Picker pro SSID-RSSI anzeigen"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Wenn diese Option aktiviert ist, ist das WLAN bei schwachem Signal bei der Übergabe der Datenverbindung an den Mobilfunk aggressiver"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Keine"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Auf Debugger warten"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"App wartet vor der Ausführung auf den Start des Debuggers"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Mit Telephony Monitor werden Protokolle erfasst, wenn ein Problem mit der Telefon- oder der Modemfunktion entdeckt wird. Nutzer werden dazu aufgefordert, den Programmfehler zu melden."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Eingabe"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Zeichnung"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardwarebeschleunigtes Rendering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Einstellungen für <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> konnten nicht geöffnet werden."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Diese Eingabemethode kann den gesamten von dir eingegebenen Text erfassen, einschließlich personenbezogener Daten wie Passwörter und Kreditkartennummern. Sie ist Teil der App \"<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>\". Möchtest du diese Eingabemethode verwenden?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Hinweis: Nach einem Neustart wird diese App erst gestartet, wenn du dein Smartphone entsperrst"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-Registrierungsstatus"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registriert"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nicht registriert"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nicht verfügbar"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index cdb9518..b6b1bde 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Κωδικοποιητής LDAC ήχου Bluetooth: Ποιότητα αναπαραγωγής"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Επιλογή κωδικοποιητή LDAC ήχου Bluetooth:\nΠοιότητα αναπαραγωγής"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Ροή: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS μέσω TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Εάν ενεργοποιηθεί, γίνεται προσπάθεια DNS μέσω TLS στη θύρα 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Εμφάνιση επιλογών για πιστοποίηση ασύρματης οθόνης"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Αύξηση επιπέδου καταγ. Wi-Fi, εμφάνιση ανά SSID RSSI στο εργαλείο επιλογής Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Όταν είναι ενεργό, το Wi-Fi θα μεταβιβάζει πιο επιθετικά τη σύνδ.δεδομένων σε δίκτυο κινητής τηλ., όταν το σήμα Wi-Fi είναι χαμηλό"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Καμία"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Περιμένετε το εργαλείο εντοπισμού σφαλμάτων"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Αναμονή εφαρμογής για να συνδεθεί ο εντοπισμός σφαλμάτων"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Παρακολούθηση τηλεφωνίας"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Η υπηρεσία TelephonyMonitor θα συλλέξει αρχεία καταγραφής μόλις εντοπίσει κάποιο πρόβλημα στη λειτουργία του τηλεφώνου/μόντεμ και θα εμφανίσει μια ειδοποίηση στον χρήστη για να υποβάλει αναφορά σφάλματος"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Εισαγωγή"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Σχέδιο"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Απόδοση με επιτάχυνση από υλικό εξοπλισμό"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Δεν ήταν δυνατό το άνοιγμα των ρυθμίσεων για την εφαρμογή <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Αυτή η μέθοδος εισαγωγής ενδέχεται να έχει τη δυνατότητα να συλλέξει όλα τα κείμενα που πληκτρολογείτε, συμπεριλαμβανομένων προσωπικών δεδομένων, όπως είναι οι κωδικοί πρόσβασης και οι αριθμοί πιστωτικής κάρτας. Προέρχεται από την εφαρμογή <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Να γίνει χρήση αυτής της μεθόδου εισαγωγής;"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Σημείωση: Μετά από μια επανεκκίνηση, δεν είναι δυνατή η έναρξη αυτής της συσκευής προτού ξεκλειδώσετε το τηλέφωνό σας"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Κατάσταση εγγραφής υπηρεσίας IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Εγγεγραμμένη"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Μη εγγεγραμμένη"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Μη διαθέσιμο"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index c964367..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nothing"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Wait for debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Debugged application waits for debugger to attach before executing"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor will collect logs when it detects a problem with telephony/modem functionality and prompt notification to user to file a bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Drawing"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardware accelerated rendering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS registration state"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registered"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Not registered"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Unavailable"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index c964367..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nothing"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Wait for debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Debugged application waits for debugger to attach before executing"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor will collect logs when it detects a problem with telephony/modem functionality and prompt notification to user to file a bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Drawing"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardware accelerated rendering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS registration state"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registered"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Not registered"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Unavailable"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index c964367..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nothing"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Wait for debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Debugged application waits for debugger to attach before executing"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor will collect logs when it detects a problem with telephony/modem functionality and prompt notification to user to file a bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Drawing"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardware accelerated rendering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS registration state"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registered"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Not registered"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Unavailable"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index c964367..e85242a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"If enabled, attempt DNS over TLS on port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Show options for wireless display certification"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nothing"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Wait for debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Debugged application waits for debugger to attach before executing"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor will collect logs when it detects a problem with telephony/modem functionality and prompt notification to user to file a bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Drawing"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardware accelerated rendering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Failed to open settings for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"This input method may be able to collect all the text that you type, including personal data like passwords and credit card numbers. It comes from the app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Use this input method?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Note: After a reboot, this app can\'t start until you unlock your phone"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS registration state"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registered"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Not registered"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Unavailable"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 9c313ee..a3430fc 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎Bluetooth Audio LDAC Codec: Playback Quality‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎Select Bluetooth Audio LDAC Codec:‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Playback Quality‎‏‎‎‏‎"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎Streaming: ‎‏‎‎‏‏‎<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎DNS over TLS‎‏‎‎‏‎"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎‏‎‎‎If enabled, attempt DNS over TLS on port 853.‎‏‎‎‏‎"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎Show options for wireless display certification‎‏‎‎‏‎"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎Increase Wi‑Fi logging level, show per SSID RSSI in Wi‑Fi Picker‎‏‎‎‏‎"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‎When enabled, Wi‑Fi will be more aggressive in handing over the data connection to mobile, when Wi‑Fi signal is low‎‏‎‎‏‎"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‏‎‎‎‎Nothing‎‏‎‎‏‎"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎Wait for debugger‎‏‎‎‏‎"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎Debugged application waits for debugger to attach before executing‎‏‎‎‏‎"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‎‏‎‏‎‎Telephony Monitor‎‏‎‎‏‎"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‏‎‏‏‏‎‏‎‎‏‏‎TelephonyMonitor will collect logs when it detects a problem with telephony/modem functionality and prompt notification to user to file a bug‎‏‎‎‏‎"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‏‏‎‎Input‎‏‎‎‏‎"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‏‎‎‎Drawing‎‏‎‎‏‎"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‏‎‎Hardware accelerated rendering‎‏‎‎‏‎"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎Failed to open settings for ‎‏‎‎‏‏‎<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‎This input method may be able to collect all the text you type, including personal data like passwords and credit card numbers. It comes from the app ‎‏‎‎‏‏‎<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. Use this input method?‎‏‎‎‏‎"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‎Note: After a reboot, this app can\'t start until you unlock your phone‎‏‎‎‏‎"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎IMS registration state‎‏‎‎‏‎"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‎Registered‎‏‎‎‏‎"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‎‏‎‏‏‏‎‎Not registered‎‏‎‎‏‎"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‎‎‏‎‎Unavailable‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index fdf39c8..ea62383 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -163,7 +163,7 @@
     <string name="choose_profile" msgid="6921016979430278661">"Elegir perfil"</string>
     <string name="category_personal" msgid="1299663247844969448">"Personal"</string>
     <string name="category_work" msgid="8699184680584175622">"Trabajo"</string>
-    <string name="development_settings_title" msgid="215179176067683667">"Opciones del programador"</string>
+    <string name="development_settings_title" msgid="215179176067683667">"Opciones para programadores"</string>
     <string name="development_settings_enable" msgid="542530994778109538">"Activar opciones para programador"</string>
     <string name="development_settings_summary" msgid="1815795401632854041">"Establecer opciones para desarrollar aplicaciones"</string>
     <string name="development_settings_not_available" msgid="4308569041701535607">"Las opciones de programador no están disponibles para este usuario."</string>
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Códec del audio Bluetooth LDAC: calidad de reproducción"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleccionar códec del audio Bluetooth LDAC:\nCalidad de reproducción"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmitiendo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS mediante TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Si esta opción está habilitada, prueba DNS mediante TLS en el puerto 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opciones de certificación de pantalla inalámbrica"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar nivel de registro Wi-Fi; mostrar por SSID RSSI en el selector de Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si habilitas esta opción, se priorizará el cambio de Wi-Fi a datos móviles cuando la señal de Wi-Fi sea débil"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ninguna"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Esperar al depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Esperar que se conecte el depurador para iniciar la aplicación"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telephony Monitor recopilará registros cuando se detecte un problema con la funcionalidad de telefonía/módem y además enviará al usuario una notificación para que reporte el error"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Dibujo"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Representación acelerada mediante hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Se produjo un error al abrir la configuración de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"El método de entrada puede recopilar todo el texto que escribas, incluidos los datos personales como contraseñas y números de tarjetas de crédito. Proviene de la aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. ¿Deseas utilizar este método de entrada?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: Luego de reiniciar el dispositivo, esta app no podrá iniciarse hasta que desbloquees tu teléfono"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estado de registro de IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrado"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Sin registrar"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"No disponible"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 6065bb0..8474c49 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Selecciona el códec LDAC por Bluetooth: calidad de reproducción"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecciona el códec LDAC de audio por Bluetooth:\nCalidad de reproducción"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS a través de TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Si esta opción está inhabilitada, prueba DNS a través de TLS en el puerto 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opciones para la certificación de la pantalla inalámbrica"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar el nivel de registro de Wi-Fi, mostrar por SSID RSSI en el selector Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si se activa esta opción, la conexión Wi-Fi será más agresiva al pasar la conexión a datos móviles (si la señal Wi-Fi es débil)"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ninguna"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Esperar al depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"La aplicación depurada espera a que se active el depurador para ejecutarse"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor de telefonía"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor recopilará los registros al detectar un problema con la función de módem o telefonía y mostrará una notificación al usuario para registrar un error"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Dibujo"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Renderización acelerada por hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Error al abrir los ajustes de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Este método de entrada puede registrar todo lo que escribas, incluidos datos personales, como las contraseñas y los números de las tarjetas de crédito. Procede de la aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. ¿Quieres usar este método de entrada?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: Después de reiniciar, tienes que desbloquear el teléfono para que esta aplicación se pueda iniciar"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estado del registro de IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrado"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"No registrado"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"No disponible"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 48b7719..efbbac1 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetoothi LDAC-helikodek: taasesituskvaliteet"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Valige Bluetoothi LDAC-helikodek:\ntaasesituskvaliteet"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Voogesitus: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS TLS-i kaudu"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Kui on lubatud, katsetatakse DNS-i TLS-i kaudu (port 853)."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Juhtmeta ekraaniühenduse sertifitseerimisvalikute kuvamine"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Suurenda WiFi logimistaset, kuva WiFi valijas SSID RSSI järgi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kui seade on lubatud, asendatakse nõrga signaaliga WiFi-ühendus agressiivsemalt mobiilse andmesideühendusega"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Mitte ühtegi"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Oodake silurit"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Silutud rakendus ootab toimimiseks siluri lisamist"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Teenus Telephony Monitor kogub telefoni/modemi funktsioonide probleemide korral logisid ja esitab kasutajale märguande veaaruande esitamiseks"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Sisend"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Joonis"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Tarkvarakiirendusega renderdamine"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Rakenduse <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> seadete avamine ebaõnnestus"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"See sisestusmeetod võib koguda kogu teie sisestatava teksti, sh isikuandmed (nt paroolid ja krediitkaardinumbrid). See pärineb rakendusest <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Kas soovite seda sisestusmeetodit kasutada?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Märkus. Pärast taaskäivitamist ei saa see rakendus käivituda enne, kui olete telefoni avanud"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-i registreerimise olek"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registreeritud"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ei ole registreeritud"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Pole saadaval"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index fc60954..bd2f94e 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audioaren LDAC kodeka: erreprodukzioaren kalitatea"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Hautatu Bluetooth audioaren LDAC kodeka:\nerreprodukzioaren kalitatea"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Igortzean: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS bidezko DNSa"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Gaituz gero, erabili TLS bidezko DNSa 853 atakan."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Erakutsi hari gabeko bistaratze-egiaztapenaren aukerak"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Erakutsi datu gehiago Wi-Fi sareetan saioa hasterakoan. Erakutsi sarearen identifikatzailea eta seinalearen indarra Wi‑Fi sareen hautagailuan."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Aukera hori gaituz gero, gailua nahitaez aldatuko da datu mugikorren konexiora Wi-Fi seinalea ahultzen dela nabaritutakoan"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ezer ez"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Itxaron araztaileari"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Araztutako aplikazioa araztailea erantsi arte itxaroten ari da exekutatu aurretik"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefono-gainbegiratzailea"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telefono-gainbegiratzaileak erregistroak bilduko ditu telefono/modem funtzioarekin arazoren bat dagoela hautematen duenean, eta akatsaren berri emateko eskatuko dio erabiltzaileari"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Sarrera"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Marrazkia"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardware bidez bizkortutako errendatzea"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Ezin izan dira <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> aplikazioaren ezarpenak ireki."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Idazketa-metodoak idazten duzun testu guztia bil dezake, pasahitzak eta kreditu-txarteleko zenbakiak bezalako datu pertsonalak barne. <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> aplikazioak egin du eskaera. Idazketa-metodo hori erabili nahi duzu?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Oharra: berrabiarazi ondoren, ezin izango da abiarazi aplikazio hau telefonoa desblokeatzen duzun arte"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS erregistratzearen egoera"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Erregistratuta"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Erregistratu gabe"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Ez dago erabilgarri"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 2500d5d..bc287bc 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"‏کدک LDAC صوتی بلوتوث: کیفیت پخش"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"‏انتخاب کدک LDAC صوتی بلوتوث:\nکیفیت پخش"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"پخش جریانی: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"‏DNS ازطریق امنیت لایه انتقال"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"‏درصورت فعال بودن، DNS ازطریق امنیت لایه انتقال در درگاه ۸۵۳ انجام شود."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"نمایش گزینه‌ها برای گواهینامه نمایش بی‌سیم"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"‏افزایش سطح گزارش‌گیری Wi‑Fi، نمایش به ازای SSID RSSI در انتخاب‌کننده Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"‏زمانی‌که فعال است، درشرایطی که سیگنال Wi-Fi ضعیف باشد، Wi‑Fi برای واگذاری اتصال داده به دستگاه همراه قوی‌تر عمل خواهد کرد."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"هیچ چیز"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"انتظار برای اشکال‌زدا"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"برنامه اشکال‌زدایی شده منتظر پیوست شدن اشکال‌زدا قبل از اجرا است"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"‏TelephonyMonitor وقتی مشکلی در ارتباط با عملکرد تلفن/مودم تشخیص می‌دهد، گزارش جمع‌آوری می‌کند و با اعلانی از کاربر می‌خواهد گزارش اشکال تهیه کند"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ورودی"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"طراحی"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"پردازش سخت‌افزاری سریع"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"تنظیمات <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> بازنشد"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"این روش ورودی ممکن است بتواند تمام متنی را که تایپ می‌کنید جمع‌آوری کند، از جمله اطلاعات شخصی مانند گذرواژه‌ها و شماره‌های کارت اعتباری. این روش توسط برنامه <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ارائه می‌شود. از این روش ورودی استفاده می‌کنید؟"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"توجه: بعد از راه‌اندازی تا زمانی‌که قفل تلفنتان را باز نکنید، این برنامه نمی‌تواند شروع شود"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"‏وضعیت ثبت IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"ثبت‌شده"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"ثبت نشده است"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"در دسترس نیست"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 414a4c7f..6742aca 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-äänen LDAC-koodekki: Toiston laatu"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Valitse Bluetooth-äänen LDAC-koodekki:\nToiston laatu"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Striimaus: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS ennen TLS:ää"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Jos tämä on käytössä, yritä käyttää DNS:ää mieluummin kuin TLS:ää portin 853 kautta."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Näytä langattoman näytön sertifiointiin liittyvät asetukset"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Lisää Wi‑Fin lokikirjaustasoa, näytä SSID RSSI -kohtaisesti Wi‑Fi-valitsimessa."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kun asetus on käytössä, datayhteys siirtyy helpommin Wi-Fistä matkapuhelinverkkoon, jos Wi-Fi-signaali on heikko."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ei mitään"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Odota vianetsintää"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Sovellus odottaa vianetsinnän lisäämistä, ja käynnistyy sen jälkeen."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Puhelinpalvelujen seuranta"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Puhelinpalvelujen seuranta kerää tietoja virheistä puhelinpalvelujen tai modeemin toiminnassa ja kehottaa käyttäjää tekemään virheilmoituksen."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Syöte"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Piirustus"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Laitteistokiihdytetty hahmonnus"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Sovelluksen <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> asetuksia ei voi avata"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Tämä syöttötapa saattaa kerätä kaiken kirjoittamasi tekstin, mukaan luettuna henkilökohtaiset tiedot kuten salasanat ja luottokortin numerot. Se on lähtöisin sovelluksesta <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Käytetäänkö tätä syöttötapaa?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Huom. Jotta voit käynnistää tämän sovelluksen uudelleenkäynnistyksen jälkeen, sinun täytyy avata puhelimen lukitus."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-rekisteröinnin tila"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Rekisteröity"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ei rekisteröity"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Ei käytettävissä"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 6306306..bb5d2d8 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec audio Bluetooth LDAC : qualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Sélectionner le codec audio Bluetooth LDAC :\nQualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Diffusion : <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS par TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Si cette option est activée, essayez le DNS par TLS sur le port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options pour la certification d\'affichage sans fil"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler davantage les données Wi-Fi, afficher par SSID RSSI dans sélect. Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si cette option est activée, le passage du Wi-Fi aux données cellulaires est forcé lorsque le signal Wi-Fi est faible"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Aucune"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Attendre l\'intervention du débogueur"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Avant de s\'exécuter, l\'application déboguée doit attendre que le débogueur soit attaché."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"TelephonyMonitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor recueille des journaux lorsqu\'il détecte un problème lié à la fonctionnalité de téléphonie ou de modem, puis invite l\'utilisateur à soumettre un bogue."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrée"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Dessin"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Accélération matérielle"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Échec de l\'ouverture des paramètres de l\'application <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ce mode de saisie est susceptible d\'enregistrer le texte que vous saisissez, y compris vos données personnelles, telles que les mots de passe et les numéros de carte de paiement. Il provient de l\'application <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Voulez-vous vraiment l\'activer?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Remarque : Après un redémarrage, vous ne pouvez pas lancer cette application tant que vous n\'avez pas déverrouillé votre téléphone."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"État d\'enregistrement IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Enregistré"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Non enregistré"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Non accessible"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index f8cf107..9238c21 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec audio Bluetooth LDAC : qualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Sélectionner le codec audio Bluetooth LDAC :\nQualité de lecture"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Diffusion : <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS sur TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Si l\'option est activée, essayez le protocole DNS sur TLS sur le port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afficher les options de la certification de l\'affichage sans fil"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Détailler plus infos Wi-Fi, afficher par RSSI de SSID dans outil sélection Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Si cette option est activée, le passage du Wi-Fi aux données mobiles est forcé en cas de signal Wi-Fi faible."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Aucune"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Attendre l\'intervention du débogueur"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Avant de s\'exécuter, l\'application déboguée doit attendre que le débogueur soit attaché."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telephony Monitor recueille des journaux lorsqu\'il détecte un problème lié à la fonctionnalité de téléphonie ou de modem, puis invite l\'utilisateur à créer un bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Saisie"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Tracé"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Accélération matérielle"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Échec de l\'ouverture des paramètres de l\'application <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ce mode de saisie est susceptible d\'enregistrer le texte que vous saisissez, y compris vos données personnelles, telles que les mots de passe et les numéros de carte de paiement. Il provient de l\'application <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Voulez-vous vraiment l\'activer ?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Remarque : Après un redémarrage, vous ne pouvez pas lancer cette application tant que vous n\'avez pas déverrouillé votre téléphone."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"État de l\'enregistrement IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Enregistré"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Non enregistré"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Non disponible"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 39fd72c..29b1e2b 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Códec LDAC de audio por Bluetooth: calidade de reprodución"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleccionar códec LDAC de audio por Bluetooth:\ncalidade de reprodución"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Reprodución en tempo real: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS a través de TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Se se activa esta opción, téntase utilizar o método DNS a través de TLS no porto 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra opcións para o certificado de visualización sen fíos"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nivel de rexistro da wifi, mostrar por SSID RSSI no selector de wifi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Cando estea activada esta función, a wifi será máis agresiva ao transferir a conexión de datos ao móbil cando o sinal wifi sexa feble"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nada"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Agardar polo depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"A aplicación depurada agarda a que o depurador se conecte antes de executarse"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor de telefonía"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"O monitor de telefonía recompilará rexistros cando detecte un problema coa función da telefonía ou do módem e enviará unha notificación ao usuario para solicitarlle que informe dun erro"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Debuxo"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Procesamento acelerado mediante hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Non se puido abrir a configuración de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"É posible que este método de entrada poida recompilar todo o texto que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de entrada?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: Tras un reinicio, non se pode iniciar esta aplicación ata que desbloquees o teléfono"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estado de rexistro de IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Rexistrado"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Non rexistrado"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Non dispoñible"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index fccac47..cbaa414 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"બ્લૂટૂથ ઑડિઓ LDAC કોડેક: પ્લેબૅક ગુણવત્તા"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"બ્લૂટૂથ ઑડિઓ LDAC કોડેક પસંદ કરો:\nપ્લેબૅક ગુણવત્તા"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"સ્ટ્રીમિંગ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS ઓવર TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"જો ચાલુ કરવામાં આવે, તો પોર્ટ 853 પરથી DNS ઓવર TLSનો પ્રયાસ કરો."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"વાયરલેસ ડિસ્પ્લે પ્રમાણપત્ર માટેના વિકલ્પો બતાવો"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"વાઇ-ફાઇ લોગિંગ સ્તર વધારો, વાઇ-ફાઇ પીકરમાં SSID RSSI દીઠ બતાવો"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"જ્યારે સક્ષમ કરેલ હોય, ત્યારે વાઇ-ફાઇ સિગ્નલ નબળું હોવા પર, વાઇ-ફાઇ વધુ ઝડપથી ડેટા કનેક્શનને મોબાઇલ પર મોકલશે"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"કંઈ નહીં"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ડીબગર માટે રાહ જુઓ"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ડીબગ કરેલ ઍપ્લિકેશનો ક્રિયાન્વિત થતા પહેલાં ડીબગર જોડાઈ તેની રાહ જુએ છે"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor ને જ્યારે ટેલિફોની/મૉડેમની કાર્યક્ષમતામાં કોઈ સમસ્યા મળે ત્યારે તે લૉગ એકત્રિત કરશે અને વપરાશકર્તાને બગની જાણ કરવાની સૂચનાનો સંકેત આપશે"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ઇનપુટ"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"રેખાંકન"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"હાર્ડવેર પ્રવેગક રેન્ડરિંગ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> માટેની સેટિંગ્સ ખોલવામાં નિષ્ફળ"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"આ ઇનપુટ પદ્ધતિ પાસવર્ડ્સ અને ક્રેડિટ કાર્ડ નંબર જેવી વ્યક્તિગત માહિતી સહિત તમે લખો છો તે તમામ ટેક્સ્ટ એકત્રિત કરવા માટે સક્ષમ હોઈ શકે છે. તે ઍપ્લિકેશન <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> માંથી આવે છે. આ ઇનપુટ પદ્ધતિ વાપરીએ?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"નોંધ: રીબૂટ કર્યાં પછી, જ્યાં સુધી તમે તમારો ફોન અનલૉક કરશો નહીં ત્યાં સુધી આ ઍપ્લિકેશન શરૂ થઈ શકશે નહીં"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS રજિસ્ટ્રેશનની સ્થિતિ"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"રજિસ્ટર કરેલ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"રજિસ્ટર કરેલ નથી"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"અનુપલબ્ધ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 7b01903..5d25d20 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -45,7 +45,7 @@
     <string name="available_via_carrier" msgid="1469036129740799053">"%1$s के ज़रिए उपलब्ध"</string>
     <string name="speed_label_very_slow" msgid="1867055264243608530">"अत्‍यधिक धीमी"</string>
     <string name="speed_label_slow" msgid="813109590815810235">"धीमी"</string>
-    <string name="speed_label_okay" msgid="2331665440671174858">"ठीक"</string>
+    <string name="speed_label_okay" msgid="2331665440671174858">"ठीक है"</string>
     <string name="speed_label_medium" msgid="3175763313268941953">"मध्यम"</string>
     <string name="speed_label_fast" msgid="7715732164050975057">"तेज़"</string>
     <string name="speed_label_very_fast" msgid="2265363430784523409">"अत्‍यधिक तेज़"</string>
@@ -209,6 +209,10 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लूटूथ ऑडियो LDAC कोडेक: प्लेबैक क्वालिटी"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लूटूथ ऑडियो LDAC कोडेक चुनें:\nप्लेबैक क्वालिटी"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"चलाया जा रहा है: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <!-- no translation found for dns_tls (6773814174391131955) -->
+    <skip />
+    <!-- no translation found for dns_tls_summary (3692494150251071380) -->
+    <skip />
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"वायरलेस दिखाई देने के लिए प्रमाणन विकल्प दिखाएं"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"वाई-फ़ाई प्रवेश स्तर बढ़ाएं, वाई-फ़ाई पिकर में प्रति SSID RSSI दिखाएं"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"इसके सक्षम होने पर, जब वाई-फ़ाई संकेत कमज़ोर हों तो वाई-फ़ाई, डेटा कनेक्शन को मोबाइल पर ज़्यादा तेज़ी से भेजेगा"</string>
@@ -248,8 +252,6 @@
     <string name="no_application" msgid="2813387563129153880">"कुछ भी नहीं"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"डीबगर की प्रतीक्षा करें"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"डीबग किया गया ऐप्स  निष्पादन के पहले अनुलग्न करने के लिए डीबगर की प्रतीक्षा करता है"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"टेलीफ़ोनी मॉनिटर"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"टेलीफ़ोनी मॉनिटर को जब टेलीफ़ोनी/मॉडेम के फंक्शन में कोई समस्या मिलती है, तो वह लॉग इकट्ठा करता है और उपयोगकर्ता को एक गड़बड़ी दर्ज करने के लिए सूचना देता है"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"हिंदी में लिखें"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ड्रॉइंग"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"हार्डवेयर त्वरित रेंडरिंग"</string>
@@ -289,7 +291,7 @@
     <string name="transition_animation_scale_title" msgid="387527540523595875">"संक्रमण एनिमेशन स्‍केल"</string>
     <string name="animator_duration_scale_title" msgid="3406722410819934083">"एनिमेटर अवधि स्केल"</string>
     <string name="overlay_display_devices_title" msgid="5364176287998398539">"द्वितीयक डिस्प्ले अनुरूपित करें"</string>
-    <string name="debug_applications_category" msgid="4206913653849771549">"ऐप्स"</string>
+    <string name="debug_applications_category" msgid="4206913653849771549">"ऐप"</string>
     <string name="immediately_destroy_activities" msgid="1579659389568133959">"गतिविधियों को न रखें"</string>
     <string name="immediately_destroy_activities_summary" msgid="3592221124808773368">"उपयोगकर्ता के छोड़ते ही हर गतिविधि को खत्म करें"</string>
     <string name="app_process_limit_title" msgid="4280600650253107163">"पृष्ठभूमि प्रक्रिया सीमा"</string>
@@ -390,4 +392,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> के लिए सेटिंग खोलने में विफल रहा"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"यह इनपुट विधि, पासवर्ड और क्रेडिट कार्ड नंबर जैसे निजी डेटा सहित आपके द्वारा लिखे जाने वाले सभी लेख को एकत्र कर सकती है. यह <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ऐप्स से आती है. इस इनपुट विधि का उपयोग करें?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"नोट: पुनः बूट करने के बाद, यह ऐप्लिकेशन तब तक शुरू नहीं हो सकता है जब तक कि आप अपना फ़ोन अनलॉक ना कर लें"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS रजिस्ट्रेशन की स्थिति"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"रजिस्टर है"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"रजिस्टर नहीं है"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"अनुपलब्ध"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index a58561a..c134970 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek za Bluetooth Audio LDAC: kvaliteta reprodukcije"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Odaberi kodek za Bluetooth Audio LDAC:\nkvaliteta reprodukcije"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strujanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS putem TLS-a"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ako je omogućeno, pokušava se upotrijebiti DNS putem TLS-a na priključku 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Prikaži opcije za certifikaciju bežičnog prikaza"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povećana razina prijave na Wi‑Fi, prikaz po SSID RSSI-ju u Biraču Wi‑Fi-ja"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ako je omogućeno, Wi-Fi će aktivno prebacivati podatkovnu vezu mobilnoj mreži kada je Wi-Fi signal slab."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ništa"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Čeka se program za otklanjanje pogrešaka"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikacija čeka priključivanje programa za otklanjanje pogrešaka"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"TelephonyMonitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor prikupljat će zapisnike kada otkrije problem s funkcioniranjem telefona/modema i obavijestiti korisnika da prijavi programsku pogrešku"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ulaz"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Crtež"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardverski ubrzano renderiranje"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Otvaranje postavki za aplikaciju <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> nije uspjelo"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ovaj način unosa možda može prikupljati sav tekst koji unosite, uključujući osobne podatke poput zaporki i brojeva kreditnih kartica. To omogućuje aplikacija <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Upotrijebiti taj način unosa?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Napomena: ova se aplikacija ne može pokrenuti nakon ponovnog pokretanja dok ne otključate telefon"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stanje registracije IMS-a"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrirano"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nije registrirano"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nije dostupno"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 82e7432..da206cb 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC hangkodek: lejátszási minőség"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth LDAC hangkodek kiválasztása:\nlejátszási minőség"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamelés: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS-en keresztüli DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"TLS-en keresztüli DNS megkísérlése a 853-as porton, ha engedélyezve van."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vezeték nélküli kijelző tanúsítványával kapcsolatos lehetőségek megjelenítése"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi-naplózási szint növelése, RSSI/SSID megjelenítése a Wi‑Fi-választóban"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ha engedélyezi, a Wi-Fi agresszívebben fogja átadni az adatkapcsolatot a mobilhálózatnak gyenge Wi-Fi-jel esetén"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Semmi"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Várjon a hibakeresőre."</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"A javított alkalmazás a hibakeresőre vár."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"A TelephonyMonitor begyűjti a naplókat, ha problémát észlel a telefonos szolgáltatások vagy a modem működésében, és javasolja a felhasználónak a hiba jelentését"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Bevitel"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Rajz"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardveres gyorsítású megjelenítés"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nem sikerült megnyitni a(z) <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> beállításait."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ez a beviteli módszer alkalmas lehet a beírt szövegek – köztük az olyan személyes adatok, mint a jelszavak és a hitelkártyaszámok - összegyűjtésére. A <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> alkalmazás kapcsolta be. Használja ezt a módszert?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Megjegyzés: Újraindítás után ez az alkalmazás csak a telefon feloldását követően indul el"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-regisztráció állapota"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Regisztrált"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nem regisztrált"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nem érhető el"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 9e6e101..2365058 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth աուդիո LDAC կոդեկ՝ նվագարկման որակ"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Ընտրեք Bluetooth աուդիո LDAC կոդեկը՝\nնվագարկման որակ"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Հեռարձակում՝ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS TLS-ի միջոցով"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Միացնելու դեպքում փորձել DNS-ը TLS-ի միջոցով միացք 853-ում:"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Ցույց տալ անլար էկրանի հավաստագրման ընտրանքները"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Բարձրացնել մակարդակը, Wi‑Fi ընտրիչում ամեն մի SSID-ի համար ցույց տալ RSSI"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Եթե այս գործառույթը միացված է, Wi-Fi-ի թույլ ազդանշանի դեպքում Wi‑Fi ինտերնետից բջջային ինտերնետի անցումը ավելի կտրուկ կկատարվի"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ոչինչ"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Սպասել վրիպազերծիչին"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Վրիպազերծված ծրագրիը սպասում է վրիպազերծիչի կցմանը մինչ կատարումը"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Հեռախոսակապի/մոդեմի հետ կապված խնդիրներ հայտնաբերելու դեպքում TelephonyMonitor-ը կհավաքի մատյանները և օգտվողին կհուշի վրիպակ գրանցելու անհրաժեշտության մասին"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Մուտքագրում"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Պատկերում"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Սարքաշարի արագացված նյութավորում"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Չստացվեց ցույց տալ <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>-ի տվյալները"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Այս ներմուծման եղանակը հնարավորություն է տալիս հավաքել ձեր մուտքագրած ողջ տեքստը՝ ընդգրկելով անձնական տեղեկություններ, ինչպես գաղտնաբառն ու բանկային քարտի համարները: Սկզբնաղբյուրը <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> հավելվածն է: Կիրառե՞լ այս եղանակը:"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Ուշադրություն. Վերաբեռնումից հետո այս հավելվածը չի գործարկվի մինչև չապակողպեք հեռախոսը"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS ծառայության գրանցման կարգավիճակը"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Գրանցված է"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Գրանցված չէ"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Անհասանելի"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 5dcc045..7e2848f 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC Audio Bluetooth: Kualitas Pemutaran"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pilih Codec LDAC Audio Bluetooth:\nKualitas Pemutaran"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS melalui TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Jika diaktifkan, coba DNS melalui TLS pada port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Tampilkan opsi untuk sertifikasi layar nirkabel"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tingkatkan level pencatatan log Wi-Fi, tampilkan per SSID RSSI di Pemilih Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Jika diaktifkan, Wi-Fi akan menjadi lebih agresif dalam mengalihkan sambungan data ke seluler saat sinyal Wi-Fi lemah"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Tidak ada"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Tunggu debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikasi yang di-debug menunggu debugger menempel sebelum berjalan"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor Telefoni"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Monitor Telefoni akan mengumpulkan log jika mendeteksi masalah pada fungsi telefoni/modem dan mengirimkan notifikasi ke pengguna untuk melaporkan bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Masukan"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Gambar"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Render yang dipercepat hardware"</string>
@@ -279,7 +279,7 @@
     <string name="debug_layout_summary" msgid="2001775315258637682">"Tampilkan batas klip, margin, dll."</string>
     <string name="force_rtl_layout_all_locales" msgid="2259906643093138978">"Paksa arah tata letak RTL"</string>
     <string name="force_rtl_layout_all_locales_summary" msgid="9192797796616132534">"Paksa arah tata letak layar RTL untuk semua lokal"</string>
-    <string name="force_hw_ui" msgid="6426383462520888732">"Paksa perenderan GPU"</string>
+    <string name="force_hw_ui" msgid="6426383462520888732">"Paksa rendering GPU"</string>
     <string name="force_hw_ui_summary" msgid="5535991166074861515">"Paksa penggunaan GPU untuk gambar 2d"</string>
     <string name="force_msaa" msgid="7920323238677284387">"Paksa 4x MSAA"</string>
     <string name="force_msaa_summary" msgid="9123553203895817537">"Aktifkan 4x MSAA dalam aplikasi OpenGL ES 2.0"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Gagal membuka setelan untuk <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Metode masukan ini mungkin dapat mengumpulkan semua teks yang Anda ketik, termasuk data pribadi seperti sandi dan nomor kartu kredit. Metode ini berasal dari aplikasi <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Gunakan metode masukan ini?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Catatan: Setelah boot ulang, aplikasi ini tidak dapat dimulai hingga kunci ponsel dibuka"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Status pendaftaran IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Terdaftar"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Tidak terdaftar"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Tidak Tersedia"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index c4d6777..5f4d401 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC-hljóðkóðari: gæði spilunar"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Velja Bluetooth LDAC-hljóðkóðara:\ngæði spilunar"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streymi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS yfir TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ef kveikt er á þessu, reyna DNS yfir TLS á gátt 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Sýna valkosti fyrir vottun þráðlausra skjáa"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Auka skráningarstig Wi-Fi, sýna RSSI fyrir hvert SSID í Wi-Fi vali"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Þegar þetta er virkt mun Wi-Fi skipta hraðar yfir í farsímagagnatengingu þegar Wi-Fi-tenging er léleg"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ekkert"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Bíða eftir villuleit"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Villuleituð forrit bíða eftir að villuleit tengist fyrir keyrslu"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Fjarskiptaumsjón"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Fjarskiptaumsjón mun safna annálum þegar það skynjar vandamál með fjarskipti/virkni mótalds og veita notanda beiðni um að senda inn villutilkynningu"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Inntak"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Teikning"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Myndþýðing með vélbúnaðarhröðun"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Ekki tókst að opna stillingar <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Þessi innsláttaraðferð getur hugsanlega skráð allan texta sem þú slærð inn, þ. á m. persónuupplýsingar á borð við aðgangsorð og kreditkortanúmer. Hún kemur frá forritinu <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Viltu nota þessa innsláttaraðferð?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Athugaðu: Eftir endurræsingu er ekki hægt að ræsa þetta forrit fyrr en þú tekur símann úr lás"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Staða IMS-skráningar"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Skráð"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ekki skráð"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Ekki tiltækt"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 85993aa..439fea1 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC audio Bluetooth: qualità di riproduzione"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Seleziona il codec LDAC audio Bluetooth:\nQualità di riproduzione"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS tramite TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Se l\'opzione è attiva, prova DNS tramite TLS sulla porta 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostra opzioni per la certificazione display wireless"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumenta il livello di registrazione Wi-Fi, mostrando il SSID RSSI nel selettore Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Con questa impostazione attivata, il Wi-Fi è più aggressivo nel passare la connessione dati al cellulare, con segnale Wi-Fi basso"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nessuna"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Attendi debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"L\'app in debug attende il debugger prima dell\'esecuzione"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"TelephonyMonitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor raccoglierà log quando rileverà un problema con la funzionalità di telefonia/del modem e avviserà tempestivamente l\'utente per segnalare il bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Disegno"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Rendering con accelerazione hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Impossibile aprire le impostazioni di <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Questo metodo di immissione potrebbe riuscire a raccogliere tutto il testo digitato, compresi i dati personali come password e numeri di carte di credito. Deriva dall\'applicazione <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Utilizzare questo metodo di immissione?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: dopo il riavvio, devi sbloccare il telefono per poter avviare l\'app."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stato di registrazione IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrato"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Non registrato"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Non disponibile"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index df690ef..435abbb 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"‏Codec אודיו LDAC ל-Bluetooth: איכות נגינה"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"‏בחירת Codec אודיו LDAC ל-Bluetooth:‏\nאיכות נגינה"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"סטרימינג: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"‏DNS באמצעות TLS (אבטחת שכבת התעבורה)"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"‏אם האפשרות מופעלת, יש לנסות DNS באמצעות TLS (אבטחת שכבת התעבורה) ביציאה 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"‏הצג אפשרויות עבור אישור של תצוגת WiFi"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"‏העלה את רמת הרישום של Wi‑Fi ביומן, הצג לכל SSID RSSI ב-Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"‏כשאפשרות זו מופעלת, Wi-Fi יתנהג בצורה אגרסיבית יותר בעת העברת חיבור הנתונים לרשת הסלולרית כשאות ה-Wi-Fi חלש."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"אף אחת"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"המתן למנקה באגים"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"אפליקציה שנוקו בה הבאגים ממתינה למנקה הבאגים לצירוף לפני ביצוע"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"‏TelephonyMonitor יאסוף מידע ביומנים כשיזהה בעיה בפונקציונליות של טלפוניה/מודם. הוא ישלח הודעה למשתמש כדי שיוכל להגיש דוח על באג"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"קלט"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"שרטוט"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"עיבוד מואץ של חומרה"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"פתיחת הגדרות עבור <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> נכשלה"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ייתכן ששיטת קלט זו תוכל לאסוף את כל הטקסט שאתה מקליד, כולל נתונים אישיים כגון סיסמאות ומספרי כרטיס אשראי. היא מגיעה מהאפליקציה <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. האם להשתמש בשיטת קלט זו?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"שים לב: לאחר הפעלה מחדש של המכשיר, ניתן להפעיל את האפליקציה רק לאחר שתבטל את נעילת הטלפון"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"‏סטטוס הרשמה ל-IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"רשום"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"לא רשום"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"לא זמין"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 46b51d1..308e97d8 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth オーディオ LDAC コーデック: 再生音質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth オーディオ LDAC コーデックを選択:\n再生音質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ストリーミング: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"この設定を有効にした場合、ポート 853 で DNS over TLS を試行します。"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ワイヤレスディスプレイ認証のオプションを表示"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fiログレベルを上げて、Wi-Fi選択ツールでSSID RSSIごとに表示します"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ON にすると、Wi-Fi の電波強度が弱い場合は強制的にモバイルデータ接続に切り替わるようになります"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"なし"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"デバッガを待機"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"アプリは実行前にデバッガのアタッチを待機します"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telephony Monitor は、電話やモデムの機能で問題が検出されたときにログを収集し、バグを報告するようユーザーに通知を表示します"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"入力"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"描画"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ハードウェアアクセラレーテッドレンダリング"</string>
@@ -392,4 +392,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>の設定を開くことができませんでした"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"この入力方法を選択すると、すべての入力内容の収集をアプリ(<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>)に許可することになります。これにはパスワードやクレジットカード番号などの個人情報も含まれます。この入力方法を使用しますか?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"注: 再起動後、スマートフォンのロックを解除するまでこのアプリを起動することはできません"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS 登録ステータス"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"登録済み"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"未登録"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"不明"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 49a6902..f455324 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth აუდიოს LDAC კოდეკის დაკვრის ხარისხი"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"აირჩიეთ Bluetooth აუდიოს LDAC კოდეკის\nდაკვრის ხარისხი"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"სტრიმინგი: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS-ის TLS-ის მეშვეობით გადაცემა"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ჩართვის შემთხვევაში, DNS-ის TLS-ის მეშვეობით გადაცემის ცდა, პორტზე 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"უსადენო ეკრანის სერტიფიცირების ვარიანტების ჩვენება"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi-ს აღრიცხვის დონის გაზრდა, Wi‑Fi ამომრჩეველში ყოველ SSID RSSI-ზე ჩვენება"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ჩართვის შემთხვევაში, Wi‑Fi უფრო აქტიურად შეეცდება მობილურ ინტერნეტზე გადართვას, როცა Wi‑Fi სიგნალი სუსტია"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"არაფერი"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"დაილოდეთ, სანამ ჩაირთვება გამმართველი"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"გამართული აპლიკაცია ელოდება გამმართველის ჩართვას გაშვებამდე"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"ტელეფონიის კონტროლიორი"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"ტელეფონიის/მოდემის გამართულ მუშაობასთან დაკავშირებული პრობლემის გამოვლენისას, ტელეფონიის კონტროლიორი შეაგროვებს ჟურნალების ჩანაწერებს, ხოლო მომხმარებელს შეცდომის შესახებ მოხსენებას შეთავაზებს"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ტექსტის შეყვანა"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ნახაზი"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"აპარატურით დაჩქარებული გამოსახულება"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>-ისთვის პარამეტრების გახსნა ვერ მოხერხდა"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"შეყვანის ამ მეთოდმა შესაძლოა მოახერხოს თქვენი აკრეფილი ყველა ტექსტის, მათ შორის პერსონალური მონაცემების, პაროლებისა და საკრედიტო ბარათის ნომრების შენახვა. ეს მეთოდი ეკუთვნის <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>-ს. გამოვიყენო შეყვანის ეს მეთოდი?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"შენიშვნა: გადატვირთვის შემდეგ, ეს აპი ვერ გაეშვება, სანამ ტელეფონს არ განბლოკავთ"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS რეგისტრაციის სტატუსი"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"რეგისტრირებული"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"არარეგისტრირებული"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"მიუწვდომელია"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 7dea44a..223a328 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth LDAC аудиокодегі: ойнату сапасы"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth LDAC аудиокодегін таңдау:\nойнату сапасы"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS бойынша DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Қосулы болса, 853 портында DNS жүйесін TLS протоколы арқылы қосып көріңіз."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Сымсыз дисплей растау опцияларын көрсету"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi жур. тір. дең. арт., Wi‑Fi желісін таңдағышта әр SSID RSSI бойынша көрсету"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Wi‑Fi сигналы әлсіз болғанда, деректер байланысы мәжбүрлі түрде мобильдік желіге ауысады"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ешнәрсе"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Жөндеушіні күту"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Орындау алдында бекіту үшін жөнделген қолданба жөндеушіні күтеді."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telephony Monitor функциясы телефон не модем жұмысында ақау байқаған жағдайда деректерді жинайды да, пайдаланушыға қате туралы ақпаратты жіберуді ұсынады"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Кіріс"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Сызу"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Бейнелеуді жабдықпен жылдамдату"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> қолданбасы үшін параметрлерді ашу орындалмады"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Бұл енгізу әдісі сіз терген барлық мәтінді, кілтсөз және кредит карта нөмірлері сияқты жеке ақпаратты қоса, жинауы мүмкін. Бұл <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> қолданбасы арқылы жасалады. Осы әдіс қолданылсын ба?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Ескертпе: қайта жүктегеннен кейін, телефонның құлпын ашпайынша, бұл қолданба іске қосылмайды"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS тіркеу күйі"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Тіркелген"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Тіркелмеген"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Қол жетімсіз"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 4dd298d..873d15a 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"កូឌិកប្រភេទ LDAC នៃសំឡេង​ប៊្លូធូស៖ គុណភាព​ចាក់​សំឡេង"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ជ្រើសរើស​កូឌិក​ប្រភេទ​ LDAC នៃសំឡេង​ប៊្លូធូស៖\nគុណភាព​ចាក់​សំឡេង"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"កំពុង​ចាក់៖ <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS តាមរយៈ TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ប្រសិនបើបើក វានឹងព្យាយាមប្រើ DNS តាមរៈ TLS នៅលើរន្ធ 853។"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"បង្ហាញ​ជម្រើស​សម្រាប់​វិញ្ញាបនបត្រ​បង្ហាញ​ឥត​ខ្សែ"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"បង្កើនកម្រិតកំណត់ហេតុវ៉ាយហ្វាយបង្ហាញក្នុង SSID RSSI ក្នុងកម្មវិធីជ្រើស​វ៉ាយហ្វាយ"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"នៅពេលដែលបើក នោះ Wi‑Fi នឹងផ្តល់ការតភ្ជាប់ទិន្នន័យយ៉ាងគំហុកទៅបណ្តាញទូរសព្ទចល័ត នៅពេលរលកសញ្ញា Wi‑Fi ចុះខ្សោយ។"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"គ្មាន​អ្វីទេ"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"រង់ចាំ​កម្មវិធី​កែ​កំហុស"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"កម្មវិធី​បាន​កែ​កំហុស​រង់ចាំ​ឲ្យ​ភ្ជាប់​កម្មវិធី​កែ​កំហុស​មុន​ពេល​អនុវត្ត"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor នឹង​ប្រមូល​កំណត់​ហេតុ នៅពេល​វា​រកឃើញ​បញ្ហា​ទាក់ទង​នឹង​មុខងារ​ទូរសព្ទ/ម៉ូដឹម និង​បញ្ជូនការ​ជូន​ដំណឹង​ទៅកាន់​អ្នក​ប្រើប្រាស់​ដើម្បី​រាយការណ៍​ពី​បញ្ហា​"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"បញ្ចូល"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"គំនូរ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"បង្ហាញ​ផ្នែក​រឹង​បាន​បង្កើន​ល្បឿន"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"បាន​បរាជ័យ​ក្នុង​ការ​បើក​ការ​កំណត់​សម្រាប់ <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"វិធី​សាស្ត្រ​បញ្ចូល​នេះ​អាច​​ប្រមូល​អត្ថបទ​ដែល​អ្នក​វាយ​ទាំងអស់ រួម​មាន​ទិន្នន័យ​ផ្ទាល់ខ្លួន ដូច​ជ ពាក្យ​សម្ងាត់ និង​លេខ​កាត​ឥណទាន។ វា​បាន​មក​ពី​កម្មវិធី <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ។ ប្រើ​វិធី​សាស្ត្រ​បញ្ចូល​នេះ?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ចំណាំ៖ បន្ទាប់ពីបិទបើកឡើងវិញហើយ កម្មវិធីនេះមិនអាចចាប់ផ្តើមបានទេ រហូតទាល់តែអ្នកដោះសោទូរស័ព្ទរបស់អ្នក"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"ស្ថានភាព​នៃការ​ចុះឈ្មោះ IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"បាន​ចុះឈ្មោះ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"មិនបាន​ចុះឈ្មោះ"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"មិន​មាន"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index bbfef4b..8d9a7e1 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -121,7 +121,7 @@
     <string name="running_process_item_user_label" msgid="3129887865552025943">"ಬಳಕೆದಾರ: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="launch_defaults_some" msgid="313159469856372621">"ಕೆಲವು ಡೀಫಾಲ್ಟ್‌ಗಳನ್ನು ಹೊಂದಿಸಲಾಗಿದೆ"</string>
     <string name="launch_defaults_none" msgid="4241129108140034876">"ಡೀಫಾಲ್ಟ್‌ಗಳನ್ನು ಹೊಂದಿಸಲಾಗಿಲ್ಲ"</string>
-    <string name="tts_settings" msgid="8186971894801348327">"ಪಠ್ಯದಿಂದ ಧ್ವನಿಗೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="tts_settings" msgid="8186971894801348327">"ಪಠ್ಯದಿಂದ ಧ್ವನಿಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="tts_settings_title" msgid="1237820681016639683">"ಧ್ವನಿಗೆ-ಪಠ್ಯದ ಔಟ್‌ಪುಟ್‌"</string>
     <string name="tts_default_rate_title" msgid="6030550998379310088">"ಧ್ವನಿಯ ಪ್ರಮಾಣ"</string>
     <string name="tts_default_rate_summary" msgid="4061815292287182801">"ಪಠ್ಯವನ್ನು ಹೇಳಿದ ವೇಗ"</string>
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ LDAC ಕೋಡೆಕ್: ಪ್ಲೇಬ್ಯಾಕ್ ಗುಣಮಟ್ಟ"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ಬ್ಲೂಟೂತ್‌ ಆಡಿಯೊ LDAC ಕೋಡೆಕ್:\nಪ್ಲೇಬ್ಯಾಕ್‌ ಗುಣಮಟ್ಟ ಆಯ್ಕೆ ಮಾಡಿ"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ಸ್ಟ್ರೀಮಿಂಗ್: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS ಮೇಲೆ DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ಸಕ್ರಿಯಗೊಂಡರೆ, ಪೋರ್ಟ್ 853 ನಲ್ಲಿ TLS ಮೇಲೆ DNS ಅನ್ನು ಪ್ರಯತ್ನಿಸಿ."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ವೈರ್‌ಲೆಸ್‌‌‌ ಪ್ರದರ್ಶನ ಪ್ರಮಾಣೀಕರಣಕ್ಕಾಗಿ ಆಯ್ಕೆಗಳನ್ನು ತೋರಿಸು"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ಲಾಗಿಂಗ್ ಮಟ್ಟನ್ನು ಹೆಚ್ಚಿಸಿ, Wi‑Fi ಆಯ್ಕೆಯಲ್ಲಿ ಪ್ರತಿಯೊಂದು SSID RSSI ತೋರಿಸಿ"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ಇದು ಸಕ್ರಿಯಗೊಂಡರೆ, ವೈ-ಫೈ ಸಿಗ್ನಲ್ ದುರ್ಬಲವಾಗಿದ್ದಾಗ, ಮೊಬೈಲ್‌ಗೆ ಡೇಟಾ ಸಂಪರ್ಕವನ್ನು ಹಸ್ತಾಂತರಿಸುವಲ್ಲಿ ವೈ-ಫೈ ಹೆಚ್ಚು ಆಕ್ರಮಣಕಾರಿಯಾಗಿರುತ್ತದೆ"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ಏನೂ ಇಲ್ಲ"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ಡೀಬಗರ್‌‌ಗಾಗಿ ನಿರೀಕ್ಷಿಸಿ"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ಲಗತ್ತನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುವ ಮೊದಲು ಡೀಬಗರ್‌‌ಗಾಗಿ ಡೀಬಗ್‌‌‌ ಮಾಡಿದ ಅಪ್ಲಿಕೇಶನ್‌‌ ಕಾಯುತ್ತದೆ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"ದೂರವಾಣಿ ಮಾನಿಟರ್"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"ದೂರವಾಣಿ/ಮೊಡೆಮ್ ಕಾರ್ಯಾಚರಣೆಯಲ್ಲಿ ಸಮಸ್ಯೆಗಳು ಕಂಡುಬಂದಾಗ, ದೂರವಾಣಿ ಮಾನಿಟರ್ ಲಾಗ್‌ಗಳನ್ನು ಸಂಗ್ರಹಿಸುತ್ತದೆ ಮತ್ತು ದೋಷದ ಕುರಿತು ವರದಿ ಸಲ್ಲಿಸಲು ಬಳಕೆದಾರನಿಗೆ ಸೂಚನೆ ನೀಡುತ್ತದೆ"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ಇನ್‌ಪುಟ್"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ಚಿತ್ರಣ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ಹಾರ್ಡ್‌ವೇರ್‌ ವೇಗವರ್ಧಿತ ರೆಂಡರಿಂಗ್"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> ಗಾಗಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಲು ವಿಫಲವಾಗಿದೆ"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ವೈಯಕ್ತಿಕ ಡೇಟಾಗಳಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಮತ್ತು ಕ್ರೆಡಿಟ್ ಕಾರ್ಡ್ ಸಂಖ್ಯೆಗಳನ್ನು ಒಳಗೊಂಡಂತೆ ನೀವು ಟೈಪ್ ಮಾಡುವ ಎಲ್ಲ ಪಠ್ಯವನ್ನು ಸಂಗ್ರಹಿಸಲು ಈ ಇನ್‌ಪುಟ್ ವಿಧಾನಕ್ಕೆ ಸಾಧ್ಯವಾಗಬಹುದು. ಇದು <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ನಿಂದ ಬರುತ್ತದೆ. ಈ ಇನ್‌ಪುಟ್ ವಿಧಾನವನ್ನು ಬಳಸುವುದೇ?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ಗಮನಿಸಿ: ರೀಬೂಟ್ ನಂತರ, ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ನೀವು ಅನ್‌ಲಾಕ್ ಮಾಡುವ ತನಕ ಈ ಆಪ್ ಪ್ರಾರಂಭಗೊಳ್ಳುವುದಿಲ್ಲ"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS ನೋಂದಣಿ ಸ್ಥಿತಿ"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"ನೋಂದಾಯಿಸಲಾಗಿದೆ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"ನೋಂದಾಯಿಸಲಾಗಿಲ್ಲ"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"ಲಭ್ಯವಿಲ್ಲ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 6633558..4e3bc33 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"블루투스 오디오 LDAC 코덱: 재생 품질"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"블루투스 오디오 LDAC 코덱 선택:\n재생 품질"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"스트리밍: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS를 통한 DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"사용 설정한 경우, 포트 853에서 TLS를 통해 DNS를 시도합니다."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"무선 디스플레이 인증서 옵션 표시"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi 로깅 수준을 높이고, Wi‑Fi 선택도구에서 SSID RSSI당 값을 표시합니다."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"사용 설정하면 Wi-Fi 신호가 약할 때 데이터 연결을 Wi-Fi에서 모바일 네트워크로 더욱 적극적으로 핸드오버합니다."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"없음"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"디버거 연결을 위해 대기"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"디버깅된 애플리케이션이 실행되기 전에 디버거 연결을 위해 대기"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"통신 모니터"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"통신 모니터는 통신/모뎀 기능에서 문제가 감지될 경우 로그를 수집하며 사용자에게 버그를 신고하라는 알림을 표시합니다."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"입력"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"그림"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"하드웨어 가속 렌더링"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> 설정을 열지 못했음"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> 앱에서 지원하는 이 입력 방법을 사용하면 비밀번호 및 신용카드 번호와 같은 개인 정보를 비롯하여 입력한 모든 텍스트가 수집될 수 있습니다. 사용하시겠습니까?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"참고: 재부팅한 후 이 앱은 휴대전화를 잠금 해제해야 시작됩니다."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS 등록 상태"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"등록됨"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"등록되지 않음"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"사용할 수 없음"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index e59d3f8..09edefe 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth аудио LDAC кодеги: Ойнотуу сапаты"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth аудио LDAC кодегин тандаңыз:\nОйнотуу сапаты"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляция: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS аркылуу DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Эгер иштетилсе, DNS сервери TLS аркылуу 853-оюкчага аракет кылсын."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Зымсыз дисплейди сертификатто мүмкүнчүлүктөрүн көргөзүү"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi Кармагычта Wi‑Fi протокол деңгээлин жогорулатуу жана ар бир SSID RSSI үчүн көрсөтүү."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Иштетилсе, Wi-Fi байланышы үзүл-кесил болуп жатканда, Wi-Fi тармагы туташууну мобилдик Интернетке өжөрлүк менен өткөрүп берет"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Эч бирөө"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Жөндөөчү күтүлүүдө"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Жөндөлүүчү колдонмо аткаруудан мурун жөндөөчүнүнүн тиркелишин күтүп жатат"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor телефондун/модемдин функцияларында көйгөй тапса, анын таржымалын аныктайт жана мүчүлүштүк тууралуу кабарлоо үчүн колдонуучуга эскертме жөнөтөт"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Киргизүү"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Тартуу"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Визуалдаштырууну аппарат менен ылдамдатуу"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> тууралоолору ачылган жок"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Бул киргизүү ыкмасы сиз терген бардык тексттер, сырсөздөр жана кредиттик  карталар сыяктуу жеке маалыматтарды кошо чогултушу мүмкүн. Бул <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> колдонмосу менен байланыштуу. Ушул киргизүү ыкма колдонулсунбу?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Эскертүү: Өчүрүп-күйгүзгөндөн кийин, бул колдонмо телефондун кулпусу ачылмайынча иштебейт"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS каттоо абалы"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Катталган"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Катталган эмес"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Жеткиликсиз"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 20869a7..f1c92b07 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Audio LDAC Codec: Playback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Select Bluetooth Audio LDAC Codec:\nPlayback Quality"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS ຜ່ານ TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ຫາກເປີດໃຊ້, ລະບົບຈະພະຍາຍາມໃຊ້ DNS ຜ່ານ TLS ຢູ່ຜອດ 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ສະແດງໂຕເລືອກສຳລັບການສະແດງການຮັບຮອງລະບົບໄຮ້ສາຍ"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ເພີ່ມ​ລະ​ດັບ​ການ​ເກັບ​ປະ​ຫວັດ Wi‑Fi, ສະ​ແດງ​ຕໍ່ SSID RSSI ​ໃນ​ Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ເມື່ອເປີດໃຊ້ແລ້ວ, Wi-Fi ຈະສົ່ງຜ່ານການເຊື່ອມຕໍ່ຂໍ້ມູນໄປຫາເຄືອຂ່າຍມືຖືເມື່ອສັນຍານ Wi-Fi ອ່ອນ"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ບໍ່ມີຫຍັງ"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ລໍຖ້າໂຕດີບັ໊ກ"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ແອັບພລິເຄຊັນທີ່ດີບັ໊ກແລ້ວ ຈະຖ້າໂຕດີບັ໊ກກ່ອນການເຮັດວຽກ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor ຈະເກັບກຳບັນທຶກການເຮັດວຽກເມື່ອມັນກວດພົບບັນຫາກັບການເຮັດວຽກຂອງລະບົບໂທລະສັບ/ໂມເດັມ ແລະ ແຈ້ງເຕືອນໃຫ້ຜູ້ໃຊ້ໃຫ້ລາຍງານບັກ"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ການປ້ອນຂໍ້ມູນ"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ການແຕ້ມ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ການສະແດງຜົນໂດຍໃຊ້ຮາດແວຊ່ວຍ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"ລົ້ມເຫລວໃນການເປີດການຕັ້ງຄ່າຂອງ <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ວິທີການປ້ອນຂໍ້ມູນນີ້ ອາດສາມາດເກັບກຳທຸກຂໍ້ຄວາມທີ່ທ່ານພິມ, ຮວມເຖິງຂໍ້ມູນສ່ວນໂຕເຊັ່ນລະຫັດຜ່ານ ແລະໝາຍເລກບັດເຄຣດິດນຳ. ມັນມາຈາກແອັບຯ <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. ທ່ານຕ້ອງການໃຊ້ວິທີການປ້ອນຂໍ້ມູນນີ້ບໍ່?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ໝາຍເຫດ: ຫຼັງຈາກເປີດຂຶ້ນມາໃໝ່ແລ້ວ, ແອັບນີ້ຈະບໍ່ສາມາດເລີ່ມໄດ້ຈົນກວ່າທ່ານຈະປົດລັອກໂທລະສັບຂອງທ່ານ"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"ສະຖານະການລົງທະບຽນ IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"ລົງທະບຽນແລ້ວ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"ບໍ່ໄດ້ລົງທະບຽນ"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"ບໍ່ມີຂໍ້ມູນ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 8d17a3a..f888504 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"„Bluetooth“ garso LDAC kodekas: atkūrimo kokybė"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pasirinkite „Bluetooth“ garso LDAC kodeką:\natkūrimo kokybė"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Srautinis perdavimas: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS naudojant TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Jei parinktis įgalinta, bandykite pateikti DNS užklausą naudojant TLS 853 prievadu."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Rodyti belaidžio rodymo sertifikavimo parinktis"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Padidinti „Wi‑Fi“ įrašymo į žurnalą lygį, rodyti SSID RSSI „Wi-Fi“ rinkiklyje"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Jei ši parinktis įgalinta, „Wi‑Fi“ agresyviau perduos duomenų ryšiu į mobiliojo ryšio tinklą, kai „Wi‑Fi“ signalas silpnas"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nieko"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Laukti derintuvės"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Derinta progr. laukia derint., kad galėtų tęsti."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefonijos stebėjimo priemonė"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telefonijos stebėjimo priemonė rinks žurnalus, kai aptiks problemą dėl telefonijos / modemo funkcijų, ir naudotojui pateiks raginimą pranešti apie riktą"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Įvestis"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Atvaizdavimas"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Aparatinės įrangos paspartintas pateikimas"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nepavyko atidaryti „<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>“ nustatymų"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Naudojant šį įvesties metodą galima rinkti visą įvedamą tekstą, įskaitant asmeninius duomenis, pvz., slaptažodžius ir kredito kortelių numerius. Jis pateikiamas naudojant programą „<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>“. Naudoti šį įvesties metodą?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Pastaba: paleidus iš naujo nebus galima paleisti programos, kol neatrakinsite telefono"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS registracijos būsena"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Užregistruota"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Neužregistruota"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Užimta"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 101b30b..dbe13b5 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth audio LDAC kodeks: atskaņošanas kvalitāte"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Atlasīt Bluetooth audio LDAC kodeku:\natskaņošanas kvalitāte"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Straumēšana: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS, izmantojot TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ja opcija ir iespējota, tiek mēģināts pieprasīt DNS, izmantojot TLS portā 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Rādīt bezvadu attēlošanas sertifikācijas iespējas"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Palieliniet Wi‑Fi reģistrēšanas līmeni; rādīt katram SSID RSSI Wi‑Fi atlasītājā."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ja opcija ir iespējota un Wi‑Fi signāls ir vājš, datu savienojuma pāreja no Wi-Fi uz mobilo tīklu tiks veikta agresīvāk."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nekas"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Gaidīt atkļūdotāju"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Gaida atkļūdotāju, ko pirms izp. piev. atkļ. liet."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefonijas pārraugs"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Ja tiks konstatēta problēma saistībā ar telefonijas/modema funkcionalitāti, telefonijas pārraugs apkopos žurnālus un paziņojumā aicinās lietotāju reģistrēt kļūdu."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ievade"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Zīmējums"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Aparatūras paātrinātā atveidošana"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Neizdevās atvērt lietotnes <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> iestatījumus."</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Izmantojot šo ievades metodi, var tikt vākta informācija par visu ierakstīto tekstu, tostarp personiskiem datiem, piemēram, parolēm un kredītkaršu numuriem. Šī metode ir saistīta ar lietotni <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Vai lietot šo ievades metodi?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Piezīme. Šo lietotni pēc atkārtotas palaišanas nevarēs startēt, kamēr netiks atbloķēts tālrunis."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS reģistrācijas statuss"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Reģistrēts"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Nav reģistrēts"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nepieejams"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 40feed0..0ebb44d 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек за LDAC-аудио преку Bluetooth: квалитет на репродукција"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изберете кодек за LDAC-аудио преку Bluetooth:\nКвалитет на репродукција"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Емитување: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS преку TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ако е овозможено, обидете се со DNS преку TLS на портата 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Покажи ги опциите за безжичен приказ на сертификат"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Зголеми Wi‑Fi ниво на пријавување, прикажи по SSID RSSI во Wi‑Fi бирач"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Кога е овозможено, Wi-Fi ќе биде поагресивна при предавање на интернет-врската на мобилната мрежа при слаб сигнал на Wi-Fi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ништо"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Почекај ја програмата за отстранување грешки"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Пред да се изврши, апликација за отстранување грешки чека програмата за отстранување грешки да се закачи"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Монитор за телефонија"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Мониторот за телефонија ќе води евиденција кога ќе открие проблем со функционалноста на телефонијата/модемот и ќе го извести корисникот да пријави проблем"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Внес"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Цртање"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Прикажување забрзување на хардвер"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Подесувањата за <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> не се отворија"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Овој метод на внес може да го собере сиот текст кој го пишувате, вклучувајќи и лични податоци како што се, лозинки и броеви на кредитни картички. Тоа го прави апликацијата <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Користи го овој метод на внес?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Забелешка: по рестартирање, апликацијава не може да се вклучи додека не го отклучите телефонот"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Состојба на IMS-регистрација"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Регистриран"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Не е регистриран"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Недостапен"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 40525ed..a2a10f2 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth ഓഡിയോ LDAC കോഡെക്: പ്ലേബാക്ക് ‌നിലവാരം"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth ഓഡിയോ LDAC കോഡെക് തിരഞ്ഞെടുക്കുക:\nപ്ലേബാക്ക് ‌നിലവാരം"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"സ്ട്രീമിംഗ്: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS വഴിയുള്ള DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ, പോർട്ട് 853-ലെ TLS വഴി DNS-ന് ശ്രമിക്കുക."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"വയർലെസ് ഡിസ്‌പ്ലേ സർട്ടിഫിക്കേഷനായി ഓപ്‌ഷനുകൾ ദൃശ്യമാക്കുക"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"വൈഫൈ പിക്കറിൽ ഓരോ SSID RSSI പ്രകാരം കാണിക്കാൻ വൈഫൈ ലോഗിംഗ് നില വർദ്ധിപ്പിക്കുക"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"പ്രവർത്തനക്ഷമമായിരിക്കുമ്പോൾ, വൈഫൈ സിഗ്‌നൽ കുറവായിരിക്കുന്ന സമയത്ത് മൊബൈലിലേക്ക് ഡാറ്റ കണക്ഷൻ വഴി കൈമാറുന്നതിൽ വൈഫൈ കൂടുതൽ സക്രിയമായിരിക്കും"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ഒന്നുമില്ല"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ഡീബഗ്ഗറിനായി കാത്തിരിക്കുക"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ഡീബഗ്ഗുചെയ്‌ത അപ്ലിക്കേഷൻ നിർവ്വഹണത്തിനുമുമ്പായി അറ്റാച്ചുചെയ്യുന്നതിന് ഡീബഗ്ഗറിനായി കാത്തിരിക്കുന്നു."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"ടെലിഫോണി മോണിറ്റർ"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"ടെലിഫോണി മോണിറ്റർ, ടെലിഫോണി/മോഡവുമായി ബന്ധപ്പെട്ട് എന്തെങ്കിലും പ്രശ്‌നം കണ്ടെത്തുമ്പോൾ, അതിന്റെ ലോഗുകൾ ശേഖരിക്കുകയും ഒരു ബഗ് ഫയൽ ചെയ്യാൻ ഉപയോക്താവിന് അറിയിപ്പ് നൽകുകയും ചെയ്യും"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ഇൻപുട്ട്"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ഡ്രോയിംഗ്"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ഹാർഡ്‌വെയർ ത്വരിതപ്പെടുത്തിയ റെൻഡർ ചെയ്യൽ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> എന്നതിനായുള്ള ക്രമീകരണങ്ങൾ തുറക്കുന്നതിൽ പരാജയപ്പെട്ടു"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"പാസ്‌വേഡുകൾ, ക്രെഡിറ്റ് കാർഡ് നമ്പറുകൾ എന്നിവ പോലുള്ള വ്യക്തിഗതമായ ഡാറ്റയുൾപ്പെടെ നിങ്ങൾ ടൈപ്പുചെയ്യുന്ന എല്ലാ വാചകവും ഈ ടൈപ്പുചെയ്യൽ രീതിയ്‌ക്ക് ശേഖരിക്കാനായേക്കും. ഇത് <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> അപ്ലിക്കേഷനിൽ നിന്നും വരുന്നു. ഈ ടൈപ്പുചെയ്യൽ രീതി ഉപയോഗിക്കണോ?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ശ്രദ്ധിക്കുക: റീബൂട്ടിന് ശേഷം, ഫോൺ അൺലോക്കുചെയ്യുന്നത് വരെ ഈ ആപ്പ് ആരംഭിക്കാൻ കഴിയില്ല"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS രജിസ്‌ട്രേഷൻ നില"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"രജിസ്റ്റർ ചെയ്‌തു"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"രജിസ്‌റ്റർ ചെയ്‌തിട്ടില്ല"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"ലഭ്യമല്ല"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index f6abd54..0990ecf 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Аудио LDAC Кодлогч: Тоглуулагчийн чанар"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Аудио LDAC Кодлогч сонгох:\nТоглуулагчийн чанар"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Дамжуулж байна: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS дээрх DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Идэвхжүүлсэн тохиолдолд TLS-р DNS-г 853-р портод оролдоно уу."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Утасгүй дэлгэцийн сертификатын сонголтыг харуулах"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi лог-н түвшинг нэмэгдүүлэх, Wi‑Fi Сонгогч дээрх SSID-д ногдох RSSI-г харуулах"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Идэвхжүүлсэн үед Wi‑Fi холболт сул байх үед дата холболтыг мобайлд шилжүүлэхэд илүү идэвхтэй байх болно"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Юуг ч биш"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Согог засагчийг хүлээх"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Согог засагдсан аппликейшн ажиллахын өмнө согог засагчийг хавсаргагдахыг хүлээнэ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Утасны хяналт"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Утасны хяналт нь утас/модемын ажиллагаанд асуудал илрүүлсэн тохиолдолд лог цуглуулж, хэрэглэгчид алдааг засах мэдэгдэл илгээнэ"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Оруулах"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Зураг"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Техник хангамжийн хурдатгалтай үзүүлэлт"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>-н тохиргоог нээж чадсангүй"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Энэ оруулах арга нь таны нууц үгс, зээлийн картын дугаар гэх мэт бичсэн хувийн мэдээллийг цуглуулах боломжтой байж болно. Үүнийг <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> апп нийлүүлдэг. Энэ оруулах аргыг ашиглах уу?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Санамж: Дахин асаасны дараа энэ апп нь таныг утасны түгжээгээ тайлах хүртэл эхлэх боломжгүй"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS бүртгэлийн байдал"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Бүртгэсэн"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Бүртгээгүй"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Байхгүй"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 5e9ecdb..42ee581 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लूटूथ ऑडिओ LDAC कोडेक: प्लेबॅक गुणवत्ता"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लूटूथ ऑडिओ LDAC कोडेक निवडा:\nप्लेबॅक गुणवत्ता"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"स्ट्रीमिंग: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS ओव्हर TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"सुरू केल्यास, पोर्ट 853 वर DNS ओव्हर TLS करण्याचा प्रयत्न करा."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"वायरलेस डिस्प्ले प्रमाणिकरणाचे पर्याय दाखवा"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"वाय-फाय लॉगिंग स्‍तर वाढवा, वाय-फाय सिलेक्टरमध्‍ये प्रति SSID RSSI दर्शवा"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"सक्षम केले असताना, वाय-फाय सिग्‍नल कमी असताना, मोबाइलकडे डेटा कनेक्‍शन सोपवण्यासाठी वाय-फाय अधिक अॅग्रेसिव्ह असेल."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"काहीही नाही"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"डीबगरची प्रतीक्षा करा"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"डीबग केलेले अॅप्लिकेशन अंमलात आणण्यापूर्वी डीबगर संलग्न करण्याची प्रतीक्षा करतो"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"टेलिफोनी मॉनिटर"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"टेलिफोनी/मोडेमच्‍या कार्यक्षमतेत समस्‍या आढळल्‍यावर टेलिफोनी मॉनिटर लॉग्‍ज गोळा करेल आणि दोष फाइल करण्‍यासाठी वापरकर्त्याला सूचनेचे संकेत देईल"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"इनपुट"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"रेखांकन"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"हार्डवेअर प्रवेगक प्रस्तुती"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत संकेतशब्द आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही आपला फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS नोंदणी स्थिती"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"नोंदवलेले"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"नोंदवलेले नाही"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"अनुपलब्ध"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index f252c2a..857093a 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC Audio Bluetooth: Kualiti Main Balik"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Pilih Codec LDAC Audio Bluetooth:\nKualiti Main Balik"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Penstriman: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS di atas TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Jika di dayakan, cuba DNS di atas TLS pada port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Tunjukkan pilihan untuk pensijilan paparan wayarles"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tingkatkan tahap pengelogan Wi-Fi, tunjuk setiap SSID RSSI dalam Pemilih Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Apabila didayakan, Wi-Fi akan menjadi lebih agresif dalam menyerahkan sambungan data ke mudah alih, apabila isyarat Wi-Fi rendah"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Tiada apa-apa"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Nantikan penyahpepijat"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Menanti penyahpepijat sebelum aplikasi melaksana"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor akan mengumpulkan log apabila apl ini mengesan masalah berhubung kefungsian telefoni/modem dan memaparkan pemberitahuan kepada pengguna supaya memfailkan pepijat"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Lukisan"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Pemaparan dipercepat perkakasan"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Gagal membuka tetapan untuk <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Kaedah input ini mungkin boleh mengumpulkan semua teks yang anda taipkan, termasuk data peribadi seperti kata laluan dan nombor kad kredit. Ia datang daripada aplikasi <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Gunakan kaedah input ini?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Perhatian: Selepas but semula, apl ini tidak dapat dimulakan sehingga anda membuka kunci telefon"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Keadaan pendaftaran IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Berdaftar"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Tidak didaftarkan"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Tidak tersedia"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 74302a0..7d166b4 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ဘလူးတုသ်အသံ LDAC ကိုးဒက်ခ်− နားထောင်ရန် အရည်အသွေး"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ဘလူးတုသ်အသံ LDAC ကိုးဒက်ခ်ကို ရွေးပါ−\nနားထောင်ရန် အရည်အသွေး"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"တိုက်ရိုက်လွှင့်နေသည်− <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ဖွင့်ထားပါက DNS over TLS ကို ပို့တ် ၈၅၃ တွင် စမ်းကြည့်ပါ။"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ကြိုးမဲ့ အခင်းအကျင်း အသိအမှတ်ပြုလက်မှတ်အတွက် ရွေးချယ်စရာများပြရန်"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi မှတ်တမ်းတင်ခြင်း နှုန်းအားမြင့်ကာ၊ Wi‑Fi ရွေးရာတွင် SSID RSSI ဖြင့်ပြပါ"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ဖွင့်ထားပါက Wi‑Fi လွှင့်အား နည်းချိန်တွင် Wi‑Fi မှ မိုဘိုင်းသို့ ဒေတာချိတ်ဆက်မှုကို လွှဲပြောင်းရာ၌ ပိုမိုထိရောက်ပါသည်"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"တခုမှမရှိ"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"အပြစ်ရှာဖွေ ဖယ်ရှားချက်ကိုစောင့်ရန်"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"အမှားပြင်ဆင်ရှာဖွေသည့် အပလီကေးရှင်းသည် လုပ်ငန်းမစမီ တွဲဖက်ရန် အမှားရှာဖွေမည့်သူကို စောင့်နေသည်။"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"တယ်လီဖုန်းဆက်သွယ်မှု မော်နီတာ"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"တယ်လီဖုန်းဆက်သွယ်မှု မော်နီတာသည် တယ်လီဖုန်းဆက်သွယ်မှု/မိုဒမ် လုပ်ဆောင်ချက်တို့တွင် ပြဿနာရှိနေလျှင် မှတ်တမ်းပြုစုပြီး ချွတ်ယွင်းချက် အစီရင်ခံရန် အသုံးပြုသူကို အကြောင်းကြားပေးမည် ဖြစ်သည်။"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ထည့်သွင်းရန်"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ရေးဆွဲခြင်း"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ဟာ့ဒ်ဝဲ အရှိန်မြှင့် ပုံဖော်ခြင်း"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>အတွက် ဆက်တင်းများဖွင့်ရန် မအောင်မြင်ပါ။"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ဤထည့်သွင်းမှုနည်းလမ်းမှာ သင့်ကိုယ်ရေးအချက်အလက်များဖြစ်သော စကားဝှက်များနှင့် ကရက်ဒစ်ကဒ်နံပါတ်စသည်တို့ကို ရယူသွားမည်ဖြစ်သည်။ <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>အပလီကေးရှင်းမှလာပါသည်။ ဤထည့်သွင်းမှုနည်းလမ်းကို အသုံးပြုမည်လား?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"မှတ်ချက် − ပြန်လည်စတင်ပြီးနောက် သင့်ဖုန်းကိုလော့ခ်မဖွင့်မချင်း ဤအက်ပ်ကို အသုံးပြု၍မရပါ"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS မှတ်ပုံတင်ခြင်း အခြေအနေ"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"မှတ်ပုံတင်ထားသည်"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"မှတ်ပုံတင်မထားပါ"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"မရရှိနိုင်ပါ။"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index cc73ef9..f88df54 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-kodek for Bluetooth-lyd: Avspillingskvalitet"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Velg LDAC-kodek for Bluetooth-lyd:\nAvspillingskvalitet"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strømming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS over TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Hvis det er slått på, forsøk DNS over TLS på port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Vis alternativer for sertifisering av trådløs skjerm"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Øk Wi-Fi-loggenivå – vis per SSID RSSI i Wi-Fi-velgeren"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Hvis dette slås på, overfører Wi-Fi-nettverket datatilkoblingen til mobil mer aggressivt når Wi-Fi-signalet er svakt"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ingen"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Vent på feilsøkingsverktøyet"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Feilsøkt app venter til feilsøkingsverktøyet er lagt til før den kjører"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefonimonitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor logger problemer som oppdages med funksjonaliteten til telefoni/modem, og varsler brukeren om å sende inn en feilrapport."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Inndata"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Tegning"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Maskinvareakselerert gjengivelse"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Kunne ikke åpne innstillingene for <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Denne inndatametoden har tilgang til all tekst du skriver, blant annet personlige opplysninger som for eksempel passord og kredittkortnumre. Den kommer fra appen <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Vil du bruke denne inndatametoden?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Merk: Etter en omstart kan ikke denne appen starte før du låser opp telefonen din"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Tilstand for IMS-registrering"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrert"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ikke registrert"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Ikke tilgjengelig"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index fef1ebe..a3875a6 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ब्लुटुथ अडियो LDAC कोडेक: प्लेब्याक गुणस्तर"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ब्लुटुथ अडियो LDAC कोडेक चयन गर्नुहोस्‌:\nप्लेब्याक गुणस्तर"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"स्ट्रिमिङ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS मा DNS को प्रयोग"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"सक्षम पारिएको खण्डमा पोर्ट ८५३ मा TLS मा DNS प्रयोग गर्ने अनुरोध गर्नुहोस्।"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ताररहित प्रदर्शन प्रमाणीकरणका लागि विकल्पहरू देखाउनुहोस्"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi लग स्तर बढाउनुहोस्, Wi-Fi चयनकर्तामा प्रति SSID RSSI देखाइन्छ"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"सक्षम गरिएको अवस्थामा, Wi-Fi सिग्नल न्यून हुँदा, Wi-Fi ले बढी आक्रामक ढंगले मोबाइलमा डेटा जडान हस्तान्तरण गर्नेछ"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"केही पनि होइन"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"डिबग गर्नेलाई पर्खनुहोस्"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"डिबग भएको अनुप्रयोग कार्यन्वयन हुनु अघि संलग्न हुन डिबग गर्नेलाई पर्खन्छ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"टेलिफोनी मनिटर"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor ले टेलिफोन/मोडेमको सञ्चालनमा कुनै समस्या भेट्टायो भने लगहरू सङ्कलन गर्नेछ र प्रयोगकर्तालाई बग बारे रिपोर्ट गर्न सूचना पठाउनेछ"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"इनपुट"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"रेखाचित्र"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"हार्डवेयर प्रतिपादन फुर्तिलो बनाइयो"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>का लागि सेटिङहरू खोल्न विफल भयो।"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"यस इनपुट विधिले तपाईँले टाइप गर्नुहुने सम्पूर्ण पाठ बटु्ल्न सक्छ, व्यक्तिगत डेटा जस्तै पासवर्ड र क्रेडिट कार्ड नम्बर लगायतका। यो <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अनुप्रयोगबाट आउँदछ। यो इनपुट विधि प्रयोग गर्ने हो?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टिपोट: पुनःबुट पछि तपाईँले आफ्नो फोनलाई अनलक नगरेसम्म यो अनुप्रयोग सुरु हुन सक्दैन"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS दर्ताको स्थिति"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"दर्ता गरिएको"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"दर्ता नगरिएको"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"अनुपलब्ध"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 25d7cc5..4d0d7fe 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -173,8 +173,8 @@
     <string name="enable_adb" msgid="7982306934419797485">"USB-foutopsporing"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"Foutopsporingsmodus bij USB-verbinding"</string>
     <string name="clear_adb_keys" msgid="4038889221503122743">"Autorisatie USB-foutopsporing intrekken"</string>
-    <string name="bugreport_in_power" msgid="7923901846375587241">"Snelle link naar foutenrapport"</string>
-    <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Een knop in het voedingsmenu weergeven om een foutenrapport te maken"</string>
+    <string name="bugreport_in_power" msgid="7923901846375587241">"Snelle link naar bugrapport"</string>
+    <string name="bugreport_in_power_summary" msgid="1778455732762984579">"Een knop in het voedingsmenu weergeven om een bugrapport te maken"</string>
     <string name="keep_screen_on" msgid="1146389631208760344">"Stand-by"</string>
     <string name="keep_screen_on_summary" msgid="2173114350754293009">"Scherm gaat nooit uit tijdens het opladen"</string>
     <string name="bt_hci_snoop_log" msgid="3340699311158865670">"Snoop-logbestand voor Bluetooth-HCI inschakelen"</string>
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC-codec voor Bluetooth-audio: afspeelkwaliteit"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"LDAC-codec voor Bluetooth-audio selecteren:\nafspeelkwaliteit"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS via TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Als deze optie is ingeschakeld, wordt geprobeerd om DNS via TLS uit te voeren via poort 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Opties weergeven voor certificering van draadloze weergave"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Logniveau voor wifi verhogen, weergeven per SSID RSSI in wifi-kiezer"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Indien ingeschakeld, is wifi agressiever bij het overgeven van de gegevensverbinding aan mobiel wanneer het wifi-signaal zwak is"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Niets"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Wachten op debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Gedebugde app wacht op koppelen van debugger vóór uitvoering"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor verzamelt logbestanden wanneer een probleem wordt gedetecteerd met de functionaliteit van telefonie/modem. De gebruiker krijgt een melding te zien waarin wordt gevraagd of hij een bug wil indienen."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Invoer"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Tekening"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Rendering met hardwareversnelling"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Instellingen openen voor <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> mislukt"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Deze invoermethode verzamelt mogelijk alle tekst die je typt, inclusief persoonlijke gegevens zoals wachtwoorden en creditcardnummers. De methode is afkomstig uit de app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Deze invoermethode inschakelen?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Opmerking: Wanneer je telefoon opnieuw is opgestart, kan deze app pas worden gestart nadat je je telefoon hebt ontgrendeld"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-registratiestatus"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Geregistreerd"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Niet geregistreerd"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Niet beschikbaar"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 6e93e869..56e361f 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਕੋਡੇਕ: ਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"ਬਲੂਟੁੱਥ ਔਡੀਓ LDAC ਕੋਡੇਕ ਚੁਣੋ:\nਪਲੇਬੈਕ ਗੁਣਵੱਤਾ"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ਸਟ੍ਰੀਮਿੰਗ: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS \'ਤੇ DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ਚਾਲੂ ਕੀਤੇ ਜਾਣ \'ਤੇ, ਪੋਰਟ 853 \'ਤੇ TLS \'ਤੇ DNS ਵਰਤਣ ਦੀ ਬੇਨਤੀ ਕਰੋ।"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"ਵਾਇਰਲੈੱਸ ਡਿਸਪਲੇ ਪ੍ਰਮਾਣੀਕਰਨ ਲਈ ਚੋਣਾਂ ਪ੍ਰਦਰਸ਼ਿਤ ਕਰੋ"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"ਵਾਈ‑ਫਾਈ ਲੌਗਿੰਗ ਪੱਧਰ ਵਧਾਓ, ਵਾਈ‑ਫਾਈ Picker ਵਿੱਚ ਪ੍ਰਤੀ SSID RSSI ਦਿਖਾਓ"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ਜਦੋਂ ਯੋਗ ਬਣਾਇਆ ਹੋਵੇ, ਤਾਂ ਵਾਈ‑ਫਾਈ ਸਿਗਨਲ ਘੱਟ ਹੋਣ \'ਤੇ ਵਾਈ‑ਫਾਈ ਡਾਟਾ ਕਨੈਕਸ਼ਨ ਮੋਬਾਈਲ ਨੂੰ ਹੈਂਡ ਓਵਰ ਕਰਨ ਵਿੱਚ ਵੱਧ ਆਕਰਮਣਸ਼ੀਲ ਹੋਵੇਗਾ।"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ਕੁਝ ਨਹੀਂ"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ਡੀਬੱਗਰ ਦੀ ਉਡੀਕ ਕਰੋ"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ਡੀਬੱਗ ਕੀਤੇ ਐਪਲੀਕੇਸ਼ਨ ਐਗਜੀਕਿਊਟ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਅਟੈਚ ਕਰਨ ਲਈ ਡੀਬੱਗਰ ਦੀ ਉਡੀਕ ਕਰਦੇ ਹਨ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"ਟੈਲੀਫ਼ੋਨੀ ਮੋਨੀਟਰ"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"ਟੈਲੀਫ਼ੋਨੀ ਮੋਨੀਟਰ ਟੈਲੀਫ਼ੋਨੀ/ਮੌਡਮ ਪ੍ਰਕਾਰਜਾਤਮਕਤਾ ਵਿੱਚ ਕਿਸੇ ਸਮੱਸਿਆ ਦਾ ਪਤਾ ਲੱਗਣ \'ਤੇ ਲੌਗਾਂ ਨੂੰ ਇਕੱਤਰ ਕਰੇਗਾ ਅਤੇ ਵਰਤੋਂਕਾਰ ਨੂੰ ਇੱਕ ਬੱਗ ਦਾਇਰ ਕਰਨ ਲਈ ਸੂਚਨਾ ਦੇਵੇਗਾ"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ਇਨਪੁਟ"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ਡਰਾਇੰਗ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ਹਾਰਡਵੇਅਰ ਐਕਸੇਲਰੇਟਿਡ ਰੈਂਡਰਿੰਗ"</string>
@@ -331,7 +331,7 @@
     <string name="convert_to_file_encryption_enabled" msgid="2861258671151428346">"ਤਬਦੀਲ ਕਰੋ ..."</string>
     <string name="convert_to_file_encryption_done" msgid="7859766358000523953">"ਫ਼ਾਈਲ ਪਹਿਲਾਂ ਤੋਂ ਇਨਕ੍ਰਿਪਟਡ ਹੈ"</string>
     <string name="title_convert_fbe" msgid="1263622876196444453">"ਫ਼ਾਈਲ ਆਧਾਰਿਤ ਇਨਕ੍ਰਿਪਸ਼ਨ ਵਿੱਚ ਤਬਦੀਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
-    <string name="convert_to_fbe_warning" msgid="6139067817148865527">" ਡਾਟਾ  ਪਾਰਟੀਸ਼ਨ ਦਾ ਫ਼ਾਈਲ ਆਧਾਰਿਤ ਇਨਕ੍ਰਿਪਸ਼ਨ ਵਿੱਚ ਰੁਪਾਂਤਰਣ ਕਰੋ\n !! ਚਿਤਾਵਨੀ !! ਇਹ ਤੁਹਾਡੇ ਸਾਰੇ ਡੈਟੇ ਨੂੰ ਮਿਟਾ ਦੇਵੇਗਾ\n ਇਹ ਵਿਸ਼ੇਸ਼ਤਾ ਪ੍ਰਯੋਗਿਕ ਹੈ, ਅਤੇ ਸ਼ਾਇਦ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।\n ਜਾਰੀ ਰੱਖਣ ਲਈ \'ਮਿਟਾਓ ਅਤੇ ਰੁਪਾਂਤਰਣ ਕਰੋ...\' ਨੂੰ ਦਬਾਓ।"</string>
+    <string name="convert_to_fbe_warning" msgid="6139067817148865527">" ਡਾਟਾ  ਪਾਰਟੀਸ਼ਨ ਦਾ ਫ਼ਾਈਲ ਆਧਾਰਿਤ ਇਨਕ੍ਰਿਪਸ਼ਨ ਵਿੱਚ ਰੁਪਾਂਤਰਣ ਕਰੋ\n !! ਚਿਤਾਵਨੀ !! ਇਹ ਤੁਹਾਡੇ ਸਾਰੇ  ਡਾਟੇ  ਨੂੰ ਮਿਟਾ ਦੇਵੇਗਾ\n ਇਹ ਵਿਸ਼ੇਸ਼ਤਾ ਪ੍ਰਯੋਗਿਕ ਹੈ, ਅਤੇ ਸ਼ਾਇਦ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ।\n ਜਾਰੀ ਰੱਖਣ ਲਈ \'ਮਿਟਾਓ ਅਤੇ ਰੁਪਾਂਤਰਣ ਕਰੋ...\' ਨੂੰ ਦਬਾਓ।"</string>
     <string name="button_convert_fbe" msgid="5152671181309826405">"ਮਿਟਾਓ ਅਤੇ ਰੁਪਾਂਤਰਣ ਕਰੋ..."</string>
     <string name="picture_color_mode" msgid="4560755008730283695">"ਤਸਵੀਰ ਰੰਗ ਮੋਡ"</string>
     <string name="picture_color_mode_desc" msgid="1141891467675548590">"sRGB ਵਰਤੋਂ ਕਰੋ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> ਲਈ ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹਣ ਵਿੱਚ ਅਸਫਲ"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ਇਹ ਇਨਪੁੱਟ ਵਿਧੀ ਤੁਹਾਡੇ ਵੱਲੋਂ ਟਾਈਪ ਕੀਤਾ ਜਾਣ ਵਾਲੀ ਸਾਰੀ ਲਿਖਤ ਇਕੱਤਰ ਕਰਨ ਵਿੱਚ ਸਮਰੱਥ ਹੋ ਸਕਦੀ ਹੈ, ਨਿੱਜੀ ਡਾਟਾ ਸਮੇਤ ਜਿਵੇਂ ਪਾਸਵਰਡ ਅਤੇ ਕ੍ਰੈਡਿਟ ਕਾਰਡ ਨੰਬਰ। ਇਹ <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ਐਪ ਤੋਂ ਆਉਂਦਾ ਹੈ। ਕੀ ਇਹ ਸਪੈੱਲ ਚੈਕਰ ਵਰਤਣਾ ਹੈ?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"ਨੋਟ ਕਰੋ: ਰੀਬੂਟ ਤੋਂ ਬਾਅਦ, ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਆਪਣਾ ਫ਼ੋਨ ਅਣਲਾਕ ਨਹੀਂ ਕਰਦੇ ਤਦ ਤੱਕ ਇਹ ਐਪ ਚਾਲੂ ਨਹੀਂ ਹੋ ਸਕਦੀ"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS ਰਜਿਸਟਰੇਸ਼ਨ ਸਥਿਤੀ"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"ਰਜਿਸਟਰ ਕੀਤੀ ਗਈ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"ਰਜਿਸਟਰ ਨਹੀਂ ਕੀਤੀ ਗਈ"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"ਅਣਉਪਲਬਧ"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 59d4724..51c8505 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek dźwięku Bluetooth LDAC: jakość odtwarzania"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Wybierz kodek dźwięku Bluetooth LDAC:\njakość odtwarzania"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Strumieniowe przesyłanie danych: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS przez TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Po włączeniu wypróbuj komunikację DNS przez TLS przez port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaż opcje certyfikacji wyświetlacza bezprzewodowego"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zwiększ poziom rejestrowania Wi‑Fi, pokazuj według RSSI SSID w selektorze Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Po włączeniu połączenie danych będzie bardziej agresywnie przełączać się z Wi-Fi na sieć komórkową przy słabym sygnale Wi-Fi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Brak"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Poczekaj na debugera"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikacja do debugowania czeka na przyłączenie debugera"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitorowanie telefonii"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor zbiera dzienniki po wykryciu problemu z funkcją telefonu/modemu i powiadamia użytkownika o możliwości zgłoszenia błędu"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ekran dotykowy"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Rysowanie"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Sprzętowa akceleracja renderowania"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nie udało się otworzyć ustawień aplikacji <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ta metoda wprowadzania tekstu może gromadzić cały wpisywany tekst, w tym dane osobowe takie jak hasła czy numery kart kredytowych. Pochodzi ona z aplikacji <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Użyć jej?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Uwaga: po restarcie ta aplikacja będzie mogła uruchomić się dopiero po odblokowaniu telefonu"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stan rejestracji IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Zarejestrowane"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Niezarejestrowane"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Niedostępny"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index 4e85684..6b2a5ca 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec de áudio Bluetooth LDAC: qualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec de áudio Bluetooth LDAC:\nqualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS por TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Se essa opção estiver ativada, tente o DNS por TLS na porta 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções de certificação de Display sem fio"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de registro do Wi-Fi; mostrar conforme o RSSI de SSID na Seleção de Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Quando ativada, o Wi-Fi será mais agressivo em passar a conexão de dados para móvel, quando o sinal de Wi-Fi estiver fraco"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nada"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Aguardar depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"App depurado espera conexão com debugger antes de ser executado."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor de telefonia"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Ao detectar um problema com a funcionalidade de modem/telefonia, o Monitor de telefonia coletará registros e enviará uma notificação ao usuário para informar um bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Desenho"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Renderização acelerada por hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Falha ao abrir as configurações de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Este método de entrada pode coletar todo o texto que você digita, incluindo dados pessoais, como senhas e números de cartão de crédito. É um método do app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Usar este método de entrada?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Observação: após uma reinicialização, não é possível iniciar este app até que você desbloqueie seu smartphone"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estado do registro de IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrado"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Não registrado"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Não disponível"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 44035d4..1a6f0c6 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC de áudio Bluetooth: qualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec LDAC de áudio Bluetooth:\nQualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmissão em fluxo contínuo: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS através de TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Se a opção estiver ativada, tente efetuar DNS através de TLS na porta 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções da certificação de display sem fios"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de reg. de Wi-Fi, mostrar por RSSI de SSID no Selec. de Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Se estiver ativado, o Wi-Fi será mais agressivo ao transmitir a lig. de dados para a rede móvel quando o sinal Wi-Fi estiver fraco"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nenhuma"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Aguarde pelo depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"A aplicação depurada aguarda a anexação do depurador antes da execução"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor de telefonia"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"O Monitor de telefonia recolhe registos quando deteta um problema de funcionalidade de telefonia/modem e apresenta uma notificação ao utilizador para reportar um erro"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Introdução"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Desenho"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Conversão acelerada de hardware"</string>
@@ -359,9 +359,9 @@
     <string name="battery_info_status_discharging" msgid="310932812698268588">"Não está a carregar"</string>
     <string name="battery_info_status_not_charging" msgid="8523453668342598579">"Ligada à corrente, não é possível carregar neste momento"</string>
     <string name="battery_info_status_full" msgid="2824614753861462808">"Completo"</string>
-    <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlado pelo administrador"</string>
-    <string name="enabled_by_admin" msgid="5302986023578399263">"Ativada pelo administrador"</string>
-    <string name="disabled_by_admin" msgid="8505398946020816620">"Desativada pelo administrador"</string>
+    <string name="disabled_by_admin_summary_text" msgid="6750513964908334617">"Controlado pelo gestor"</string>
+    <string name="enabled_by_admin" msgid="5302986023578399263">"Ativada pelo gestor"</string>
+    <string name="disabled_by_admin" msgid="8505398946020816620">"Desativada pelo gestor"</string>
     <string name="disabled" msgid="9206776641295849915">"Desativada"</string>
     <string name="external_source_trusted" msgid="2707996266575928037">"Autorizada"</string>
     <string name="external_source_untrusted" msgid="2677442511837596726">"Não autorizada"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Falha ao abrir as definições para <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Este método de introdução pode permitir a recolha de todo o texto que digitar, incluindo dados pessoais como, por exemplo, palavras-passe e números de cartões de crédito. Decorre da aplicação <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Utilizar este método de introdução?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: após reiniciar, só é possível iniciar esta aplicação quando o telemóvel for desbloqueado."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estado do registo IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registado"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Não registado"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Indisponível"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index 4e85684..6b2a5ca 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec de áudio Bluetooth LDAC: qualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selecionar codec de áudio Bluetooth LDAC:\nqualidade de reprodução"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS por TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Se essa opção estiver ativada, tente o DNS por TLS na porta 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Mostrar opções de certificação de Display sem fio"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Aumentar o nível de registro do Wi-Fi; mostrar conforme o RSSI de SSID na Seleção de Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Quando ativada, o Wi-Fi será mais agressivo em passar a conexão de dados para móvel, quando o sinal de Wi-Fi estiver fraco"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nada"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Aguardar depurador"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"App depurado espera conexão com debugger antes de ser executado."</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitor de telefonia"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Ao detectar um problema com a funcionalidade de modem/telefonia, o Monitor de telefonia coletará registros e enviará uma notificação ao usuário para informar um bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Entrada"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Desenho"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Renderização acelerada por hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Falha ao abrir as configurações de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Este método de entrada pode coletar todo o texto que você digita, incluindo dados pessoais, como senhas e números de cartão de crédito. É um método do app <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Usar este método de entrada?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Observação: após uma reinicialização, não é possível iniciar este app até que você desbloqueie seu smartphone"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Estado do registro de IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrado"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Não registrado"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Não disponível"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 09bcc8f..7be64e4 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codecul LDAC audio pentru Bluetooth: calitatea redării"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Selectați codecul LDAC audio pentru Bluetooth:\ncalitatea redării"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmitere în flux: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS prin TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Dacă opțiunea este activată, încercați DNS prin TLS pe portul 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Afișați opțiunile pentru certificarea Ecran wireless"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Măriți niv. de înr. prin Wi‑Fi, afișați în fcț. de SSID RSSI în Selectorul Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Când este activată, Wi-Fi va fi mai agresivă la predarea conexiunii de date către rețeaua mobilă când semnalul Wi-Fi este slab"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Niciuna"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Așteptați depanatorul"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Înaintea executării, aplicația așteaptă atașarea depanatorului"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor colectează jurnale când detectează o problemă privind performanțele serviciilor de telefonie/modemului și trimite notificări utilizatorului să semnaleze o eroare"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Intrare"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Desen"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Redare accelerată hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Deschiderea setărilor pentru <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> a eșuat"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Această metodă de introducere de text poate culege în întregime textul introdus, inclusiv datele personale, cum ar fi parolele și numerele cardurilor de credit. Metoda provine de la aplicația <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Utilizați această metodă de introducere de text?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Notă: după repornire, această aplicație nu poate porni până nu deblocați telefonul"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Situația înregistrării IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Înregistrat"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Neînregistrat"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Indisponibilă"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 6563ac1..d88adea 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Аудиокодек LDAC для Bluetooth: качество воспроизведения"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Аудиокодек LDAC для Bluetooth:\nкачество воспроизведения"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Потоковая передача: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS по TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Если функция включена, соединение с DNS будет выполняться по TLS через порт 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показывать параметры сертификации беспроводных мониторов"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"При выборе Wi‑Fi указывать в журнале RSSI для каждого SSID"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Принудительно переключаться на мобильную сеть, если сигнал Wi-Fi слабый"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Нет"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Подождите, пока подключится отладчик"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Приложение ожидает подключения отладчика"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Обнаружив проблему в работе модема или телефонной связи, Telephony Monitor будет собирать журналы и предлагать пользователю отправить информацию об ошибке"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ввод"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Отрисовка"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Аппаратное ускорение визуализации"</string>
@@ -295,7 +295,7 @@
     <string name="app_process_limit_title" msgid="4280600650253107163">"Лимит фоновых процессов"</string>
     <string name="show_all_anrs" msgid="28462979638729082">"Все ANR"</string>
     <string name="show_all_anrs_summary" msgid="641908614413544127">"Уведомлять о том, что приложение не отвечает"</string>
-    <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Показывать предупреждения канала передачи оповещения"</string>
+    <string name="show_notification_channel_warnings" msgid="1399948193466922683">"Показывать предупреждения канала передачи уведомлений"</string>
     <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"Показывать предупреждение о новых уведомлениях приложения вне допустимого канала"</string>
     <string name="force_allow_on_external" msgid="3215759785081916381">"Разрешить сохранение на внешние накопители"</string>
     <string name="force_allow_on_external_summary" msgid="3640752408258034689">"Разрешить сохранение приложений на внешних накопителях (независимо от значений в манифесте)"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Не удалось открыть настройки приложения \"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>\""</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"При использовании этого способа могут собираться все вводимые данные, в том числе пароли и номера кредитных карт. Запрос поступил от приложения \"<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>\". Использовать этот способ ввода?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Примечание. Чтобы запустить это приложение после перезагрузки, разблокируйте экран."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Статус регистрации сервиса IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Зарегистрирован"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Не зарегистрирован"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Недоступно"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 3fb05b0..db01eea 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"බ්ලූටූත් ශ්‍රව්‍ය LDAC පසුධාවන ගුණත්වය"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"බ්ලූටූත් ශ්‍රව්‍ය LDAC කොඩෙක් තෝරන්න:\nපසුධාවන ගුණත්වය"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ප්‍රවාහ කරමින්: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS හරහා DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"සබල නම්, 853 තොට මත TLS හරහා DNS උත්සාහ කරන්න."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"නොරැහැන් සංදර්ශක සහතිකය සඳහා විකල්ප පෙන්වන්න"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ලොග් මට්ටම වැඩි කරන්න, Wi‑Fi තෝරනයෙහි SSID RSSI අනුව පෙන්වන්න"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"සබල විට Wi‑Fi සිග්නලය අඩු විට Wi‑Fi දත්ත සම්බන්ධතාවය ජංගම වෙත භාර දීමට වඩා ආක්‍රමණික වේ"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"කිසිවක් නැත"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"නිදොස්කරණය සඳහා රැඳෙන්න"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"නිදොස් කළ යෙදුම් ක්‍රියාකිරීමට පෙර ඇමිණීම සඳහා නිදොස්කරණය වෙත බලා සිටියි"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"දුරකථන/මොඩම් ක්‍රියාකාරිත්වයේ ගැටළුවක් හඳුනාගත් විට TelephonyMonitor ලොග එකතු කර දෝෂයක්ගොනු කිරීමට පරිශීලකයා වෙත දැනුම් දෙයි"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ආදානය"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ඇඳීම්"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"දෘඩාංග වේගය වැඩි කළ පිරිනැමීම"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> සඳහා සැකසීම් විවෘත කිරීම අසාර්ථක විය"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"මෙම ආදාන ක්‍රමය මුරපද සහ ණයපත් අංක වැනි පෞද්ගලික දත්ත ඇතුළුව ඔබ ටයිප් කරන පෙළ එකතු කිරීමට හැකිය, එය <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> යෙදුමන් ලැබේ. මෙම ආදාන ක්‍රමය භාවිතා කරන්නද?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"සටහන: නැවත පණ ගැන්වීමකට පසුව, ඔබ ඔබේ දුරකථනය අගුලු හරින තෙක් මෙම යෙදුම ආරම්භ කළ නොහැකිය"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS ලියාපදිංචි තත්ත්වය"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"ලියාපදිංචි වී ඇත"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"ලියාපදිංචි වී නැත"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"ලබාගත නොහැක"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 1ccfda6..fc7e1c3 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodek LDAC Bluetooth Audio: Kvalita prehrávania"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Vybrať kodek LDAC Bluetooth Audio:\nKvalita prehrávania"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streamovanie: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS cez TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ak túto možnosť aktivujete, zariadenie sa bude pripájať k serverom DNS pomocou protokolu TLS na porte 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Zobraziť možnosti certifikácie bezdrôtového zobrazenia"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Zvýšiť úroveň denníkov Wi‑Fi, zobrazovať podľa SSID RSSI pri výbere siete Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Keď túto možnosť zapnete, Wi‑Fi bude agresívnejšie odovzdávať dátové pripojenie na mobilnú sieť vtedy, keď bude slabý signál Wi‑Fi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nič"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Čakať na ladiaci nástroj"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikácia čaká na pripojenie ladiaceho nástroja"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitorovanie telefonických služieb"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Keď monitorovanie telefonických služieb zistí problém s fungovaním telefonických služieb alebo modemu, bude zhromažďovať denníky a zobrazí používateľovi upozornenie s výzvou na nahlásenie chyby"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Vstup"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Vykreslovanie"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Hardvérom zrýchlené vykresľovanie"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nastavenia aplikácie <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> sa nepodarilo otvoriť"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Pri tejto metóde vstupu sa môže zhromažďovať zadávaný text vrátane osobných údajov, ako sú heslá alebo čísla kreditných kariet. Táto metóda je poskytovaná aplikáciou <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Chcete použiť túto metódu vstupu?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Poznámka: Po reštartovaní sa táto aplikácia spustí až vtedy, keď odomknete telefón"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stav registrácie IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrované"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Neregistrované"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nie je k dispozícii"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index de13147..07475d3 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Zvočni kodek LDAC za Bluetooth: kakovost predvajanja"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Izberi zvočni kodek LDAC za Bluetooth:\nKakovost predvajanja"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Pretočno predvajanje: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS s šifriranjem TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Če je ta možnost omogočena, poskusi vzpostaviti povezavo DNS s šifriranjem TLS na vratih 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Pokaži možnosti za potrdilo brezžičnega zaslona"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Povečaj raven zapis. dnev. za Wi-Fi; v izbir. Wi‑Fi-ja pokaži glede na SSID RSSI"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Če je ta možnost omogočena, Wi-Fi odločneje preda podatkovno povezavo mobilnemu omrežju, ko je signal Wi-Fi šibek."</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Nič"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Počakajte na iskalnik napak"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikacija, v kateri iščete napako, pred izvajanjem čaka na povezavo z iskalnikom napak"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Nadziranje telefonije"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Funkcija Nadziranje telefonije pridobi dnevnike, ko zazna težavo s funkcijami telefonije/modema, in uporabnika z obvestilom pozove, naj prijavi napako."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Vnos"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Risba"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Upodabljanje s strojnim pospeševanjem"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Ni mogoče odpreti nastavitev za <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ta način vnosa lahko morda zbere besedilo, ki ga vtipkate, vključno z osebnimi podatki, kot so gesla in številke kreditnih kartic. Omogoča ga aplikacija <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Ali želite uporabiti ta način vnosa?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Opomba: po vnovičnem zagonu te aplikacije ni mogoče zagnati, če ne odklenete telefona."</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Stanje registracije IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrirana"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ni registrirana"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Ni na voljo"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index f77818d..4fcfc5c 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodeku LDAC i audios së Bluetooth-it: Cilësia e luajtjes"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Zgjidh kodekun LDAC të audios së Bluetooth-it:\nCilësia e luajtjes"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Transmetimi: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS nëpërmjet protokollit TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Nëse është aktivizuar, provo DNS-në nëpërmjet protokollit TLS në portën 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Shfaq opsionet për certifikimin e ekranit valor"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Rrit nivelin regjistrues të Wi‑Fi duke shfaqur SSID RSSI-në te Zgjedhësi i Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kur ky funksion aktivizohet, Wi‑Fi bëhet më agresiv në kalimin e lidhjes së të dhënave te rrjeti celular, në rastet kur sinjali Wi‑Fi është i dobët"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Asnjë"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Prit për korrigjuesin"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Aplikacioni i korrigjimit të gabimeve pret që korrigjuesi të bashkëngjitet para ekzekutimit"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Monitori i telefonisë"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Monitori i telefonisë do të mbledhë regjistrat kur të zbulojë një problem me funksionalitetin e telefonisë/modemit dhe do t\'i paraqesë një njoftim përdoruesit që të regjistrojë një defekt në kod"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Hyrja"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Vizatime"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Interpretimi i përshpejtuar i harduerit"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Dështoi në hapjen e cilësimeve për <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Kjo metodë hyrjeje mund të mbledhë të gjithë tekstin që shkruan, përfshirë të dhënat personale si fjalëkalimet dhe numrat e kartave të kreditit. Kjo vjen nga aplikacioni <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Të përdoret kjo metodë hyrjeje?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Shënim: Pas një rinisjeje, ky aplikacion nuk mund të niset derisa të shkyçësh telefonin"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Gjendja e regjistrimit të IMS-së"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Regjistruar"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Paregjistruar"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Nuk ofrohet"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 2b59fee..e591aa1 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth аудио кодек LDAC: квалитет репродукције"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Изаберите Bluetooth аудио кодек LDAC:\nквалитет репродукције"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Стримовање: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS преко TLS-а"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ако је омогућено, пробајte DNS преко TLS-а на порту 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Приказ опција за сертификацију бежичног екрана"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Повећава ниво евидентирања за Wi‑Fi. Приказ по SSID RSSI-у у бирачу Wi‑Fi мреже"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Кад се омогући, Wi‑Fi ће бити агресивнији при пребацивању мреже за пренос података на мобилну ако је Wi‑Fi сигнал слаб"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ниједна"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Сачекај програм за отклањање грешака"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Апликација чека програм за отклањање грешака да приложи пре извршавања"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"TelephonyMonitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor ће прикупљати евиденцију када открије проблем са функционисањем телефоније/модема и затражиће од корисника да пријави грешку"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Унос"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Цртање"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Хардверски убрзано приказивање"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Отварање подешавања за апликацију <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> није успело"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Овај метод уноса можда може да прикупља сав текст који уносите, укључујући личне податке, као што су лозинке и бројеви кредитних картица. Потиче од апликације <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Желите ли да користите овај метод уноса?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Напомена: После рестартовања ова апликација не може да се покрене док не откључате телефон"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Статус IMS регистрације"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Регистрован je"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Није регистрован"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Недоступно"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 6ab3799..cb86b5b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth-ljud via LDAC-kodek: uppspelningskvalitet"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Välj Bluetooth-ljud via LDAC-kodek:\nuppspelningskvalitet"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS via TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Om inställningen är aktiverad görs försök att använda DNS via TLS på port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Visa certifieringsalternativ för Wi-Fi-skärmdelning"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Öka loggningsnivån för Wi-Fi, visa per SSID RSSI i Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"När funktionen har aktiverats kommer dataanslutningen lämnas över från Wi-Fi till mobilen på ett aggressivare sätt när Wi-Fi-signalen är svag"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Ingen"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Vänta på felsökningen"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Felsökaren måste ansluta till appen först"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor samlar in loggar när ett problem upptäcks i telefoni-/modemfunktionen och uppmanar användaren att skicka in en felrapport"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Indata"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Ritning"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Maskinvaruaccelererad rendering"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Det gick inte att öppna inställningarna för <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Den här inmatningsmetoden kan samla all text som du skriver, inklusive personliga uppgifter som lösenord och kreditkortsnummer. Den kommer från appen <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Vill du använda inmatningsmetoden?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Obs! När du har startat om enheten måste du låsa upp mobilen innan du kan starta den här appen"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS-registrering"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registrerad"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Ej registrerad"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Inte tillgängligt"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index e5aa83f..756254b 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Kodeki ya LDAC ya Sauti ya Bluetooth: Ubora wa Kucheza"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Chagua Kodeki ya LDAC ya Sauti ya Bluetooth:\nUbora wa Kucheza"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Kutiririsha: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS kupitia TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Ukiwasha mipangilio hii, jaribu kutumia DNS kupitia TLS kwenye mlango wa 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Onyesha chaguo za cheti cha kuonyesha pasiwaya"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Ongeza hatua ya uwekaji kumbukumbu ya Wi-Fi, onyesha kwa kila SSID RSSI kwenye Kichukuzi cha Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Ikiwashwa, Wi-Fi itakabidhi kwa hima muunganisho wa data kwa mtandao wa simu, wakati mtandao wa Wi-Fi si thabiti"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Hakuna chochote"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Subiri kitatuaji"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Programu ya utatuaji husubiri kitatuaji ili kuambatisha kabla ya kutekeleza"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Kichunguzi cha Shughuli za Simu"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Kichunguzi cha Shughuli za Simu kitakusanya kumbukumbu wakati kinatambua tatizo katika utendaji wa simu au modemu, na kumwarifu mtumiaji kuwasilisha ripoti ya hitilafu"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ingizo"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Uchoraji"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Kutunguliza kwa maunzi kulikoharakishwa"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Imeshindwa kufungua mipangilio ya <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Huenda mbinu hii ya kuingiza ikakusanya maandishi yote unayoandika, pamoja na data ya kibinafsi kama vile manenosiri na nambari za kadi za mkopo. Inatoka kwa programu <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Je, ungependa kutumia mbinu hii ya kingiza?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Kumbuka: Baada ya kuwasha tena programu hii, hutaweza kuitumia hadi utakapofungua simu yako"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Hali ya usajili wa IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Imesajiliwa"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Haijasajiliwa"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Hapatikani"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 75420bd..e31e3e0 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"புளூடூத் ஆடியோ LDAC கோடெக்: வீடியோவின் தரம்"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"புளூடூத் ஆடியோ LDAC கோடெக்கைத் தேர்ந்தெடுக்கவும்:\nவீடியோவின் தரம்"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ஸ்ட்ரீமிங்: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS வழியாக DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"இயக்கப்பட்டிருந்தால், போர்ட் 853 இல் TLS வழியாக DNSஐ முயலவும்."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"வயர்லெஸ் காட்சி சான்றுக்கான விருப்பங்களைக் காட்டு"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wifi நுழைவு அளவை அதிகரித்து, வைஃபை தேர்வியில் ஒவ்வொன்றிற்கும் SSID RSSI ஐ காட்டுக"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"இயக்கப்பட்டதும், வைஃபை சிக்னல் குறையும் போது, வைஃபை முழுமையாக ஒத்துழைக்காமல் இருந்தால் மொபைல் தரவிற்கு மாறும்"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ஒன்றுமில்லை"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"பிழைதிருத்திக்குக் காத்திருக்கவும்"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"பிழைதிருத்தப்பட்ட பயன்பாடு செயல்படுவதற்கு முன்பு பிழைதிருத்தியை இணைப்பதற்குக் காத்திருக்கிறது"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"டெலிஃபோனி மானிட்டர்"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor ஆனது டெலிஃபோனி/மோடம் செயல்பாட்டில் சிக்கல் இருப்பதைக் கண்டறியும் போது, பதிவுகளைச் சேகரிக்கும். அத்துடன், பிழையைப் புகாரளிக்கும்படி பயனருக்கு அறிவிப்பைக் காட்டும்"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"உள்ளீடு"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"வரைபொருள்"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"வன்பொருள் முடுக்கத்துடன் கூடிய காட்சியாக்கம்"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> க்கான அமைப்புகளைத் திறப்பதில் தோல்வி"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"இந்த உள்ளீட்டு முறையானது, கடவுச்சொற்கள் மற்றும் கிரெடிட் கார்டு எண்கள் போன்ற தனிப்பட்ட தகவல் உள்பட நீங்கள் உள்ளிடும் எல்லா உரையையும் சேகரிக்கக்கூடும். இது <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> பயன்பாட்டிலிருந்து வந்துள்ளது. இந்த உள்ளீட்டு முறையைப் பயன்படுத்தவா?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"குறிப்பு: மறுதொடக்கம் செய்த பிறகு, மொபைலைத் திறக்கும் வரை இந்தப் பயன்பாட்டால் தொடங்க முடியாது"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS பதிவின் நிலை"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"பதிவு செய்யப்பட்டது"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"பதிவு செய்யப்படவில்லை"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"கிடைக்கவில்லை"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 23a98c2..0c28ef2 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ నాణ్యత"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"బ్లూటూత్ ఆడియో LDAC కోడెక్‌ని ఎంచుకోండి:\nప్లేబ్యాక్ నాణ్యత"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"ప్రసారం చేస్తోంది: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLSపై DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"ప్రారంభించబడితే, పోర్ట్ 853లో TLSపై DNSని ప్రయత్నించండి."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"వైర్‌లెస్ ప్రదర్శన సర్టిఫికెట్ కోసం ఎంపికలను చూపు"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi‑Fi ఎంపికలో SSID RSSI ప్రకారం చూపబడే Wi‑Fi లాగింగ్ స్థాయిని పెంచండి"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"ప్రారంభించబడినప్పుడు, Wi‑Fi సిగ్నల్ బలహీనంగా ఉంటే డేటా కనెక్షన్‌ను మొబైల్‌కి మార్చేలా Wi‑Fi చురుగ్గా వ్యవహరిస్తుంది"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ఏదీ వద్దు"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"డీబగ్గర్ కోసం వేచి ఉండండి"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"డీబగ్ చేయబడిన యాప్ అమలు కావడానికి ముందు జోడించాల్సిన డీబగ్గర్ కోసం వేచి ఉంటుంది"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"టెలిఫోనీ మానిటర్"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"టెలిఫోనీ/మోడెమ్ కార్యాచరణలో సమస్యను గుర్తించినప్పుడు TelephonyMonitor లాగ్‌లను సేకరిస్తుంది మరియు బగ్‌ని ఫైల్ చేయమని వినియోగదారును ప్రోత్సహిస్తుంది"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ఇన్‌పుట్"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"డ్రాయింగ్"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"హార్డ్‌వేర్ వేగవంతమైన భాషాంతరీకరణ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> యొక్క సెట్టింగ్‌లను తెరవడం విఫలమైంది"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"ఈ ఇన్‌పుట్ పద్ధతి మీరు టైప్ చేసే మొత్తం వచనాన్ని అలాగే పాస్‌వర్డ్‌లు మరియు క్రెడిట్ కార్డు నంబర్‌ల వంటి వ్యక్తిగత డేటాను సేకరించగలదు. ఇది <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> అనువర్తనంలో అందించబడుతుంది. ఈ ఇన్‌పుట్ పద్ధతిని ఉపయోగించాలా?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"గమనిక: రీబూట్ చేసాక, మీరు మీ ఫోన్‌ను అన్‌లాక్ చేసే వరకు ఈ యాప్ ప్రారంభం కాదు"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS నమోదు స్థితి"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"నమోదు చేయబడింది"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"నమోదు కాలేదు"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"అందుబాటులో లేదు"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 8608ac4..5ff39b6 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"ตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC: คุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"เลือกตัวแปลงรหัสเสียงบลูทูธที่ใช้ LDAC:\nคุณภาพการเล่น"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"สตรีมมิง: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS ผ่าน TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"หากเปิดใช้แล้ว ให้ลองใช้ DNS ผ่าน TLS บนพอร์ต 853"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"แสดงตัวเลือกสำหรับการรับรองการแสดงผล แบบไร้สาย"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"เพิ่มระดับการบันทึก Wi‑Fi แสดงต่อ SSID RSSI ในตัวเลือก Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"เมื่อเปิดใช้แล้ว Wi-Fi จะส่งผ่านการเชื่อมต่อข้อมูลไปยังเครือข่ายมือถือเมื่อสัญญาณ Wi-Fi อ่อน"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"ไม่มี"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"รอโปรแกรมแก้ไขข้อบกพร่อง"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"แอปพลิเคชันที่มีการแก้ปัญหาจะรอให้โปรแกรมแก้ไขข้อบกพร่องแนบข้อมูลก่อนปฏิบัติการ"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"การตรวจสอบโทรศัพท์"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"การตรวจสอบโทรศัพท์จะรวบรวมบันทึกเมื่อตรวจพบปัญหาด้านฟังก์ชันการทำงานของโทรศัพท์/โมเด็มและแจ้งเตือนให้ผู้ใช้ส่งข้อบกพร่อง"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"อินพุต"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"การวาดภาพ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"การแสดงผลที่มีการเร่งด้วยฮาร์ดแวร์"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"ไม่สามารถเปิดการตั้งค่าสำหรับ <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"วิธีการป้อนข้อมูลนี้อาจเก็บข้อความทั้งหมดที่คุณพิมพ์ รวมถึงข้อมูลส่วนบุคคล เช่น รหัสผ่านและหมายเลขบัตรเครดิต โดยมาจากแอปพลิเคชัน <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ใช้วิธีการป้อนข้อมูลนี้หรือไม่"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"หมายเหตุ: หลังจากเริ่มต้นใหม่ แอปนี้จะไม่สามารถเริ่มการทำงานได้จนกว่าคุณจะปลดล็อกโทรศัพท์"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"สถานะการลงทะเบียน IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"ลงทะเบียนแล้ว"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"ไม่ได้ลงทะเบียน"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"ไม่ว่าง"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index cc6121e..6021d79 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Audio LDAC Codec ng Bluetooth: Kalidad ng Pag-playback"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Piliin ang Audio LDAC Codec ng Bluetooth:\nKalidad ng Pag-playback"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Streaming: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS sa pamamagitan ng TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Kung naka-enable, subukan ang DNS sa pamamagitan ng TLS sa port 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Ipakita ang mga opsyon para sa certification ng wireless display"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Pataasin ang antas ng Wi‑Fi logging, ipakita sa bawat SSID RSSI sa Wi‑Fi Picker"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Kapag na-enable, magiging mas agresibo ang Wi‑Fi sa paglipat sa koneksyon ng mobile data kapag mahina ang signal ng Wi‑Fi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Wala"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Maghintay ng debugger"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Hinihintay ng na-debug na application na ma-attach ang debugger bago magsagawa"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Mangongolekta ang TelephonyMonitor ng mga log kapag nakatukoy ito ng problema sa functionality ng telephony/modem at magpo-prompt ito ng notification sa user na mag-file ng bug"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Input"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Pagguhit"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Pag-render na pinapabilis ng hardware"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Nabigong buksan ang mga setting para sa <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Maaaring magawa ng pamamaraan ng pag-input na ito na kolektahin ang lahat ng tekstong iyong tina-type, kabilang ang personal na data katulad ng mga password at mga numero ng credit card. Nagmumula ito sa app na <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Gamitin ang pamamaraan ng pag-input na ito?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Tandaan: Pagkatapos ng pag-reboot, hindi makakapagsimula ang app na ito hangga\'t hindi mo ina-unlock ang iyong telepono"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Status ng pagpaparehistro ng IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Nakarehistro"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Hindi nakarehistro"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Hindi available"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index fda52ef..367f84b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Bluetooth Ses LDAC Codec\'i: Oynatma Kalitesi"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Bluetooth Ses LDAC Codec\'ini Seçin:\nOynatma Kalitesi"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Akış: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS üzerinden DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Etkinleştirildiyse, 853 numaralı bağlantı noktasında TLS üzerinden DNS\'yi deneyin."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Kablosuz ekran sertifikası seçeneklerini göster"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Kablosuz günlük kaydı seviyesini artır. Kablosuz Seçici\'de her bir SSID RSSI için göster."</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Etkinleştirildiğinde, kablosuz ağ sinyali zayıfken veri bağlantısının mobil ağa geçirilmesinde daha agresif olunur"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Hiçbiri"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Hata ayıklayıcıyı bekle"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Hata ayıklanan uygulama, çalıştırılmadan önce hata ayıklayıcının eklenmesini bekler"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor, telefon/modem işleviyle ilgili bir sorun algıladığında günlükleri toplar ve hatayı bildirmesi için kullanıcıya bir bildirim gösterir."</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Girdi"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Çizim"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Donanım hızlandırmalı oluşturma"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> ayarları açılamadı"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Bu giriş yöntemi, şifreler ve kredi kartı numaraları gibi kişisel veriler de dahil olmak üzere yazdığınız tüm metni toplayabilir. Bu yöntem, <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> uygulamasının bir özelliğidir. Bu giriş yöntemini kullanmak istiyor musunuz?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Not: Yeniden başlatma sonrasında, telefonunuzun kilidi açılıncaya kadar bu uygulama başlatılamaz"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS kaydı durumu"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Kaydettirildi"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Kaydettirilmedi"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Kullanılamıyor"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 6d86a8d..2693002 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Кодек для аудіо Bluetooth LDAC: якість відтворення"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Вибрати кодек для аудіо Bluetooth LDAC:\nякість відтворення"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Трансляція: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS через протокол TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Якщо ввімкнено, спробуйте DNS через протокол TLS у порту 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Показати параметри сертифікації бездротового екрана"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Показувати в журналі RSSI для кожного SSID під час вибору Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Примусово перемикатися на мобільну мережу, коли сигнал Wi-Fi слабкий"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Нічого"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Зачекайте на налагоджувач"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Додаток очікує підключення налагоджувача"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Коли функція Telephony Monitor виявить проблеми в роботі телефона чи модема, вона збере дані та запропонує користувачеві повідомити про помилку"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Ввід"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Рисування"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Апаратне прискорення"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Не вдалося відкрити налаштування для програми <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Під час використання цього методу введення може записуватись весь текст, який ви вводите, зокрема паролі й номери кредитних карток. Цей метод запитує програма <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Використати цей метод введення?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Примітка: щоб запустити цей додаток після перезавантаження, спершу потрібно буде розблокувати телефон"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Статус реєстрації IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Зареєстровано"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Не зареєстровано"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Недоступно"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 3dd1b3e..782bb6f 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -110,11 +110,11 @@
     <string name="process_kernel_label" msgid="3916858646836739323">"Android OS"</string>
     <string name="data_usage_uninstalled_apps" msgid="614263770923231598">"ہٹائی گئی ایپس"</string>
     <string name="data_usage_uninstalled_apps_users" msgid="7986294489899813194">"ہٹائی گئی ایپس اور صارفین"</string>
-    <string name="tether_settings_title_usb" msgid="6688416425801386511">"‏USB ٹیتھرنگ"</string>
+    <string name="tether_settings_title_usb" msgid="6688416425801386511">"‏USB ٹیدرنگ"</string>
     <string name="tether_settings_title_wifi" msgid="3277144155960302049">"پورٹیبل ہاٹ اسپاٹ"</string>
-    <string name="tether_settings_title_bluetooth" msgid="355855408317564420">"بلوٹوتھ ٹیتھرنگ"</string>
-    <string name="tether_settings_title_usb_bluetooth" msgid="5355828977109785001">"ٹیتھرنگ"</string>
-    <string name="tether_settings_title_all" msgid="8356136101061143841">"ٹیتھرنگ و پورٹیبل ہاٹ اسپاٹ"</string>
+    <string name="tether_settings_title_bluetooth" msgid="355855408317564420">"بلوٹوتھ ٹیدرنگ"</string>
+    <string name="tether_settings_title_usb_bluetooth" msgid="5355828977109785001">"ٹیدرنگ"</string>
+    <string name="tether_settings_title_all" msgid="8356136101061143841">"ٹیدرنگ و پورٹیبل ہاٹ اسپاٹ"</string>
     <string name="managed_user_title" msgid="8109605045406748842">"تمام کام کی ایپس"</string>
     <string name="user_guest" msgid="8475274842845401871">"مہمان"</string>
     <string name="unknown" msgid="1592123443519355854">"نامعلوم"</string>
@@ -168,7 +168,7 @@
     <string name="development_settings_summary" msgid="1815795401632854041">"ایپ ڈویلپمنٹ کیلئے اختیارات سیٹ کریں"</string>
     <string name="development_settings_not_available" msgid="4308569041701535607">"اس صارف کیلئے ڈیولپر کے اختیارات دستیاب نہیں ہیں"</string>
     <string name="vpn_settings_not_available" msgid="956841430176985598">"‏VPN ترتیبات اس صارف کیلئے دستیاب نہیں ہیں"</string>
-    <string name="tethering_settings_not_available" msgid="6765770438438291012">"ٹیتھرنگ ترتیبات اس صارف کیلئے دستیاب نہیں ہیں"</string>
+    <string name="tethering_settings_not_available" msgid="6765770438438291012">"ٹیدرنگ ترتیبات اس صارف کیلئے دستیاب نہیں ہیں"</string>
     <string name="apn_settings_not_available" msgid="7873729032165324000">"رسائی کی جگہ کے نام کی ترتیبات اس صارف کیلئے دستیاب نہیں ہیں"</string>
     <string name="enable_adb" msgid="7982306934419797485">"‏USB ڈیبگ کرنا"</string>
     <string name="enable_adb_summary" msgid="4881186971746056635">"‏USB مربوط ہونے پر ڈيبگ کرنے کی وضع"</string>
@@ -192,7 +192,7 @@
     <string name="wifi_aggressive_handover" msgid="5309131983693661320">"‏Wi‑Fi سے موبائل کو جارحانہ ہینڈ اوور"</string>
     <string name="wifi_allow_scan_with_traffic" msgid="3601853081178265786">"‏ہمیشہ Wi‑Fi روم اسکینز کی اجازت دیں"</string>
     <string name="mobile_data_always_on" msgid="8774857027458200434">"موبائل ڈیٹا ہمیشہ فعال رکھیں"</string>
-    <string name="tethering_hardware_offload" msgid="7470077827090325814">"ہارڈویئر کی سرعت کاری میں ربط بنایا جا رہا ہے"</string>
+    <string name="tethering_hardware_offload" msgid="7470077827090325814">"ٹیدرنگ ہارڈویئر سرعت کاری"</string>
     <string name="bluetooth_show_devices_without_names" msgid="4708446092962060176">"بغیر نام والے بلوٹوتھ آلات دکھائیں"</string>
     <string name="bluetooth_disable_absolute_volume" msgid="2660673801947898809">"مطلق والیوم کو غیر فعال کریں"</string>
     <string name="bluetooth_enable_inband_ringing" msgid="3291686366721786740">"ان بینڈ رنگنگ فعال کریں"</string>
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"‏بلوٹوتھ آڈیو LDAC کوڈیک: پلے بیک کا معیار"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"‏بلوٹوتھ آڈیو LDAC کوڈیک منتخب کریں:\nپلے بیک کا معیار"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"سلسلہ بندی: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"‏TLS پر DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"‏فعال ہونے پر پورٹ 853 پر TLS پر DNS استعمال کرنے کی کوشش کریں۔"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"وائرلیس ڈسپلے سرٹیفیکیشن کیلئے اختیارات دکھائیں"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"‏Wi‑Fi لاگنگ لیول میں اضافہ کریں، Wi‑Fi منتخب کنندہ میں فی SSID RSSI دکھائیں"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"‏فعال کئے جانے پر، جب Wi‑Fi سگنل کمزور ہوگا، تو Wi‑Fi موبائل پر ڈیٹا کنکشن بھیجنے کیلئے مزید جارحانہ کارروائی کرے گا"</string>
@@ -225,7 +227,7 @@
     <string name="allow_mock_location_summary" msgid="317615105156345626">"فرضی مقامات کی اجازت دیں"</string>
     <string name="debug_view_attributes" msgid="6485448367803310384">"منظر انتساب کے معائنہ کو فعال کریں"</string>
     <string name="mobile_data_always_on_summary" msgid="8149773901431697910">"‏Wi‑Fi فعال ہونے پر بھی موبائل ڈیٹا کو ہمیشہ فعال رکھیں (تیزی سے نیٹ ورک سوئچ کرنے کیلئے)۔"</string>
-    <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"اگر دستیاب ہو، تو ہارڈویئر کی سرعت کاری میں ربط کاری کا استعمال کریں"</string>
+    <string name="tethering_hardware_offload_summary" msgid="7726082075333346982">"اگر دستیاب ہو تو ٹیدرنگ ہارڈویئر سرعت کاری کا استعمال کریں"</string>
     <string name="adb_warning_title" msgid="6234463310896563253">"‏USB ڈیبگ کرنے کی اجازت دیں؟"</string>
     <string name="adb_warning_message" msgid="7316799925425402244">"‏USB ڈیبگ کرنا صرف ڈیولپمنٹ کے مقاصد کیلئے ہے۔ اپنے کمپیوٹر اور اپنے آلہ کے درمیان ڈیٹا کاپی کرنے کیلئے اسے استعمال کریں، بغیر اطلاع کے اپنے آلہ پر ایپس انسٹال کریں اور لاگ ڈیٹا پڑھیں۔"</string>
     <string name="adb_keys_warning_message" msgid="5659849457135841625">"‏اپنے ذریعہ پہلے سے اجازت یافتہ سبھی کمپیوٹرز سے USB ڈیبگ کرنے کی رسائی کو کالعدم کریں؟"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"کچھ نہیں"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"ڈیبگر کا انتظار کریں"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"ڈیبگ کردہ ایپلیکیشن کاروائی سے پہلے ڈیبگر کے منسلک ہونے کا انتظار کرتی ہے"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"‏ٹیلیفونی/موڈم کی فعالیت کے ساتھ در پیش مسئلہ کا پتہ چلنے پر TelephonyMonitor لاگز اکٹھا کر کے بگ دائر کرنے کیلئے صارف کو اطلاع بھیجے گا"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"ان پٹ"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"ڈرائنگ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"ہارڈ ویئر کے ذریعے تیز کردہ رینڈرنگ"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> کیلئے ترتیبات کھولنے میں ناکام ہوگیا"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"اندراج کا یہ طریقہ آپ کے ٹائپ کردہ سبھی متن کو جمع کر سکتا ہے، بشمول ذاتی ڈیٹا جیسے پاس ورڈز اور کریڈٹ کارڈ نمبرز۔ یہ <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ایپ سے آتا ہے۔ اندراج کا یہ طریقہ استعمال کریں؟"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"نوٹ: ریبوٹ کرنے کے بعد یہ ایپ تب تک شروع نہیں ہو سکتی جب تک آپ اپنا فون غیر مقفل نہ کر لیں"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"‏IMS رجسٹریشن کی صورتحال"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"رجسٹر شدہ"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"رجسٹر نہیں ہے"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"غیر دستیاب"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index dbdcb1c..95e4455 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"LDAC audiokodeki bilan ijro etish sifati (Bluetooth orqali)"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"LDAC audiokodeki:\nijro sifati"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Translatsiya: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"TLS orqali DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Yoqilgan bo‘lsa, TLS orqali DNS serverini 853-port yordamida sozlab ko‘ring"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Simsiz monitorlarni sertifikatlash parametrini ko‘rsatish"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Wi-Fi ulanishini tanlashda har bir SSID uchun jurnalda ko‘rsatilsin"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Agar ushbu funksiya yoqilsa, Wi-Fi signali past bo‘lganda internetga ulanish majburiy ravishda mobil internetga o‘tkaziladi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Yo‘q"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Tuzatuvchi dasturni kuting"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Ilova nosozliklarni tuzatuvchi dasturga ulanishni kutmoqda"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telefoniya nazorati"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telefoniya nazorati telefoniya/model funksiyalari bilan bog‘liq muammolar aniqlansa va foydalanuvchiga xatolikni yuborishi uchun bildirishnoma yuborib, jurnallarni to‘playdi"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Matn kiritish"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Chizma"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Vizualizatsiyani apparatli tezlatish"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> sozlamalarini ochmadi"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Ushbu yozish usuli barcha yozgan matnlaringizni to‘plab olishi mumkin, jumladan kredit karta raqamlari va parollar kabi shaxsiy ma‘lumotlarni ham. Usul  <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> ilovasi bilan o‘rnatiladi. Ushbu usuldan foydalanilsinmi?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Eslatma: O‘chirib-yoqilgandan so‘ng, bu ilova to telefoningiz qulfdan chiqarilmaguncha ishga tushmaydi"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS registratsiyasi holati"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Registratsiya qilingan"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Registratsiya qilinmagan"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Mavjud emas"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index e6d4eb7..8beb819 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"Codec LDAC âm thanh Bluetooth: Chất lượng phát lại"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Chọn Codec LDAC âm thanh Bluetooth:\nChất lượng phát lại"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Truyền trực tuyến: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"DNS qua TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Nếu được bật, hãy thử DNS qua TLS trên cổng 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Hiển thị tùy chọn chứng nhận hiển thị không dây"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"Tăng mức ghi nhật ký Wi‑Fi, hiển thị mỗi SSID RSSI trong bộ chọn Wi‑Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Khi được bật, Wi‑Fi sẽ tích cực hơn trong việc chuyển vùng kết nối dữ liệu sang mạng di động khi tín hiệu Wi‑Fi yếu"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Không có ứng dụng"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Đợi trình gỡ lỗi"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Ứng dụng được gỡ lỗi chờ trình gỡ lỗi đính kèm trước khi thực hiện"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Giám sát điện thoại"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"TelephonyMonitor sẽ thu thập nhật ký khi phát hiện thấy sự cố với chức năng điện thoại/modem và gửi thông báo khẩn cấp tới người dùng để gửi lỗi"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Nhập"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Vẽ"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Kết xuất có tăng tốc phần cứng"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Không thể mở cài đặt cho <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Phương thức nhập này có thể thu thập tất cả văn bản bạn nhập, bao gồm dữ liệu cá nhân như mật khẩu và số thẻ tín dụng. Phương thức nhập này đến từ ứng dụng <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Sử dụng phương thức nhập này?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Lưu ý: Sau khi khởi động lại, ứng dụng này không thể khởi động cho đến khi bạn mở khóa điện thoại"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Trạng thái đăng ký IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Đã đăng ký"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Chưa được đăng ký"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Không có"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
index 3a80e7b..dc24afd 100644
--- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml
@@ -24,7 +24,7 @@
     <item msgid="1922181315419294640"></item>
     <item msgid="8934131797783724664">"正在扫描..."</item>
     <item msgid="8513729475867537913">"正在连接..."</item>
-    <item msgid="515055375277271756">"正在进行身份验证..."</item>
+    <item msgid="515055375277271756">"正在验证身份…"</item>
     <item msgid="1943354004029184381">"正在获取IP地址..."</item>
     <item msgid="4221763391123233270">"已连接"</item>
     <item msgid="624838831631122137">"已暂停"</item>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 45d9835..094bb90 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"蓝牙音频 LDAC 编解码器:播放质量"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"选择蓝牙音频 LDAC 编解码器:\n播放质量"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"正在流式传输:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"通过传输层安全协议 (TLS) 执行 DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"如果启用,即可在端口 853 上尝试“通过传输层安全协议 (TLS) 执行 DNS”。"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"显示无线显示认证选项"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"提升WLAN日志记录级别(在WLAN选择器中显示每个SSID的RSSI)"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"开启此设置后,系统会在 WLAN 信号较弱时,主动将网络模式从 WLAN 网络切换到移动数据网络"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"无"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"等待调试程序"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"调试应用会在执行前等待附加调试器"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"电话监控器"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"电话监控器会在检测到电话/调制解调器功能存在问题时收集相关日志,并向用户发出通知,提醒用户提交错误报告"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"输入"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"绘图"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"硬件加速渲染"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"无法打开 <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> 的设置"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"此输入法可能会收集您输入的所有内容,包括密码和信用卡号等个人数据。它来自“<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>”应用。要使用此输入法吗?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"注意:重新启动后,您必须将手机解锁才能运行此应用"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS 注册状态"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"已注册"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"未注册"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"无法获取"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index a47e1e8..c489850 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"藍牙音訊 LDAC 編解碼器:播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"選擇藍牙音訊 LDAC 編解碼器:\n播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"正在串流:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"透過傳輸層安全性 (TLS) 執行網域名稱系統 (DNS)"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"如果啟用這個選項,即可在連接埠 853 上嘗試「透過傳輸層安全性 (TLS) 執行網域名稱系統 (DNS)」。"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"啟用後,Wi-Fi 連線會在訊號不穩定的情況下更積極轉換成流動數據連線"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"無"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"等待除錯程式"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"執行已除錯的應用程式前等待附加除錯程式"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"電話監控工具"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telephony Monitor 會在偵測到電話/數據機功能發生問題時收集記錄,並通知使用者報告錯誤"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"輸入"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"繪圖"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"硬件加速轉譯"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"無法開啟 <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> 的設定"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"這個輸入法可能會收集您輸入的所有文字,包括密碼和信用卡號碼等個人資料。這個輸入法由 <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> 應用程式提供,您要使用嗎?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"注意:重新啟動後,您必須解鎖手機,才可開始使用此應用程式"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS 註冊狀態"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"已註冊"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"未註冊"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"無法使用"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index d13a4f6..2554388 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"藍牙音訊 LDAC 轉碼器:播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"選取藍牙音訊 LDAC 轉碼器:\n播放品質"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"串流中:<xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"透過傳輸層安全標準 (TLS) 執行 DNS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"如果啟用這個選項,即可在通訊埠 853 上嘗試透過傳輸層安全標準 (TLS) 執行 DNS。"</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"顯示無線螢幕分享認證的選項"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"讓 Wi‑Fi 記錄功能升級,在 Wi‑Fi 選擇器中依每個 SSID RSSI 顯示 Wi‑Fi 詳細紀錄"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"啟用時,Wi-Fi 連線在訊號不穩的情況下會更積極轉換成行動數據連線"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"無"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"等待偵錯程式"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"執行受偵錯的應用程式之前,先等待偵錯程序附加完成"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Telephony Monitor"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"Telephony Monitor 會在偵測到電話/數據機功能發生問題時收集相關紀錄,並顯示通知方便使用者回報錯誤"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"輸入"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"繪圖"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"硬體加速轉譯"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"無法開啟「<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>」的設定"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"這個輸入法是由「<xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>」應用程式所提供,它可能會收集你輸入的所有文字,包括密碼和信用卡號等個人資料。要啟用這個輸入法嗎?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"注意:重新啟動後,你必須將手機解鎖,才能執行這個應用程式"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"IMS 註冊狀態"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"已註冊"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"未註冊"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"無法取得"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 35d84a2..928eac5 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -209,6 +209,8 @@
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3619694372407843405">"I-Bluetooth Audio LDAC Codec: Ikhwalithi yokudlala"</string>
     <string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="3181967377574368400">"Khetha i-Bluetooth Audio LDAC Codec:\nIkhwalithi yokudlala"</string>
     <string name="bluetooth_select_a2dp_codec_streaming_label" msgid="5347862512596240506">"Ukusakaza: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
+    <string name="dns_tls" msgid="6773814174391131955">"I-DNS nge-TLS"</string>
+    <string name="dns_tls_summary" msgid="3692494150251071380">"Uma inikwe amandla, zama i-DNS nge-TLS embobeni 853."</string>
     <string name="wifi_display_certification_summary" msgid="1155182309166746973">"Bonisa izinketho zokunikeza isitifiketi ukubukeka okungenantambo"</string>
     <string name="wifi_verbose_logging_summary" msgid="6615071616111731958">"khuphula izinga lokungena le-Wi-Fi, bonisa nge-SSID RSSI engayodwana kusikhethi se-Wi-Fi"</string>
     <string name="wifi_aggressive_handover_summary" msgid="7266329646559808827">"Uma inikwe amandla, i-Wi-Fi izoba namandla kakhulu ekudluliseleni ukuxhumeka kwedatha kuselula, uma isignali ye-Wi-Fi iphansi"</string>
@@ -248,8 +250,6 @@
     <string name="no_application" msgid="2813387563129153880">"Lutho"</string>
     <string name="wait_for_debugger" msgid="1202370874528893091">"Linda isilungisi senkinga"</string>
     <string name="wait_for_debugger_summary" msgid="1766918303462746804">"Uhlelo lokusebenza olulungiswe inkinga lulinda isilungisi senkinga ukuba sinamathisele ngaphambi kokuphuma"</string>
-    <string name="telephony_monitor_switch" msgid="1764958220062121194">"Ukuqashwa kwefoni"</string>
-    <string name="telephony_monitor_switch_summary" msgid="7695552966547975635">"I-TelephonyMonitor izoqoqa amalogi uma ithola inkinga ngokusebenza kwefoni/imodemu iphinde ikhiphe isaziso esiya kumsebenzisi ukuthi athumele isiphazamisi"</string>
     <string name="debug_input_category" msgid="1811069939601180246">"Okufakwayo"</string>
     <string name="debug_drawing_category" msgid="6755716469267367852">"Umdwebo"</string>
     <string name="debug_hw_drawing_category" msgid="6220174216912308658">"Ukunikezelwa okusheshisiwe kwezingxenyekazi zekhompyutha"</string>
@@ -390,4 +390,8 @@
     <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Yehlulekile ukuvula izilungiselelo ze-<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string>
     <string name="ime_security_warning" msgid="4135828934735934248">"Indlela yokufakwayo ingase ikwazi ukuqoqa wonke umbhalo owuthayiphayo, kuhlanganise idatha yomuntu siqu njengamaphasiwedi nezinombolo zekhadi lesikoloto. Iphuma kuhlelo lokusebenza <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Sebenzisa indlela yokufaka?"</string>
     <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Yazi: Ngemuva kokuqalisa, lolu hlelo lokusebenza alukwazi ukuqala uze uvule ifoni yakho"</string>
+    <string name="ims_reg_title" msgid="7609782759207241443">"Isimo sokubhaliswa se-IMS"</string>
+    <string name="ims_reg_status_registered" msgid="933003316932739188">"Kubhalisiwe"</string>
+    <string name="ims_reg_status_not_registered" msgid="6529783773485229486">"Akubhalisiwe"</string>
+    <string name="status_unavailable" msgid="7862009036663793314">"Ayitholakali"</string>
 </resources>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index e261570..bd963e9 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -32,6 +32,7 @@
     <dimen name="user_spinner_padding_sides">20dp</dimen>
     <dimen name="user_spinner_item_height">56dp</dimen>
 
+    <dimen name="two_target_pref_small_icon_size">24dp</dimen>
     <!-- Lock icon for preferences locked by admin -->
     <dimen name="restricted_icon_size">16dp</dimen>
     <dimen name="restricted_icon_padding">4dp</dimen>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index bd884a3..360848f 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -235,6 +235,27 @@
     <!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. -->
     <string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string>
 
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=4875089335641234463] -->
+    <string name="bluetooth_talkback_computer">Computer</string>
+
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5140152177885220949] -->
+    <string name="bluetooth_talkback_headset">Headset</string>
+
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=4260255181240622896] -->
+    <string name="bluetooth_talkback_phone">Phone</string>
+
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=551146170554589119] -->
+    <string name="bluetooth_talkback_imaging">Imaging</string>
+
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=26580326066627664] -->
+    <string name="bluetooth_talkback_headphone">Headphone</string>
+
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5165842622743212268] -->
+    <string name="bluetooth_talkback_input_peripheral">Input Peripheral</string>
+
+    <!-- Message for telling the user the kind of BT device being displayed in list. [CHAR LIMIT=30 BACKUP_MESSAGE_ID=5615463912185280812] -->
+    <string name="bluetooth_talkback_bluetooth">Bluetooth</string>
+
     <!-- Content description of the WIFI signal when WIFI is disabled for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_wifi_off">Wifi off.</string>
     <!-- Content description of the WIFI signal when no signal for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -289,7 +310,8 @@
     <string name="launch_defaults_some">Some defaults set</string>
     <!-- Launch defaults preference summary with none set [CHAR LIMIT=40] -->
     <string name="launch_defaults_none">No defaults set</string>
-
+    <!-- DO NOT TRANSLATE Empty summary for dynamic preferences -->
+    <string name="summary_empty" translatable="false"></string>
     <!-- Text-To-Speech (TTS) settings --><skip />
     <!-- Name of the TTS package as listed by the package manager. -->
     <string name="tts_settings">Text-to-speech settings</string>
@@ -524,6 +546,12 @@
     <!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
     <string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
 
+    <!-- Title of the developer option for DNS over TLS. -->
+    <string name="dns_tls">DNS over TLS</string>
+    <!-- Summary to explain the developer option for DNS over TLS.  This allows the user to
+    request that the system attempt TLS with all DNS servers, or none. -->
+    <string name="dns_tls_summary">If enabled, attempt DNS over TLS on port 853.</string>
+
     <!-- setting Checkbox summary whether to show options for wireless display certification  -->
     <string name="wifi_display_certification_summary">Show options for wireless display certification</string>
     <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] -->
@@ -608,11 +636,6 @@
     <string name="wait_for_debugger_summary">Debugged application waits for debugger to
         attach before executing</string>
 
-    <!-- UI debug setting: title for Telephonymonitor switch [CHAR LIMIT=50] -->
-    <string name="telephony_monitor_switch">Telephony Monitor</string>
-    <!-- UI debug setting: summary for switch of Telephonymonitor [CHAR LIMIT=500] -->
-    <string name="telephony_monitor_switch_summary">TelephonyMonitor will collect logs when it detects a problem with telephony/modem functionality and prompt notification to user to file a bug</string>
-
     <!-- Preference category for input debugging development settings. [CHAR LIMIT=25] -->
     <string name="debug_input_category">Input</string>
 
@@ -987,4 +1010,14 @@
     <!-- [CHAR LIMIT=NONE] Dialog body explaining that the app just selected by the user will not work after a reboot until until after the user enters their credentials, such as a PIN or password. -->
     <string name="direct_boot_unaware_dialog_message">Note: After a reboot, this app can\'t start until you unlock your phone</string>
 
+    <!--Label of IMS registration header -->
+    <string name="ims_reg_title">"IMS registration state"</string>
+    <!--Used when IMS registration state is registered -->
+    <string name="ims_reg_status_registered">"Registered"</string>
+    <!--Used when IMS registration state is not registered -->
+    <string name="ims_reg_status_not_registered">"Not registered"</string>
+
+    <!-- About phone, status item value if the actual value is not available. -->
+    <string name="status_unavailable">Unavailable</string>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java
index 253ca11..90124f1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.CallSuper;
 import android.support.v14.preference.EditTextPreferenceDialogFragment;
 import android.support.v7.preference.EditTextPreference;
 import android.util.AttributeSet;
@@ -30,7 +31,8 @@
 
     private CustomPreferenceDialogFragment mFragment;
 
-    public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+    public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
@@ -69,7 +71,12 @@
     protected void onClick(DialogInterface dialog, int which) {
     }
 
+    @CallSuper
     protected void onBindDialogView(View view) {
+        final EditText editText = view.findViewById(android.R.id.edit);
+        if (editText != null) {
+            editText.requestFocus();
+        }
     }
 
     private void setFragment(CustomPreferenceDialogFragment fragment) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
index 1c161df..8b39f60 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java
@@ -21,41 +21,56 @@
 import android.support.v7.preference.PreferenceViewHolder;
 import android.util.AttributeSet;
 import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
 
 public class TwoTargetPreference extends Preference {
 
+    private boolean mUseSmallIcon;
+    private int mSmallIconSize;
+
     public TwoTargetPreference(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        init();
+        init(context);
     }
 
     public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
+        init(context);
     }
 
     public TwoTargetPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
+        init(context);
     }
 
     public TwoTargetPreference(Context context) {
         super(context);
-        init();
+        init(context);
     }
 
-    private void init() {
+    private void init(Context context) {
         setLayoutResource(R.layout.preference_two_target);
+        mSmallIconSize = context.getResources().getDimensionPixelSize(
+                R.dimen.two_target_pref_small_icon_size);
         final int secondTargetResId = getSecondTargetResId();
         if (secondTargetResId != 0) {
             setWidgetLayoutResource(secondTargetResId);
         }
     }
 
+    public void setUseSmallIcon(boolean useSmallIcon) {
+        mUseSmallIcon = useSmallIcon;
+    }
+
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
+        if (mUseSmallIcon) {
+            ImageView icon = holder.itemView.findViewById(android.R.id.icon);
+            icon.setLayoutParams(new LinearLayout.LayoutParams(mSmallIconSize, mSmallIconSize));
+        }
         final View divider = holder.findViewById(R.id.two_target_divider);
         final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
         final boolean shouldHideSecondTarget = shouldHideSecondTarget();
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8def036..d90386f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -12,9 +12,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Color;
-import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.net.ConnectivityManager;
 import android.os.BatteryManager;
@@ -297,4 +295,9 @@
         }
         return defaultDays;
     }
+
+    public static boolean isWifiOnly(Context context) {
+        return !context.getSystemService(ConnectivityManager.class)
+                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 40c2b1f..fa2499f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -52,6 +52,11 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
 
 import java.io.File;
 import java.io.IOException;
@@ -180,7 +185,11 @@
     }
 
     public Session newSession(Callbacks callbacks) {
-        Session s = new Session(callbacks);
+        return newSession(callbacks, null);
+    }
+
+    public Session newSession(Callbacks callbacks, Lifecycle lifecycle) {
+        Session s = new Session(callbacks, lifecycle);
         synchronized (mEntriesMap) {
             mSessions.add(s);
         }
@@ -586,7 +595,7 @@
                 .replaceAll("").toLowerCase();
     }
 
-    public class Session {
+    public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy {
         final Callbacks mCallbacks;
         boolean mResumed;
 
@@ -600,11 +609,19 @@
         ArrayList<AppEntry> mLastAppList;
         boolean mRebuildForeground;
 
-        Session(Callbacks callbacks) {
+        private final boolean mHasLifecycle;
+
+        Session(Callbacks callbacks, Lifecycle lifecycle) {
             mCallbacks = callbacks;
+            if (lifecycle != null) {
+                lifecycle.addObserver(this);
+                mHasLifecycle = true;
+            } else {
+                mHasLifecycle = false;
+            }
         }
 
-        public void resume() {
+        public void onResume() {
             if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
             synchronized (mEntriesMap) {
                 if (!mResumed) {
@@ -616,7 +633,7 @@
             if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
         }
 
-        public void pause() {
+        public void onPause() {
             if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
             synchronized (mEntriesMap) {
                 if (mResumed) {
@@ -735,8 +752,11 @@
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
 
-        public void release() {
-            pause();
+        public void onDestroy() {
+            if (!mHasLifecycle) {
+                // TODO: Legacy, remove this later once all usages are switched to Lifecycle
+                onPause();
+            }
             synchronized (mEntriesMap) {
                 mSessions.remove(this);
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
index 8fc9fa6..9fbadee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java
@@ -131,7 +131,7 @@
         }
 
         public long getTotalBytes() {
-            return mStats.getCacheBytes() + mStats.getCodeBytes() + mStats.getDataBytes();
+            return mStats.getAppBytes() + mStats.getDataBytes();
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 0946181..764c5922 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
+import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -42,7 +43,6 @@
     private Context mContext;
 
     private BluetoothA2dp mService;
-    BluetoothA2dpWrapper.Factory mWrapperFactory;
     private BluetoothA2dpWrapper mServiceWrapper;
     private boolean mIsProfileReady;
 
@@ -67,7 +67,7 @@
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (V) Log.d(TAG,"Bluetooth service connected");
             mService = (BluetoothA2dp) proxy;
-            mServiceWrapper = mWrapperFactory.getInstance(mService);
+            mServiceWrapper = new BluetoothA2dpWrapper(mService);
             // We just bound to the service, so refresh the UI for any connected A2DP devices.
             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
             while (!deviceList.isEmpty()) {
@@ -101,14 +101,13 @@
         mLocalAdapter = adapter;
         mDeviceManager = deviceManager;
         mProfileManager = profileManager;
-        mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory();
         mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
                 BluetoothProfile.A2DP);
     }
 
     @VisibleForTesting
-    void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) {
-        mWrapperFactory = factory;
+    void setBluetoothA2dpWrapper(BluetoothA2dpWrapper wrapper) {
+        mServiceWrapper = wrapper;
     }
 
     public boolean isConnectable() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
deleted file mode 100644
index aa3e835..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java
+++ /dev/null
@@ -1,58 +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.settingslib.bluetooth;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothCodecStatus;
-import android.bluetooth.BluetoothDevice;
-
-/**
- * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
- * yet available in our current version of  Robolectric. It provides a thin wrapper to call the real
- * methods in production and a mock in tests.
- */
-public interface BluetoothA2dpWrapper {
-
-    static interface Factory {
-        BluetoothA2dpWrapper getInstance(BluetoothA2dp service);
-    }
-
-    /**
-     * @return the real {@code BluetoothA2dp} object
-     */
-    BluetoothA2dp getService();
-
-    /**
-     * Wraps {@code BluetoothA2dp.getCodecStatus}
-     */
-    public BluetoothCodecStatus getCodecStatus();
-
-    /**
-     * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
-     */
-    int supportsOptionalCodecs(BluetoothDevice device);
-
-    /**
-     * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
-     */
-    int getOptionalCodecsEnabled(BluetoothDevice device);
-
-    /**
-     * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
-     */
-    void setOptionalCodecsEnabled(BluetoothDevice device, int value);
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
deleted file mode 100644
index 14fa796..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java
+++ /dev/null
@@ -1,62 +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.settingslib.bluetooth;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothCodecStatus;
-import android.bluetooth.BluetoothDevice;
-
-public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper {
-
-    public static class Factory implements BluetoothA2dpWrapper.Factory {
-        @Override
-        public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) {
-            return new BluetoothA2dpWrapperImpl(service);
-        }
-    }
-
-    private BluetoothA2dp mService;
-
-    public BluetoothA2dpWrapperImpl(BluetoothA2dp service) {
-        mService = service;
-    }
-
-    @Override
-    public BluetoothA2dp getService() {
-        return mService;
-    }
-
-    @Override
-    public BluetoothCodecStatus getCodecStatus() {
-        return mService.getCodecStatus();
-    }
-
-    @Override
-    public int supportsOptionalCodecs(BluetoothDevice device) {
-        return mService.supportsOptionalCodecs(device);
-    }
-
-    @Override
-    public int getOptionalCodecsEnabled(BluetoothDevice device) {
-        return mService.getOptionalCodecsEnabled(device);
-    }
-
-    @Override
-    public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
-        mService.setOptionalCodecsEnabled(device, value);
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 28105e2..f57d02b 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -107,14 +107,16 @@
         addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
 
         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
+        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
     }
 
     void registerProfileIntentReceiver() {
-        mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
+        mContext.registerReceiver(mProfileBroadcastReceiver, mProfileIntentFilter, null, mReceiverHandler);
     }
 
     public void setReceiverHandler(android.os.Handler handler) {
         mContext.unregisterReceiver(mBroadcastReceiver);
+        mContext.unregisterReceiver(mProfileBroadcastReceiver);
         mReceiverHandler = handler;
         mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter, null, mReceiverHandler);
         registerProfileIntentReceiver();
@@ -148,11 +150,31 @@
         }
     };
 
+    private final BroadcastReceiver mProfileBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            BluetoothDevice device = intent
+                    .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+            Handler handler = mHandlerMap.get(action);
+            if (handler != null) {
+                handler.onReceive(context, intent, device);
+            }
+        }
+    };
+
     private class AdapterStateChangedHandler implements Handler {
         public void onReceive(Context context, Intent intent,
                 BluetoothDevice device) {
             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                                     BluetoothAdapter.ERROR);
+            // Reregister Profile Broadcast Receiver as part of TURN OFF
+            if (state == BluetoothAdapter.STATE_OFF)
+            {
+                context.unregisterReceiver(mProfileBroadcastReceiver);
+                registerProfileIntentReceiver();
+            }
             // update local profiles and get paired devices
             mLocalAdapter.setBluetoothStateInt(state);
             // send callback to update UI and possibly start scanning
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index d1621da..213002f 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -19,7 +19,7 @@
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.util.Log;
@@ -35,7 +35,7 @@
     private static final String TAG = "HidProfile";
     private static boolean V = true;
 
-    private BluetoothInputDevice mService;
+    private BluetoothHidHost mService;
     private boolean mIsProfileReady;
 
     private final LocalBluetoothAdapter mLocalAdapter;
@@ -53,7 +53,7 @@
 
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
             if (V) Log.d(TAG,"Bluetooth service connected");
-            mService = (BluetoothInputDevice) proxy;
+            mService = (BluetoothHidHost) proxy;
             // We just bound to the service, so refresh the UI for any connected HID devices.
             List<BluetoothDevice> deviceList = mService.getConnectedDevices();
             while (!deviceList.isEmpty()) {
@@ -87,7 +87,7 @@
         mDeviceManager = deviceManager;
         mProfileManager = profileManager;
         adapter.getProfileProxy(context, new InputDeviceServiceListener(),
-                BluetoothProfile.INPUT_DEVICE);
+                BluetoothProfile.HID_HOST);
     }
 
     public boolean isConnectable() {
@@ -190,7 +190,7 @@
         if (V) Log.d(TAG, "finalize()");
         if (mService != null) {
             try {
-                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HID_HOST,
                                                                        mService);
                 mService = null;
             }catch (Throwable t) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 0750dc7..9cda669 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -21,9 +21,9 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothHidHost;
 import android.bluetooth.BluetoothMap;
 import android.bluetooth.BluetoothMapClient;
-import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
@@ -123,7 +123,7 @@
         // Always add HID and PAN profiles
         mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
         addProfile(mHidProfile, HidProfile.NAME,
-                BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+                BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
 
         mPanProfile = new PanProfile(context);
         addPanProfile(mPanProfile, PanProfile.NAME,
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
new file mode 100644
index 0000000..7162121
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -0,0 +1,8 @@
+# Default reviewers for this and subdirectories.
+asapperstein@google.com
+asargent@google.com
+eisenbach@google.com
+jackqdyulei@google.com
+siyuanh@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 7bda231..3299cb2 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -32,7 +32,7 @@
 /**
  * PanProfile handles Bluetooth PAN profile (NAP and PANU).
  */
-public final class PanProfile implements LocalBluetoothProfile {
+public class PanProfile implements LocalBluetoothProfile {
     private static final String TAG = "PanProfile";
     private static boolean V = true;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
index c919426..0ee1dad9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
@@ -1,9 +1,17 @@
 package com.android.settingslib.bluetooth;
 
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.util.Pair;
 
 import com.android.settingslib.R;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
+
+import java.util.List;
 
 public class Utils {
     public static final boolean V = false; // verbose logging
@@ -40,4 +48,78 @@
         void onShowError(Context context, String name, int messageResId);
     }
 
+    public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+            CachedBluetoothDevice cachedDevice) {
+        return getBtClassDrawableWithDescription(context, cachedDevice, 1 /* iconScale */);
+    }
+
+    public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
+            CachedBluetoothDevice cachedDevice, float iconScale) {
+        BluetoothClass btClass = cachedDevice.getBtClass();
+        final int level = cachedDevice.getBatteryLevel();
+        if (btClass != null) {
+            switch (btClass.getMajorDeviceClass()) {
+                case BluetoothClass.Device.Major.COMPUTER:
+                    return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level,
+                            iconScale),
+                            context.getString(R.string.bluetooth_talkback_computer));
+
+                case BluetoothClass.Device.Major.PHONE:
+                    return new Pair<>(
+                            getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level,
+                                    iconScale),
+                            context.getString(R.string.bluetooth_talkback_phone));
+
+                case BluetoothClass.Device.Major.PERIPHERAL:
+                    return new Pair<>(
+                            getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass),
+                                    level, iconScale),
+                            context.getString(R.string.bluetooth_talkback_input_peripheral));
+
+                case BluetoothClass.Device.Major.IMAGING:
+                    return new Pair<>(
+                            getBluetoothDrawable(context, R.drawable.ic_settings_print, level,
+                                    iconScale),
+                            context.getString(R.string.bluetooth_talkback_imaging));
+
+                default:
+                    // unrecognized device class; continue
+            }
+        }
+
+        List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+        for (LocalBluetoothProfile profile : profiles) {
+            int resId = profile.getDrawableResource(btClass);
+            if (resId != 0) {
+                return new Pair<>(getBluetoothDrawable(context, resId, level, iconScale), null);
+            }
+        }
+        if (btClass != null) {
+            if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+                return new Pair<>(
+                        getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level,
+                                iconScale),
+                        context.getString(R.string.bluetooth_talkback_headset));
+            }
+            if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+                return new Pair<>(
+                        getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level,
+                                iconScale),
+                        context.getString(R.string.bluetooth_talkback_headphone));
+            }
+        }
+        return new Pair<>(
+                getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale),
+                context.getString(R.string.bluetooth_talkback_bluetooth));
+    }
+
+    public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId,
+            int batteryLevel, float iconScale) {
+        if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
+            return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel,
+                    iconScale);
+        } else {
+            return context.getDrawable(resId);
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 1771208..1cb255b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -43,6 +43,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TimeZone;
+import libcore.util.TimeZoneFinder;
 
 /**
  * ZoneGetter is the utility class to get time zone and zone list, and both of them have display
@@ -376,10 +377,9 @@
             }
 
             // Create a lookup of local zone IDs.
-            localZoneIds = new HashSet<String>();
-            for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) {
-                localZoneIds.add(olsonId);
-            }
+            List<String> zoneIds =
+                    TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(locale.getCountry());
+            localZoneIds = new HashSet<>(zoneIds);
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 47cbb77..6aae226 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -28,17 +28,20 @@
 import android.support.v7.preference.TwoStatePreference;
 import android.text.TextUtils;
 
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.ConfirmationDialogController;
 
-public abstract class AbstractEnableAdbPreferenceController extends AbstractPreferenceController
-        implements ConfirmationDialogController {
+public abstract class AbstractEnableAdbPreferenceController extends
+        DeveloperOptionsPreferenceController implements ConfirmationDialogController {
     private static final String KEY_ENABLE_ADB = "enable_adb";
     public static final String ACTION_ENABLE_ADB_STATE_CHANGED =
             "com.android.settingslib.development.AbstractEnableAdbController."
                     + "ENABLE_ADB_STATE_CHANGED";
 
-    private SwitchPreference mPreference;
+    public static final int ADB_SETTING_ON = 1;
+    public static final int ADB_SETTING_OFF = 0;
+
+
+    protected SwitchPreference mPreference;
 
     public AbstractEnableAdbPreferenceController(Context context) {
         super(context);
@@ -64,12 +67,13 @@
 
     private boolean isAdbEnabled() {
         final ContentResolver cr = mContext.getContentResolver();
-        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) != 0;
+        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF)
+                != ADB_SETTING_OFF;
     }
 
     @Override
     public void updateState(Preference preference) {
-        ((TwoStatePreference)preference).setChecked(isAdbEnabled());
+        ((TwoStatePreference) preference).setChecked(isAdbEnabled());
     }
 
     public void enablePreference(boolean enabled) {
@@ -105,7 +109,7 @@
 
     protected void writeAdbSetting(boolean enabled) {
         Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
+                Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
         notifyStateChanged();
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index c167723..f79be7e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -26,29 +26,34 @@
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 
-public abstract class AbstractLogdSizePreferenceController extends AbstractPreferenceController
-        implements Preference.OnPreferenceChangeListener {
+public abstract class AbstractLogdSizePreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener {
     public static final String ACTION_LOGD_SIZE_UPDATED = "com.android.settingslib.development."
             + "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED";
     public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE";
 
+    @VisibleForTesting
+    static final String LOW_RAM_CONFIG_PROPERTY_KEY = "ro.config.low_ram";
     private static final String SELECT_LOGD_SIZE_KEY = "select_logd_size";
     @VisibleForTesting
     static final String SELECT_LOGD_SIZE_PROPERTY = "persist.logd.size";
     static final String SELECT_LOGD_TAG_PROPERTY = "persist.log.tag";
     // Tricky, isLoggable only checks for first character, assumes silence
     static final String SELECT_LOGD_TAG_SILENCE = "Settings";
-    private static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log";
+    @VisibleForTesting
+    static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log";
     private static final String SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY = "log.tag.snet_event_log";
     private static final String SELECT_LOGD_DEFAULT_SIZE_PROPERTY = "ro.logd.size";
     @VisibleForTesting
     static final String SELECT_LOGD_DEFAULT_SIZE_VALUE = "262144";
     private static final String SELECT_LOGD_SVELTE_DEFAULT_SIZE_VALUE = "65536";
     // 32768 is merely a menu marker, 64K is our lowest log buffer size we replace it with.
-    private static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536";
+    @VisibleForTesting
+    static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536";
     static final String SELECT_LOGD_OFF_SIZE_MARKER_VALUE = "32768";
+    @VisibleForTesting
+    static final String DEFAULT_SNET_TAG = "I";
 
     private ListPreference mLogdSize;
 
@@ -57,11 +62,6 @@
     }
 
     @Override
-    public boolean isAvailable() {
-        return true;
-    }
-
-    @Override
     public String getPreferenceKey() {
         return SELECT_LOGD_SIZE_KEY;
     }
@@ -160,7 +160,7 @@
             if ((snetValue == null) || (snetValue.length() == 0)) {
                 snetValue = SystemProperties.get(SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY);
                 if ((snetValue == null) || (snetValue.length() == 0)) {
-                    SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, "I");
+                    SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, DEFAULT_SNET_TAG);
                 }
             }
             // Silence all log sources, security logs notwithstanding
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index 502fb17..77b2d86 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -30,16 +30,15 @@
 import android.text.TextUtils;
 
 import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.ConfirmationDialogController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnCreate;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
 
-public abstract class AbstractLogpersistPreferenceController extends AbstractPreferenceController
-        implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnCreate, OnDestroy,
-        ConfirmationDialogController {
+public abstract class AbstractLogpersistPreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+        LifecycleObserver, OnCreate, OnDestroy, ConfirmationDialogController {
 
     private static final String SELECT_LOGPERSIST_KEY = "select_logpersist";
     private static final String SELECT_LOGPERSIST_PROPERTY = "persist.logd.logpersistd";
@@ -69,7 +68,7 @@
 
     public AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle) {
         super(context);
-        if (isAvailable()) {
+        if (isAvailable() && lifecycle != null) {
             lifecycle.addObserver(this);
         }
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
new file mode 100644
index 0000000..f68c04f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * 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.settingslib.development;
+
+import android.content.Context;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * This controller is used handle changes for the master switch in the developer options page.
+ *
+ * All Preference Controllers that are a part of the developer options page should inherit this
+ * class.
+ */
+public abstract class DeveloperOptionsPreferenceController extends
+        AbstractPreferenceController {
+
+    public DeveloperOptionsPreferenceController(Context context) {
+        super(context);
+    }
+
+    /**
+     * Child classes should override this method to create custom logic for hiding preferences.
+     *
+     * @return true if the preference is to be displayed.
+     */
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    /**
+     * Called when developer options is enabled
+     */
+    public void onDeveloperOptionsEnabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchEnabled();
+        }
+    }
+
+    /**
+     * Called when developer options is disabled
+     */
+    public void onDeveloperOptionsDisabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchDisabled();
+        }
+    }
+
+    /**
+     * Called when developer options is enabled and the preference is available
+     */
+    protected void onDeveloperOptionsSwitchEnabled() {
+    }
+
+    /**
+     * Called when developer options is disabled and the preference is available
+     */
+    protected void onDeveloperOptionsSwitchDisabled() {
+    }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
new file mode 100644
index 0000000..ba358f8
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java
@@ -0,0 +1,85 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for bluetooth address
+ */
+public abstract class AbstractBluetoothAddressPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_BT_ADDRESS = "bt_address";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            BluetoothAdapter.ACTION_STATE_CHANGED
+    };
+
+    private Preference mBtAddress;
+
+    public AbstractBluetoothAddressPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, lifecycle);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return BluetoothAdapter.getDefaultAdapter() != null;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_BT_ADDRESS;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mBtAddress = screen.findPreference(KEY_BT_ADDRESS);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @SuppressLint("HardwareIds")
+    @Override
+    protected void updateConnectivity() {
+        BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
+        if (bluetooth != null && mBtAddress != null) {
+            String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null;
+            if (!TextUtils.isEmpty(address)) {
+                // Convert the address to lowercase for consistency with the wifi MAC address.
+                mBtAddress.setSummary(address.toLowerCase());
+            } else {
+                mBtAddress.setSummary(R.string.status_unavailable);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java
new file mode 100644
index 0000000..c6552f7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java
@@ -0,0 +1,114 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Base class for preference controllers which listen to connectivity broadcasts
+ */
+public abstract class AbstractConnectivityPreferenceController
+        extends AbstractPreferenceController implements LifecycleObserver, OnStart, OnStop {
+
+    private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (ArrayUtils.contains(getConnectivityIntents(), action)) {
+                getHandler().sendEmptyMessage(EVENT_UPDATE_CONNECTIVITY);
+            }
+        }
+    };
+
+    private static final int EVENT_UPDATE_CONNECTIVITY = 600;
+
+    private Handler mHandler;
+
+    public AbstractConnectivityPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context);
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        mContext.unregisterReceiver(mConnectivityReceiver);
+    }
+
+    @Override
+    public void onStart() {
+        final IntentFilter connectivityIntentFilter = new IntentFilter();
+        final String[] intents = getConnectivityIntents();
+        for (String intent : intents) {
+            connectivityIntentFilter.addAction(intent);
+        }
+
+        mContext.registerReceiver(mConnectivityReceiver, connectivityIntentFilter,
+                android.Manifest.permission.CHANGE_NETWORK_STATE, null);
+    }
+
+    protected abstract String[] getConnectivityIntents();
+
+    protected abstract void updateConnectivity();
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            mHandler = new ConnectivityEventHandler(this);
+        }
+        return mHandler;
+    }
+
+    private static class ConnectivityEventHandler extends Handler {
+        private WeakReference<AbstractConnectivityPreferenceController> mPreferenceController;
+
+        public ConnectivityEventHandler(AbstractConnectivityPreferenceController activity) {
+            mPreferenceController = new WeakReference<>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AbstractConnectivityPreferenceController preferenceController
+                    = mPreferenceController.get();
+            if (preferenceController == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case EVENT_UPDATE_CONNECTIVITY:
+                    preferenceController.updateConnectivity();
+                    break;
+                default:
+                    throw new IllegalStateException("Unknown message " + msg.what);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
new file mode 100644
index 0000000..bb8404b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java
@@ -0,0 +1,95 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.PersistableBundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for IMS status
+ */
+public abstract class AbstractImsStatusPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            BluetoothAdapter.ACTION_STATE_CHANGED,
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+    };
+
+    private Preference mImsStatus;
+
+    public AbstractImsStatusPreferenceController(Context context,
+            Lifecycle lifecycle) {
+        super(context, lifecycle);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
+        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        PersistableBundle config = null;
+        if (configManager != null) {
+            config = configManager.getConfigForSubId(subId);
+        }
+        return config != null && config.getBoolean(
+                CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_IMS_REGISTRATION_STATE;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mImsStatus = screen.findPreference(KEY_IMS_REGISTRATION_STATE);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @Override
+    protected void updateConnectivity() {
+        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        if (mImsStatus != null) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ?
+                    R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
new file mode 100644
index 0000000..ded3022
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java
@@ -0,0 +1,112 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.wifi.WifiManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.net.InetAddress;
+import java.util.Iterator;
+
+/**
+ * Preference controller for IP address
+ */
+public abstract class AbstractIpAddressPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_IP_ADDRESS = "wifi_ip_address";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+    };
+
+    private Preference mIpAddress;
+    private final ConnectivityManager mCM;
+
+    public AbstractIpAddressPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, lifecycle);
+        mCM = context.getSystemService(ConnectivityManager.class);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_IP_ADDRESS;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mIpAddress = screen.findPreference(KEY_IP_ADDRESS);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @Override
+    protected void updateConnectivity() {
+        String ipAddress = getDefaultIpAddresses(mCM);
+        if (ipAddress != null) {
+            mIpAddress.setSummary(ipAddress);
+        } else {
+            mIpAddress.setSummary(R.string.status_unavailable);
+        }
+    }
+
+    /**
+     * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
+     * addresses.
+     * @param cm ConnectivityManager
+     * @return the formatted and newline-separated IP addresses, or null if none.
+     */
+    private static String getDefaultIpAddresses(ConnectivityManager cm) {
+        LinkProperties prop = cm.getActiveLinkProperties();
+        return formatIpAddresses(prop);
+    }
+
+    private static String formatIpAddresses(LinkProperties prop) {
+        if (prop == null) return null;
+        Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
+        // If there are no entries, return null
+        if (!iter.hasNext()) return null;
+        // Concatenate all available addresses, newline separated
+        StringBuilder addresses = new StringBuilder();
+        while (iter.hasNext()) {
+            addresses.append(iter.next().getHostAddress());
+            if (iter.hasNext()) addresses.append("\n");
+        }
+        return addresses.toString();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
new file mode 100644
index 0000000..ff7536a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
+ */
+public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
+    private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+    private final String mSerialNumber;
+
+    public AbstractSerialNumberPreferenceController(Context context) {
+        this(context, Build.getSerial());
+    }
+
+    @VisibleForTesting
+    AbstractSerialNumberPreferenceController(Context context, String serialNumber) {
+        super(context);
+        mSerialNumber = serialNumber;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !TextUtils.isEmpty(mSerialNumber);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER);
+        if (pref != null) {
+            pref.setSummary(mSerialNumber);
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SERIAL_NUMBER;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
new file mode 100644
index 0000000..a78440c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java
@@ -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.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settingslib.Utils;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+public abstract class AbstractSimStatusImeiInfoPreferenceController
+        extends AbstractPreferenceController {
+    public AbstractSimStatusImeiInfoPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mContext.getSystemService(UserManager.class).isAdminUser()
+                && !Utils.isWifiOnly(mContext);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
new file mode 100644
index 0000000..ac61ade
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java
@@ -0,0 +1,119 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Preference controller for uptime
+ */
+public abstract class AbstractUptimePreferenceController extends AbstractPreferenceController
+        implements LifecycleObserver, OnStart, OnStop {
+
+    @VisibleForTesting
+    static final String KEY_UPTIME = "up_time";
+    private static final int EVENT_UPDATE_STATS = 500;
+
+    private Preference mUptime;
+    private Handler mHandler;
+
+    public AbstractUptimePreferenceController(Context context, Lifecycle lifecycle) {
+        super(context);
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        getHandler().sendEmptyMessage(EVENT_UPDATE_STATS);
+    }
+
+    @Override
+    public void onStop() {
+        getHandler().removeMessages(EVENT_UPDATE_STATS);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_UPTIME;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mUptime = screen.findPreference(KEY_UPTIME);
+        updateTimes();
+    }
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            mHandler = new MyHandler(this);
+        }
+        return mHandler;
+    }
+
+    private void updateTimes() {
+        mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime()));
+    }
+
+    private static class MyHandler extends Handler {
+        private WeakReference<AbstractUptimePreferenceController> mStatus;
+
+        public MyHandler(AbstractUptimePreferenceController activity) {
+            mStatus = new WeakReference<>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            AbstractUptimePreferenceController status = mStatus.get();
+            if (status == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case EVENT_UPDATE_STATS:
+                    status.updateTimes();
+                    sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
+                    break;
+
+                default:
+                    throw new IllegalStateException("Unknown message " + msg.what);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
new file mode 100644
index 0000000..d57b64f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java
@@ -0,0 +1,88 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * Preference controller for WIFI MAC address
+ */
+public abstract class AbstractWifiMacAddressPreferenceController
+        extends AbstractConnectivityPreferenceController {
+
+    @VisibleForTesting
+    static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
+
+    private static final String[] CONNECTIVITY_INTENTS = {
+            ConnectivityManager.CONNECTIVITY_ACTION,
+            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+            WifiManager.NETWORK_STATE_CHANGED_ACTION,
+    };
+
+    private Preference mWifiMacAddress;
+    private final WifiManager mWifiManager;
+
+    public AbstractWifiMacAddressPreferenceController(Context context, Lifecycle lifecycle) {
+        super(context, lifecycle);
+        mWifiManager = context.getSystemService(WifiManager.class);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_WIFI_MAC_ADDRESS;
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS);
+        updateConnectivity();
+    }
+
+    @Override
+    protected String[] getConnectivityIntents() {
+        return CONNECTIVITY_INTENTS;
+    }
+
+    @SuppressLint("HardwareIds")
+    @Override
+    protected void updateConnectivity() {
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
+        if (!TextUtils.isEmpty(macAddress)) {
+            mWifiMacAddress.setSummary(macAddress);
+        } else {
+            mWifiMacAddress.setSummary(R.string.status_unavailable);
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 35ba6ae..038dcf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -65,7 +65,7 @@
      *
      * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
      */
-    private static final String EXTRA_SETTINGS_ACTION =
+    public static final String EXTRA_SETTINGS_ACTION =
             "com.android.settings.action.EXTRA_SETTINGS";
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 817989a..f4c9bb3 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -415,8 +415,8 @@
                             : (mLevel == 100 ? 0.38f : 0.5f)));
             mTextHeight = -mTextPaint.getFontMetrics().ascent;
             pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level);
-            pctX = mWidth * 0.5f;
-            pctY = (mHeight + mTextHeight) * 0.47f;
+            pctX = mWidth * 0.5f + left;
+            pctY = (mHeight + mTextHeight) * 0.47f + top;
             pctOpaque = levelTop > pctY;
             if (!pctOpaque) {
                 mTextPath.reset();
@@ -439,8 +439,8 @@
         if (!mCharging && !mPowerSaveEnabled) {
             if (level <= mCriticalLevel) {
                 // draw the warning text
-                final float x = mWidth * 0.5f;
-                final float y = (mHeight + mWarningTextHeight) * 0.48f;
+                final float x = mWidth * 0.5f + left;
+                final float y = (mHeight + mWarningTextHeight) * 0.48f + top;
                 c.drawText(mWarningString, x, y, mWarningTextPaint);
             } else if (pctOpaque) {
                 // draw the percentage text
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index b7fd404..3c5ac8d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -73,7 +73,7 @@
         final Drawable deviceDrawable = context.getDrawable(resId);
 
         final BatteryMeterDrawable batteryDrawable = new BatteryMeterDrawable(context,
-                R.color.meter_background_color, batteryLevel);
+                context.getColor(R.color.meter_background_color), batteryLevel);
         final int pad = context.getResources().getDimensionPixelSize(R.dimen.bt_battery_padding);
         batteryDrawable.setPadding(pad, pad, pad, pad);
 
@@ -107,6 +107,8 @@
     @VisibleForTesting
     static class BatteryMeterDrawable extends BatteryMeterDrawableBase {
         private final float mAspectRatio;
+        @VisibleForTesting
+        int mFrameColor;
 
         public BatteryMeterDrawable(Context context, int frameColor, int batteryLevel) {
             super(context, frameColor);
@@ -118,6 +120,7 @@
             final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
             setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
             setBatteryLevel(batteryLevel);
+            mFrameColor = frameColor;
         }
 
         @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
new file mode 100644
index 0000000..a0e28ba
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS
@@ -0,0 +1,5 @@
+# Default reviewers for this and subdirectories.
+takaoka@google.com
+yukawa@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
index 56b8441..9c34763 100644
--- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java
@@ -261,7 +261,7 @@
         if (requiredAccountType == null) {
             return true;
         }
-        AccountManager accountManager = AccountManager.get(mContext);
+        AccountManager accountManager = mContext.getSystemService(AccountManager.class);
         Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
         boolean satisfiesRequiredAccount = accounts.length > 0;
         if (!satisfiesRequiredAccount) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
new file mode 100644
index 0000000..d5d2e9e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -0,0 +1,7 @@
+# Default reviewers for this and subdirectories.
+asapperstein@google.com
+asargent@google.com
+dling@google.com
+zhfan@google.com
+
+# Emergency approvers in case the above are not available
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 664dcfc..c56e1da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -24,7 +24,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkKey;
 import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
@@ -36,10 +35,14 @@
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiNetworkScoreCache.CacheListener;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.provider.Settings;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -47,8 +50,12 @@
 import android.util.SparseIntArray;
 import android.widget.Toast;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,7 +71,7 @@
 /**
  * Tracks saved or available wifi networks and their state.
  */
-public class WifiTracker {
+public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
     /**
      * Default maximum age in millis of cached scored networks in
      * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
@@ -80,7 +87,7 @@
      * and used so as to assist with in-the-field WiFi connectivity debugging  */
     public static boolean sVerboseLogging;
 
-    // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
+    // TODO(b/36733768): Remove flag includeSaved
 
     // TODO: Allow control of this?
     // Combo scans can take 5-6s to complete - set to 10s.
@@ -96,9 +103,9 @@
     private final WifiListener mListener;
     private final boolean mIncludeSaved;
     private final boolean mIncludeScans;
-    private final boolean mIncludePasspoints;
-    @VisibleForTesting final MainHandler mMainHandler;
-    @VisibleForTesting final WorkHandler mWorkHandler;
+    @VisibleForTesting MainHandler mMainHandler;
+    @VisibleForTesting WorkHandler mWorkHandler;
+    private HandlerThread mWorkThread;
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
@@ -142,7 +149,7 @@
     private WifiInfo mLastInfo;
 
     private final NetworkScoreManager mNetworkScoreManager;
-    private final WifiNetworkScoreCache mScoreCache;
+    private WifiNetworkScoreCache mScoreCache;
     private boolean mNetworkScoringUiEnabled;
     private long mMaxSpeedLabelScoreCacheAge;
 
@@ -169,51 +176,43 @@
         return filter;
     }
 
+    /**
+     * Use the lifecycle constructor below whenever possible
+     */
+    @Deprecated
     public WifiTracker(Context context, WifiListener wifiListener,
             boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, null, includeSaved, includeScans);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                       boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
+        this(context, wifiListener, includeSaved, includeScans,
                 context.getSystemService(WifiManager.class),
                 context.getSystemService(ConnectivityManager.class),
                 context.getSystemService(NetworkScoreManager.class),
-                Looper.myLooper(), newIntentFilter());
+                newIntentFilter());
+    }
+
+    public WifiTracker(Context context, WifiListener wifiListener,
+            @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
+        this(context, wifiListener, includeSaved, includeScans,
+                context.getSystemService(WifiManager.class),
+                context.getSystemService(ConnectivityManager.class),
+                context.getSystemService(NetworkScoreManager.class),
+                newIntentFilter());
+        lifecycle.addObserver(this);
     }
 
     @VisibleForTesting
-    WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                boolean includeSaved, boolean includeScans, boolean includePasspoints,
-                WifiManager wifiManager, ConnectivityManager connectivityManager,
-                NetworkScoreManager networkScoreManager, Looper currentLooper,
-                IntentFilter filter) {
+    WifiTracker(Context context, WifiListener wifiListener,
+            boolean includeSaved, boolean includeScans,
+            WifiManager wifiManager, ConnectivityManager connectivityManager,
+            NetworkScoreManager networkScoreManager,
+            IntentFilter filter) {
         if (!includeSaved && !includeScans) {
             throw new IllegalArgumentException("Must include either saved or scans");
         }
         mContext = context;
-        if (currentLooper == null) {
-            // When we aren't on a looper thread, default to the main.
-            currentLooper = Looper.getMainLooper();
-        }
-        mMainHandler = new MainHandler(currentLooper);
-        mWorkHandler = new WorkHandler(
-                workerLooper != null ? workerLooper : currentLooper);
+        mMainHandler = new MainHandler(Looper.getMainLooper());
         mWifiManager = wifiManager;
         mIncludeSaved = includeSaved;
         mIncludeScans = includeScans;
-        mIncludePasspoints = includePasspoints;
         mListener = wifiListener;
         mConnectivityManager = connectivityManager;
 
@@ -229,7 +228,22 @@
 
         mNetworkScoreManager = networkScoreManager;
 
-        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
+        final HandlerThread workThread = new HandlerThread(TAG
+                + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        workThread.start();
+        setWorkThread(workThread);
+    }
+
+    /**
+     * Sanity warning: this wipes out mScoreCache, so use with extreme caution
+     * @param workThread substitute Handler thread, for testing purposes only
+     */
+    @VisibleForTesting
+    void setWorkThread(HandlerThread workThread) {
+        mWorkThread = workThread;
+        mWorkHandler = new WorkHandler(workThread.getLooper());
+        mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
             @Override
             public void networkCacheUpdated(List<ScoredNetwork> networks) {
                 synchronized (mLock) {
@@ -244,6 +258,11 @@
         });
     }
 
+    @Override
+    public void onDestroy() {
+        mWorkThread.quit();
+    }
+
     /** Synchronously update the list of access points with the latest information. */
     @MainThread
     public void forceUpdate() {
@@ -272,15 +291,6 @@
     }
 
     /**
-     * Force a scan for wifi networks to happen now.
-     */
-    public void forceScan() {
-        if (mWifiManager.isWifiEnabled() && mScanner != null) {
-            mScanner.forceScan();
-        }
-    }
-
-    /**
      * Temporarily stop scanning for wifi networks.
      */
     public void pauseScanning() {
@@ -312,8 +322,9 @@
      * <p>Registers listeners and starts scanning for wifi networks. If this is not called
      * then forceUpdate() must be called to populate getAccessPoints().
      */
+    @Override
     @MainThread
-    public void startTracking() {
+    public void onStart() {
         synchronized (mLock) {
             registerScoreCache();
 
@@ -361,15 +372,16 @@
     /**
      * Stop tracking wifi networks and scores.
      *
-     * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
+     * <p>This should always be called when done with a WifiTracker (if onStart was called) to
      * ensure proper cleanup and prevent any further callbacks from occurring.
      *
      * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
      * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
      * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
      */
+    @Override
     @MainThread
-    public void stopTracking() {
+    public void onStop() {
         synchronized (mLock) {
             if (mRegistered) {
                 mContext.unregisterReceiver(mReceiver);
@@ -768,15 +780,6 @@
         }
     }
 
-    public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
-            boolean includeScans, boolean includePasspoints) {
-        WifiTracker tracker = new WifiTracker(context,
-                null, null, includeSaved, includeScans, includePasspoints);
-        tracker.forceUpdate();
-        tracker.copyAndNotifyListeners(false /*notifyListeners*/);
-        return tracker.getAccessPoints();
-    }
-
     @VisibleForTesting
     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -966,11 +969,6 @@
             }
         }
 
-        void forceScan() {
-            removeMessages(MSG_SCAN);
-            sendEmptyMessage(MSG_SCAN);
-        }
-
         void pause() {
             mRetry = 0;
             removeMessages(MSG_SCAN);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
index 79cee04..8b5863a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -16,8 +16,10 @@
 package com.android.settingslib.wifi;
 
 import android.content.Context;
-import android.os.Looper;
 import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 /**
  * Factory method used to inject WifiTracker instances.
@@ -31,12 +33,11 @@
     }
 
     public static WifiTracker create(
-            Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+            Context context, WifiTracker.WifiListener wifiListener, @NonNull Lifecycle lifecycle,
+            boolean includeSaved, boolean includeScans) {
         if(sTestingWifiTracker != null) {
             return sTestingWifiTracker;
         }
-        return new WifiTracker(
-                context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+        return new WifiTracker(context, wifiListener, lifecycle, includeSaved, includeScans);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
new file mode 100644
index 0000000..4c52a9f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.settingslib.wrapper;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * This class replicates some methods of android.bluetooth.BluetoothA2dp that are new and not
+ * yet available in our current version of Robolectric. It provides a thin wrapper to call the real
+ * methods in production and a mock in tests.
+ */
+public class BluetoothA2dpWrapper {
+
+    private BluetoothA2dp mService;
+
+    public BluetoothA2dpWrapper(BluetoothA2dp service) {
+        mService = service;
+    }
+
+    /**
+     * @return the real {@code BluetoothA2dp} object
+     */
+    public BluetoothA2dp getService() {
+        return mService;
+    }
+
+    /**
+     * Wraps {@code BluetoothA2dp.getCodecStatus}
+     */
+    public BluetoothCodecStatus getCodecStatus() {
+        return mService.getCodecStatus();
+    }
+
+    /**
+     * Wraps {@code BluetoothA2dp.supportsOptionalCodecs}
+     */
+    public int supportsOptionalCodecs(BluetoothDevice device) {
+        return mService.supportsOptionalCodecs(device);
+    }
+
+    /**
+     * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled}
+     */
+    public int getOptionalCodecsEnabled(BluetoothDevice device) {
+        return mService.getOptionalCodecsEnabled(device);
+    }
+
+    /**
+     * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled}
+     */
+    public void setOptionalCodecsEnabled(BluetoothDevice device, int value) {
+        mService.setOptionalCodecsEnabled(device, value);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
index cd62bc3..b1f3f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java
@@ -60,6 +60,13 @@
     }
 
     /**
+     * Calls {@code PackageManager.getInstalledPackagesAsUser}
+     */
+    public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        return mPm.getInstalledPackagesAsUser(flags, userId);
+    }
+
+    /**
      * Calls {@code PackageManager.hasSystemFeature()}.
      *
      * @see android.content.pm.PackageManager#hasSystemFeature
@@ -132,11 +139,11 @@
 
     /**
      * Gets information about a particular package from the package manager.
+     *
      * @param packageName The name of the package we would like information about.
-     * @param i additional options flags. see javadoc for
-     * {@link PackageManager#getPackageInfo(String, int)}
+     * @param i           additional options flags. see javadoc for
+     *                    {@link PackageManager#getPackageInfo(String, int)}
      * @return The PackageInfo for the requested package
-     * @throws NameNotFoundException
      */
     public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException {
         return mPm.getPackageInfo(packageName, i);
@@ -144,6 +151,7 @@
 
     /**
      * Retrieves the icon associated with this particular set of ApplicationInfo
+     *
      * @param info The ApplicationInfo to retrieve the icon for
      * @return The icon as a drawable.
      */
@@ -154,6 +162,7 @@
 
     /**
      * Retrieves the label associated with the particular set of ApplicationInfo
+     *
      * @param app The ApplicationInfo to retrieve the label for
      * @return the label as a CharSequence
      */
@@ -190,4 +199,48 @@
             throws PackageManager.NameNotFoundException {
         return mPm.getPackageUidAsUser(pkg, userId);
     }
+
+    /**
+     * Calls {@code PackageManager.setApplicationEnabledSetting}
+     */
+    public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
+        mPm.setApplicationEnabledSetting(packageName, newState, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationEnabledSetting}
+     */
+    public int getApplicationEnabledSetting(String packageName) {
+        return mPm.getApplicationEnabledSetting(packageName);
+    }
+
+    /**
+     * Calls {@code PackageManager.setComponentEnabledSetting}
+     */
+    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
+        mPm.setComponentEnabledSetting(componentName, newState, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationInfo}
+     */
+    public ApplicationInfo getApplicationInfo(String packageName, int flags)
+            throws NameNotFoundException {
+        return mPm.getApplicationInfo(packageName, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationLabel}
+     */
+    public CharSequence getApplicationLabel(ApplicationInfo info) {
+        return mPm.getApplicationLabel(info);
+    }
+
+    /**
+     * Calls {@code PackageManager.queryBroadcastReceivers}
+     */
+    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+        return mPm.queryBroadcastReceivers(intent, flags);
+    }
 }
+
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java
new file mode 100644
index 0000000..3dabe99
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/StorageStatsSourceTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.settingslib.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.usage.StorageStats;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StorageStatsSourceTest {
+    @Test
+    public void AppStorageStatsImpl_totalCorrectly() {
+        StorageStats storageStats = new StorageStats();
+        storageStats.cacheBytes = 1;
+        storageStats.codeBytes = 10;
+        storageStats.dataBytes = 100;
+        StorageStatsSource.AppStorageStatsImpl stats = new StorageStatsSource.AppStorageStatsImpl(
+                storageStats);
+
+        // Note that this does not double add the cache (111).
+        assertThat(stats.getTotalBytes()).isEqualTo(110);
+    }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
index f25bb28..4a1d392 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java
@@ -54,7 +54,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
@@ -147,10 +146,7 @@
     private CountDownLatch mAccessPointsChangedLatch;
     private CountDownLatch mRequestScoresLatch;
     private Handler mScannerHandler;
-    private HandlerThread mMainThread;
     private HandlerThread mWorkerThread;
-    private Looper mWorkerLooper;
-    private Looper mMainLooper;
 
     private int mOriginalScoringUiSettingValue;
 
@@ -162,10 +158,6 @@
 
         mWorkerThread = new HandlerThread("TestHandlerWorkerThread");
         mWorkerThread.start();
-        mWorkerLooper = mWorkerThread.getLooper();
-        mMainThread = new HandlerThread("TestHandlerThread");
-        mMainThread.start();
-        mMainLooper = mMainThread.getLooper();
 
         // Make sure the scanner doesn't try to run on the testing thread.
         HandlerThread scannerThread = new HandlerThread("ScannerWorkerThread");
@@ -283,18 +275,17 @@
     }
 
     private WifiTracker createMockedWifiTracker() {
-        return new WifiTracker(
+        final WifiTracker wifiTracker = new WifiTracker(
                 mContext,
                 mockWifiListener,
-                mWorkerLooper,
-                true,
                 true,
                 true,
                 mockWifiManager,
                 mockConnectivityManager,
                 mockNetworkScoreManager,
-                mMainLooper,
                 new IntentFilter()); // empty filter to ignore system broadcasts
+        wifiTracker.setWorkThread(mWorkerThread);
+        return wifiTracker;
     }
 
     private void startTracking(WifiTracker tracker)  throws InterruptedException {
@@ -302,7 +293,7 @@
         mScannerHandler.post(new Runnable() {
             @Override
             public void run() {
-                tracker.startTracking();
+                tracker.onStart();
                 latch.countDown();
             }
         });
@@ -406,7 +397,7 @@
         scanResult.capabilities = "";
 
         WifiTracker tracker = new WifiTracker(
-                InstrumentationRegistry.getTargetContext(), null, mWorkerLooper, true, true);
+                InstrumentationRegistry.getTargetContext(), null, true, true);
 
         AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>());
         assertTrue(result.mAccessPointListener != null);
@@ -422,7 +413,7 @@
         configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
 
         WifiTracker tracker = new WifiTracker(
-                InstrumentationRegistry.getTargetContext(), null, mWorkerLooper, true, true);
+                InstrumentationRegistry.getTargetContext(), null, true, true);
 
         AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>());
         assertTrue(result.mAccessPointListener != null);
@@ -452,7 +443,7 @@
                         .unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, scoreCache);
 
         // Test unregister
-        tracker.stopTracking();
+        tracker.onStop();
 
         assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
         verify(mockNetworkScoreManager)
@@ -496,7 +487,7 @@
         // Start the tracker and inject the initial scan results and then stop tracking
         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
 
-        tracker.stopTracking();
+        tracker.onStop();
         mRequestedKeys.clear();
 
         mRequestScoresLatch = new CountDownLatch(1);
@@ -515,7 +506,7 @@
         // Start the tracker and inject the initial scan results and then stop tracking
         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
         updateScoresAndWaitForAccessPointsChangedCallback(tracker);
-        tracker.stopTracking();
+        tracker.onStop();
 
         assertThat(mScoreCacheCaptor.getValue().getScoredNetwork(NETWORK_KEY_1)).isNotNull();
     }
@@ -675,7 +666,7 @@
         WifiTracker tracker =  createTrackerWithImmediateBroadcastsAndInjectInitialScanResults();
         WifiNetworkScoreCache cache = mScoreCacheCaptor.getValue();
 
-        tracker.stopTracking();
+        tracker.onStop();
         verify(mockNetworkScoreManager).unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, cache);
 
         // Verify listener is unregistered so updating a score does not throw an error by posting
@@ -795,7 +786,7 @@
         tracker.mMainHandler.sendEmptyMessage(
                 WifiTracker.MainHandler.MSG_WIFI_STATE_CHANGED);
 
-        tracker.stopTracking();
+        tracker.onStop();
 
         verify(mockWifiListener, atMost(1)).onAccessPointsChanged();
         verify(mockWifiListener, atMost(1)).onConnectedChanged();
@@ -821,7 +812,7 @@
         startTracking(tracker);
         waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
-        tracker.stopTracking();
+        tracker.onStop();
         waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker);
 
         startTracking(tracker);
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index 55b635e..bc1a834 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -42,11 +42,12 @@
 # Include the testing libraries (JUnit4 + Robolectric libs).
 LOCAL_STATIC_JAVA_LIBRARIES := \
     mockito-robolectric-prebuilt \
+    platform-robolectric-android-all-stubs \
     truth-prebuilt
 
 LOCAL_JAVA_LIBRARIES := \
     junit \
-    platform-robolectric-prebuilt
+    platform-robolectric-3.4.2-prebuilt
 
 LOCAL_INSTRUMENTATION_FOR := SettingsLibShell
 LOCAL_MODULE := SettingsLibRoboTests
@@ -69,4 +70,6 @@
 
 LOCAL_TEST_PACKAGE := SettingsLibShell
 
-include prebuilts/misc/common/robolectric/run_robotests.mk
+LOCAL_ROBOTEST_TIMEOUT := 36000
+
+include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk
diff --git a/packages/SettingsLib/tests/robotests/src/android/net/wifi/WifiNetworkScoreCache.java b/packages/SettingsLib/tests/robotests/src/android/net/wifi/WifiNetworkScoreCache.java
deleted file mode 100644
index abccd8d..0000000
--- a/packages/SettingsLib/tests/robotests/src/android/net/wifi/WifiNetworkScoreCache.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * 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.net.wifi;
-
-import android.content.Context;
-import android.os.Handler;
-
-import java.util.List;
-
-/**
- * Will be removed once robolectric is updated to O
- */
-public class WifiNetworkScoreCache {
-
-    public WifiNetworkScoreCache(Context context, WifiNetworkScoreCache.CacheListener listener) {
-    }
-
-    public abstract static class CacheListener {
-        public CacheListener(Handler handler) {
-        }
-
-        void post(List updatedNetworks) {
-        }
-
-        public abstract void networkCacheUpdated(List updatedNetworks);
-    }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java
new file mode 100644
index 0000000..17c7d13
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.settingslib;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.EditText;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CustomEditTextPreferenceTest {
+
+    @Mock
+    private View mView;
+
+    private TestPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPreference = new TestPreference(RuntimeEnvironment.application);
+    }
+
+    @Test
+    public void bindDialogView_shouldRequestFocus() {
+        final String testText = "";
+        final EditText editText = spy(new EditText(RuntimeEnvironment.application));
+        editText.setText(testText);
+        when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
+
+        mPreference.onBindDialogView(mView);
+
+        verify(editText).requestFocus();
+    }
+
+    private static class TestPreference extends CustomEditTextPreference {
+        public TestPreference(Context context) {
+            super(context);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index c3a505a..ca366ea 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -20,8 +20,11 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -56,10 +59,10 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private RestrictedLockUtils.Proxy mProxy;
 
-    private static final int mUserId = 194;
-    private static final int mProfileId = 160;
-    private static final ComponentName mAdmin1 = new ComponentName("admin1", "admin1class");
-    private static final ComponentName mAdmin2 = new ComponentName("admin2", "admin2class");
+    private final int mUserId = 194;
+    private final int mProfileId = 160;
+    private final ComponentName mAdmin1 = new ComponentName("admin1", "admin1class");
+    private final ComponentName mAdmin2 = new ComponentName("admin2", "admin2class");
 
     @Before
     public void setUp() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
index 8099eb1..698e442 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java
@@ -53,15 +53,15 @@
 
     static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) {
         paths.add(new ResourcePath(
-                packageName,
+                null,
                 Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
                 null));
         paths.add(new ResourcePath(
-                packageName,
+                null,
                 Fs.fileFromPath("./frameworks/base/core/res/res"),
                 null));
         paths.add(new ResourcePath(
-                packageName,
+                null,
                 Fs.fileFromPath("./frameworks/support/v7/appcompat/res"),
                 null));
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
index 31abecd..3af9768 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TestConfig.java
@@ -17,7 +17,7 @@
 package com.android.settingslib;
 
 public class TestConfig {
-    public static final int SDK_VERSION = 23;
+    public static final int SDK_VERSION = 25;
     public static final String MANIFEST_PATH =
             "frameworks/base/packages/SettingsLib/tests/robotests/AndroidManifest.xml";
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
index fa654cb..b5ee5ad 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/TwoTargetPreferenceTest.java
@@ -16,34 +16,31 @@
 
 package com.android.settingslib;
 
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
 import android.content.Context;
 import android.support.v7.preference.PreferenceViewHolder;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(SettingsLibRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class TwoTargetPreferenceTest {
 
     private PreferenceViewHolder mViewHolder;
-    @Mock
     private View mDivider;
-    @Mock
     private View mWidgetFrame;
+    private View mRootView;
     private TwoTargetPreference mPreference;
     private Context mContext;
 
@@ -52,11 +49,11 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
         mPreference = spy(new TwoTargetPreference(mContext));
-        mViewHolder = PreferenceViewHolder.createInstanceForTests(mock(View.class));
-        when(mViewHolder.findViewById(R.id.two_target_divider))
-                .thenReturn(mDivider);
-        when(mViewHolder.findViewById(android.R.id.widget_frame))
-                .thenReturn(mWidgetFrame);
+        mRootView = View.inflate(mContext, R.layout.preference_two_target, null /* parent */);
+        mViewHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
+
+        mDivider = mViewHolder.findViewById(R.id.two_target_divider);
+        mWidgetFrame = mViewHolder.findViewById(android.R.id.widget_frame);
     }
 
     @Test
@@ -65,8 +62,8 @@
 
         mPreference.onBindViewHolder(mViewHolder);
 
-        verify(mDivider).setVisibility(View.GONE);
-        verify(mWidgetFrame).setVisibility(View.GONE);
+        assertThat(mDivider.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -75,7 +72,37 @@
 
         mPreference.onBindViewHolder(mViewHolder);
 
-        verify(mDivider).setVisibility(View.VISIBLE);
-        verify(mWidgetFrame).setVisibility(View.VISIBLE);
+        assertThat(mDivider.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mWidgetFrame.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void bind_smallIcon_shouldUseSmallIcon() {
+        mPreference.setUseSmallIcon(true);
+
+        mPreference.onBindViewHolder(mViewHolder);
+
+        final int smallIconSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.two_target_pref_small_icon_size);
+        final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mViewHolder
+                .findViewById(android.R.id.icon)
+                .getLayoutParams();
+
+        assertThat(layoutParams.width).isEqualTo(smallIconSize);
+        assertThat(layoutParams.height).isEqualTo(smallIconSize);
+    }
+
+    @Test
+    public void bind_normalIcon_shouldUseNormalIcon() {
+        mPreference.setUseSmallIcon(false);
+
+        mPreference.onBindViewHolder(mViewHolder);
+
+        final LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mViewHolder
+                .findViewById(android.R.id.icon)
+                .getLayoutParams();
+
+        assertThat(layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT);
+        assertThat(layoutParams.height).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index 4a73c1b..ece0d51 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -25,6 +25,7 @@
 
 import com.android.settingslib.R;
 import com.android.settingslib.TestConfig;
+import com.android.settingslib.wrapper.BluetoothA2dpWrapper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,8 +74,8 @@
         }).when(mAdapter).getProfileProxy(any(Context.class), any(), eq(BluetoothProfile.A2DP));
 
         mProfile = new A2dpProfile(mContext, mAdapter, mDeviceManager, mProfileManager);
-        mProfile.setWrapperFactory((service) -> { return mBluetoothA2dpWrapper; });
         mServiceListener.onServiceConnected(BluetoothProfile.A2DP, mBluetoothA2dp);
+        mProfile.setBluetoothA2dpWrapper(mBluetoothA2dpWrapper);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index b1dbb0a..4091ce1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -59,7 +59,7 @@
     @Mock
     private A2dpProfile mA2dpProfile;
     @Mock
-    private HidProfile mHidProfile;
+    private PanProfile mPanProfile;
     @Mock
     private BluetoothDevice mDevice;
     private CachedBluetoothDevice mCachedDevice;
@@ -74,7 +74,7 @@
         when(mAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON);
         when(mHfpProfile.isProfileReady()).thenReturn(true);
         when(mA2dpProfile.isProfileReady()).thenReturn(true);
-        when(mHidProfile.isProfileReady()).thenReturn(true);
+        when(mPanProfile.isProfileReady()).thenReturn(true);
         mCachedDevice = spy(
                 new CachedBluetoothDevice(mContext, mAdapter, mProfileManager, mDevice));
         doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
@@ -92,37 +92,37 @@
     @Test
     public void testGetConnectionSummary_testSingleProfileConnectDisconnect() {
         // Test without battery level
-        // Set HID profile to be connected and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        // Set PAN profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
                 R.string.bluetooth_connected));
 
-        // Set HID profile to be disconnected and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        // Set PAN profile to be disconnected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isNull();
 
         // Test with battery level
         mBatteryLevel = 10;
-        // Set HID profile to be connected and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        // Set PAN profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
                 R.string.bluetooth_connected_battery_level,
                 com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
 
-        // Set HID profile to be disconnected and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        // Set PAN profile to be disconnected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isNull();
 
         // Test with BluetoothDevice.BATTERY_LEVEL_UNKNOWN battery level
         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
 
-        // Set HID profile to be connected and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        // Set PAN profile to be connected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
                 R.string.bluetooth_connected));
 
-        // Set HID profile to be disconnected and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        // Set PAN profile to be disconnected and test connection state summary
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isNull();
     }
 
@@ -130,10 +130,10 @@
     public void testGetConnectionSummary_testMultipleProfileConnectDisconnect() {
         mBatteryLevel = 10;
 
-        // Set HFP, A2DP and HID profile to be connected and test connection state summary
+        // Set HFP, A2DP and PAN profile to be connected and test connection state summary
         mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
         mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_CONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(mContext.getString(
                 R.string.bluetooth_connected_battery_level,
                 com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
@@ -158,7 +158,7 @@
                 com.android.settingslib.Utils.formatPercentage(mBatteryLevel)));
 
         // Disconnect all profiles and test connection state summary
-        mCachedDevice.onProfileStateChanged(mHidProfile, BluetoothProfile.STATE_DISCONNECTED);
+        mCachedDevice.onProfileStateChanged(mPanProfile, BluetoothProfile.STATE_DISCONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isNull();
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/UtilsTest.java
new file mode 100644
index 0000000..5eb543b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/UtilsTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothDevice;
+import android.graphics.drawable.Drawable;
+
+import com.android.settingslib.R;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
+import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = SettingsLibShadowResources.class)
+public class UtilsTest {
+
+    @Test
+    public void testGetBluetoothDrawable_noBatteryLevel_returnSimpleDrawable() {
+        final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
+                R.drawable.ic_bt_laptop, BluetoothDevice.BATTERY_LEVEL_UNKNOWN, 1 /* iconScale */);
+
+        assertThat(drawable).isNotInstanceOf(BluetoothDeviceLayerDrawable.class);
+    }
+
+    @Test
+    public void testGetBluetoothDrawable_hasBatteryLevel_returnLayerDrawable() {
+        final Drawable drawable = Utils.getBluetoothDrawable(RuntimeEnvironment.application,
+                R.drawable.ic_bt_laptop, 10 /* batteryLevel */, 1 /* iconScale */);
+
+        assertThat(drawable).isInstanceOf(BluetoothDeviceLayerDrawable.class);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index d6bca0e..1290391 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -35,9 +35,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.android.controller.FragmentController;
 import org.robolectric.annotation.Config;
-import org.robolectric.util.ActivityController;
-import org.robolectric.util.FragmentController;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -168,7 +168,7 @@
                 Robolectric.buildFragment(TestDialogFragment.class);
         TestDialogFragment fragment = fragmentController.get();
 
-        fragmentController.attach().create().start().resume();
+        fragmentController.create().start().resume();
         fragment.onCreateOptionsMenu(null, null);
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
@@ -192,7 +192,7 @@
                 Robolectric.buildFragment(TestFragment.class);
         TestFragment fragment = fragmentController.get();
 
-        fragmentController.attach().create().start().resume();
+        fragmentController.create().start().resume();
         fragment.onCreateOptionsMenu(null, null);
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
@@ -235,7 +235,7 @@
         OptionItemAccepter accepter = new OptionItemAccepter();
         fragment.getLifecycle().addObserver(accepter);
 
-        fragmentController.attach().create().start().resume();
+        fragmentController.create().start().resume();
         fragment.onCreateOptionsMenu(null, null);
         fragment.onPrepareOptionsMenu(null);
         fragment.onOptionsItemSelected(null);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java
index 94bfd8f..26d3570 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/LogdSizePreferenceControllerTest.java
@@ -16,9 +16,29 @@
 
 package com.android.settingslib.development;
 
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .DEFAULT_SNET_TAG;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .LOW_RAM_CONFIG_PROPERTY_KEY;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .SELECT_LOGD_MINIMUM_SIZE_VALUE;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .SELECT_LOGD_OFF_SIZE_MARKER_VALUE;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .SELECT_LOGD_SIZE_PROPERTY;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .SELECT_LOGD_SNET_TAG_PROPERTY;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .SELECT_LOGD_TAG_PROPERTY;
+import static com.android.settingslib.development.AbstractLogdSizePreferenceController
+        .SELECT_LOGD_TAG_SILENCE;
+
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.os.SystemProperties;
 import android.support.v7.preference.ListPreference;
 import android.support.v7.preference.PreferenceScreen;
@@ -27,6 +47,7 @@
 import com.android.settingslib.SettingsLibRobolectricTestRunner;
 import com.android.settingslib.TestConfig;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,22 +66,43 @@
     @Mock
     private PreferenceScreen mPreferenceScreen;
 
+    /**
+     * List Values
+     *
+     * 0: off
+     * 1: 64k
+     * 2: 256k
+     * 3: 1M
+     * 4: 4M
+     * 5: 16M
+     */
+    private String[] mListValues;
+    private String[] mListSummaries;
+    private Context mContext;
     private AbstractLogdSizePreferenceController mController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new AbstractLogdSizePreferenceController(RuntimeEnvironment.application) {};
-
+        mContext = RuntimeEnvironment.application;
+        mController = new AbstractLogdSizePreferenceController(RuntimeEnvironment.application) {
+        };
+        mListValues = mContext.getResources().getStringArray(R.array.select_logd_size_values);
+        mListSummaries = mContext.getResources().getStringArray(R.array.select_logd_size_summaries);
         doReturn(mListPreference).when(mPreferenceScreen)
                 .findPreference(mController.getPreferenceKey());
 
         mController.displayPreference(mPreferenceScreen);
     }
 
+    @After
+    public void tearDown() {
+        SystemPropertiesTestImpl.clear();
+    }
+
     @Test
-    public void testUpateLogdSizeValues_lowRamEntries() {
-        SystemProperties.set("ro.config.low_ram", "true");
+    public void testUpdateLogdSizeValues_lowRamEntries() {
+        SystemProperties.set(LOW_RAM_CONFIG_PROPERTY_KEY, "true");
         mController.updateLogdSizeValues();
         verify(mListPreference).setEntries(R.array.select_logd_size_lowram_titles);
     }
@@ -77,4 +119,79 @@
         verify(mListPreference).setValue(
                 AbstractLogdSizePreferenceController.SELECT_LOGD_OFF_SIZE_MARKER_VALUE);
     }
+
+    @Test
+    public void onPreferenceChange_noTagsSizeValueOff_shouldSetTagAndSnetTagAndSet64KSize() {
+        mController.onPreferenceChange(mListPreference, SELECT_LOGD_OFF_SIZE_MARKER_VALUE);
+
+        final String tag = SystemProperties.get(SELECT_LOGD_TAG_PROPERTY);
+        final String logSize = SystemProperties.get(SELECT_LOGD_SIZE_PROPERTY);
+        final String snetTag = SystemProperties.get(SELECT_LOGD_SNET_TAG_PROPERTY);
+
+        assertThat(tag).isEqualTo(SELECT_LOGD_TAG_SILENCE);
+        assertThat(logSize).isEqualTo(SELECT_LOGD_MINIMUM_SIZE_VALUE);
+        assertThat(snetTag).isEqualTo(DEFAULT_SNET_TAG);
+    }
+
+    @Test
+    public void onPreferenceChange_noTagsSizeValue64K_shouldNotSetTagAndSet64KSize() {
+        mController.onPreferenceChange(mListPreference, SELECT_LOGD_MINIMUM_SIZE_VALUE);
+
+        final String tag = SystemProperties.get(SELECT_LOGD_TAG_PROPERTY);
+        final String logSize = SystemProperties.get(SELECT_LOGD_SIZE_PROPERTY);
+        final String snetTag = SystemProperties.get(SELECT_LOGD_SNET_TAG_PROPERTY);
+
+        assertThat(tag).isEmpty();
+        assertThat(logSize).isEqualTo(SELECT_LOGD_MINIMUM_SIZE_VALUE);
+        assertThat(snetTag).isEmpty();
+    }
+
+    @Test
+    public void onPreferenceChange_set1M_shouldUpdateSettingLogSizeTo1M() {
+        mController.onPreferenceChange(mListPreference, mListValues[3]);
+
+        final String logSize = SystemProperties.get(SELECT_LOGD_SIZE_PROPERTY);
+
+        assertThat(logSize).isEqualTo(mListValues[3]);
+    }
+
+    @Test
+    public void onPreferenceChange_noValue_shouldUpdateSettingToEmpty() {
+        mController.onPreferenceChange(mListPreference, "" /* new value */);
+
+        final String logSize = SystemProperties.get(SELECT_LOGD_SIZE_PROPERTY);
+
+        assertThat(logSize).isEmpty();
+    }
+
+    @Test
+    public void updateLogdSizeValues_noValueSet_shouldSetDefaultTo64K() {
+        SystemProperties.set(SELECT_LOGD_SIZE_PROPERTY, "" /* new value */);
+
+        mController.updateLogdSizeValues();
+
+        verify(mListPreference).setValue(mListValues[2]);
+        verify(mListPreference).setSummary(mListSummaries[2]);
+    }
+
+    @Test
+    public void updateLogdSizeValues_noValueSetLowRamSet_shouldSetDefaultTo64K() {
+        SystemProperties.set(LOW_RAM_CONFIG_PROPERTY_KEY, Boolean.toString(true));
+        SystemProperties.set(SELECT_LOGD_SIZE_PROPERTY, "" /* new value */);
+
+        mController.updateLogdSizeValues();
+
+        verify(mListPreference).setValue(mListValues[1]);
+        verify(mListPreference).setSummary(mListSummaries[1]);
+    }
+
+    @Test
+    public void updateLogdSizeValues_64KSet_shouldSet64K() {
+        SystemProperties.set(SELECT_LOGD_SIZE_PROPERTY, mListValues[1]);
+
+        mController.updateLogdSizeValues();
+
+        verify(mListPreference).setValue(mListValues[1]);
+        verify(mListPreference).setSummary(mListSummaries[1]);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropertiesTestImpl.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropertiesTestImpl.java
index 2f89d86..6977e09 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropertiesTestImpl.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/SystemPropertiesTestImpl.java
@@ -54,4 +54,8 @@
     public static void set(String key, String val) {
         sProperties.put(key, val);
     }
+
+    public static synchronized void clear() {
+        sProperties.clear();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..1de7a7a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/BluetoothAddressPreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BluetoothAddressPreferenceControllerTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mPreference).when(mScreen)
+                .findPreference(AbstractBluetoothAddressPreferenceController.KEY_BT_ADDRESS);
+    }
+
+    @Implements(BluetoothAdapter.class)
+    public static class ShadowEmptyBluetoothAdapter {
+        @Implementation
+        public static BluetoothAdapter getDefaultAdapter() {
+            return null;
+        }
+    }
+
+    @Test
+    @Config(shadows = ShadowEmptyBluetoothAdapter.class)
+    public void testNoBluetooth() {
+        final AbstractBluetoothAddressPreferenceController bluetoothAddressPreferenceController =
+                new ConcreteBluetoothAddressPreferenceController(mContext, mLifecycle);
+
+        assertWithMessage("Should not show pref if no bluetooth")
+                .that(bluetoothAddressPreferenceController.isAvailable())
+                .isFalse();
+    }
+
+    @Test
+    public void testHasBluetooth() {
+        final AbstractBluetoothAddressPreferenceController bluetoothAddressPreferenceController =
+                new ConcreteBluetoothAddressPreferenceController(mContext, mLifecycle);
+
+        assertWithMessage("Should show pref if bluetooth is present")
+                .that(bluetoothAddressPreferenceController.isAvailable())
+                .isTrue();
+    }
+
+    @Test
+    public void testHasBluetoothStateChangedFilter() {
+        final AbstractBluetoothAddressPreferenceController bluetoothAddressPreferenceController =
+                new ConcreteBluetoothAddressPreferenceController(mContext, mLifecycle);
+
+        assertWithMessage("Filter should have BluetoothAdapter.ACTION_STATE_CHANGED")
+                .that(bluetoothAddressPreferenceController.getConnectivityIntents())
+                .asList().contains(BluetoothAdapter.ACTION_STATE_CHANGED);
+    }
+
+    private static class ConcreteBluetoothAddressPreferenceController
+            extends AbstractBluetoothAddressPreferenceController {
+
+        public ConcreteBluetoothAddressPreferenceController(Context context,
+                Lifecycle lifecycle) {
+            super(context, lifecycle);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
new file mode 100644
index 0000000..362dbd9
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ConnectivityPreferenceControllerTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.Handler;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ConnectivityPreferenceControllerTest {
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private Lifecycle mLifecycle;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testBroadcastReceiver() {
+        final AbstractConnectivityPreferenceController preferenceController =
+                spy(new ConcreteConnectivityPreferenceController(mContext, mLifecycle));
+
+        final ArgumentCaptor<BroadcastReceiver> receiverArgumentCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        final ArgumentCaptor<IntentFilter> filterArgumentCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+
+        doReturn(new String[] {"Filter1", "Filter2"})
+                .when(preferenceController).getConnectivityIntents();
+
+        preferenceController.onStart();
+
+        verify(mContext, times(1))
+                .registerReceiver(receiverArgumentCaptor.capture(),
+                        filterArgumentCaptor.capture(),
+                        anyString(), nullable(Handler.class));
+
+        final BroadcastReceiver receiver = receiverArgumentCaptor.getValue();
+        final IntentFilter filter = filterArgumentCaptor.getValue();
+
+        assertWithMessage("intent filter should match 'Filter1'")
+                .that(filter.matchAction("Filter1"))
+                .isTrue();
+        assertWithMessage("intent filter should match 'Filter2'")
+                .that(filter.matchAction("Filter2"))
+                .isTrue();
+
+        preferenceController.onStop();
+
+        verify(mContext, times(1)).unregisterReceiver(receiver);
+    }
+
+    private static class ConcreteConnectivityPreferenceController
+            extends AbstractConnectivityPreferenceController {
+
+
+        public ConcreteConnectivityPreferenceController(Context context,
+                Lifecycle lifecycle) {
+            super(context, lifecycle);
+        }
+
+        @Override
+        public boolean isAvailable() {
+            return false;
+        }
+
+        @Override
+        public String getPreferenceKey() {
+            return null;
+        }
+
+        @Override
+        protected String[] getConnectivityIntents() {
+            return new String[0];
+        }
+
+        @Override
+        protected void updateConnectivity() {
+
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java
new file mode 100644
index 0000000..112ee64
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/ImsStatusPreferenceControllerTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class ImsStatusPreferenceControllerTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mPreference).when(mScreen)
+                .findPreference(AbstractImsStatusPreferenceController.KEY_IMS_REGISTRATION_STATE);
+    }
+
+    @Test
+    @Config(shadows = ShadowSubscriptionManager.class)
+    public void testIsAvailable() {
+        CarrierConfigManager carrierConfigManager = mock(CarrierConfigManager.class);
+        doReturn(carrierConfigManager).when(mContext).getSystemService(CarrierConfigManager.class);
+
+        PersistableBundle config = new PersistableBundle(1);
+        config.putBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, true);
+        doReturn(config).when(carrierConfigManager).getConfigForSubId(anyInt());
+
+        final AbstractImsStatusPreferenceController imsStatusPreferenceController =
+                new ConcreteImsStatusPreferenceController(mContext, mLifecycle);
+
+        assertWithMessage("Should be available when IMS registration is true").that(
+                imsStatusPreferenceController.isAvailable()).isTrue();
+
+        config.putBoolean(CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
+
+        assertWithMessage("Should not be available when IMS registration is false")
+                .that(imsStatusPreferenceController.isAvailable()).isFalse();
+
+        doReturn(null).when(carrierConfigManager).getConfigForSubId(anyInt());
+
+        assertWithMessage("Should not be available when IMS registration is false")
+                .that(imsStatusPreferenceController.isAvailable()).isFalse();
+
+        doReturn(null).when(mContext).getSystemService(CarrierConfigManager.class);
+
+        assertWithMessage("Should not be available when CarrierConfigManager service is null")
+                .that(imsStatusPreferenceController.isAvailable()).isFalse();
+    }
+
+    @Implements(SubscriptionManager.class)
+    public static class ShadowSubscriptionManager {
+        @Implementation
+        public static int getDefaultDataSubscriptionId() {
+            return 1234;
+        }
+    }
+
+    private static class ConcreteImsStatusPreferenceController
+            extends AbstractImsStatusPreferenceController {
+
+        public ConcreteImsStatusPreferenceController(Context context,
+                Lifecycle lifecycle) {
+            super(context, lifecycle);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..d0ecae3
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/IpAddressPreferenceControllerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class IpAddressPreferenceControllerTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mPreference).when(mScreen)
+                .findPreference(AbstractIpAddressPreferenceController.KEY_IP_ADDRESS);
+    }
+
+    @Test
+    public void testHasIntentFilters() {
+        final AbstractIpAddressPreferenceController ipAddressPreferenceController =
+                new ConcreteIpAddressPreferenceController(mContext, mLifecycle);
+        final List<String> expectedIntents = Arrays.asList(
+                ConnectivityManager.CONNECTIVITY_ACTION,
+                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+                WifiManager.NETWORK_STATE_CHANGED_ACTION);
+
+
+        assertWithMessage("Intent filter should contain expected intents")
+                .that(ipAddressPreferenceController.getConnectivityIntents())
+                .asList().containsAllIn(expectedIntents);
+    }
+
+    private static class ConcreteIpAddressPreferenceController extends
+            AbstractIpAddressPreferenceController {
+
+        public ConcreteIpAddressPreferenceController(Context context,
+                Lifecycle lifecycle) {
+            super(context, lifecycle);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
new file mode 100644
index 0000000..4dbb957
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SerialNumberPreferenceControllerTest {
+
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock(answer = RETURNS_DEEP_STUBS)
+    private PreferenceScreen mScreen;
+
+    private AbstractSerialNumberPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testIsAvaiable_noSerial_shouldReturnFalse() {
+        mController = new ConcreteSerialNumberPreferenceController(mContext, null);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvaiable_hasSerial_shouldReturnTrue() {
+        mController = new ConcreteSerialNumberPreferenceController(mContext, "123");
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testDisplay_noSerial_shouldHidePreference() {
+        final Preference preference = mock(Preference.class);
+        when(mScreen.getPreferenceCount()).thenReturn(1);
+        when(mScreen.getPreference(0)).thenReturn(preference);
+        mController = new ConcreteSerialNumberPreferenceController(mContext, null);
+        when(preference.getKey()).thenReturn(mController.getPreferenceKey());
+
+        mController.displayPreference(mScreen);
+
+        verify(mScreen).removePreference(any(Preference.class));
+    }
+
+    @Test
+    public void testDisplay_hasSerial_shouldSetSummary() {
+        final String serial = "123";
+        final Preference preference = mock(Preference.class);
+        when(mScreen.findPreference(anyString())).thenReturn(preference);
+
+        mController = new ConcreteSerialNumberPreferenceController(mContext, serial);
+        mController.displayPreference(mScreen);
+
+        verify(mScreen, never()).removePreference(any(Preference.class));
+        verify(preference).setSummary(serial);
+    }
+
+    private static class ConcreteSerialNumberPreferenceController
+            extends AbstractSerialNumberPreferenceController {
+
+        ConcreteSerialNumberPreferenceController(Context context, String serialNumber) {
+            super(context, serialNumber);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
new file mode 100644
index 0000000..28409fa
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.net.ConnectivityManager;
+import android.os.UserManager;
+import android.util.SparseBooleanArray;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class,
+                SimStatusImeiInfoPreferenceControllerTest.ShadowConnectivityManager.class})
+public class SimStatusImeiInfoPreferenceControllerTest {
+
+    private AbstractSimStatusImeiInfoPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mController = new AbstractSimStatusImeiInfoPreferenceController(
+                RuntimeEnvironment.application) {
+            @Override
+            public String getPreferenceKey() {
+                return null;
+            }
+        };
+    }
+
+    @Test
+    public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() {
+        ShadowUserManager userManager =
+                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+        userManager.setIsAdminUser(true);
+        ShadowConnectivityManager connectivityManager =
+                extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class));
+        connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, true);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() {
+        ShadowUserManager userManager =
+                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+        userManager.setIsAdminUser(true);
+        ShadowConnectivityManager connectivityManager =
+                extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class));
+        connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_isNotAdmin_shouldReturnFalse() {
+        ShadowUserManager userManager =
+                extract(RuntimeEnvironment.application.getSystemService(UserManager.class));
+        userManager.setIsAdminUser(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Implements(UserManager.class)
+    public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager {
+
+        private boolean mAdminUser;
+
+        public void setIsAdminUser(boolean isAdminUser) {
+            mAdminUser = isAdminUser;
+        }
+
+        @Implementation
+        public boolean isAdminUser() {
+            return mAdminUser;
+        }
+    }
+
+    @Implements(ConnectivityManager.class)
+    public static class ShadowConnectivityManager
+            extends org.robolectric.shadows.ShadowConnectivityManager {
+
+        private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray();
+
+        public void setNetworkSupported(int networkType, boolean supported) {
+            mSupportedNetworkTypes.put(networkType, supported);
+        }
+
+        @Implementation
+        public boolean isNetworkSupported(int networkType) {
+            return mSupportedNetworkTypes.get(networkType);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java
new file mode 100644
index 0000000..f68533b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/UptimePreferenceControllerTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.format.DateUtils;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class UptimePreferenceControllerTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mPreference).when(mScreen)
+                .findPreference(AbstractUptimePreferenceController.KEY_UPTIME);
+    }
+
+    @Test
+    public void testDisplayPreference() {
+        final AbstractUptimePreferenceController uptimePreferenceController =
+                new ConcreteUptimePreferenceController(mContext, mLifecycle);
+
+        uptimePreferenceController.displayPreference(mScreen);
+
+        // SystemClock is shadowed so it shouldn't advance unexpectedly while the test is running
+        verify(mPreference).setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime()));
+    }
+
+    @Test
+    public void testUptimeTick() {
+        final AbstractUptimePreferenceController uptimePreferenceController =
+                new ConcreteUptimePreferenceController(mContext, null /* lifecycle */);
+
+        uptimePreferenceController.displayPreference(mScreen);
+
+        verify(mPreference, times(1)).setSummary(any(CharSequence.class));
+
+        uptimePreferenceController.onStart();
+
+        verify(mPreference, times(2)).setSummary(any(CharSequence.class));
+
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+        verify(mPreference, times(3)).setSummary(any(CharSequence.class));
+
+        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+
+        verify(mPreference, times(4)).setSummary(any(CharSequence.class));
+    }
+
+    private static class ConcreteUptimePreferenceController
+            extends AbstractUptimePreferenceController {
+        public ConcreteUptimePreferenceController(Context context,
+                Lifecycle lifecycle) {
+            super(context, lifecycle);
+        }
+    }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
new file mode 100644
index 0000000..265a60b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.settingslib.deviceinfo;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settingslib.R;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SuppressLint("HardwareIds")
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WifiMacAddressPreferenceControllerTest {
+    @Mock
+    private Context mContext;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private PreferenceScreen mScreen;
+    @Mock
+    private Preference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mPreference).when(mScreen)
+                .findPreference(AbstractWifiMacAddressPreferenceController.KEY_WIFI_MAC_ADDRESS);
+    }
+
+    @Test
+    public void testHasIntentFilters() {
+        final AbstractWifiMacAddressPreferenceController wifiMacAddressPreferenceController =
+                new ConcreteWifiMacAddressPreferenceController(mContext, mLifecycle);
+        final List<String> expectedIntents = Arrays.asList(
+                ConnectivityManager.CONNECTIVITY_ACTION,
+                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
+                WifiManager.NETWORK_STATE_CHANGED_ACTION);
+
+
+        assertWithMessage("Intent filter should contain expected intents")
+                .that(wifiMacAddressPreferenceController.getConnectivityIntents())
+                .asList().containsAllIn(expectedIntents);
+    }
+
+    @Test
+    public void testWifiMacAddress() {
+        final WifiManager wifiManager = mock(WifiManager.class);
+        final WifiInfo wifiInfo = mock(WifiInfo.class);
+        doReturn("00:11:22:33:44:55").when(wifiInfo).getMacAddress();
+
+        doReturn(null).when(wifiManager).getConnectionInfo();
+        doReturn(wifiManager).when(mContext).getSystemService(WifiManager.class);
+
+        final AbstractWifiMacAddressPreferenceController wifiMacAddressPreferenceController =
+                new ConcreteWifiMacAddressPreferenceController(mContext, mLifecycle);
+
+        wifiMacAddressPreferenceController.displayPreference(mScreen);
+
+        verify(mPreference).setSummary(R.string.status_unavailable);
+
+        doReturn(wifiInfo).when(wifiManager).getConnectionInfo();
+
+        wifiMacAddressPreferenceController.displayPreference(mScreen);
+
+        verify(mPreference).setSummary("00:11:22:33:44:55");
+    }
+
+    private static class ConcreteWifiMacAddressPreferenceController
+            extends AbstractWifiMacAddressPreferenceController {
+
+        public ConcreteWifiMacAddressPreferenceController(Context context,
+                Lifecycle lifecycle) {
+            super(context, lifecycle);
+        }
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 3e90435..dad3a28 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.RuntimeEnvironment.application;
+import static org.robolectric.shadow.api.Shadow.extract;
 
 import android.app.ActivityManager;
 import android.content.ContentResolver;
@@ -66,7 +67,6 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
-import org.robolectric.internal.ShadowExtractor;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -79,7 +79,6 @@
         shadows = {TileUtilsTest.TileUtilsShadowRemoteViews.class})
 public class TileUtilsTest {
 
-    @Mock
     private Context mContext;
     @Mock
     private PackageManager mPackageManager;
@@ -97,6 +96,7 @@
 
     @Before
     public void setUp() throws NameNotFoundException {
+        mContext = spy(application);
         MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(mResources);
@@ -456,8 +456,7 @@
         assertThat(tile.remoteViews).isNotNull();
         assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
         // Make sure the summary TextView got a new text string.
-        TileUtilsShadowRemoteViews shadowRemoteViews =
-                (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews);
+        TileUtilsShadowRemoteViews shadowRemoteViews = extract(tile.remoteViews);
         assertThat(shadowRemoteViews.overrideViewId).isEqualTo(android.R.id.summary);
         assertThat(shadowRemoteViews.overrideText).isEqualTo("new summary text");
     }
@@ -494,8 +493,7 @@
         assertThat(tile.remoteViews).isNotNull();
         assertThat(tile.remoteViews.getLayoutId()).isEqualTo(R.layout.user_preference);
         // Make sure the summary TextView didn't get any text view updates.
-        TileUtilsShadowRemoteViews shadowRemoteViews =
-                (TileUtilsShadowRemoteViews) ShadowExtractor.extract(tile.remoteViews);
+        TileUtilsShadowRemoteViews shadowRemoteViews = extract(tile.remoteViews);
         assertThat(shadowRemoteViews.overrideViewId).isNull();
         assertThat(shadowRemoteViews.overrideText).isNull();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
new file mode 100644
index 0000000..3522b8a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BatteryMeterDrawableBaseTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.settingslib.graph;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = SettingsLibShadowResources.class)
+public class BatteryMeterDrawableBaseTest {
+    private static final int PADDING = 5;
+    private static final int HEIGHT = 80;
+    private static final int WIDTH = 40;
+    @Mock
+    private Canvas mCanvas;
+    private Context mContext;
+    private BatteryMeterDrawableBase mBatteryMeterDrawableBase;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mBatteryMeterDrawableBase = new BatteryMeterDrawableBase(mContext, 0 /* frameColor */);
+    }
+
+    @Test
+    public void testDraw_hasPaddingAndBounds_drawWarningInCorrectPosition() {
+        mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING);
+        mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING);
+        mBatteryMeterDrawableBase.setBatteryLevel(3);
+
+        mBatteryMeterDrawableBase.draw(mCanvas);
+
+        // WIDTH * 0.5 + PADDING = 25
+        // (HEIGHT + TEXT_HEIGHT) * 0.48 + PADDING = 43.3999998
+        verify(mCanvas).drawText(eq("!"), eq(25f), eq(43.399998f), any(Paint.class));
+    }
+
+    @Test
+    public void testDraw_hasPaddingAndBounds_drawBatteryLevelInCorrectPosition() {
+        mBatteryMeterDrawableBase.setPadding(PADDING, PADDING, PADDING, PADDING);
+        mBatteryMeterDrawableBase.setBounds(0, 0, WIDTH + 2 * PADDING, HEIGHT + 2 * PADDING);
+        mBatteryMeterDrawableBase.setBatteryLevel(20);
+        mBatteryMeterDrawableBase.setShowPercent(true);
+
+        mBatteryMeterDrawableBase.draw(mCanvas);
+
+        // WIDTH * 0.5 + PADDING = 25
+        // (HEIGHT + TEXT_HEIGHT) * 0.47 + PADDING = 42.6
+        verify(mCanvas).drawText(eq("20"), eq(25f), eq(42.6f), any(Paint.class));
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
index 1e65a0a..94f80d3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
@@ -100,4 +100,15 @@
         assertThat(twinDrawable.getLayerInsetTop(1)).isEqualTo(
                 drawable.getLayerInsetTop(1));
     }
+
+    @Test
+    public void testCreateLayerDrawable_bluetoothDrawable_hasCorrectFrameColor() {
+        BluetoothDeviceLayerDrawable drawable = BluetoothDeviceLayerDrawable.createLayerDrawable(
+                mContext, RES_ID, BATTERY_LEVEL);
+        BluetoothDeviceLayerDrawable.BatteryMeterDrawable batteryMeterDrawable =
+                (BluetoothDeviceLayerDrawable.BatteryMeterDrawable) drawable.getDrawable(1);
+
+        assertThat(batteryMeterDrawable.mFrameColor).isEqualTo(
+                mContext.getColor(R.color.meter_background_color));
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
index f31d2e1..db599a7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionParserTest.java
@@ -18,8 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.robolectric.RuntimeEnvironment.application;
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.app.ApplicationPackageManager;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ResolveInfo;
@@ -34,22 +37,21 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.res.ResourceLoader;
-import org.robolectric.res.builder.DefaultPackageManager;
-import org.robolectric.res.builder.RobolectricPackageManager;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowApplicationPackageManager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 @RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = SuggestionParserTest.TestPackageManager.class)
 public class SuggestionParserTest {
 
-    private Context mContext;
-    private RobolectricPackageManager mPackageManager;
+    private TestPackageManager mPackageManager;
     private SuggestionParser mSuggestionParser;
     private SuggestionCategory mMultipleCategory;
     private SuggestionCategory mExclusiveCategory;
@@ -61,11 +63,8 @@
 
     @Before
     public void setUp() {
-        RuntimeEnvironment.setRobolectricPackageManager(
-                new TestPackageManager(RuntimeEnvironment.getAppResourceLoader()));
-        mContext = RuntimeEnvironment.application;
-        mPackageManager = RuntimeEnvironment.getRobolectricPackageManager();
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+        mPackageManager = extract(application.getPackageManager());
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(application);
         mSuggestion = new Tile();
         mSuggestion.intent = new Intent("action");
         mSuggestion.intent.setComponent(new ComponentName("pkg", "cls"));
@@ -81,7 +80,7 @@
         mExpiredExclusiveCategory.exclusive = true;
         mExpiredExclusiveCategory.exclusiveExpireDaysInMillis = 0;
 
-        mSuggestionParser = new SuggestionParser(mContext, mPrefs,
+        mSuggestionParser = new SuggestionParser(application, mPrefs,
                 Arrays.asList(mMultipleCategory, mExclusiveCategory, mExpiredExclusiveCategory),
                 "0");
 
@@ -199,7 +198,7 @@
 
         final Tile suggestion = mSuggestionsBeforeDismiss.get(0);
         if (mSuggestionParser.dismissSuggestion(suggestion)) {
-            RuntimeEnvironment.getRobolectricPackageManager().removeResolveInfosForIntent(
+            mPackageManager.removeResolveInfosForIntent(
                     new Intent(Intent.ACTION_MAIN).addCategory(mMultipleCategory.category),
                     suggestion.intent.getComponent().getPackageName());
         }
@@ -207,13 +206,10 @@
                 mMultipleCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled);
     }
 
-    private static class TestPackageManager extends DefaultPackageManager {
+    @Implements(ApplicationPackageManager.class)
+    public static class TestPackageManager extends ShadowApplicationPackageManager {
 
-        TestPackageManager(ResourceLoader appResourceLoader) {
-            super(appResourceLoader);
-        }
-
-        @Override
+        @Implementation
         public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
             return super.queryIntentActivities(intent, flags);
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java
index a376dcd..b53cc37 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.testutils.shadow;
 
-import static org.robolectric.internal.Shadow.directlyOn;
+import static org.robolectric.shadow.api.Shadow.directlyOn;
 
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 6da67df..1be0645 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -193,4 +193,7 @@
 
     <!-- Default for Settings.Secure.BACKUP_MANAGER_CONSTANTS -->
     <string name="def_backup_manager_constants"></string>
+
+    <!-- Default setting for Settings.Global.MOBILE_DATA_ALWAYS_ON -->
+    <bool name="def_mobile_data_always_on">true</bool>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 06d00be..1557d91 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2319,8 +2319,6 @@
             loadBooleanSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_MODE,
                     R.bool.def_screen_brightness_automatic_mode);
 
-            loadDefaultAnimationSettings(stmt);
-
             loadBooleanSetting(stmt, Settings.System.ACCELEROMETER_ROTATION,
                     R.bool.def_accelerometer_rotation);
 
@@ -2514,6 +2512,8 @@
             loadSetting(stmt, Settings.Global.MODE_RINGER,
                     AudioManager.RINGER_MODE_NORMAL);
 
+            loadDefaultAnimationSettings(stmt);
+
             // --- Previously in 'secure'
             loadBooleanSetting(stmt, Settings.Global.PACKAGE_VERIFIER_ENABLE,
                     R.bool.def_package_verifier_enable);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index ec6f831..67fb4d9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -720,9 +720,6 @@
                 Settings.Global.DEVICE_IDLE_CONSTANTS,
                 GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
         dumpSetting(s, p,
-                Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
-                GlobalSettingsProto.DEVICE_IDLE_CONSTANTS_WATCH);
-        dumpSetting(s, p,
                 Settings.Global.APP_IDLE_CONSTANTS,
                 GlobalSettingsProto.APP_IDLE_CONSTANTS);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a463db6..36f9b84 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2896,7 +2896,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 149;
+            private static final int SETTINGS_VERSION = 150;
 
             private final int mUserId;
 
@@ -3470,9 +3470,25 @@
                                     true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
                     }
-
                     currentVersion = 149;
                 }
+
+                if (currentVersion == 149) {
+                    // Version 150: Set a default value for mobile data always on
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting currentSetting = globalSettings.getSettingLocked(
+                            Settings.Global.MOBILE_DATA_ALWAYS_ON);
+                    if (currentSetting.isNull()) {
+                        globalSettings.insertSettingLocked(
+                                Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                                getContext().getResources().getBoolean(
+                                        R.bool.def_mobile_data_always_on) ? "1" : "0",
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 150;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b758e7f5..eab42da 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -96,6 +96,9 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.CREATE_USERS" />
     <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
+    <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.RETRIEVE_WINDOW_TOKEN" />
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 2fd7e87..2c5eb27 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -31,6 +31,7 @@
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     SystemUIPluginLib \
+    SystemUISharedLib \
     android-support-v4 \
     android-support-v7-recyclerview \
     android-support-v7-preference \
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 87971cb..7b8d0db 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -273,6 +273,8 @@
             </intent-filter>
             <meta-data android:name="com.android.settings.category"
                     android:value="com.android.settings.category.ia.system" />
+            <meta-data android:name="com.android.settings.summary"
+                    android:resource="@string/summary_empty"/>
         </activity>
 
         <activity-alias android:name=".DemoMode"
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java
index 1f633da..95ff13b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/GlobalActions.java
@@ -29,6 +29,9 @@
     default void showShutdownUi(boolean isReboot, String reason) {
     }
 
+    default void destroy() {
+    }
+
     @ProvidesInterface(version = GlobalActionsManager.VERSION)
     public interface GlobalActionsManager {
         int VERSION = 1;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index b4b4e19..c52c0aa 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.qs.QSTile.State;
 
 import java.util.Objects;
+import java.util.function.Supplier;
 
 @ProvidesInterface(version = QSTile.VERSION)
 @DependsOn(target = QSIconView.class)
@@ -104,6 +105,7 @@
     public static class State {
         public static final int VERSION = 1;
         public Icon icon;
+        public Supplier<Icon> iconSupplier;
         public int state = Tile.STATE_ACTIVE;
         public CharSequence label;
         public CharSequence contentDescription;
@@ -118,6 +120,7 @@
             if (other == null) throw new IllegalArgumentException();
             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
             final boolean changed = !Objects.equals(other.icon, icon)
+                    || !Objects.equals(other.iconSupplier, iconSupplier)
                     || !Objects.equals(other.label, label)
                     || !Objects.equals(other.contentDescription, contentDescription)
                     || !Objects.equals(other.dualLabelContentDescription,
@@ -130,6 +133,7 @@
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash);
             other.icon = icon;
+            other.iconSupplier = iconSupplier;
             other.label = label;
             other.contentDescription = contentDescription;
             other.dualLabelContentDescription = dualLabelContentDescription;
@@ -150,6 +154,7 @@
         protected StringBuilder toStringBuilder() {
             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
             sb.append(",icon=").append(icon);
+            sb.append(",iconSupplier=").append(iconSupplier);
             sb.append(",label=").append(label);
             sb.append(",contentDescription=").append(contentDescription);
             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index b68566f..eac0455 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Voer wagwoord in om te ontsluit"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Tik PIN in om te ontsluit"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Verkeerde PIN-kode."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Gelaai"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Laai"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Laai tans vinnig"</string>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index 21815e1..8fb0539 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"ለመክፈት የይለፍ ቃል ይተይቡ"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"ለመክፈት ፒን ይተይቡ"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ትክክል ያልሆነ ፒን  ኮድ።"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ባትሪ ሞልቷል"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ኃይል በመሙላት ላይ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"ኃይል በፍጥነት በመሙላት ላይ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index f33fa9a..04ce752 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"اكتب كلمة المرور لإلغاء التأمين"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"اكتب رمز رقم التعريف الشخصي لإلغاء التأمين"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"رمز رقم التعريف الشخصي غير صحيح."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"بطاقة غير صالحة."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"تم الشحن"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"جارٍ الشحن"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"الشحن سريعًا"</string>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 1b802fb..ab0e8ea 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Kilidi açmaq üçün parol daxil edin"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Kilidi açmaq üçün PIN daxil edin"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Yanlış PIN kod."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Enerji yığdı"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Enerji yığır"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Sürətlə enerji yığır"</string>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index efd5631..e24001c 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Unesite lozinku da biste otključali"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Unesite PIN za otključavanje"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kôd je netačan."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Nevažeća kartica."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Napunjena je"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Puni se"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Brzo se puni"</string>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 3288971..b33c523 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Увядзіце пароль для разблакіравання"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Каб разблакіраваць, увядзіце PIN-код"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Няправільны PIN-код."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Зараджаны"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Ідзе зарадка"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Зараджаецца хутка"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index f12967f..8dbdd5c 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Въведете парола, за да отключите"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Въведете ПИН кода, за да отключите"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Неправилен ПИН код."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Заредена"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Зарежда се"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Зарежда се бързо"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 06175eb..22e67b8 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"আনলক করতে পাসওয়ার্ড লিখুন"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"আনলক করতে পিন লিখুন"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ভুল পিন কোড দেওয়া হয়েছে।"</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"ভুল কার্ড।"</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"চার্জ হয়েছে"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"চার্জ হচ্ছে"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"দ্রুত চার্জ হচ্ছে"</string>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index c49dd4f..52712287 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -21,14 +21,15 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="3171996292755059205">"Zaključavanje tastature"</string>
-    <string name="keyguard_password_enter_pin_code" msgid="3420548423949593123">"Upišite PIN kôd"</string>
-    <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"Upišite PUK kôd za SIM karticu i novi PIN kôd"</string>
+    <string name="keyguard_password_enter_pin_code" msgid="3420548423949593123">"Upišite PIN"</string>
+    <string name="keyguard_password_enter_puk_code" msgid="670683628782925409">"Upišite PUK kôd za SIM karticu i novi PIN"</string>
     <string name="keyguard_password_enter_puk_prompt" msgid="3747778500166059332">"PUK kôd za SIM karticu"</string>
-    <string name="keyguard_password_enter_pin_prompt" msgid="8188243197504453830">"Novi PIN kôd za SIM karticu"</string>
+    <string name="keyguard_password_enter_pin_prompt" msgid="8188243197504453830">"Novi PIN za SIM karticu"</string>
     <string name="keyguard_password_entry_touch_hint" msgid="5790410752696806482"><font size="17">"Dodirnite da upišete lozinku"</font></string>
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Upišite lozinku za otključavanje"</string>
-    <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Upišite PIN kôd za otključavanje"</string>
-    <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Pogrešan PIN kôd."</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Upišite PIN za otključavanje"</string>
+    <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Pogrešan PIN."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Nevažeća kartica."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Napunjeno"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Punjenje"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Brzo punjenje"</string>
@@ -46,8 +47,8 @@
     <string name="keyguard_sim_locked_message" msgid="953766009432168127">"SIM kartica je zaključana."</string>
     <string name="keyguard_sim_puk_locked_message" msgid="1772789643694942073">"SIM kartica je zaključana PUK kodom."</string>
     <string name="keyguard_sim_unlock_progress_dialog_message" msgid="3586601150825821675">"Otključavanje SIM kartice…"</string>
-    <string name="keyguard_accessibility_pin_area" msgid="703175752097279029">"Prostor za PIN kôd"</string>
-    <string name="keyguard_accessibility_sim_pin_area" msgid="912702510825058921">"Prostor za PIN kôd za SIM karticu"</string>
+    <string name="keyguard_accessibility_pin_area" msgid="703175752097279029">"Prostor za PIN"</string>
+    <string name="keyguard_accessibility_sim_pin_area" msgid="912702510825058921">"Prostor za PIN za SIM karticu"</string>
     <string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"Prostor za PUK kôd za SIM karticu"</string>
     <string name="keyguard_accessibility_next_alarm" msgid="5835196989158584991">"Naredni alarm je podešen za <xliff:g id="ALARM">%1$s</xliff:g>"</string>
     <string name="keyboardview_keycode_delete" msgid="6883116827512721630">"Izbriši"</string>
@@ -56,29 +57,29 @@
     <string name="kg_forgot_pattern_button_text" msgid="534245177645252620">"Zaboravili ste uzorak?"</string>
     <string name="kg_wrong_pattern" msgid="7620081431514773802">"Pogrešan uzorak"</string>
     <string name="kg_wrong_password" msgid="4580683060277329277">"Pogrešna lozinka"</string>
-    <string name="kg_wrong_pin" msgid="4785660766909463466">"Pogrešan PIN kôd"</string>
+    <string name="kg_wrong_pin" msgid="4785660766909463466">"Pogrešan PIN"</string>
     <plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="4368805541257003755">
       <item quantity="one">Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekundu.</item>
       <item quantity="few">Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekunde.</item>
       <item quantity="other">Pokušajte ponovo za <xliff:g id="NUMBER">%d</xliff:g> sekundi.</item>
     </plurals>
     <string name="kg_pattern_instructions" msgid="5547646893001491340">"Nacrtajte uzorak"</string>
-    <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Unesite PIN kôd SIM kartice."</string>
-    <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Unesite PIN kôd SIM kartice operatera \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
+    <string name="kg_sim_pin_instructions" msgid="6389000973113699187">"Unesite PIN SIM kartice."</string>
+    <string name="kg_sim_pin_instructions_multi" msgid="1643757228644271861">"Unesite PIN SIM kartice operatera \"<xliff:g id="CARRIER">%1$s</xliff:g>\""</string>
     <string name="kg_sim_lock_instructions_esim" msgid="4957650659201013804">"Onemogućite eSIM karticu za korištenje uređaja bez mobilne usluge."</string>
-    <string name="kg_pin_instructions" msgid="4069609316644030034">"Unesite PIN kôd"</string>
+    <string name="kg_pin_instructions" msgid="4069609316644030034">"Unesite PIN"</string>
     <string name="kg_password_instructions" msgid="136952397352976538">"Unesite lozinku"</string>
     <string name="kg_puk_enter_puk_hint" msgid="2288964170039899277">"SIM kartica je sada onemogućena. Unesite PUK kôd da nastavite. Za više informacija obratite se operateru."</string>
     <string name="kg_puk_enter_puk_hint_multi" msgid="1373131883510840794">"Operater SIM kartice \"<xliff:g id="CARRIER">%1$s</xliff:g>\" sada je onemogućen. Unesite PUK kôd da nastavite. Za više informacija obratite se operateru."</string>
-    <string name="kg_puk_enter_pin_hint" msgid="3137789674920391087">"Unesite željeni PIN kôd"</string>
-    <string name="kg_enter_confirm_pin_hint" msgid="3089485999116759671">"Potvrdite željeni PIN kôd"</string>
+    <string name="kg_puk_enter_pin_hint" msgid="3137789674920391087">"Unesite željeni PIN"</string>
+    <string name="kg_enter_confirm_pin_hint" msgid="3089485999116759671">"Potvrdite željeni PIN"</string>
     <string name="kg_sim_unlock_progress_dialog_message" msgid="4471738151810900114">"Otključavanje SIM kartice…"</string>
-    <string name="kg_invalid_sim_pin_hint" msgid="3057533256729513335">"Unesite PIN kôd koji sadrži 4 do 8 brojeva."</string>
+    <string name="kg_invalid_sim_pin_hint" msgid="3057533256729513335">"Unesite PIN koji sadrži 4 do 8 brojeva."</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6003602401368264144">"PUK kôd treba sadržavati najmanje 8 brojeva."</string>
     <string name="kg_invalid_puk" msgid="5399287873762592502">"Ponovo unesite ispravan PUK kôd. Ponovljeni pokušaji će trajno onemogućiti SIM karticu."</string>
-    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="5672736555427444330">"PIN kodovi se ne poklapaju"</string>
+    <string name="kg_invalid_confirm_pin_hint" product="default" msgid="5672736555427444330">"PIN-ovi se ne poklapaju"</string>
     <string name="kg_login_too_many_attempts" msgid="6604574268387867255">"Previše puta ste pokušali otključati uređaj crtanjem uzorka"</string>
-    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8637788033282252027">"Pogrešno ste unijeli PIN kôd <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sek."</string>
+    <string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="8637788033282252027">"Pogrešno ste unijeli PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sek."</string>
     <string name="kg_too_many_failed_password_attempts_dialog_message" msgid="7724148763268377734">"Pogrešno ste unijeli lozinku <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sek."</string>
     <string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4820967667848302092">"Pogrešno ste nacrtali svoj uzorak za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. \n\nPokušajte ponovo za <xliff:g id="NUMBER_1">%2$d</xliff:g> sek."</string>
     <string name="kg_failed_attempts_almost_at_wipe" product="tablet" msgid="1629351522209932316">"Pokušali ste <xliff:g id="NUMBER_0">%1$d</xliff:g> puta neispravno otključati tablet. U slučaju još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaja bez uspjeha, tablet će se vratiti na fabričke postavke i svi podaci će se izbrisati."</string>
@@ -95,11 +96,11 @@
     <string name="kg_failed_attempts_now_erasing_profile" product="default" msgid="8476407539834855">"Pokušali ste <xliff:g id="NUMBER">%d</xliff:g> puta neispravno otključati telefon. Poslovni profil će se ukloniti i svi podaci s profila će se izbrisati."</string>
     <string name="kg_failed_attempts_almost_at_login" product="tablet" msgid="956706236554092172">"Pogrešno ste nacrtali uzorak za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. U slučaju još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaja bez uspjeha, od vas će se tražiti da tablet otključate koristeći račun e-pošte.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
     <string name="kg_failed_attempts_almost_at_login" product="default" msgid="8364140853305528449">"Pogrešno ste nacrtali uzorak za otključavanje <xliff:g id="NUMBER_0">%1$d</xliff:g> puta. U slučaju još <xliff:g id="NUMBER_1">%2$d</xliff:g> pokušaja bez uspjeha, od vas će se tražiti da telefon otključate koristeći račun e-pošte.\n\n Pokušajte ponovo za <xliff:g id="NUMBER_2">%3$d</xliff:g> sek."</string>
-    <string name="kg_password_wrong_pin_code_pukked" msgid="3389829202093674267">"PIN kôd za SIM karticu je netačan. Za otključavanje uređaja sada se morate obratiti svom operateru."</string>
+    <string name="kg_password_wrong_pin_code_pukked" msgid="3389829202093674267">"PIN za SIM karticu je netačan. Za otključavanje uređaja sada se morate obratiti svom operateru."</string>
     <plurals name="kg_password_wrong_pin_code" formatted="false" msgid="4314341367727055967">
-      <item quantity="one">PIN kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaj.</item>
-      <item quantity="few">PIN kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja.</item>
-      <item quantity="other">PIN kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja.</item>
+      <item quantity="one">PIN za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaj.</item>
+      <item quantity="few">PIN za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja.</item>
+      <item quantity="other">PIN za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja.</item>
     </plurals>
     <string name="kg_password_wrong_puk_code_dead" msgid="3329017604125179374">"SIM kartica je neupotrebljiva. Obratite se svom operateru."</string>
     <plurals name="kg_password_wrong_puk_code" formatted="false" msgid="2287504898931957513">
@@ -107,20 +108,20 @@
       <item quantity="few">PUK kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja prije nego što SIM kartica postane trajno neupotrebljiva.</item>
       <item quantity="other">PUK kôd za SIM karticu je netačan. Imate još <xliff:g id="NUMBER_1">%d</xliff:g> pokušaja prije nego što SIM kartica postane trajno neupotrebljiva.</item>
     </plurals>
-    <string name="kg_password_pin_failed" msgid="8769990811451236223">"Korištenje PIN koda za SIM karticu nije uspjelo!"</string>
+    <string name="kg_password_pin_failed" msgid="8769990811451236223">"Korištenje PIN-a za SIM karticu nije uspjelo!"</string>
     <string name="kg_password_puk_failed" msgid="1331621440873439974">"Korištenje PUK koda za SIM karticu nije uspjelo!"</string>
     <string name="kg_pin_accepted" msgid="7637293533973802143">"Kôd je prihvaćen"</string>
     <string name="keyguard_carrier_default" msgid="4274828292998453695">"Nema mreže."</string>
     <string name="accessibility_ime_switch_button" msgid="2695096475319405612">"Promjena načina unosa"</string>
     <string name="airplane_mode" msgid="3807209033737676010">"Način rada u avionu"</string>
     <string name="kg_prompt_reason_restart_pattern" msgid="7246972020562621506">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
-    <string name="kg_prompt_reason_restart_pin" msgid="6303592361322290145">"Potreban je PIN kôd nakon što se uređaj ponovo pokrene"</string>
+    <string name="kg_prompt_reason_restart_pin" msgid="6303592361322290145">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="6984641181515902406">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_timeout_pattern" msgid="5304487696073914063">"Uzorak je potreban radi dodatne sigurnosti"</string>
-    <string name="kg_prompt_reason_timeout_pin" msgid="8851462864335757813">"PIN kôd je potreban radi dodatne sigurnosti"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="8851462864335757813">"PIN je potreban radi dodatne sigurnosti"</string>
     <string name="kg_prompt_reason_timeout_password" msgid="6563904839641583441">"Lozinka je potrebna radi dodatne sigurnosti"</string>
     <string name="kg_prompt_reason_switch_profiles_pattern" msgid="3398054847288438444">"Potreban je uzorak nakon prelaska na drugi profil"</string>
-    <string name="kg_prompt_reason_switch_profiles_pin" msgid="7426368139226961699">"Potreban je PIN kôd nakon prelaska na drugi profil"</string>
+    <string name="kg_prompt_reason_switch_profiles_pin" msgid="7426368139226961699">"Potreban je PIN nakon prelaska na drugi profil"</string>
     <string name="kg_prompt_reason_switch_profiles_password" msgid="8383831046318421845">"Potrebna je lozinka nakon prelaska na drugi profil"</string>
     <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"Uređaj je zaključao administrator"</string>
     <string name="kg_prompt_reason_user_request" msgid="8236951765212462286">"Uređaj je ručno zaključan"</string>
@@ -130,9 +131,9 @@
       <item quantity="other">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sati. Potvrdite uzorak.</item>
     </plurals>
     <plurals name="kg_prompt_reason_time_pin" formatted="false" msgid="34586942088144385">
-      <item quantity="one">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sat. Potvrdite PIN kôd.</item>
-      <item quantity="few">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sata. Potvrdite PIN kôd.</item>
-      <item quantity="other">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sati. Potvrdite PIN kôd.</item>
+      <item quantity="one">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sat. Potvrdite PIN.</item>
+      <item quantity="few">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sata. Potvrdite PIN.</item>
+      <item quantity="other">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sati. Potvrdite PIN.</item>
     </plurals>
     <plurals name="kg_prompt_reason_time_password" formatted="false" msgid="257297696215346527">
       <item quantity="one">Uređaj nije otključavan <xliff:g id="NUMBER_1">%d</xliff:g> sat. Potvrdite lozinku.</item>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 6eaa1b9..9aa8229 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Escriu la contrasenya per desbloquejar"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Escriu el PIN per desbloquejar"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"El codi PIN no és correcte."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"La targeta no és vàlida."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Bateria carregada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"S\'està carregant"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"S\'està carregant ràpidament"</string>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 92e4e82..0665ac0 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Zadejte heslo pro odemknutí"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Zadejte kód PIN pro odemknutí"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Nesprávný kód PIN."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Nabito"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Nabíjení"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Rychlé nabíjení"</string>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 2f9b9a8..354aa96 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Indtast adgangskoden for at låse op"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Indtast pinkoden for at låse op"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Forkert pinkode."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Opladet"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Oplader"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Oplader hurtigt"</string>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index f350671..9a5ad0e 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Bitte gib das Passwort zum Entsperren ein"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Bitte gib die PIN zum Entsperren ein"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Falscher PIN-Code."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Aufgeladen"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Wird aufgeladen"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Schnelles Aufladen"</string>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 5a21c2e..6ec3e57 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Πληκτρολογήστε τον κωδικό πρόσβασης για ξεκλείδωμα"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Πληκτρολογήστε τον αριθμό PIN για ξεκλείδωμα"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Λανθασμένος κωδικός PIN."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Φορτίστηκε"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Φόρτιση σε εξέλιξη"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Ταχεία φόρτιση"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 57a15fe..dede142 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎Type password to unlock‎‏‎‎‏‎"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‏‎‎Type PIN to unlock‎‏‎‎‏‎"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎Incorrect PIN code.‎‏‎‎‏‎"</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‏‎‎‎‎Invalid Card.‎‏‎‎‏‎"</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‎‏‎‏‎‏‎Charged‎‏‎‎‏‎"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‎‏‎‎‎‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‎Charging‎‏‎‎‏‎"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎Charging rapidly‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 66e3507..0c5989b 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Ingresa la contraseña para desbloquearlo"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Ingresa el PIN para desbloquearlo"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Código PIN incorrecto"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Cargada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Cargando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Carga rápida"</string>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index bc52f66..ac99a84 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Escribe la contraseña para desbloquear"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Escribe el código PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"El código PIN es incorrecto."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Tarjeta no válida."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Cargada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Cargando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Cargando rápidamente"</string>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 44691b6..4c3cf6f 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Avamiseks sisestage parool"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Avamiseks sisestage PIN-kood"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Vale PIN-kood."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Laetud"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Laadimine"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Kiiresti laadimine"</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 985006d..f7f3d58 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Idatzi desblokeatzeko pasahitza"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Idatzi desblokeatzeko PIN kodea"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kode hori ez da zuzena."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Kargatuta"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Kargatzen"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Bizkor kargatzen"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index ffb3621..43f8197 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"برای بازکردن قفل، گذرواژه را وارد کنید"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"برای بازکردن قفل، پین را تایپ کنید"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"کد پین اشتباه است."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"شارژ کامل شد"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"درحال شارژ شدن"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"شارژ سریع"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index b9e280a..b8689ee 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Poista lukitus antamalla salasana."</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Poista lukitus antamalla PIN-koodi."</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Väärä PIN-koodi"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Ladattu"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Ladataan"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Nopea lataus käynnissä"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 53fb15c..e70dca3 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Entrez le mot de passe pour déverrouiller le clavier."</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Entrez le NIP pour déverrouiller le clavier."</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"NIP erroné."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Chargé"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Pile en cours de charge"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Charge rapide"</string>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index c8ace66..1e7548e1 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Saisissez le mot de passe pour déverrouiller le clavier"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Saisissez le code pour déverrouiller le clavier"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Le code est incorrect."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Chargé"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"En charge…"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Chargement rapide…"</string>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 25ed7bf..4fe50ed 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Escribe o contrasinal para desbloquear"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Escribe o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Código PIN incorrecto"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Cargada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Cargando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Cargando rapidamente"</string>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index dbbd57a..72d9b75 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"અનલૉક કરવા માટે પાસવર્ડ લખો"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"અનલૉક કરવા માટે પિન લખો"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ખોટો પિન કોડ."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ચાર્જ થઈ ગયું"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ચાર્જ થઈ રહ્યું છે"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"ઝડપથી ચાર્જ થઈ રહ્યું છે"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index c5a31d8..62ed064 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलॉक करने के लिए पासवर्ड लिखें"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"अनलॉक करने के लिए पिन लिखें"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"गलत पिन कोड."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"गलत कार्ड."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"चार्ज हो गई है"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"चार्ज हो रही है"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"तेज़ी से चार्ज हो रही है"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index d4ec34c..34c3a5a 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Unesite zaporku da biste otključali"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Unesite PIN da biste otključali"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kôd nije točan."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Napunjeno"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Punjenje"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Brzo punjenje"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 9246510..a1922ee 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"A feloldáshoz írja be a jelszót"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"A feloldáshoz írja be a PIN-kódot"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Helytelen PIN-kód."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Feltöltve"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Töltés folyamatban"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Gyors töltés folyamatban"</string>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index b9536c0..7914b5f 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Ապակողպելու համար մուտքագրեք գաղտնաբառը"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Ապակողպելու համար մուտքագրեք PIN կոդը"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN կոդը սխալ է։"</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Սխալ քարտ"</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Լիցքավորված է"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Լիցքավորում"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Արագ լիցքավորում"</string>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 586cd7e..e417a3f 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Ketik sandi untuk membuka kunci"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Ketik PIN untuk membuka kunci"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Kode PIN salah."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Kartu Tidak Valid"</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Terisi"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Mengisi daya"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Mengisi daya dengan cepat"</string>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index d3760ff..191c66e 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Sláðu inn aðgangsorðið til að opna"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Sláðu inn PIN-númer til að opna"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Rangt PIN-númer."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Fullhlaðin"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Í hleðslu"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Hröð hleðsla"</string>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 5392220..9ea32df 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Inserisci password per sbloccare"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Inserisci PIN per sbloccare"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Codice PIN errato."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Carico"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"In carica"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Ricarica veloce"</string>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index 518c1c2..cb61b7d 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"הזן סיסמה לביטול הנעילה"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"הזן את קוד הגישה לביטול הנעילה"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"קוד הגישה שגוי"</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"כרטיס לא חוקי."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"הסוללה טעונה"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"הסוללה נטענת"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"הסוללה נטענת מהר"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index 3c76c2f..4b5be4f 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"ロックを解除するにはパスワードを入力してください"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"ロックを解除するには PIN を入力してください"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN コードが無効です。"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"充電が完了しました"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"充電しています"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"急速充電しています"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index e342fcc..5da8122 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"განსაბლოკად აკრიფეთ პაროლი"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"განსაბლოკად აკრიფეთ PIN-კოდი"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN-კოდი არასწორია."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"დატენილია"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"მიმდინარეობს დატენა"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"მიმდინარეობს სწრაფი დატენა"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index e1dda1b..a99f2ec 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Құлпын ашу үшін құпия сөзді теріңіз"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Құлпын ашу үшін PIN кодын енгізіңіз"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN коды қате"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Зарядталды"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Зарядталуда"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Жылдам зарядталуда"</string>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 400edbe8..7241ac3 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"វាយ​បញ្ចូល​ពាក្យ​សម្ងាត់​ ដើម្បី​ដោះ​សោ"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"វាយ​បញ្ចូល​កូដ PIN ដើម្បី​ដោះ​សោ"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"កូដ PIN មិន​ត្រឹមត្រូវ​ទេ។"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"បាន​សាក​ថ្ម"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"កំពុង​សាក​ថ្ម"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"សាកយ៉ាងឆាប់រហ័ស"</string>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 8c69b48..5b94468 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"ಅನ್‌ಲಾಕ್‌ ಮಾಡಲು ಪಾಸ್‌ವರ್ಡ್‌ ಟೈಪ್‌ ಮಾಡಿ"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"ಅನ್‌ಲಾಕ್‌ ಮಾಡಲು ಪಿನ್‌ ಟೈಪ್‌ ಮಾಡಿ"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ತಪ್ಪಾದ ಪಿನ್‌ ಕೋಡ್."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"ವೇಗವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 0d3603b..9b48a4b 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"잠금 해제하려면 비밀번호 입력"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"잠금 해제하려면 PIN 입력"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"잘못된 PIN 코드입니다."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"유효하지 않은 카드"</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"충전됨"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"충전 중"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"고속 충전 중"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index c61945c..ff27639 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Кулпуну ачуу үчүн сырсөздү териңиз"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Кулпуну ачуу үчүн PIN-кодду териңиз"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN-код туура эмес."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Кубатталды"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Кубатталууда"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Ыкчам кубатталууда"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 993a6b7..9e59fb1 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Įveskite slaptažodį, kad atrakintumėte"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Įveskite PIN kodą, kad atrakintumėte"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Netinkamas PIN kodas."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Įkrauta"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Įkraunama"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Greitai įkraunama"</string>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index 7bd1cfd..3447973 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Ievadiet paroli, lai atbloķētu."</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Ievadiet PIN kodu, lai atbloķētu."</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kods nav pareizs."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Akumulators uzlādēts"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Notiek uzlāde"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Notiek ātrā uzlāde"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index c4405e0..263b3c9 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Напишете ја лозинката за да отклучите"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Напишете PIN-код за да отклучите"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Погрешен PIN-код."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Полна"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Се полни"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Брзо полнење"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index bf72bcc..b00584f 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"അൺലോക്കുചെയ്യുന്നതിന് പാസ്‌വേഡ് ടൈപ്പുചെയ്യുക"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"അൺലോക്കുചെയ്യുന്നതിന് പിൻ ടൈപ്പുചെയ്യുക"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"പിൻ കോഡ് തെറ്റാണ്."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ചാർജായി"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ചാർജ്ജുചെയ്യുന്നു"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"വേഗത്തിൽ ചാർജുചെയ്യുന്നു"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index d440124..44c186f 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Түгжээг тайлахын тулд нууц үгийг оруулна уу"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Түгжээг тайлахын тулд ПИН кодыг оруулна уу"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ПИН код буруу байна."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Цэнэглэсэн"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Цэнэглэж байна"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Хурдан цэнэглэж байна"</string>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index ecee06b..8ab95f9 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलॉक करण्यासाठी संकेतशब्द टाइप करा"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"अनलॉक करण्यासाठी पिन टाइप करा"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"चुकीचा पिन कोड."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"चार्ज झाली"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"चार्ज होत आहे"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"द्रुतपणे चार्ज होत आहे"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 23295d3..efffa8c 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Taip kata laluan untuk membuka kunci"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Taip PIN untuk membuka kunci"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Kod PIN salah."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Sudah dicas"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Mengecas"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Mengecas dengan cepat"</string>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 4789688..7b598e1 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"လော့ခ်ဖွင့်ရန် စကားဝှက်ကို ထည့်ပါ"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"လော့ခ်ဖွင့်ရန် ပင်နံပါတ်ထည့်ပါ"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ပင်နံပါတ် မှားနေသည်။"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"အားသွင်းပြီးပါပြီ"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"အားသွင်းနေပါသည်"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"လျှင်မြန်စွာ အားသွင်းနေသည်"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index d3187f3..1fef576 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Skriv inn passordet for å låse opp"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Skriv inn PIN-koden for å låse opp"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Feil PIN-kode."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Oppladet"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Lader"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Lader raskt"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index b8dc313..d5ac1d8 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"अनलक गर्न पासवर्ड टाइप गर्नुहोस्"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"अनलक गर्न PIN कोड टाइप गर्नुहोस्"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN कोड गलत छ।"</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"अमान्य कार्ड।"</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"चार्ज भयो"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"चार्ज हुँदै"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"छिटो चार्ज हुँदै"</string>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index d1615aa..6a1f3c9 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Typ het wachtwoord om te ontgrendelen"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Typ pincode om te ontgrendelen"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Onjuiste pincode."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Ongeldige kaart."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Opgeladen"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Opladen"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Snel opladen"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 82f7227..bedcf17 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -29,13 +29,15 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪਾਸਵਰਡ ਟਾਈਪ ਕਰੋ"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪਿੰਨ ਟਾਈਪ ਕਰੋ"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"ਗਲਤ ਪਿੰਨ ਕੋਡ।"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ਚਾਰਜ ਹੋ ਗਿਆ"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"ਤੇਜ਼ੀ ਨਾਲ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="keyguard_plugged_in_charging_slowly" msgid="6637043106038550407">"ਹੌਲੀ-ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ"</string>
     <string name="keyguard_low_battery" msgid="9218432555787624490">"ਆਪਣਾ ਚਾਰਜਰ ਕਨੈਕਟ ਕਰੋ।"</string>
     <string name="keyguard_instructions_when_pattern_disabled" msgid="8566679946700751371">"ਅਣਲਾਕ ਕਰਨ ਲਈ \"ਮੀਨੂ\" ਦਬਾਓ।"</string>
-    <string name="keyguard_network_locked_message" msgid="6743537524631420759">"ਨੈੱਟਵਰਕ ਲੌਕ ਕੀਤਾ ਗਿਆ"</string>
+    <string name="keyguard_network_locked_message" msgid="6743537524631420759">"ਨੈੱਟਵਰਕ  ਲਾਕ  ਕੀਤਾ ਗਿਆ"</string>
     <string name="keyguard_missing_sim_message_short" msgid="6327533369959764518">"ਕੋਈ ਸਿਮ ਕਾਰਡ ਨਹੀਂ"</string>
     <string name="keyguard_missing_sim_message" product="tablet" msgid="4550152848200783542">"ਟੈਬਲੈੱਟ ਵਿੱਚ ਕੋਈ ਸਿਮ ਕਾਰਡ ਮੌਜੂਦ ਨਹੀਂ।"</string>
     <string name="keyguard_missing_sim_message" product="default" msgid="6585414237800161146">"ਫ਼ੋਨ ਵਿੱਚ ਕੋਈ ਸਿਮ ਕਾਰਡ ਮੌਜੂਦ ਨਹੀਂ।"</string>
@@ -43,9 +45,9 @@
     <string name="keyguard_missing_sim_instructions_long" msgid="589889372883904477">"SIM ਕਾਰਡ ਮੌਜੂਦ ਨਹੀਂ ਜਾਂ ਪੜ੍ਹਨਯੋਗ ਨਹੀਂ ਹੈ। ਇੱਕ SIM ਕਾਰਡ ਪਾਓ।"</string>
     <string name="keyguard_permanent_disabled_sim_message_short" msgid="654102080186420706">"ਨਾ-ਵਰਤਣਯੋਗ SIM ਕਾਰਡ।"</string>
     <string name="keyguard_permanent_disabled_sim_instructions" msgid="4683178224791318347">"ਤੁਹਾਡਾ ਸਿਮ ਕਾਰਡ ਸਥਾਈ ਤੌਰ \'ਤੇ ਅਯੋਗ ਬਣਾ ਦਿੱਤਾ ਗਿਆ ਹੈ।\n ਇੱਕ ਹੋਰ ਸਿਮ ਕਾਰਡ ਲਈ ਆਪਣੇ ਵਾਇਰਲੈੱਸ ਸੇਵਾ ਪ੍ਰਦਾਨਕ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।"</string>
-    <string name="keyguard_sim_locked_message" msgid="953766009432168127">"SIM ਕਾਰਡ ਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
-    <string name="keyguard_sim_puk_locked_message" msgid="1772789643694942073">"SIM ਕਾਰਡ PUK-ਲੌਕ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
-    <string name="keyguard_sim_unlock_progress_dialog_message" msgid="3586601150825821675">"SIM ਕਾਰਡ ਨੂੰ ਅਨਲੌਕ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
+    <string name="keyguard_sim_locked_message" msgid="953766009432168127">"SIM ਕਾਰਡ  ਲਾਕ  ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
+    <string name="keyguard_sim_puk_locked_message" msgid="1772789643694942073">"SIM ਕਾਰਡ PUK- ਲਾਕ  ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string>
+    <string name="keyguard_sim_unlock_progress_dialog_message" msgid="3586601150825821675">"SIM ਕਾਰਡ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="keyguard_accessibility_pin_area" msgid="703175752097279029">"ਪਿੰਨ ਖੇਤਰ"</string>
     <string name="keyguard_accessibility_sim_pin_area" msgid="912702510825058921">"ਸਿਮ ਪਿੰਨ ਖੇਤਰ"</string>
     <string name="keyguard_accessibility_sim_puk_area" msgid="136979425761438705">"SIM PUK ਖੇਤਰ"</string>
@@ -71,7 +73,7 @@
     <string name="kg_puk_enter_puk_hint_multi" msgid="1373131883510840794">"ਸਿਮ \"<xliff:g id="CARRIER">%1$s</xliff:g>\" ਹੁਣ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="kg_puk_enter_pin_hint" msgid="3137789674920391087">"ਇੱਛਤ ਪਿੰਨ ਕੋਡ ਦਾਖਲ ਕਰੋ"</string>
     <string name="kg_enter_confirm_pin_hint" msgid="3089485999116759671">"ਇੱਛਤ ਪਿੰਨ ਕੋਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ"</string>
-    <string name="kg_sim_unlock_progress_dialog_message" msgid="4471738151810900114">"SIM ਕਾਰਡ ਨੂੰ ਅਨਲੌਕ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
+    <string name="kg_sim_unlock_progress_dialog_message" msgid="4471738151810900114">"SIM ਕਾਰਡ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
     <string name="kg_invalid_sim_pin_hint" msgid="3057533256729513335">"ਕੋਈ ਪਿੰਨ ਟਾਈਪ ਕਰੋ ਜੋ 4 ਤੋਂ 8 ਨੰਬਰਾਂ ਦਾ ਹੋਵੇ।"</string>
     <string name="kg_invalid_sim_puk_hint" msgid="6003602401368264144">"PUK ਕੋਡ 8 ਜਾਂ ਵੱਧ ਨੰਬਰਾਂ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ।"</string>
     <string name="kg_invalid_puk" msgid="5399287873762592502">"ਸਹੀ PUK ਕੋਡ ਮੁੜ-ਦਾਖਲ ਕਰੋ। ਬਾਰ-ਬਾਰ ਕੀਤੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ ਸਿਮ ਨੂੰ ਸਥਾਈ ਤੌਰ \'ਤੇ ਬੰਦ ਕਰ ਦੇਣਗੀਆਂ।"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 7597802..59f4412 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Wpisz hasło, aby odblokować"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Wpisz kod PIN, aby odblokować"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Nieprawidłowy kod PIN."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Nieprawidłowa karta."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Naładowana"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Ładowanie"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Szybkie ładowanie"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index 8d5e097..60c6f49 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Digite a senha para desbloquear"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Insira o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Código PIN incorreto."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Carregada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Carregando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Carregando rapidamente"</string>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index 4d1a721..4f6fcb9 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Introduza a palavra-passe para desbloquear"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Introduza o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Código PIN incorreto."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Cartão inválido."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Carregada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"A carregar…"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"A carregar rapidamente…"</string>
@@ -119,7 +120,7 @@
     <string name="kg_prompt_reason_switch_profiles_pattern" msgid="3398054847288438444">"É necessário um padrão quando muda de perfil"</string>
     <string name="kg_prompt_reason_switch_profiles_pin" msgid="7426368139226961699">"É necessário um PIN quando muda de perfil"</string>
     <string name="kg_prompt_reason_switch_profiles_password" msgid="8383831046318421845">"É necessária uma palavra-passe quando muda de perfil"</string>
-    <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"Dispositivo bloqueado pelo administrador"</string>
+    <string name="kg_prompt_reason_device_admin" msgid="3452168247888906179">"Dispositivo bloqueado pelo gestor"</string>
     <string name="kg_prompt_reason_user_request" msgid="8236951765212462286">"O dispositivo foi bloqueado manualmente"</string>
     <plurals name="kg_prompt_reason_time_pattern" formatted="false" msgid="71299470072448533">
       <item quantity="one">O dispositivo não é desbloqueado há <xliff:g id="NUMBER_0">%d</xliff:g> hora. Confirme o padrão.</item>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index 8d5e097..60c6f49 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Digite a senha para desbloquear"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Insira o PIN para desbloquear"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Código PIN incorreto."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Carregada"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Carregando"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Carregando rapidamente"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index c772a86..743bbc1 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Introduceți parola pentru a debloca"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Introduceți codul PIN pentru a debloca"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Cod PIN incorect."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Încărcată"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Se încarcă"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Se încarcă rapid"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 25691d3..3f27010 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Введите пароль"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Введите PIN-код для разблокировки"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Неверный PIN-код."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Батарея заряжена"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Зарядка батареи"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Быстрая зарядка"</string>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 807d54f..e48d81d 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"අගුළු ඇරීමට මුරපදය ටයිප් කරන්න"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"අගුළු හැරීමට PIN එක ටයිප් කරන්න"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"වැරදි PIN කේතයකි."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"අරෝපිතයි"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ආරෝපණය වෙමින්"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"වේගයෙන් ආරෝපණය වෙමින්"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index 5b433ce..9f397e6 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Zadajte heslo na odomknutie"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Zadajte kód PIN na odomknutie"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Nesprávny kód PIN."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Nabité"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Nabíja sa"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Rýchle nabíjanie"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 243b0be..9004a1e 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Vnesite geslo za odklepanje"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Vnesite kodo PIN za odklepanje"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Napačna koda PIN."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Akumulator napolnjen"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Polnjenje"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Hitro polnjenje"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index 74f822b..2521b99 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Shkruaj fjalëkalimin për të shkyçur"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Shkruaj kodin PIN për ta shkyçur"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Kodi PIN është i pasaktë."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"I ngarkuar"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Po ngarkon"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Po ngarkon me shpejtësi"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index c1cf1d2..9d06e00 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Унесите лозинку да бисте откључали"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Унесите PIN за откључавање"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN кôд је нетачан."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Неважећа картица."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Напуњена је"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Пуни се"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Брзо се пуни"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index 1d6dcbdd..03f2086 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Lås upp med lösenordet"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Lås upp med pinkoden"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Fel pinkod."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Laddat"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Laddas"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Laddas snabbt"</string>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 0ba106d..7ee0902 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Andika nenosiri ili ufungue"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Andika PIN ili ufungue"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Nambari ya PIN si sahihi."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Betri imejaa"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Inachaji"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Inachaji kwa kasi"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 2c403f0..0ba941c 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"திறக்க, கடவுச்சொல்லை உள்ளிடவும்"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"திறக்க, பின்னை உள்ளிடவும்"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"தவறான பின் குறியீடு."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"செல்லாத சிம் கார்டு."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"சார்ஜ் செய்யப்பட்டது"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"சார்ஜ் ஆகிறது"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"வேகமாகச் சார்ஜாகிறது"</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index 23349ae..bbcc57e 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"అన్‌లాక్ చేయడానికి పాస్‌వర్డ్‌ను టైప్ చేయండి"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"అన్‌లాక్ చేయడానికి పిన్ టైప్ చేయండి"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"పిన్ కోడ్ తప్పు."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ఛార్జ్ చేయబడింది"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"ఛార్జ్ అవుతోంది"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"వేగంగా ఛార్జ్ అవుతోంది"</string>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index b4a54ce..46d6ba8 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"พิมพ์รหัสผ่านเพื่อปลดล็อก"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"พิมพ์ PIN เพื่อปลดล็อก"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"รหัส PIN ไม่ถูกต้อง"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"ชาร์จแล้ว"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"กำลังชาร์จ"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"กำลังชาร์จเร็ว"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 18e12ee..2998904 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"I-type ang password upang i-unlock"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"I-type ang PIN upang i-unlock"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Mali ang PIN code."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Tapos nang mag-charge"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Nagcha-charge"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Mabilis na nagcha-charge"</string>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 7cc7650..b80e4db 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Kilidi açmak için şifreyi yazın"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Kilidi açmak için PIN kodunu yazın"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Yanlış PIN kodu."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"Geçersiz Kart."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Ödeme alındı"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Şarj oluyor"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Hızlı şarj oluyor"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index fa56d3b..43d01b5 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Введіть пароль, щоб розблокувати"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Введіть PIN-код, щоб розблокувати"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Неправильний PIN-код."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Заряджено"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Заряджається"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Швидке заряджання"</string>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 320e545..4f09c98 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"غیر مقفل کرنے کیلئے پاس ورڈ ٹائپ کریں"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"‏غیر مقفل کرنے کیلئے PIN ٹائپ کریں"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"‏غلط PIN کوڈ۔"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"چارج ہوگئی"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"چارج ہو رہا ہے"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"تیزی سے چارج ہو رہا ہے"</string>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index 062eb82..699ad60 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -29,6 +29,7 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Qulfni ochish uchun parolni kiriting"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Qulfni ochish uchun PIN kodni kiriting"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN kodi xato."</string>
+    <string name="keyguard_sim_error_message_short" msgid="592109500618448312">"SIM karta yaroqsiz."</string>
     <string name="keyguard_charged" msgid="2222329688813033109">"Batareya quvvati to‘ldi"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Quvvatlash"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Tezkor quvvat olmoqda"</string>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 348f77e..6b32d3a 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Nhập mật khẩu để mở khóa"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Nhập mã PIN để mở khóa"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Mã PIN không chính xác."</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Đã sạc đầy"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Đang sạc"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Đang sạc nhanh"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 3dd328e..30737cc 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"输入密码即可解锁"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"输入 PIN 码即可解锁"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN 码有误。"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"已充满电"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"正在充电"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"正在快速充电"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index 9d7b7ce..1c159ca 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"輸入密碼即可解鎖"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"輸入 PIN 碼即可解鎖"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN 碼不正確。"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"已完成充電"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"正在充電"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"正在快速充電"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index db97648..a1d072a 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"輸入密碼即可解鎖"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"輸入 PIN 碼即可解鎖"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"PIN 碼不正確。"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"充電完成"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"充電中"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"快速充電中"</string>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index 6156e8a..4602237 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -29,6 +29,8 @@
     <string name="keyguard_password_enter_password_code" msgid="595980919238127672">"Bhala iphasiwedi ukuze kuvuleke"</string>
     <string name="keyguard_password_enter_pin_password_code" msgid="7504123374204446086">"Faka i-PIN ukuvula"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="6535018036285012028">"Ikhodi ye-PIN engalungile!"</string>
+    <!-- no translation found for keyguard_sim_error_message_short (592109500618448312) -->
+    <skip />
     <string name="keyguard_charged" msgid="2222329688813033109">"Kushajiwe"</string>
     <string name="keyguard_plugged_in" msgid="89308975354638682">"Iyashaja"</string>
     <string name="keyguard_plugged_in_charging_fast" msgid="8869226755413795173">"Ishaja ngokushesha"</string>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index b0f7d28..3dd0e6c 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -48,6 +48,9 @@
          to unlock the keyguard.  Displayed in one line in a large font.  -->
     <string name="keyguard_password_wrong_pin_code">Incorrect PIN code.</string>
 
+    <!-- Shown in the lock screen when there is SIM card IO error. -->
+    <string name="keyguard_sim_error_message_short">Invalid Card.</string>
+
     <!-- When the lock screen is showing, the phone is plugged in and the battery is fully
          charged, say that it is charged. -->
     <string name="keyguard_charged">Charged</string>
diff --git a/packages/SystemUI/res/drawable/recents_dismiss_all_history.xml b/packages/SystemUI/res/drawable/recents_dismiss_all_history.xml
deleted file mode 100644
index 6a417e6..0000000
--- a/packages/SystemUI/res/drawable/recents_dismiss_all_history.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<!--
-Copyright (C) 2016 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="16dp"
-    android:width="28dp"
-    android:viewportHeight="48"
-    android:viewportWidth="72" >
-    <group
-        android:name="dismiss_all"
-        android:translateX="48"
-        android:translateY="6" >
-        <group
-            android:name="3"
-            android:translateX="-24"
-            android:translateY="36" >
-            <path
-                android:name="rectangle_path_1_2"
-                android:pathData="M -24.0,-6.0 l 48.0,0 l 0,12.0 l -48.0,0 Z"
-                android:fillColor="#FFFFFFFF"
-                android:fillAlpha="1" />
-        </group>
-        <group
-            android:name="2"
-            android:translateX="-12"
-            android:translateY="18" >
-            <path
-                android:name="rectangle_path_1_1"
-                android:pathData="M -24.0,-6.0 l 48.0,0 l 0,12.0 l -48.0,0 Z"
-                android:fillColor="#FFFFFFFF"
-                android:fillAlpha="1" />
-        </group>
-        <group
-            android:name="1" >
-            <path
-                android:name="rectangle_path_1"
-                android:pathData="M -24.0,-6.0 l 48.0,0 l 0,12.0 l -48.0,0 Z"
-                android:fillColor="#FFFFFFFF"
-                android:fillAlpha="1" />
-        </group>
-    </group>
-</vector>
diff --git a/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml b/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
index db2eb3a..bff97f6 100644
--- a/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
+++ b/packages/SystemUI/res/drawable/recents_low_ram_stack_button_background.xml
@@ -17,6 +17,6 @@
 
       <corners android:radius="@dimen/borderless_button_radius" />
 
-      <solid android:color="#CC000000" />
+      <solid android:color="?attr/clearAllBackgroundColor" />
 
 </shape>
diff --git a/packages/SystemUI/res/layout/operator_name.xml b/packages/SystemUI/res/layout/operator_name.xml
new file mode 100644
index 0000000..c4f75e9
--- /dev/null
+++ b/packages/SystemUI/res/layout/operator_name.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/operator_name_frame"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    >
+    <com.android.systemui.statusbar.OperatorNameView
+        android:id="@+id/operator_name"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:maxLength="20"
+        android:gravity="center_vertical|start"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:singleLine="true" />
+</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 35a9477..b138df0 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -63,7 +63,8 @@
             android:layout_width="18dp"
             android:layout_height="match_parent"
             android:src="@drawable/qs_dual_tile_caret"
-            android:tint="?android:attr/textColorPrimary" />
+            android:tint="?android:attr/textColorPrimary"
+            android:visibility="gone" />
     </LinearLayout>
 
     <TextView
diff --git a/packages/SystemUI/res/layout/recents_task_view_header.xml b/packages/SystemUI/res/layout/recents_task_view_header.xml
index 5ee242d..1734506 100644
--- a/packages/SystemUI/res/layout/recents_task_view_header.xml
+++ b/packages/SystemUI/res/layout/recents_task_view_header.xml
@@ -66,14 +66,6 @@
         android:alpha="0"
         android:visibility="gone" />
 
-    <!-- The progress indicator shows if auto-paging is enabled -->
-    <ViewStub android:id="@+id/focus_timer_indicator_stub"
-               android:inflatedId="@+id/focus_timer_indicator"
-               android:layout="@layout/recents_task_view_header_progress_bar"
-               android:layout_width="match_parent"
-               android:layout_height="5dp"
-               android:layout_gravity="bottom" />
-
     <!-- The app overlay shows as the user long-presses on the app icon -->
     <ViewStub android:id="@+id/app_overlay_stub"
                android:inflatedId="@+id/app_overlay"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index c6452c0..6de27ac 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -48,6 +48,11 @@
         android:paddingEnd="8dp"
         android:orientation="horizontal"
         >
+        <ViewStub
+            android:id="@+id/operator_name"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout="@layout/operator_name" />
 
         <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
              PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 8296a1c..b8c4029 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Alle onlangse programme is toegemaak."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Maak <xliff:g id="APP">%s</xliff:g>-programinligting oop."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Begin tans <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Kennisgewing is toegemaak."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Kennisgewingskerm."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Vinnige instellings."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Verdeel skerm na bo"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Verdeel skerm na links"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Verdeel skerm na regs"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Gelaai"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Laai tans"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> tot vol"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 324c9ff..1ad6367 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"ሁሉም የቅርብ ጊዜ ማመልከቻዎች ተሰናብተዋል።"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"የ<xliff:g id="APP">%s</xliff:g> መተግበሪያ መረጃውን ይክፈቱ።"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> በመጀመር ላይ።"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"ማሳወቂያ ተወግዷል።"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"የማሳወቂያ ጥላ።"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ፈጣን ቅንብሮች።"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ማያ ገጽ ወደ ላይ ክፈል"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ማያ ገጽ ወደ ግራ ክፈል"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ማያ ገጽ ወደ ቀኝ ክፈል"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ባትሪ ሞልቷል"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ኃይል በመሙላት ላይ"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> እስኪሞላ ድረስ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 520df00..b5ac31a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -169,7 +169,7 @@
     <string name="accessibility_battery_level_charging" msgid="1147587904439319646">"جارٍ شحن البطارية، <xliff:g id="BATTERY_PERCENTAGE">%d</xliff:g> بالمائة."</string>
     <string name="accessibility_settings_button" msgid="799583911231893380">"إعدادات النظام."</string>
     <string name="accessibility_notifications_button" msgid="4498000369779421892">"الإشعارات."</string>
-    <string name="accessibility_overflow_action" msgid="5681882033274783311">"الاطلاع على جميع الإشعارات"</string>
+    <string name="accessibility_overflow_action" msgid="5681882033274783311">"الاطّلاع على جميع الإشعارات"</string>
     <string name="accessibility_remove_notification" msgid="3603099514902182350">"محو الإشعار."</string>
     <string name="accessibility_gps_enabled" msgid="3511469499240123019">"‏تم تمكين GPS."</string>
     <string name="accessibility_gps_acquiring" msgid="8959333351058967158">"‏الحصول على GPS."</string>
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"تم تجاهل كل التطبيقات المستخدمة مؤخرًا."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"فتح معلومات تطبيق <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"جارٍ بدء <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"تم تجاهل الإشعار."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"مركز الإشعارات."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"الإعدادات السريعة."</string>
@@ -351,8 +350,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"تقسيم الشاشة بمحاذاة الجزء العلوي"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"تقسيم الشاشة بمحاذاة اليسار"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"تقسيم الشاشة بمحاذاة اليمين"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"تم الشحن"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"جارٍ الشحن"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> حتى الاكتمال"</string>
@@ -480,7 +477,7 @@
     <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> قيد التشغيل"</string>
     <string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"سيظل الجهاز مقفلاً إلى أن يتم إلغاء قفله يدويًا"</string>
     <string name="hidden_notifications_title" msgid="7139628534207443290">"الحصول على الإشعارات بشكل أسرع"</string>
-    <string name="hidden_notifications_text" msgid="2326409389088668981">"الاطلاع عليها قبل إلغاء القفل"</string>
+    <string name="hidden_notifications_text" msgid="2326409389088668981">"الاطّلاع عليها قبل إلغاء القفل"</string>
     <string name="hidden_notifications_cancel" msgid="3690709735122344913">"لا، شكرًا"</string>
     <string name="hidden_notifications_setup" msgid="41079514801976810">"إعداد"</string>
     <string name="zen_mode_and_condition" msgid="4462471036429759903">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string>
@@ -659,7 +656,7 @@
     <string name="volume_dnd_silent" msgid="4363882330723050727">"اختصار أزرار مستوى الصوت"</string>
     <string name="volume_up_silent" msgid="7141255269783588286">"تعطيل \"عدم الإزعاج\" عند رفع مستوى الصوت"</string>
     <string name="battery" msgid="7498329822413202973">"البطارية"</string>
-    <string name="clock" msgid="7416090374234785905">"ساعة"</string>
+    <string name="clock" msgid="7416090374234785905">"الساعة"</string>
     <string name="headset" msgid="4534219457597457353">"سماعة الرأس"</string>
     <string name="accessibility_status_bar_headphones" msgid="9156307120060559989">"تم توصيل سماعات رأس"</string>
     <string name="accessibility_status_bar_headset" msgid="8666419213072449202">"تم توصيل سماعات رأس"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 9e49466..82fc76a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Bütün son tətbiqlər kənarlaşdırıldı."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> tətbiqi haqqında məlumatı açın."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> başlanır."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Bildiriş uzaqlaşdırıldı."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Bildiriş kölgəsi."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Tez ayarlar."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ekranı yuxarıdan ayırın"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ekranı soldan ayırın"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ekranı sağdan ayırın"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Dolub"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Enerji doldurulur"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> dolana kimi"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index bf2ff90..ce0c51f 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -181,7 +181,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Sve nedavno korišćene aplikacije su odbačene."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Otvorite informacije o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Pokrećemo <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Obaveštenje je odbačeno."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Prozor sa obaveštenjima."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Brza podešavanja."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podeli ekran nagore"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podeli ekran nalevo"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podeli ekran nadesno"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjena je"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Punjenje"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> dok se ne napuni"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 9006a5e..6de7852 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Усе апошнія праграмы адхілены."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Адкрыць інфармацыю пра праграму \"<xliff:g id="APP">%s</xliff:g>\"."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Запускаецца <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Апавяшчэнне прапушчана."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Цень апавяшчэння.."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Хуткія налады."</string>
@@ -349,8 +348,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Падзяліць экран зверху"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Падзяліць экран злева"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Падзяліць экран справа"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Зараджаны"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Зарадка"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> да поўнай зарадкі"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index c79b527..35b5177 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Всички скорошни приложения са отхвърлени."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Отворете информацията за приложението <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> се стартира."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Известието е отхвърлено."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Падащ панел с известия."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Бързи настройки."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Разделяне на екрана нагоре"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Разделяне на екрана наляво"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Разделяне на екрана надясно"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Заредена"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Зарежда се"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> до пълно зареждане"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 44b01e8..ab1b0a1 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"সমস্ত সাম্প্রতিক অ্যাপ্লিকেশন খারিজ করা হয়েছে।"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> অ্যাপ্লিকেশানের তথ্য খুলবে৷"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> তারাঙ্কিত করা হচ্ছে।"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"বিজ্ঞপ্তি খারিজ করা হয়েছে৷"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"বিজ্ঞপ্তি শেড৷"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"দ্রুত সেটিংস৷"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"স্ক্রিনটি উপরের দিকে বিভক্ত করুন"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"স্ক্রিনটি বাঁদিকে বিভক্ত করুন"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"স্ক্রিনটি ডানদিকে বিভক্ত করুন"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"চার্জ হয়েছে"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"চার্জ হচ্ছে"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"পূর্ণ হতে <xliff:g id="CHARGING_TIME">%s</xliff:g> সময় লাগবে"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 6c6eb36..5d621cb 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -181,7 +181,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Sve nedavno korištene aplikacije su odbačene."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Otvaranje informacija o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Pokrećem aplikaciju <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Obavještenje je uklonjeno."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Obavještenja sa sjenčenjem."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Brze postavke."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podijeli ekran nagore"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podijeli ekran nalijevo"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podijeli ekran nadesno"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjeno"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Punjenje"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Do kraja punjenja preostalo <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
@@ -704,7 +701,7 @@
     <string name="accessibility_action_divider_top_70" msgid="5090779195650364522">"Gore 70%"</string>
     <string name="accessibility_action_divider_top_50" msgid="6385859741925078668">"Gore 50%"</string>
     <string name="accessibility_action_divider_top_30" msgid="6201455163864841205">"Gore 30%"</string>
-    <string name="accessibility_action_divider_bottom_full" msgid="301433196679548001">"Dole cijeli ekran"</string>
+    <string name="accessibility_action_divider_bottom_full" msgid="301433196679548001">"Donji ekran kao cijeli ekran"</string>
     <string name="accessibility_qs_edit_tile_label" msgid="8374924053307764245">"Pozicija <xliff:g id="POSITION">%1$d</xliff:g>, <xliff:g id="TILE_NAME">%2$s</xliff:g>. Dvaput dodirnite za uređivanje."</string>
     <string name="accessibility_qs_edit_add_tile_label" msgid="8133209638023882667">"<xliff:g id="TILE_NAME">%1$s</xliff:g> Dvaput dodirnite za dodavanje."</string>
     <string name="accessibility_qs_edit_position_label" msgid="5055306305919289819">"Pozicija <xliff:g id="POSITION">%1$d</xliff:g>. Dvaput dodirnite za odabir."</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index e561239..28b0ffe 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"S\'han descartat totes les aplicacions recents."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Obre la informació sobre l\'aplicació <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"S\'està iniciant <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificació omesa."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Àrea de notificacions"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configuració ràpida"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Divideix la pantalla cap amunt"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Divideix la pantalla cap a l\'esquerra"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Divideix la pantalla cap a la dreta"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"S\'està carregant"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> per completar la càrrega"</string>
@@ -407,7 +404,7 @@
     <string name="battery_saver_notification_title" msgid="237918726750955859">"Estalvi de bateria activat"</string>
     <string name="battery_saver_notification_text" msgid="820318788126672692">"Redueix el rendiment i l\'ús de les dades en segon pla."</string>
     <string name="battery_saver_notification_action_text" msgid="109158658238110382">"Desactiva l\'estalvi de bateria"</string>
-    <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> començarà a enregistrar tot el que es mostri a la pantalla."</string>
+    <string name="media_projection_dialog_text" msgid="3071431025448218928">"<xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> començarà a gravar tot el que es mostri a la pantalla."</string>
     <string name="media_projection_remember_text" msgid="3103510882172746752">"No ho tornis a mostrar"</string>
     <string name="clear_all_notifications_text" msgid="814192889771462828">"Esborra-ho tot"</string>
     <string name="media_projection_action_text" msgid="8470872969457985954">"Comença ara"</string>
@@ -536,8 +533,8 @@
     <string name="activity_not_found" msgid="348423244327799974">"L\'aplicació no està instal·lada al dispositiu"</string>
     <string name="clock_seconds" msgid="7689554147579179507">"Mostra els segons del rellotge"</string>
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra els segons del rellotge a la barra d\'estat. Això pot afectar la durada de la bateria."</string>
-    <string name="qs_rearrange" msgid="8060918697551068765">"Reorganitza Configuració ràpida"</string>
-    <string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a Configuració ràpida"</string>
+    <string name="qs_rearrange" msgid="8060918697551068765">"Reorganitza la configuració ràpida"</string>
+    <string name="show_brightness" msgid="6613930842805942519">"Mostra la brillantor a configuració ràpida"</string>
     <string name="experimental" msgid="6198182315536726162">"Experimental"</string>
     <string name="enable_bluetooth_title" msgid="5027037706500635269">"Vols activar el Bluetooth?"</string>
     <string name="enable_bluetooth_message" msgid="9106595990708985385">"Per connectar el teclat amb la tauleta, primer has d\'activar el Bluetooth."</string>
@@ -705,7 +702,7 @@
     <string name="accessibility_qs_edit_tile_added" msgid="8050200862063548309">"<xliff:g id="TILE_NAME">%1$s</xliff:g> s\'ha afegit a la posició <xliff:g id="POSITION">%2$d</xliff:g>"</string>
     <string name="accessibility_qs_edit_tile_removed" msgid="8584304916627913440">"<xliff:g id="TILE_NAME">%1$s</xliff:g> s\'ha suprimit"</string>
     <string name="accessibility_qs_edit_tile_moved" msgid="4343693412689365038">"<xliff:g id="TILE_NAME">%1$s</xliff:g> s\'ha mogut a la posició <xliff:g id="POSITION">%2$d</xliff:g>"</string>
-    <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Editor de la configuració ràpida."</string>
+    <string name="accessibility_desc_quick_settings_edit" msgid="8073587401747016103">"Editor de configuració ràpida."</string>
     <string name="accessibility_desc_notification_icon" msgid="8352414185263916335">"Notificació de <xliff:g id="ID_1">%1$s</xliff:g>: <xliff:g id="ID_2">%2$s</xliff:g>"</string>
     <string name="dock_forced_resizable" msgid="5914261505436217520">"És possible que l\'aplicació no funcioni amb la pantalla dividida."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="3871617304250207291">"L\'aplicació no admet la pantalla dividida."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 38ac499..e014af6 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Všechny naposledy použité aplikace byly odstraněny."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Otevře informace o aplikaci <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Spouštění aplikace <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Oznámení je zavřeno."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Panel oznámení."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Rychlé nastavení."</string>
@@ -349,8 +348,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Rozdělit obrazovku nahoru"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Rozdělit obrazovku vlevo"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Rozdělit obrazovku vpravo"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nabito"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Nabíjení"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do plného nabití"</string>
@@ -758,7 +755,7 @@
     <string name="lockscreen_unlock_left" msgid="2043092136246951985">"Zkratka vlevo také odemyká"</string>
     <string name="lockscreen_unlock_right" msgid="1529992940510318775">"Zkratka vpravo také odemyká"</string>
     <string name="lockscreen_none" msgid="4783896034844841821">"Žádné"</string>
-    <string name="tuner_launch_app" msgid="1527264114781925348">"Spustit aplikaci <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="tuner_launch_app" msgid="1527264114781925348">"Do aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="tuner_other_apps" msgid="4726596850501162493">"Další aplikace"</string>
     <string name="tuner_circle" msgid="2340998864056901350">"Kruh"</string>
     <string name="tuner_plus" msgid="6792960658533229675">"Plus"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 24d2976..58e9a2f 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Alle de seneste applikationer er lukket."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Åbn appoplysningerne for <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> startes."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Underretningen er annulleret."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Underretningspanel."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hurtige indstillinger."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Delt skærm øverst"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Delt skærm til venstre"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Delt skærm til højre"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Opladet"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Oplader"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> indtil fuld opladet"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 46dcf3e..2f615fe 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Alle kürzlich verwendeten Apps wurden entfernt."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Infos zur App \"<xliff:g id="APP">%s</xliff:g>\" öffnen."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> wird gestartet."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Benachrichtigung geschlossen"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Benachrichtigungsleiste"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Schnelleinstellungen"</string>
@@ -347,8 +346,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Geteilten Bildschirm oben anzeigen"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Geteilten Bildschirm auf linker Seite anzeigen"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Geteilten Bildschirm auf der rechten Seite anzeigen"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Aufgeladen"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Wird aufgeladen"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Voll in <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 92a0976..ef47851 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -180,11 +180,10 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Έγινε παράβλεψη όλων των πρόσφατων εφαρμογών."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Άνοιγμα πληροφοριών εφαρμογής <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Έναρξη <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Η ειδοποίηση έχει απορριφθεί."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Πλαίσιο σκίασης ειδοποιήσεων."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Γρήγορες ρυθμίσεις."</string>
-    <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Κλείδωμα οθόνης."</string>
+    <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"Οθόνη κλειδώματος"</string>
     <string name="accessibility_desc_settings" msgid="3417884241751434521">"Ρυθμίσεις"</string>
     <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"Επισκόπηση."</string>
     <string name="accessibility_desc_work_lock" msgid="4288774420752813383">"Οθόνη κλειδωμένης εργασίας"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Διαχωρισμός οθόνης στην κορυφή"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Διαχωρισμός οθόνης στα αριστερά"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Διαχωρισμός οθόνης στα δεξιά"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Φορτίστηκε"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Φόρτιση"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> για πλήρη φόρτιση"</string>
@@ -721,7 +718,7 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"Άνοιγμα ρυθμίσεων <xliff:g id="ID_1">%s</xliff:g>."</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"Επεξεργασία σειράς ρυθμίσεων."</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"Σελίδα <xliff:g id="ID_1">%1$d</xliff:g> από <xliff:g id="ID_2">%2$d</xliff:g>"</string>
-    <string name="tuner_lock_screen" msgid="5755818559638850294">"Κλείδωμα οθόνης"</string>
+    <string name="tuner_lock_screen" msgid="5755818559638850294">"Οθόνη κλειδώματος"</string>
     <string name="pip_phone_expand" msgid="5889780005575693909">"Ανάπτυξη"</string>
     <string name="pip_phone_minimize" msgid="1079119422589131792">"Ελαχιστοποίηση"</string>
     <string name="pip_phone_close" msgid="8416647892889710330">"Κλείσιμο"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 39af7ec..e1326de 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎All recent applications dismissed.‎‏‎‎‏‎"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‎Open ‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎ application info.‎‏‎‎‏‎"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎Starting ‎‏‎‎‏‏‎<xliff:g id="APP">%s</xliff:g>‎‏‎‎‏‏‏‎.‎‏‎‎‏‎"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ ‎‏‎‎‏‏‎<xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎Notification dismissed.‎‏‎‎‏‎"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎Notification shade.‎‏‎‎‏‎"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎Quick settings.‎‏‎‎‏‎"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎Split screen to the top‎‏‎‎‏‎"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‏‎Split screen to the left‎‏‎‎‏‎"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‏‏‎Split screen to the right‎‏‎‎‏‎"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‏‏‎Charged‎‏‎‎‏‎"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎Charging‎‏‎‎‏‎"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME">%s</xliff:g>‎‏‎‎‏‏‏‎ until full‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 8db0a99..9dd1b01 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Se descartaron todas las aplicaciones recientes."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Abre la información de la aplicación de <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificación ignorada"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Pantalla de notificaciones"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configuración rápida"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir pantalla en la parte superior"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir pantalla a la izquierda"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir pantalla a la derecha"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Cargando"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> para completarse"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 6eabfc0..52e7c51 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Se han ignorado todas las aplicaciones recientes."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Abre la información de la aplicación <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificación ignorada"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Pantalla de notificaciones"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Ajustes rápidos"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir la pantalla en la parte superior"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir la pantalla a la izquierda"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir la pantalla a la derecha"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Cargando"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> para completarse"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 2fc49ff..2b37558 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Kõikidest hiljutistest rakendustest on loobutud"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Rakenduse <xliff:g id="APP">%s</xliff:g> teabe avamine."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Rakenduse <xliff:g id="APP">%s</xliff:g> käivitamine."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g>, <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Märguandest on loobutud."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Märguande vari."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Kiirseaded."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Poolita ekraan üles"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Poolita ekraan vasakule"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Poolita ekraan paremale"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Laetud"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Laadimine"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Täislaadimiseks kulub <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index cd0e9a8..a7b5938 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Azken aplikazio guztiak baztertu da."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Ireki <xliff:g id="APP">%s</xliff:g> aplikazioari buruzko informazioa."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> hasten."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Jakinarazpena baztertu da."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Jakinarazpenen panela."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Ezarpen bizkorrak."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Zatitu pantaila eta ezarri goian"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Zatitu pantaila eta ezarri ezkerrean"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Zatitu pantaila eta ezarri eskuinean"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Kargatuta"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Kargatzen"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> falta zaizkio guztiz kargatzeko"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index b3e1b4e..64e34a9 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"همه برنامه‌های اخیر رد شدند."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"باز کردن اطلاعات برنامه <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> در حال شروع به کار است."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"اعلان ردشد."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"مجموعه اعلان."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"تنظیمات سریع."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"تقسیم کردن صفحه به بالا"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"تقسیم کردن صفحه به چپ"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"تقسیم کردن صفحه به راست"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"شارژ کامل شد"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"در حال شارژ شدن"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> مانده تا شارژ کامل شود"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 2dc96d6..7b6dc54 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Kaikki viimeisimmät sovellukset on hylätty."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Avaa sovelluksen <xliff:g id="APP">%s</xliff:g> tiedot."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Käynnistetään <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Ilmoitus hylätty."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Ilmoitusalue."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Pika-asetukset."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Jaa näyttö ylös"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Jaa näyttö vasemmalle"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Jaa näyttö oikealle"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Ladattu"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Ladataan"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> kunnes täynnä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 6cab2ec..2344c00 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Toutes les applications récentes ont été supprimées."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Ouvre les détails de l\'application <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Lancement de <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notification masquée"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Volet des notifications"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Paramètres rapides"</string>
@@ -336,7 +335,7 @@
     <string name="recents_lock_to_app_button_label" msgid="6942899049072506044">"épinglage d\'écran"</string>
     <string name="recents_search_bar_label" msgid="8074997400187836677">"rechercher"</string>
     <string name="recents_launch_error_message" msgid="2969287838120550506">"Impossible de lancer <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="recents_launch_disabled_message" msgid="1624523193008871793">"<xliff:g id="APP">%s</xliff:g> est désactivée en mode sécurisé."</string>
+    <string name="recents_launch_disabled_message" msgid="1624523193008871793">"<xliff:g id="APP">%s</xliff:g> est désactivée en mode sans échec."</string>
     <string name="recents_stack_action_button_label" msgid="6593727103310426253">"Effacer tout"</string>
     <string name="recents_drag_hint_message" msgid="2649739267073203985">"Glissez l\'élément ici pour utiliser l\'écran partagé"</string>
     <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Séparation horizontale"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Écran partagé dans le haut"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Écran partagé à la gauche"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Écran partagé à la droite"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Chargée"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Charge en cours..."</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Chargée dans <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index e14f8be..4b4e5b7 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Toutes les applications récentes ont été supprimées."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Ouvre les informations sur l\'application <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Lancement de <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> : <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notification masquée"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Volet des notifications"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Paramètres rapides"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Partager l\'écran en haut"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Partager l\'écran sur la gauche"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Partager l\'écran sur la droite"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Chargé"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"En charge"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Chargé dans <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 70ee57a..7f11ba2 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Rexeitáronse todas as aplicacións recentes."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Abre a información da aplicación <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificación rexeitada"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Sombra de notificación"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configuración rápida"</string>
@@ -288,7 +287,7 @@
     <string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localización desactivada"</string>
     <string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispositivo multimedia"</string>
     <string name="quick_settings_rssi_label" msgid="7725671335550695589">"RSSI"</string>
-    <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Só chamadas de emerxencia"</string>
+    <string name="quick_settings_rssi_emergency_only" msgid="2713774041672886750">"Só chamadas de urxencia"</string>
     <string name="quick_settings_settings_label" msgid="5326556592578065401">"Configuración"</string>
     <string name="quick_settings_time_label" msgid="4635969182239736408">"Hora"</string>
     <string name="quick_settings_user_label" msgid="5238995632130897840">"Eu"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir pantalla na parte superior"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir pantalla á esquerda"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir pantalla á dereita"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Cargada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Cargando"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> para completar a carga"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index c04da24..2dbd57b 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"તમામ તાજેતરની ઍપ્લિકેશનો કાઢી નાખી."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ઍપ્લિકેશન માહિતી ખોલો."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> પ્રારંભ કરી રહ્યું છે."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"સૂચના કાઢી નાખી."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"નોટિફિકેશન શેડ."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ઝડપી સેટિંગ્સ."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"સ્ક્રીનને ઉપરની તરફ વિભાજિત કરો"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"સ્ક્રીનને ડાબી તરફ વિભાજિત કરો"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"સ્ક્રીનને જમણી તરફ વિભાજિત કરો"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ચાર્જ થઈ ગયું"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ચાર્જ થઈ રહ્યું છે"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"પૂર્ણ થવામાં <xliff:g id="CHARGING_TIME">%s</xliff:g> બાકી"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 0a5c736..93dadd2 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"हाल ही के सभी ऐप्लिकेशन ख़ारिज कर दिए गए."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ऐप्लिकेशन की जानकारी खोलें."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> प्रारंभ हो रहा है."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"सूचना खारिज की गई."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"सूचना शेड."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"त्वरित सेटिंग."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ऊपर की ओर दो स्क्रीन बनाएं"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"बाईं ओर दो स्क्रीन बनाएं"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"दाईं ओर दो स्क्रीन बनाएं"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज हो गई है"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"चार्ज हो रही है"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"पूर्ण होने में <xliff:g id="CHARGING_TIME">%s</xliff:g> शेष"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index adb41b3..43aa345 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -181,7 +181,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Odbačene su sve nedavne aplikacije."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Otvaranje informacija o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Pokretanje aplikacije <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Obavijest je odbačena."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Zaslon obavijesti."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Brze postavke."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podijeli zaslon na vrhu"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podijeli zaslon slijeva"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podijeli zaslon zdesna"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Napunjeno"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Punjenje"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do napunjenosti"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 609992f..4d8b697 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Az összes alkalmazás eltávolítva a nemrég használtak közül."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"A(z) <xliff:g id="APP">%s</xliff:g> alkalmazás adatainak megnyitása."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"A(z) <xliff:g id="APP">%s</xliff:g> indítása."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Értesítés elvetve."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Értesítési felület."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Gyorsbeállítások."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Osztott képernyő felülre"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Osztott képernyő balra"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Osztott képernyő jobbra"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Feltöltve"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Töltés"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> a teljes töltöttségig"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 0022f7b..923e4ec 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Բոլոր վերջին հավելվածները հեռացվել են ցուցակից:"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Բացել <xliff:g id="APP">%s</xliff:g> հավելվածի մասին տեղեկությունները"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Մեկնարկել <xliff:g id="APP">%s</xliff:g>-ը:"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Ծանուցումը անտեսվեց:"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Ծանուցումների վահանակ:"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Արագ կարգավորումներ:"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Տրոհել էկրանը վերևից"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Տրոհել էկրանը ձախից"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Տրոհել էկրանն աջից"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Լիցքավորված է"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Լիցքավորվում է"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Լրիվ լիցքավորմանը մնաց <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 0411fa9..27d5f1b 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Semua aplikasi terbaru telah ditutup."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Buka info aplikasi <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Memulai <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notifikasi disingkirkan."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Bayangan pemberitahuan."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Setelan cepat."</string>
@@ -234,8 +233,8 @@
     <string name="accessibility_quick_settings_work_mode_on" msgid="7650588553988014341">"Mode kerja aktif."</string>
     <string name="accessibility_quick_settings_work_mode_changed_off" msgid="5605534876107300711">"Mode kerja dinonaktifkan."</string>
     <string name="accessibility_quick_settings_work_mode_changed_on" msgid="249840330756998612">"Mode kerja diaktifkan."</string>
-    <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Penghemat Data nonaktif."</string>
-    <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Penghemat Data diaktifkan."</string>
+    <string name="accessibility_quick_settings_data_saver_changed_off" msgid="650231949881093289">"Penghemat Kuota Internet nonaktif."</string>
+    <string name="accessibility_quick_settings_data_saver_changed_on" msgid="4218725402373934151">"Penghemat Kuota Internet diaktifkan."</string>
     <string name="accessibility_brightness" msgid="8003681285547803095">"Kecerahan tampilan"</string>
     <string name="accessibility_ambient_display_charging" msgid="9084521679384069087">"Mengisi daya"</string>
     <string name="data_usage_disabled_dialog_3g_title" msgid="5281770593459841889">"Data 2G-3G dijeda"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Pisahkan layar ke atas"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Pisahkan layar ke kiri"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Pisahkan layar ke kanan"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Terisi"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Mengisi daya"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> sampai penuh"</string>
@@ -639,9 +636,9 @@
     <string name="headset" msgid="4534219457597457353">"Headset"</string>
     <string name="accessibility_status_bar_headphones" msgid="9156307120060559989">"Headphone terhubung"</string>
     <string name="accessibility_status_bar_headset" msgid="8666419213072449202">"Headset terhubung"</string>
-    <string name="data_saver" msgid="5037565123367048522">"Penghemat Data"</string>
-    <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Penghemat Data aktif"</string>
-    <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Penghemat Data nonaktif"</string>
+    <string name="data_saver" msgid="5037565123367048522">"Penghemat Kuota Internet"</string>
+    <string name="accessibility_data_saver_on" msgid="8454111686783887148">"Penghemat Kuota Internet aktif"</string>
+    <string name="accessibility_data_saver_off" msgid="8841582529453005337">"Penghemat Kuota Internet nonaktif"</string>
     <string name="switch_bar_on" msgid="1142437840752794229">"Aktif"</string>
     <string name="switch_bar_off" msgid="8803270596930432874">"Nonaktif"</string>
     <string name="nav_bar" msgid="1993221402773877607">"Bilah navigasi"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index eefb363..58bdc3b 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Öll nýleg forrit fjarlægð."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Opna forritsupplýsingar <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Ræsir <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Tilkynningu lokað."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Tilkynningasvæði."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Flýtistillingar."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Skipta skjá að ofanverðu"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Skipta skjá til vinstri"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Skipta skjá til hægri"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Fullhlaðin"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Í hleðslu"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> þar til fullri hleðslu er náð"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 915f40a..56d5348 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Tutte le applicazioni recenti sono state rimosse."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Mostra informazioni sull\'applicazione <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Avvio di <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notifica eliminata."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Area notifiche."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Impostazioni rapide."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Schermo diviso in alto"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Schermo diviso a sinistra"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Schermo diviso a destra"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carica"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"In carica"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> al termine della carica"</string>
@@ -506,7 +503,7 @@
     <string name="volume_stream_content_description_mute_a11y" msgid="8995013018414535494">"%1$s. Tocca per disattivare l\'audio."</string>
     <string name="volume_dialog_accessibility_shown_message" msgid="1834631467074259998">"%s comandi del volume mostrati. Fai scorrere verso l\'alto per ignorare."</string>
     <string name="volume_dialog_accessibility_dismissed_message" msgid="51543526013711399">"Comandi del volume nascosti"</string>
-    <string name="system_ui_tuner" msgid="708224127392452018">"Sintetizzatore interfaccia utente di sistema"</string>
+    <string name="system_ui_tuner" msgid="708224127392452018">"Ottimizzatore UI di sistema"</string>
     <string name="show_battery_percentage" msgid="5444136600512968798">"Mostra percentuale batteria incorporata"</string>
     <string name="show_battery_percentage_summary" msgid="3215025775576786037">"Mostra la percentuale di carica della batteria nell\'icona della barra di stato quando non è in carica"</string>
     <string name="quick_settings" msgid="10042998191725428">"Impostazioni rapide"</string>
@@ -529,12 +526,12 @@
     <string name="accessibility_status_bar_hotspot" msgid="4099381329956402865">"Hotspot"</string>
     <string name="accessibility_managed_profile" msgid="6613641363112584120">"Profilo di lavoro"</string>
     <string name="tuner_warning_title" msgid="7094689930793031682">"Il divertimento riservato a pochi eletti"</string>
-    <string name="tuner_warning" msgid="8730648121973575701">"Il sintetizzatore interfaccia utente di sistema mette a disposizione altri metodi per modificare e personalizzare l\'interfaccia utente di Android. Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
+    <string name="tuner_warning" msgid="8730648121973575701">"L\'Ottimizzatore UI di sistema mette a disposizione altri metodi per modificare e personalizzare l\'interfaccia utente di Android. Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
     <string name="tuner_persistent_warning" msgid="8597333795565621795">"Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string>
     <string name="got_it" msgid="2239653834387972602">"OK"</string>
-    <string name="tuner_toast" msgid="603429811084428439">"Complimenti! Il sintetizzatore interfaccia utente di sistema è stato aggiunto alle impostazioni."</string>
+    <string name="tuner_toast" msgid="603429811084428439">"Complimenti! L\'Ottimizzatore UI di sistema è stato aggiunto alle impostazioni."</string>
     <string name="remove_from_settings" msgid="8389591916603406378">"Rimuovi dalle impostazioni"</string>
-    <string name="remove_from_settings_prompt" msgid="6069085993355887748">"Vuoi rimuovere il sintetizzatore interfaccia utente di sistema dalle impostazioni e smettere di utilizzare tutte le sue funzioni?"</string>
+    <string name="remove_from_settings_prompt" msgid="6069085993355887748">"Vuoi rimuovere l\'Ottimizzatore UI di sistema dalle impostazioni e smettere di utilizzare tutte le sue funzioni?"</string>
     <string name="activity_not_found" msgid="348423244327799974">"Applicazione non installata sul dispositivo"</string>
     <string name="clock_seconds" msgid="7689554147579179507">"Mostra i secondi nell\'orologio"</string>
     <string name="clock_seconds_desc" msgid="6282693067130470675">"Mostra i secondi nell\'orologio nella barra di stato. Ciò potrebbe ridurre la durata della carica della batteria."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index c51c025..eb85099 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"כל האפליקציות האחרונות נסגרו."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"פתח מידע על האפליקציה <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"מפעיל את <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"הודעה נדחתה."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"לוח הודעות."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"הגדרות מהירות."</string>
@@ -347,8 +346,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"פיצול מסך למעלה"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"פיצול מסך לשמאל"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"פיצול מסך לימין"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"טעון"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"טוען"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> עד למילוי"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index e400fff..ac29ca7 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"最近のアプリケーションをすべて消去しました。"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"「<xliff:g id="APP">%s</xliff:g>」のアプリ情報を開きます。"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g>を開始しています。"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"通知が削除されました。"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"通知シェード"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"クイック設定"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"画面を上に分割"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"画面を左に分割"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"画面を右に分割"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"充電が完了しました"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"充電しています"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"充電完了まで<xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 55a0b5f..6750756 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"ყველა ბოლო აპლიკაცია გაუქმდა."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> აპლიკაციის ინფორმაციის გახსნა."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> იწყება."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"შეტყობინება წაიშალა."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"შეტყობინებების ფარდა"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"სწრაფი პარამეტრები"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ეკრანის გაყოფა ზემოთ"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ეკრანის გაყოფა მარცხნივ"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ეკრანის გაყოფა მარჯვნივ"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"დატენილია"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"მიმდინარეობს დატენვა"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> სრულად დატენვამდე"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 8937b7f..38d84c4 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Барлық жақындағы қабылданбаған қолданбалар."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> қолданбасы туралы ақпаратты ашады."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> іске қосылуда."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Хабар алынып тасталды."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Хабарландыру тақтасы"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Жылдам параметрлер."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Экранды жоғарыға қарай бөлу"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Экранды солға қарай бөлу"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Экранды оңға қарай бөлу"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Зарядталды"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Зарядталуда"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Толғанға дейін <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index f50003c..097d21d 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"កម្មវិធីថ្មីៗទាំងអស់ត្រូវបានបោះបង់។"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"បើកព័ត៌មានកម្មវិធី <xliff:g id="APP">%s</xliff:g>"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"ចាប់ផ្ដើម <xliff:g id="APP">%s</xliff:g> ។"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"បាន​បដិសេធ​ការ​ជូនដំណឹង"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"ពណ៌​ការ​ជូន​ដំណឹង"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ការ​កំណត់​រហ័ស។"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"បំបែក​អេក្រង់​ទៅ​ខាងលើ"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"បំបែក​អេក្រង់​ទៅ​ខាងឆ្វេង"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"បំបែក​អេក្រង់​ទៅ​ខាងស្តាំ"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"បាន​បញ្ចូល​ថ្ម​​"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"កំពុងសាក​ថ្ម"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> រហូត​ដល់ពេញ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 087da86..ca1a650 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"ಇತ್ತೀಚಿನ ಎಲ್ಲಾ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ವಜಾಗೊಳಿಸಲಾಗಿದೆ."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ಅಪ್ಲಿಕೇಶನ್ ಮಾಹಿತಿ ತೆರೆಯಿರಿ."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"ಅಧಿಸೂಚನೆ ವಜಾಗೊಂಡಿದೆ."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"ಅಧಿಸೂಚನೆಯ ಛಾಯೆ."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ತ್ವರಿತ ಸೆಟ್ಟಿಂಗ್‍ಗಳು."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ಮೇಲ್ಭಾಗಕ್ಕೆ ಪರದೆಯನ್ನು ವಿಭಜಿಸಿ"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ಎಡಕ್ಕೆ ಪರದೆಯನ್ನು ವಿಭಜಿಸಿ"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ಬಲಕ್ಕೆ ಪರದೆಯನ್ನು ವಿಭಜಿಸಿ"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ಚಾರ್ಜ್ ಆಗಿದೆ"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ಪೂರ್ಣಗೊಳ್ಳುವವರೆಗೆ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index ff3100b..ddc201b 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"최근 사용한 애플리케이션을 모두 닫았습니다."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> 애플리케이션 정보를 엽니다."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g>을(를) 시작하는 중입니다."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"알림이 제거되었습니다."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"알림 세부정보"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"빠른 설정"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"위쪽으로 화면 분할"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"왼쪽으로 화면 분할"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"오른쪽으로 화면 분할"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"충전됨"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"충전 중"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"완충까지 <xliff:g id="CHARGING_TIME">%s</xliff:g> 남음"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 8a41051..cbd3ec02 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Акыркы колдонмолордун баары көз жаздымда калтырылды."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> колдонмосу жөнүндө маалыматты ачыңыз."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> иштеп баштоодо."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Эскертме жок кылынды."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Эскертмелер көшөгөсү."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Тез тууралоолор."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Экранды өйдө жакка бөлүү"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Экранды сол жакка бөлүү"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Экранды оң жакка бөлүү"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Кубатталды"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Кубатталууда"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> толгонго чейин"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index ee627d7..774a123 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Atsisakyta visų naujausių programų."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Atidaryti programos „<xliff:g id="APP">%s</xliff:g>“ informaciją."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Paleidžiama <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"„<xliff:g id="APP">%1$s</xliff:g>“ <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Pranešimo atsisakyta."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Pranešimų gaubtas."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Spartieji nustatymai."</string>
@@ -347,8 +346,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Skaidyti ekraną į viršų"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Skaidyti ekraną į kairę"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Skaidyti ekraną į dešinę"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Įkrautas"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Kraunamas"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> iki visiško įkrovimo"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index e101ff0..dfbd0d2 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -181,7 +181,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Visas nesen izmantotās lietojumprogrammas tika noņemtas."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Atveriet lietojumprogrammas <xliff:g id="APP">%s</xliff:g> informāciju."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Notiek lietotnes <xliff:g id="APP">%s</xliff:g> palaišana."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Paziņojums netiek rādīts."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Paziņojumu panelis"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Ātrie iestatījumi"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Sadalīt ekrānu augšdaļā"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Sadalīt ekrānu kreisajā pusē"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Sadalīt ekrānu labajā pusē"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Akumulators uzlādēts"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Notiek uzlāde"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> līdz pilnam akumulatoram"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 11673fd..d62775c 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Сите неодамнешни апликации се отфрлени."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Отвори информации за апликацијата <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Се стартува <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Известувањето е отфрлено."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Панел за известување"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Брзи поставки."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Поделен екран во горниот дел"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Поделен екран на левата страна"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Поделен екран на десната страна"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Наполнета"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Се полни"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> додека не се наполни"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index fd78108..855ae6a 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"അടുത്തിടെയുള്ള എല്ലാ അപ്ലിക്കേഷനും നിരസിച്ചു."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ആപ്പ് വിവരങ്ങൾ തുറക്കുക."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> ആരംഭിക്കുന്നു."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"അറിയിപ്പ് നിരസിച്ചു."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"അറിയിപ്പ് ഷെയ്‌ഡ്."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ദ്രുത ക്രമീകരണങ്ങൾ."</string>
@@ -188,7 +187,7 @@
     <string name="accessibility_desc_settings" msgid="3417884241751434521">"ക്രമീകരണം"</string>
     <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"അവലോകനം."</string>
     <string name="accessibility_desc_work_lock" msgid="4288774420752813383">"ഔദ്യോഗിക ലോക്ക് സ്ക്രീൻ"</string>
-    <string name="accessibility_desc_close" msgid="7479755364962766729">"അടയ്‌ക്കുക"</string>
+    <string name="accessibility_desc_close" msgid="7479755364962766729">"അവസാനിപ്പിക്കുക"</string>
     <string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>."</string>
     <string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"വൈഫൈ ഓഫാക്കി."</string>
     <string name="accessibility_quick_settings_wifi_changed_on" msgid="6440117170789528622">"വൈഫൈ ഓണാക്കി."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"സ്ക്രീൻ മുകളിലേക്ക് സ്പ്ലിറ്റ് ചെയ്യുക"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"സ്ക്രീൻ ഇടതുവശത്തേക്ക് സ്പ്ലിറ്റ് ചെയ്യുക"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"സ്ക്രീൻ വലതുവശത്തേക്ക് പിളർത്തുക"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ചാർജായി"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ചാർജ്ജുചെയ്യുന്നു"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"ഫുൾ ചാർജാകാൻ, <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
@@ -724,7 +721,7 @@
     <string name="tuner_lock_screen" msgid="5755818559638850294">"ലോക്ക് സ്‌ക്രീൻ"</string>
     <string name="pip_phone_expand" msgid="5889780005575693909">"വികസിപ്പിക്കുക"</string>
     <string name="pip_phone_minimize" msgid="1079119422589131792">"ചെറുതാക്കുക‍"</string>
-    <string name="pip_phone_close" msgid="8416647892889710330">"അടയ്‌ക്കുക"</string>
+    <string name="pip_phone_close" msgid="8416647892889710330">"അവസാനിപ്പിക്കുക"</string>
     <string name="pip_phone_dismiss_hint" msgid="6351678169095923899">"തള്ളിക്കളയാൻ താഴേക്ക് വലിച്ചിടുക"</string>
     <string name="pip_menu_title" msgid="4707292089961887657">"മെനു"</string>
     <string name="pip_notification_title" msgid="3204024940158161322">"<xliff:g id="NAME">%s</xliff:g> ചിത്രത്തിനുള്ളിലെ ചിത്രത്തിലാണ്"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 8455c5a..c9d1d31 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -178,7 +178,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Хамгийн сүүлийн бүх програмыг арилгасан байна."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> апп-н мэдээллийг нээнэ үү."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g>-г эхлүүлж байна."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Мэдэгдэл хаагдсан."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Мэдэгдлийн хураангуй самбар"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Шуурхай тохиргоо."</string>
@@ -341,8 +340,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Дэлгэцийг дээд хэсэгт хуваах"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Дэлгэцийг зүүн хэсэгт хуваах"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Дэлгэцийг баруун хэсэгт хуваах"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Цэнэглэгдсэн"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Цэнэглэж байна"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"дүүргэхэд <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 0503b48..b5975b1 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"अलीकडील सर्व अॅप्लिकेशन डिसमिस झाले."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> अॅप्लिकेशन माहिती उघडा."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> प्रारंभ करीत आहे."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"सूचना डिसमिस केल्या."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"सूचना शेड."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"द्रुत सेटिंग्ज."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"स्क्रीन शीर्षस्थानी विभाजित करा"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"स्क्रीन डावीकडे विभाजित करा"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"स्क्रीन उजवीकडे विभाजित करा"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज झाली"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"चार्ज होत आहे"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> पूर्ण होईपर्यंत"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 1a1d337..fad7563 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Semua aplikasi terbaharu diketepikan."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Buka maklumat aplikasi <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Memulakan <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Pemberitahuan diketepikan."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Bidai pemberitahuan."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Tetapan pantas."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Pisahkan skrin ke atas"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Pisahkan skrin ke kiri"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Pisahkan skrin ke kanan"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Sudah dicas"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Mengecas"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Lagi <xliff:g id="CHARGING_TIME">%s</xliff:g> untuk penuh"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 89ea9f1..faa1f1a 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -79,7 +79,7 @@
     <string name="usb_preference_title" msgid="6551050377388882787">"USB ဖိုင်ပြောင်း ရွေးမှုများ"</string>
     <string name="use_mtp_button_title" msgid="4333504413563023626">"မီဒီယာပလေရာအနေဖြင့် တပ်ဆင်ရန် (MTP)"</string>
     <string name="use_ptp_button_title" msgid="7517127540301625751">"ကင်မရာအနေဖြင့် တပ်ဆင်ရန် (PTP)"</string>
-    <string name="installer_cd_button_title" msgid="2312667578562201583">"Macအတွက်Andriodဖိုင်ပြောင်းအပ်ပလီကေးရှင်းထည့်ခြင်း"</string>
+    <string name="installer_cd_button_title" msgid="2312667578562201583">"Mac အတွက် Android File Transfer အက်ပ်ထည့်ခြင်း"</string>
     <string name="accessibility_back" msgid="567011538994429120">"နောက်သို့"</string>
     <string name="accessibility_home" msgid="8217216074895377641">"ပင်မစာမျက်နှာ"</string>
     <string name="accessibility_menu" msgid="316839303324695949">"မီနူး"</string>
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"မကြာသေးမီက အပလီကေးရှင်းများအားလုံး ဖယ်ထုတ်ပြီးပါပြီ။"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> အပလီကေးရှင်းအချက်အလက်ကို ဖွင့်ပါ။"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g>ကို စတင်နေသည်။"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g><xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"အကြောင်းကြားချက်ကိုဖယ်ရှားပြီး"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"အ​ကြောင်းကြားစာအကွက်"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"အမြန်လုပ် အပြင်အဆင်"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"မျက်နှာပြင်ကို အပေါ်သို့ ခွဲရန်"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"မျက်နှာပြင်ကို ဘယ်ဘက်သို့ ခွဲရန်"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"မျက်နှာပြင်ကို ညာဘက်သို့ ခွဲရန်"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"အားသွင်းပြီး"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"အားသွင်းနေ"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ပြည်သည့် အထိ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 87c71bc..77af297 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Alle nylig brukte apper er avvist."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Åpne appinformasjonen for <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Starter <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Varselet ble skjult."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Varselskygge."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hurtiginnstillinger."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Delt skjerm øverst"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Delt skjerm til venstre"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Delt skjerm til høyre"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Oppladet"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Lader"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Fulladet om <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 63c1b79..b2bf111 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -69,7 +69,7 @@
     <string name="compat_mode_off" msgid="4434467572461327898">"स्क्रिन भर्न तन्काउनुहोस्"</string>
     <string name="screenshot_saving_ticker" msgid="7403652894056693515">"स्क्रिनसट बचत गर्दै…"</string>
     <string name="screenshot_saving_title" msgid="8242282144535555697">"स्क्रिनसट बचत गर्दै…"</string>
-    <string name="screenshot_saving_text" msgid="2419718443411738818">"स्क्रिनसट बचत हुँदै छ।"</string>
+    <string name="screenshot_saving_text" msgid="2419718443411738818">"स्क्रिनसट बचत हुँदैछ।"</string>
     <string name="screenshot_saved_title" msgid="6461865960961414961">"स्क्रिनसट क्याप्चर गरियो।"</string>
     <string name="screenshot_saved_text" msgid="2685605830386712477">"आफ्नो स्क्रिनसट हेर्न ट्याप गर्नुहोस्।"</string>
     <string name="screenshot_failed_title" msgid="705781116746922771">"स्क्रिनसट क्याप्चर गर्न सकिएन।"</string>
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"सबै हालका अनुप्रयोगहरू खारेज गरियो।"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> अनुप्रयोग सम्बन्धी जानकारी खोल्नुहोस्।"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g>सुरु गर्दै।"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"सूचना खारेज।"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"सूचना कक्ष।"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"द्रुत सेटिङहरू"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"विभाजित-स्क्रिनलाई शीर्ष स्थानमा राख्नुहोस्‌"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"विभाजित-स्क्रिनलाई बायाँतर्फ राख्नुहोस्‌"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"विभाजित-स्क्रिनलाई दायाँतर्फ राख्नुहोस्‌"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"चार्ज भयो"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"चार्ज हुँदै"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> पूर्ण नभएसम्म"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index ec9fde8..815e5f9 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Alle recente apps gesloten."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"App-gegevens voor <xliff:g id="APP">%s</xliff:g> openen."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> starten."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Melding verwijderd."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Meldingenpaneel."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Snelle instellingen."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Scherm bovenaan gesplitst"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Scherm links gesplitst"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Scherm rechts gesplitst"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Opgeladen"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Opladen"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> tot volledig opgeladen"</string>
@@ -752,7 +749,7 @@
     <string name="tuner_left" msgid="8404287986475034806">"Links"</string>
     <string name="tuner_right" msgid="6222734772467850156">"Rechts"</string>
     <string name="tuner_menu" msgid="191640047241552081">"Menu"</string>
-    <string name="tuner_app" msgid="3507057938640108777">"Apps <xliff:g id="APP">%1$s</xliff:g>"</string>
+    <string name="tuner_app" msgid="3507057938640108777">"<xliff:g id="APP">%1$s</xliff:g>-app"</string>
     <string name="notification_channel_alerts" msgid="4496839309318519037">"Meldingen"</string>
     <string name="notification_channel_battery" msgid="5786118169182888462">"Batterij"</string>
     <string name="notification_channel_screenshot" msgid="6314080179230000938">"Screenshots"</string>
diff --git a/packages/SystemUI/res/values-pa-land/strings.xml b/packages/SystemUI/res/values-pa-land/strings.xml
index fef122a..dfe2a5d 100644
--- a/packages/SystemUI/res/values-pa-land/strings.xml
+++ b/packages/SystemUI/res/values-pa-land/strings.xml
@@ -19,5 +19,5 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="toast_rotation_locked" msgid="7609673011431556092">"ਸਕ੍ਰੀਨ ਹੁਣ ਲੈਂਡਸਕੇਪ ਅਨੁਕੂਲਨ ਵਿੱਚ ਲੌਕ ਕੀਤੀ ਗਈ ਹੈ।"</string>
+    <string name="toast_rotation_locked" msgid="7609673011431556092">"ਸਕ੍ਰੀਨ ਹੁਣ ਲੈਂਡਸਕੇਪ ਅਨੁਕੂਲਨ ਵਿੱਚ  ਲਾਕ  ਕੀਤੀ ਗਈ ਹੈ।"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 166e0cb..cec5c96 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -180,14 +180,13 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"ਸਾਰੀਆਂ ਹਾਲੀਆ ਐਪਲੀਕੇਸ਼ਨਾਂ ਰੱਦ ਕੀਤੀਆਂ।"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ਐਪਲੀਕੇਸ਼ਨਾਂ ਜਾਣਕਾਰੀ ਖੋਲ੍ਹੋ।"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> ਚਾਲੂ ਕਰ ਰਿਹਾ ਹੈ।"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"ਸੂਚਨਾ ਰੱਦ ਕੀਤੀ।"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"ਸੂਚਨਾ ਸ਼ੇਡ।"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ਤਤਕਾਲ ਸੈਟਿੰਗਾਂ।"</string>
-    <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">"ਲੌਕ ਸਕ੍ਰੀਨ।"</string>
+    <string name="accessibility_desc_lock_screen" msgid="5625143713611759164">" ਲਾਕ  ਸਕ੍ਰੀਨ।"</string>
     <string name="accessibility_desc_settings" msgid="3417884241751434521">"ਸੈਟਿੰਗਾਂ"</string>
     <string name="accessibility_desc_recent_apps" msgid="4876900986661819788">"ਰੂਪ-ਰੇਖਾ।"</string>
-    <string name="accessibility_desc_work_lock" msgid="4288774420752813383">"ਕਾਰਜ-ਸਥਾਨ ਲੌਕ ਸਕ੍ਰੀਨ"</string>
+    <string name="accessibility_desc_work_lock" msgid="4288774420752813383">"ਕਾਰਜ-ਸਥਾਨ  ਲਾਕ  ਸਕ੍ਰੀਨ"</string>
     <string name="accessibility_desc_close" msgid="7479755364962766729">"ਬੰਦ ਕਰੋ"</string>
     <string name="accessibility_quick_settings_wifi" msgid="5518210213118181692">"<xliff:g id="SIGNAL">%1$s</xliff:g>।"</string>
     <string name="accessibility_quick_settings_wifi_changed_off" msgid="8716484460897819400">"Wifi ਬੰਦ ਕੀਤਾ।"</string>
@@ -258,11 +257,11 @@
     <string name="status_bar_notification_inspect_item_title" msgid="5668348142410115323">"ਸੂਚਨਾ ਸੈਟਿੰਗਾਂ"</string>
     <string name="status_bar_notification_app_settings_title" msgid="5525260160341558869">"<xliff:g id="APP_NAME">%s</xliff:g> ਸੈਟਿੰਗਾਂ"</string>
     <string name="accessibility_rotation_lock_off" msgid="4062780228931590069">"ਸਕ੍ਰੀਨ ਆਟੋਮੈਟਿਕਲੀ ਰੋਟੇਟ ਕਰੇਗੀ।"</string>
-    <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"ਸਕ੍ਰੀਨ ਲੈਂਡਸਕੇਪ ਅਨੁਕੂਲਨ ਵਿੱਚ ਲੌਕ ਕੀਤੀ ਹੈ।"</string>
-    <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"ਸਕ੍ਰੀਨ ਪੋਰਟਰੇਟ ਅਨੁਕੂਲਨ ਵਿੱਚ ਲੌਕ ਕੀਤੀ ਗਈ ਹੈ।"</string>
+    <string name="accessibility_rotation_lock_on_landscape" msgid="6731197337665366273">"ਸਕ੍ਰੀਨ ਲੈਂਡਸਕੇਪ ਅਨੁਕੂਲਨ ਵਿੱਚ  ਲਾਕ  ਕੀਤੀ ਹੈ।"</string>
+    <string name="accessibility_rotation_lock_on_portrait" msgid="5809367521644012115">"ਸਕ੍ਰੀਨ ਪੋਰਟਰੇਟ ਅਨੁਕੂਲਨ ਵਿੱਚ  ਲਾਕ  ਕੀਤੀ ਗਈ ਹੈ।"</string>
     <string name="accessibility_rotation_lock_off_changed" msgid="8134601071026305153">"ਸਕ੍ਰੀਨ ਹੁਣ ਆਟੋਮੈਟਿਕਲੀ ਰੋਟੇਟ ਕਰੇਗੀ।"</string>
-    <string name="accessibility_rotation_lock_on_landscape_changed" msgid="3135965553707519743">"ਸਕ੍ਰੀਨ ਹੁਣ ਲੈਂਡਸਕੇਪ ਅਨੁਕੂਲਨ ਵਿੱਚ ਲੌਕ ਕੀਤੀ ਗਈ ਹੈ।"</string>
-    <string name="accessibility_rotation_lock_on_portrait_changed" msgid="8922481981834012126">"ਸਕ੍ਰੀਨ ਹੁਣ ਪੋਰਟਰੇਟ ਅਨੁਕੂਲਨ ਵਿੱਚ ਲੌਕ ਕੀਤੀ ਗਈ ਹੈ।"</string>
+    <string name="accessibility_rotation_lock_on_landscape_changed" msgid="3135965553707519743">"ਸਕ੍ਰੀਨ ਹੁਣ ਲੈਂਡਸਕੇਪ ਅਨੁਕੂਲਨ ਵਿੱਚ  ਲਾਕ  ਕੀਤੀ ਗਈ ਹੈ।"</string>
+    <string name="accessibility_rotation_lock_on_portrait_changed" msgid="8922481981834012126">"ਸਕ੍ਰੀਨ ਹੁਣ ਪੋਰਟਰੇਟ ਅਨੁਕੂਲਨ ਵਿੱਚ  ਲਾਕ  ਕੀਤੀ ਗਈ ਹੈ।"</string>
     <string name="dessert_case" msgid="1295161776223959221">"ਡੈਜ਼ਰਟ ਕੇਸ"</string>
     <string name="start_dreams" msgid="5640361424498338327">"ਸਕ੍ਰੀਨ ਸੇਵਰ"</string>
     <string name="ethernet_label" msgid="7967563676324087464">"ਈਥਰਨੈਟ"</string>
@@ -278,7 +277,7 @@
     <string name="quick_settings_rotation_unlocked_label" msgid="7305323031808150099">"ਆਟੋ-ਰੋਟੇਟ"</string>
     <string name="accessibility_quick_settings_rotation" msgid="4231661040698488779">"ਸਕ੍ਰੀਨ ਨੂੰ ਆਪਣੇ ਆਪ ਘੁੰਮਾਓ"</string>
     <string name="accessibility_quick_settings_rotation_value" msgid="8187398200140760213">"<xliff:g id="ID_1">%s</xliff:g> ਮੋਡ"</string>
-    <string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"ਰੋਟੇਸ਼ਨ ਲੌਕ ਕੀਤੀ"</string>
+    <string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"ਰੋਟੇਸ਼ਨ  ਲਾਕ  ਕੀਤੀ"</string>
     <string name="quick_settings_rotation_locked_portrait_label" msgid="5102691921442135053">"ਪੋਰਟਰੇਟ"</string>
     <string name="quick_settings_rotation_locked_landscape_label" msgid="8553157770061178719">"ਲੈਂਡਸਕੇਪ"</string>
     <string name="quick_settings_ime_label" msgid="7073463064369468429">"ਇਨਪੁੱਟ ਵਿਧੀ"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"ਸਕ੍ਰੀਨ ਨੂੰ ਉੱਪਰ ਵੱਲ ਵਿਭਾਜਿਤ ਕਰੋ"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"ਸਕ੍ਰੀਨ ਨੂੰ ਖੱਬੇ ਪਾਸੇ ਵਿਭਾਜਿਤ ਕਰੋ"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"ਸਕ੍ਰੀਨ ਨੂੰ ਸੱਜੇ ਪਾਸੇ ਵਿਭਾਜਿਤ ਕਰੋ"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ਚਾਰਜ ਹੋਇਆ"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ਚਾਰਜ ਕਰ ਰਿਹਾ ਹੈ"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ਪੂਰਾ ਹੋਣ ਤੱਕ"</string>
@@ -468,7 +465,7 @@
     <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"ਤੁਸੀਂ <xliff:g id="APPLICATION">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੋ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਨਿੱਜੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।"</string>
     <string name="monitoring_description_app_work" msgid="4612997849787922906">"ਤੁਹਾਡੇ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਦਾ ਪ੍ਰਬੰਧਨ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਹ ਪ੍ਰੋਫਾਈਲ <xliff:g id="APPLICATION">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਹੈ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਕਾਰਜ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।\n\nਹੋਰ ਜਾਣਕਾਰੀ ਲਈ, ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ।"</string>
     <string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"ਤੁਹਾਡੇ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਦਾ ਪ੍ਰਬੰਧਨ <xliff:g id="ORGANIZATION">%1$s</xliff:g> ਵੱਲੋਂ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਪ੍ਰੋਫਾਈਲ <xliff:g id="APPLICATION_WORK">%2$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ ਹੈ, ਜੋ ਈਮੇਲਾਂ, ਐਪਾਂ, ਅਤੇ ਵੈੱਬਸਾਈਟਾਂ ਸਮੇਤ ਤੁਹਾਡੀ ਕਾਰਜ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।\n\nਤੁਸੀਂ <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g> ਨਾਲ ਵੀ ਕਨੈਕਟ ਹੋਂ, ਜੋ ਤੁਹਾਡੀ ਨਿੱਜੀ ਨੈੱਟਵਰਕ ਸਰਗਰਮੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰ ਸਕਦੀ ਹੈ।"</string>
-    <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> ਲਈ ਅਨਲੌਕ ਕੀਤੀ ਗਈ"</string>
+    <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"<xliff:g id="USER_NAME">%1$s</xliff:g> ਲਈ ਅਣਲਾਕ ਕੀਤੀ ਗਈ"</string>
     <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string>
     <string name="keyguard_indication_trust_disabled" msgid="7412534203633528135">"ਡੀਵਾਈਸ ਲਾਕ ਰਹੇਗਾ ਜਦੋਂ ਤੱਕ ਤੁਸੀਂ ਮੈਨੂਅਲੀ ਅਣਲਾਕ ਨਹੀਂ ਕਰਦੇ"</string>
     <string name="hidden_notifications_title" msgid="7139628534207443290">"ਤੇਜ਼ੀ ਨਾਲ ਸੂਚਨਾਵਾਂ ਪ੍ਰਾਪਤ ਕਰੋ"</string>
@@ -587,7 +584,7 @@
     <string name="battery_panel_title" msgid="7944156115535366613">"ਬੈਟਰੀ ਵਰਤੋਂ"</string>
     <string name="battery_detail_charging_summary" msgid="4055327085770378335">"ਬੈਟਰੀ ਸੇਵਰ ਚਾਰਜਿੰਗ ਦੌਰਾਨ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="battery_detail_switch_title" msgid="8763441006881907058">"ਬੈਟਰੀ ਸੇਵਰ"</string>
-    <string name="battery_detail_switch_summary" msgid="9049111149407626804">"ਕਾਰਗੁਜ਼ਾਰੀ ਅਤੇ ਬੈਕਗ੍ਰਾਊਂਡ ਡੈਟੇ ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ"</string>
+    <string name="battery_detail_switch_summary" msgid="9049111149407626804">"ਕਾਰਗੁਜ਼ਾਰੀ ਅਤੇ ਬੈਕਗ੍ਰਾਊਂਡ  ਡਾਟੇ  ਨੂੰ ਘਟਾਉਂਦਾ ਹੈ"</string>
     <string name="keyboard_key_button_template" msgid="6230056639734377300">"ਬਟਨ <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="keyboard_key_home" msgid="2243500072071305073">"Home"</string>
     <string name="keyboard_key_back" msgid="2337450286042721351">"Back"</string>
@@ -721,7 +718,7 @@
     <string name="accessibility_quick_settings_open_settings" msgid="7806613775728380737">"<xliff:g id="ID_1">%s</xliff:g> ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ।"</string>
     <string name="accessibility_quick_settings_edit" msgid="7839992848995240393">"ਸੈਟਿੰਗਾਂ ਦੇ ਕ੍ਰਮ ਦਾ ਸੰਪਾਦਨ ਕਰੋ।"</string>
     <string name="accessibility_quick_settings_page" msgid="5032979051755200721">"<xliff:g id="ID_2">%2$d</xliff:g> ਦਾ <xliff:g id="ID_1">%1$d</xliff:g> ਪੰਨਾ"</string>
-    <string name="tuner_lock_screen" msgid="5755818559638850294">"ਲੌਕ ਸਕ੍ਰੀਨ"</string>
+    <string name="tuner_lock_screen" msgid="5755818559638850294">" ਲਾਕ  ਸਕ੍ਰੀਨ"</string>
     <string name="pip_phone_expand" msgid="5889780005575693909">"ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="pip_phone_minimize" msgid="1079119422589131792">"ਛੋਟਾ ਕਰੋ"</string>
     <string name="pip_phone_close" msgid="8416647892889710330">"ਬੰਦ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 7ff9b73..fdd4c02 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Wszystkie ostatnie aplikacje zostały zamknięte."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Otwórz informacje o aplikacji <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Uruchamiam <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Zamknięto powiadomienie."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Obszar powiadomień."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Szybkie ustawienia."</string>
@@ -244,7 +243,7 @@
     <string name="data_usage_disabled_dialog_4g_title" msgid="1601769736881078016">"Transmisja danych 4G została wstrzymana"</string>
     <string name="data_usage_disabled_dialog_mobile_title" msgid="6801382439018099779">"Mobilna transmisja danych jest wstrzymana"</string>
     <string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"Transmisja danych została wstrzymana"</string>
-    <string name="data_usage_disabled_dialog" msgid="4919541636934603816">"Osiągnięto ustawiony limit danych. Nie korzystasz już z komórkowej transmisji danych.\n\nJeśli włączysz ją ponownie, może zostać naliczona opłata za transmisję danych."</string>
+    <string name="data_usage_disabled_dialog" msgid="4919541636934603816">"Osiągnięto ustawiony limit danych. Nie korzystasz już z komórkowej transmisji danych.\n\nJeśli włączysz ją ponownie, może zostać naliczona opłata za użycie danych."</string>
     <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"Wznów"</string>
     <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Brak internetu"</string>
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi: połączono"</string>
@@ -347,8 +346,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Podziel ekran u góry"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Podziel ekran z lewej"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Podziel ekran z prawej"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Naładowana"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Ładowanie"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do pełnego naładowania"</string>
@@ -785,7 +782,7 @@
     <string name="qs_dnd_keep" msgid="1825009164681928736">"Zachowaj"</string>
     <string name="qs_dnd_replace" msgid="8019520786644276623">"Zastąp"</string>
     <string name="running_foreground_services_title" msgid="381024150898615683">"Aplikacje działające w tle"</string>
-    <string name="running_foreground_services_msg" msgid="6326247670075574355">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i transmisji danych"</string>
+    <string name="running_foreground_services_msg" msgid="6326247670075574355">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i użycia danych"</string>
     <string name="data_usage_disable_mobile" msgid="5116269981510015864">"Wyłączyć mobilną transmisję danych?"</string>
     <string name="touch_filtered_warning" msgid="8671693809204767551">"Aplikacja Ustawienia nie może zweryfikować Twojej odpowiedzi, ponieważ inna aplikacja zasłania prośbę o udzielenie uprawnień."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 08f5a38..4b72c29 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Todos os apps recentes foram dispensados."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Abre informações do app <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificação dispensada."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Aba de notificações."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configurações rápidas."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir a tela para a parte superior"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir a tela para a esquerda"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir a tela para a direita"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Carregando"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> até concluir"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 7d3e9d5..2bbc470 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Todas as aplicações recentes foram ignoradas."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Abrir as informações da aplicação <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"A iniciar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificação ignorada."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Painel de notificações."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Definições rápidas."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ecrã dividido na parte superior"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ecrã dividido à esquerda"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ecrã dividido à direita"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"A carregar"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> até ficar completa"</string>
@@ -439,12 +436,12 @@
     <string name="disable_vpn" msgid="4435534311510272506">"Desativar a VPN"</string>
     <string name="disconnect_vpn" msgid="1324915059568548655">"Desligar VPN"</string>
     <string name="monitoring_button_view_policies" msgid="100913612638514424">"Ver Políticas"</string>
-    <string name="monitoring_description_named_management" msgid="5281789135578986303">"O dispositivo é gerido pela <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nO administrador pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o administrador para obter mais informações."</string>
-    <string name="monitoring_description_management" msgid="4573721970278370790">"O dispositivo é gerido pela sua entidade.\n\nO administrador pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o administrador para obter mais informações."</string>
+    <string name="monitoring_description_named_management" msgid="5281789135578986303">"O dispositivo é gerido pela <xliff:g id="ORGANIZATION_NAME">%1$s</xliff:g>.\n\nO gestor pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o gestor para obter mais informações."</string>
+    <string name="monitoring_description_management" msgid="4573721970278370790">"O dispositivo é gerido pela sua entidade.\n\nO gestor pode monitorizar e gerir definições, acesso empresarial, aplicações, dados associados ao dispositivo e informações de localização do dispositivo.\n\nContacte o gestor para obter mais informações."</string>
     <string name="monitoring_description_management_ca_certificate" msgid="5202023784131001751">"A sua entidade instalou uma autoridade de certificação neste dispositivo. O tráfego da sua rede segura pode ser monitorizado ou alterado."</string>
     <string name="monitoring_description_managed_profile_ca_certificate" msgid="4683248196789897964">"A sua entidade instalou uma autoridade de certificação no seu perfil de trabalho. O tráfego da sua rede segura pode ser monitorizado ou alterado."</string>
     <string name="monitoring_description_ca_certificate" msgid="7886985418413598352">"Está instalada uma autoridade de certificação neste dispositivo. O tráfego da sua rede segura pode ser monitorizado ou alterado."</string>
-    <string name="monitoring_description_management_network_logging" msgid="7184005419733060736">"O administrador ativou os registos de rede, que monitorizam o tráfego no seu dispositivo."</string>
+    <string name="monitoring_description_management_network_logging" msgid="7184005419733060736">"O gestor ativou os registos de rede, que monitorizam o tráfego no seu dispositivo."</string>
     <string name="monitoring_description_named_vpn" msgid="7403457334088909254">"Está ligado à rede <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
     <string name="monitoring_description_two_named_vpns" msgid="4198511413729213802">"Está ligado às redes <xliff:g id="VPN_APP_0">%1$s</xliff:g> e <xliff:g id="VPN_APP_1">%2$s</xliff:g>, que podem monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
     <string name="monitoring_description_managed_profile_named_vpn" msgid="1427905889862420559">"O seu perfil de trabalho está ligado à rede <xliff:g id="VPN_APP">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
@@ -459,14 +456,14 @@
     <string name="monitoring_description_vpn_settings" msgid="6434859242636063861">"Abrir as definições de VPN"</string>
     <string name="monitoring_description_ca_cert_settings_separator" msgid="4987350385906393626">" "</string>
     <string name="monitoring_description_ca_cert_settings" msgid="5489969458872997092">"Abrir credenciais fidedignas"</string>
-    <string name="monitoring_description_network_logging" msgid="7223505523384076027">"O seu administrador ativou os registos de rede, que monitorizam o tráfego no seu dispositivo.\n\nPara obter mais informações, contacte o administrador."</string>
+    <string name="monitoring_description_network_logging" msgid="7223505523384076027">"O seu gestor ativou os registos de rede, que monitorizam o tráfego no seu dispositivo.\n\nPara obter mais informações, contacte o gestor."</string>
     <string name="monitoring_description_vpn" msgid="4445150119515393526">"Concedeu autorização a uma aplicação para configurar uma ligação VPN.\n\nEsta aplicação pode monitorizar a atividade do dispositivo e da rede, incluindo emails, aplicações e Sites."</string>
-    <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO seu administrador tem a capacidade de monitorizar a sua atividade da rede, incluindo emails, aplicações e Sites.\n\nPara obter mais informações, contacte o administrador.\n\nAlém disso, está ligado a uma VPN, que pode monitorizar a sua atividade da rede."</string>
+    <string name="monitoring_description_vpn_profile_owned" msgid="2958019119161161530">"O seu perfil de trabalho é gerido por <xliff:g id="ORGANIZATION">%1$s</xliff:g>.\n\nO seu gestor tem a capacidade de monitorizar a sua atividade da rede, incluindo emails, aplicações e Sites.\n\nPara obter mais informações, contacte o gestor.\n\nAlém disso, está ligado a uma VPN, que pode monitorizar a sua atividade da rede."</string>
     <string name="legacy_vpn_name" msgid="6604123105765737830">"VPN"</string>
     <string name="monitoring_description_app" msgid="1828472472674709532">"Está associado à aplicação <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a sua atividade de rede, incluindo emails, aplicações e Sites."</string>
     <string name="monitoring_description_app_personal" msgid="484599052118316268">"Está ligado a <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Sites."</string>
     <string name="branded_monitoring_description_app_personal" msgid="2669518213949202599">"Está ligado ao <xliff:g id="APPLICATION">%1$s</xliff:g>, que pode monitorizar a atividade da rede pessoal, incluindo emails, aplicações e Sites."</string>
-    <string name="monitoring_description_app_work" msgid="4612997849787922906">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nContacte o administrador para obter mais informações."</string>
+    <string name="monitoring_description_app_work" msgid="4612997849787922906">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nContacte o gestor para obter mais informações."</string>
     <string name="monitoring_description_app_personal_work" msgid="5664165460056859391">"O seu perfil de trabalho é gerido pela <xliff:g id="ORGANIZATION">%1$s</xliff:g>. O perfil está associado à aplicação <xliff:g id="APPLICATION_WORK">%2$s</xliff:g>, que pode monitorizar a atividade da rede de trabalho, incluindo emails, aplicações e Sites.\n\nTambém está associado à aplicação <xliff:g id="APPLICATION_PERSONAL">%3$s</xliff:g>, que pode monitorizar a atividade da rede pessoal."</string>
     <string name="keyguard_indication_trust_granted" msgid="4985003749105182372">"Desbloqueado para <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
     <string name="keyguard_indication_trust_managed" msgid="8319646760022357585">"<xliff:g id="TRUST_AGENT">%1$s</xliff:g> em execução"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 08f5a38..4b72c29 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Todos os apps recentes foram dispensados."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Abre informações do app <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Iniciando <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificação dispensada."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Aba de notificações."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Configurações rápidas."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Dividir a tela para a parte superior"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Dividir a tela para a esquerda"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Dividir a tela para a direita"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Carregada"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Carregando"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> até concluir"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 47771366..4800578 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -183,7 +183,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Toate aplicațiile recente au fost închise."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Deschideți informațiile despre aplicația <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Se inițiază <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Notificarea a fost închisă."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Fereastră pentru notificări."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Setări rapide."</string>
@@ -347,8 +346,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Divizați ecranul în partea de sus"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Divizați ecranul la stânga"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Divizați ecranul la dreapta"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Încărcată"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Se încarcă"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> până la încărcare completă"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 8465844..f78c3bb 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Все недавние приложения закрыты."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Открыть информацию о приложении \"<xliff:g id="APP">%s</xliff:g>\""</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Запуск приложения <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Уведомление закрыто"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Панель уведомлений"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Быстрые настройки"</string>
@@ -349,8 +348,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Разделить экран по верхнему краю"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Разделить экран по левому краю"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Разделить экран по правому краю"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Батарея заряжена"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Зарядка батареи"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> до полной зарядки"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 5343bb3..bab0c6e 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"සියලුම මෑත යෙඳුම් අස් කරන ලදි."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> යෙදුම් තොරතුරු විවෘත කරයි."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> ආරම්භ කරමින්."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"දැනුම්දීම නිෂ්ප්‍රභා කරඇත."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"දැනුම්දීම් ආවරණය."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"ක්ෂණික සැකසීම්."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"තිරය ඉහළට බෙදන්න"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"තිරය වමට බෙදන්න"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"තිරය දකුණට බෙදන්න"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"අරෝපිතයි"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ආරෝපණය වෙමින්"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> සම්පූර්ණ වන තෙක්"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index a4b45e5..4be2941 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Všetky nedávne aplikácie boli odmietnuté."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Otvoriť informácie o aplikácii <xliff:g id="APP">%s</xliff:g>"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Spúšťa sa aplikácia <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Upozornenie bolo zrušené."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Panel upozornení."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Rýchle nastavenia."</string>
@@ -349,8 +348,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Rozdelená obrazovka hore"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Rozdelená obrazovka naľavo"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Rozdelená obrazovka napravo"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nabitá"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Nabíja sa"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Úplné nabitie o <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
@@ -616,7 +613,7 @@
     <string name="keyboard_key_backspace" msgid="1559580097512385854">"Backspace"</string>
     <string name="keyboard_key_media_play_pause" msgid="3861975717393887428">"Prehrať/pozastaviť"</string>
     <string name="keyboard_key_media_stop" msgid="2859963958595908962">"Zastaviť"</string>
-    <string name="keyboard_key_media_next" msgid="1894394911630345607">"Nasledujúce"</string>
+    <string name="keyboard_key_media_next" msgid="1894394911630345607">"Ďalej"</string>
     <string name="keyboard_key_media_previous" msgid="4256072387192967261">"Predchádzajúce"</string>
     <string name="keyboard_key_media_rewind" msgid="2654808213360820186">"Pretočiť späť"</string>
     <string name="keyboard_key_media_fast_forward" msgid="3849417047738200605">"Pretočiť dopredu"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index a50d301..49cbdbf 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Vse nedavne aplikacije so bile opuščene."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Odpiranje podatkov o aplikaciji <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Zaganjanje aplikacije <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Obvestilo je bilo odstranjeno."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Zaslon z obvestili."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hitre nastavitve."</string>
@@ -349,8 +348,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Razdeljen zaslon na vrhu"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Razdeljen zaslon na levi"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Razdeljen zaslon na desni"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Akumulator napolnjen"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Polnjenje"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> do napolnjenosti"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index e1f2567..66d692c 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Të gjitha aplikacionet e fundit u larguan."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Hap informacionin e aplikacionit <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Po nis <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Njoftimi është hequr."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Streha e njoftimeve."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Cilësime të shpejta."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ndaje ekranin lart"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ndaje ekranin në të majtë"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ndaje ekranin në të djathtë"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"I ngarkuar"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Po ngarkohet"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> deri sa të mbushet"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index b31eb96..97b9ce4 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -181,7 +181,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Све недавно коришћене апликације су одбачене."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Отворите информације о апликацији <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Покрећемо <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Обавештење је одбачено."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Прозор са обавештењима."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Брза подешавања."</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Подели екран нагоре"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Подели екран налево"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Подели екран надесно"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Напуњена је"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Пуњење"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> док се не напуни"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 86b5c0c..88b93aa 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Alla appar har tagits bort från listan Senaste."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Öppna appinformation för <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Startar <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Meddelandet ignorerades."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Meddelandepanel."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Snabbinställningar."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Delad skärm till överkanten"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Delad skärm åt vänster"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Delad skärm åt höger"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Laddat"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Laddar"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> tills batteriet är fulladdat"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index b8878f5..524faea 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Programu za hivi majuzi zimeondolewa."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Fungua maelezo kuhusu programu ya <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Inaanzisha <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Arifa imetupwa."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Kivuli cha arifa."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Mipangilio ya haraka."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Gawa skrini kuelekea juu"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Gawa skrini upande wa kushoto"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Gawa skrini upande wa kulia"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Betri imejaa"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Inachaji"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Imebakisha <xliff:g id="CHARGING_TIME">%s</xliff:g> ijae"</string>
@@ -728,7 +725,7 @@
     <string name="pip_phone_dismiss_hint" msgid="6351678169095923899">"Buruta ili uondoe"</string>
     <string name="pip_menu_title" msgid="4707292089961887657">"Menyu"</string>
     <string name="pip_notification_title" msgid="3204024940158161322">"<xliff:g id="NAME">%s</xliff:g> iko katika hali ya picha ndani ya picha nyingine"</string>
-    <string name="pip_notification_message" msgid="5619512781514343311">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> itumie kipengele hiki, gonga ili ufungue mipangilio na uizime."</string>
+    <string name="pip_notification_message" msgid="5619512781514343311">"Ikiwa hutaki <xliff:g id="NAME">%s</xliff:g> itumie kipengele hiki, gusa ili ufungue mipangilio na uizime."</string>
     <string name="pip_play" msgid="1417176722760265888">"Cheza"</string>
     <string name="pip_pause" msgid="8881063404466476571">"Sitisha"</string>
     <string name="pip_skip_to_next" msgid="1948440006726306284">"Ruka ufikie inayofuata"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index a46facf..ada5d4b 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"எல்லா சமீபத்திய பயன்பாடுகளும் விலக்கப்பட்டன."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> பயன்பாட்டின் தகவலைத் திற."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> ஐத் தொடங்குகிறது."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"அறிவிப்பு நிராகரிக்கப்பட்டது."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"அறிவிப்பு விவரம்."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"உடனடி அமைப்பு."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"திரையை மேல்புறமாகப் பிரி"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"திரையை இடப்புறமாகப் பிரி"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"திரையை வலப்புறமாகப் பிரி"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"சார்ஜ் செய்யப்பட்டது"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"சார்ஜ் ஆகிறது"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"முழுவதும் சார்ஜாக <xliff:g id="CHARGING_TIME">%s</xliff:g> ஆகும்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 2c32fab..f23e169 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"అన్ని ఇటీవలి అనువర్తనాలు తీసివేయబడ్డాయి."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> అనువర్తన సమాచారాన్ని తెరుస్తుంది."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g>ని ప్రారంభిస్తోంది."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"నోటిఫికేషన్ తీసివేయబడింది."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"నోటిఫికేషన్ షేడ్."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"శీఘ్ర సెట్టింగ్‌లు."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"స్క్రీన్‌ని ఎగువకు విభజించు"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"స్క్రీన్‌ని ఎడమ వైపుకి విభజించు"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"స్క్రీన్‌ని కుడి వైపుకి విభజించు"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ఛార్జ్ చేయబడింది"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"ఛార్జ్ అవుతోంది"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"పూర్తిగా నిండటానికి <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 969a0a2..5f577f6 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"ปิดแอปพลิเคชันล่าสุดทั้งหมดแล้ว"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"เปิดข้อมูลแอปพลิเคชัน <xliff:g id="APP">%s</xliff:g>"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"กำลังเริ่มต้น <xliff:g id="APP">%s</xliff:g>"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"ปิดการแจ้งเตือนแล้ว"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"หน้าต่างแจ้งเตือน"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"การตั้งค่าด่วน"</string>
@@ -243,7 +242,7 @@
     <string name="data_usage_disabled_dialog_mobile_title" msgid="6801382439018099779">"หยุดการใช้อินเทอร์เน็ตมือถือชั่วคราว"</string>
     <string name="data_usage_disabled_dialog_title" msgid="3932437232199671967">"หยุดการใช้ข้อมูลชั่วคราวแล้ว"</string>
     <string name="data_usage_disabled_dialog" msgid="4919541636934603816">"คุณใช้อินเทอร์เน็ตถึงปริมาณที่กำหนดไว้แล้ว คุณไม่ได้ใช้อินเทอร์เน็ตมือถือแล้วในขณะนี้\n\nหากใช้ต่อ อาจมีการเรียกเก็บค่าบริการอินเทอร์เน็ต"</string>
-    <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ทำต่อ"</string>
+    <string name="data_usage_disabled_dialog_enable" msgid="1412395410306390593">"ใช้ต่อ"</string>
     <string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"ไม่มีอินเทอร์เน็ต"</string>
     <string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"เชื่อมต่อ WiFi แล้ว"</string>
     <string name="gps_notification_searching_text" msgid="8574247005642736060">"กำลังค้นหา GPS"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"แยกหน้าจอไปด้านบน"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"แยกหน้าจอไปทางซ้าย"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"แยกหน้าจอไปทางขวา"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"ชาร์จแล้ว"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"กำลังชาร์จ"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"อีก <xliff:g id="CHARGING_TIME">%s</xliff:g> จึงจะเต็ม"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 261bccf..fa79f7c 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Na-dismiss ang lahat ng kamakailang application."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Buksan ang impormasyon ng <xliff:g id="APP">%s</xliff:g> application."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Sinisimulan ang <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Na-dismiss ang notification."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Notification shade."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Mga mabilisang setting."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"I-split ang screen pataas"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"I-split ang screen pakaliwa"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"I-split ang screen pakanan"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Nasingil na"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Nagcha-charge"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> hanggang mapuno"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 450a310..3a5ab8c 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Tüm son uygulamalar kapatıldı."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> uygulaması bilgilerini açın."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> başlatılıyor."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Bildirim kapatıldı."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Bildirim gölgesi."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Hızlı ayarlar."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ekranı yukarıya doğru böl"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ekranı sola doğru böl"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ekranı sağa doğru böl"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Ödeme alındı"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Şarj oluyor"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"Tam şarj olmasına <xliff:g id="CHARGING_TIME">%s</xliff:g> kaldı"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index cc14af6..1e3ebb2 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -184,7 +184,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Усі останні додатки закрито."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Відкрити інформацію про додаток <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Запуск додатка <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Сповіщення відхилено."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Панель сповіщень."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Швидке налаштування."</string>
@@ -349,8 +348,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Розділити екран угорі"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Розділити екран ліворуч"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Розділити екран праворуч"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Заряджено"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Заряджається"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"До повного зарядження <xliff:g id="CHARGING_TIME">%s</xliff:g>"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 9f44d67..7f17182 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -155,7 +155,7 @@
     <string name="accessibility_cell_data" msgid="5326139158682385073">"موبائل ڈیٹا"</string>
     <string name="accessibility_cell_data_on" msgid="5927098403452994422">"موبائل ڈیٹا آن ہے"</string>
     <string name="accessibility_cell_data_off" msgid="443267573897409704">"موبائل ڈیٹا آف ہے"</string>
-    <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"بلوٹوتھ ٹیتھرنگ۔"</string>
+    <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"بلوٹوتھ ٹیدرنگ۔"</string>
     <string name="accessibility_airplane_mode" msgid="834748999790763092">"ہوائی جہاز وضع۔"</string>
     <string name="accessibility_vpn_on" msgid="5993385083262856059">"‏VPN آن ہے۔"</string>
     <string name="accessibility_no_sims" msgid="3957997018324995781">"‏کوئی SIM کارڈ نہیں ہے۔"</string>
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"سبھی حالیہ ایپلیکیشنز کو برخاست کر دیا گیا۔"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ایپلیکیشن معلومات کھولیں۔"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> شروع ہو رہی ہے۔"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"اطلاع مسترد ہوگئی۔"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"اطلاعاتی شیڈ۔"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"فوری ترتیبات۔"</string>
@@ -312,7 +311,7 @@
     <string name="quick_settings_connected" msgid="1722253542984847487">"مربوط"</string>
     <string name="quick_settings_connected_battery_level" msgid="4136051440381328892">"منسلک ہے، بیٹری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string>
     <string name="quick_settings_connecting" msgid="47623027419264404">"مربوط ہو رہا ہے…"</string>
-    <string name="quick_settings_tethering_label" msgid="7153452060448575549">"ٹیتھرنگ"</string>
+    <string name="quick_settings_tethering_label" msgid="7153452060448575549">"ٹیدرنگ"</string>
     <string name="quick_settings_hotspot_label" msgid="6046917934974004879">"ہاٹ اسپاٹ"</string>
     <string name="quick_settings_notifications_label" msgid="4818156442169154523">"اطلاعات"</string>
     <string name="quick_settings_flashlight_label" msgid="2133093497691661546">"فلیش لائٹ"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"اسکرین کو اوپر کی جانب تقسیم کریں"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"اسکرین کو بائیں جانب تقسیم کریں"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"اسکرین کو دائیں جانب تقسیم کریں"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"چارج ہوگئی"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"چارج ہو رہی ہے"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> مکمل ہونے تک"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index b8cf046..8722fb1 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Yaqinda ishlatilgan barcha ilovalar olib tashlandi."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"<xliff:g id="APP">%s</xliff:g> ilovasi haqidagi ma’lumotlarni ochadi."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"<xliff:g id="APP">%s</xliff:g> ishga tushirilmoqda."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Xabarnoma e‘tiborsiz qoldirildi."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Xabarnoma soyasi."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Tezkor sozlamalar."</string>
@@ -338,15 +337,13 @@
     <string name="recents_launch_error_message" msgid="2969287838120550506">"“<xliff:g id="APP">%s</xliff:g>” ilovasini ishga tushirib bo‘lmadi."</string>
     <string name="recents_launch_disabled_message" msgid="1624523193008871793">"Xavfsiz rejimda <xliff:g id="APP">%s</xliff:g> ilovasi o‘chirib qo‘yildi."</string>
     <string name="recents_stack_action_button_label" msgid="6593727103310426253">"Hammasini tozalash"</string>
-    <string name="recents_drag_hint_message" msgid="2649739267073203985">"Ekranni bo‘lish xususiyatidan foydalanish uchun uchun bu yerga torting"</string>
+    <string name="recents_drag_hint_message" msgid="2649739267073203985">"Ekranni bo‘lish xususiyatidan foydalanish uchun bu yerga torting"</string>
     <string name="recents_multistack_add_stack_dialog_split_horizontal" msgid="8848514474543427332">"Gorizontal yo‘nalishda bo‘lish"</string>
     <string name="recents_multistack_add_stack_dialog_split_vertical" msgid="9075292233696180813">"Vertikal yo‘nalishda bo‘lish"</string>
     <string name="recents_multistack_add_stack_dialog_split_custom" msgid="4177837597513701943">"Boshqa usulda bo‘lish"</string>
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Ekranni tepaga qadash"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Ekranni chap tomonga qadash"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Ekranni o‘ng tomonga qadash"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Batareya quvvati to‘ldi"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Quvvat olmoqda"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g>da to‘ladi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 5b5cc61..36e76fe 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Đã bỏ qua tất cả các ứng dụng gần đây."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Mở thông tin ứng dụng <xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Bắt đầu <xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Đã loại bỏ thông báo."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Bóng thông báo."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Cài đặt nhanh."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Chia đôi màn hình lên trên"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Chia đôi màn hình sang trái"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Chia đôi màn hình sang phải"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Đã sạc đầy"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Đang sạc"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> cho đến khi đầy"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 38c501b..e179c5d 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"已关闭所有最近用过的应用。"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"打开<xliff:g id="APP">%s</xliff:g>应用信息。"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"正在启动<xliff:g id="APP">%s</xliff:g>。"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"已关闭通知。"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"通知栏。"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"快捷设置。"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"将屏幕分隔线移到上方"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"将屏幕分隔线移到左侧"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"将屏幕分隔线移到右侧"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"已充满"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"正在充电"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"还需<xliff:g id="CHARGING_TIME">%s</xliff:g>充满"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 3a42827..b34ff37 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -182,7 +182,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"所有最近使用的應用程式均已關閉。"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"開啟「<xliff:g id="APP">%s</xliff:g>」應用程式的資料。"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"正在啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g><xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"通知已關閉。"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"通知欄。"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"快速設定。"</string>
@@ -345,8 +344,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"將分割畫面顯示喺頂部"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"將分割畫面顯示喺左邊"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"將分割畫面顯示喺右邊"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"已完成充電"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"充電中"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g>後完成充電"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index d767979..13ac5e7 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -73,7 +73,7 @@
     <string name="screenshot_saved_title" msgid="6461865960961414961">"已拍攝螢幕擷取畫面。"</string>
     <string name="screenshot_saved_text" msgid="2685605830386712477">"輕觸即可查看螢幕擷圖。"</string>
     <string name="screenshot_failed_title" msgid="705781116746922771">"無法拍攝螢幕擷取畫面。"</string>
-    <string name="screenshot_failed_to_save_unknown_text" msgid="7887826345701753830">"儲存螢幕擷圖時發生問題。"</string>
+    <string name="screenshot_failed_to_save_unknown_text" msgid="7887826345701753830">"儲存螢幕擷取畫面時發生問題。"</string>
     <string name="screenshot_failed_to_save_text" msgid="2592658083866306296">"由於儲存空間有限,因此無法儲存螢幕擷取畫面。"</string>
     <string name="screenshot_failed_to_capture_text" msgid="173674476457581486">"這個應用程式或貴機構不允許擷取螢幕畫面"</string>
     <string name="usb_preference_title" msgid="6551050377388882787">"USB 檔案傳輸選項"</string>
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"最近使用的應用程式已全部關閉。"</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"開啟「<xliff:g id="APP">%s</xliff:g>」應用程式資訊。"</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"正在啟動「<xliff:g id="APP">%s</xliff:g>」。"</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"已關閉通知。"</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"通知欄。"</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"快捷設定。"</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"將分割畫面顯示在頂端"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"將分割畫面顯示在左邊"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"將分割畫面顯示在右邊"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"已充飽"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"充電中"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g>後充飽"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index de61bf5..695adf8 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -180,7 +180,6 @@
     <string name="accessibility_recents_all_items_dismissed" msgid="4464697366179168836">"Zonke izinhlelo zokusebenza zakamuva zicashisiwe."</string>
     <string name="accessibility_recents_item_open_app_info" msgid="5107479759905883540">"Vula ulwazi lohlelo lokusebenza le-<xliff:g id="APP">%s</xliff:g>."</string>
     <string name="accessibility_recents_item_launched" msgid="7616039892382525203">"Iqala i-<xliff:g id="APP">%s</xliff:g>."</string>
-    <string name="accessibility_recents_task_header" msgid="1437183540924535457">"<xliff:g id="APP">%1$s</xliff:g> <xliff:g id="ACTIVITY_LABEL">%2$s</xliff:g>"</string>
     <string name="accessibility_notification_dismissed" msgid="854211387186306927">"Isaziso sichithiwe."</string>
     <string name="accessibility_desc_notification_shade" msgid="4690274844447504208">"Umthunzi wesaziso."</string>
     <string name="accessibility_desc_quick_settings" msgid="6186378411582437046">"Izilingiselelo ezisheshayo."</string>
@@ -343,8 +342,6 @@
     <string name="recents_accessibility_split_screen_top" msgid="9056056469282256287">"Hlukanisela isikrini phezulu"</string>
     <string name="recents_accessibility_split_screen_left" msgid="8987144699630620019">"Hlukanisela isikrini ngakwesokunxele"</string>
     <string name="recents_accessibility_split_screen_right" msgid="275069779299592867">"Hlukanisela isikrini ngakwesokudla"</string>
-  <string-array name="recents_blacklist_array">
-  </string-array>
     <string name="expanded_header_battery_charged" msgid="5945855970267657951">"Kushajiwe"</string>
     <string name="expanded_header_battery_charging" msgid="205623198487189724">"Iyashaja"</string>
     <string name="expanded_header_battery_charging_with_time" msgid="457559884275395376">"<xliff:g id="CHARGING_TIME">%s</xliff:g> ize igcwale"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 87f6306..f54115b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -167,12 +167,6 @@
     <!-- The animation duration for scrolling the stack to a particular item. -->
     <integer name="recents_animate_task_stack_scroll_duration">200</integer>
 
-    <!-- The animation duration for scrolling the stack to a particular item. -->
-    <integer name="recents_auto_advance_duration">750</integer>
-
-    <!-- The animation duration for subsequent scrolling the stack to a particular item. -->
-    <integer name="recents_subsequent_auto_advance_duration">1000</integer>
-
     <!-- The delay to enforce between each alt-tab key press. -->
     <integer name="recents_alt_tab_key_delay">200</integer>
 
@@ -307,6 +301,9 @@
     <!-- Enable the default volume dialog -->
     <bool name="enable_volume_ui">true</bool>
 
+    <!-- Whether to show operator name in the status bar -->
+    <bool name="config_showOperatorNameInStatusBar">false</bool>
+
     <!-- Duration of the full carrier network change icon animation. -->
     <integer name="carrier_network_change_anim_time">3000</integer>
 
@@ -328,7 +325,7 @@
     <integer name="config_showTemperatureWarning">0</integer>
 
     <!-- Temp at which to show a warning notification if config_showTemperatureWarning is true.
-         If < 0, uses the value from
+         If < 0, uses the skin temperature sensor shutdown value from
          HardwarePropertiesManager#getDeviceTemperatures - config_warningTemperatureTolerance. -->
     <integer name="config_warningTemperature">-1</integer>
 
@@ -422,4 +419,14 @@
          increase the rate of unintentional unlocks. -->
     <bool name="config_lockscreenAntiFalsingClassifierEnabled">true</bool>
 
+    <!-- Snooze: default notificaiton snooze time. -->
+    <integer name="config_notification_snooze_time_default">60</integer>
+
+    <!-- Snooze: List of snooze values in integer minutes. -->
+    <integer-array name="config_notification_snooze_times">
+        <item>15</item>
+        <item>30</item>
+        <item>60</item>
+        <item>120</item>
+    </integer-array>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 2148c80..e5f8029 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -82,10 +82,10 @@
 
     <!-- Accessibility actions for the notification menu -->
     <item type="id" name="action_snooze_undo"/>
-    <item type="id" name="action_snooze_15_min"/>
-    <item type="id" name="action_snooze_30_min"/>
-    <item type="id" name="action_snooze_1_hour"/>
-    <item type="id" name="action_snooze_2_hours"/>
+    <item type="id" name="action_snooze_shorter"/>
+    <item type="id" name="action_snooze_short"/>
+    <item type="id" name="action_snooze_long"/>
+    <item type="id" name="action_snooze_longer"/>
     <item type="id" name="action_snooze_assistant_suggestion_1"/>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7ccb6b0..1536b64 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -451,8 +451,6 @@
     <string name="accessibility_recents_item_open_app_info">Open <xliff:g id="app" example="Calendar">%s</xliff:g> application info.</string>
     <!-- Content description to tell the user an application has been launched from recents -->
     <string name="accessibility_recents_item_launched">Starting <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
-    <!-- Content description of individual recents task. -->
-    <string name="accessibility_recents_task_header"><xliff:g id="app" example="Chrome">%1$s</xliff:g> <xliff:g id="activity_label" example="www.google.com">%2$s</xliff:g></string>
     <!-- Content description to tell the user a notification has been removed from the notification shade -->
     <string name="accessibility_notification_dismissed">Notification dismissed.</string>
 
@@ -810,10 +808,6 @@
     <!-- Recents: Accessibility split to the right -->
     <string name="recents_accessibility_split_screen_right">Split screen to the right</string>
 
-    <!-- Fully qualified activity class names to be blacklisted in Recents, add package names into overlay as needed -->
-    <string-array name="recents_blacklist_array">
-    </string-array>
-
     <!-- Expanded Status Bar Header: Battery Charged [CHAR LIMIT=40] -->
     <string name="expanded_header_battery_charged">Charged</string>
 
diff --git a/packages/SystemUI/shared/Android.mk b/packages/SystemUI/shared/Android.mk
new file mode 100644
index 0000000..88a89bc
--- /dev/null
+++ b/packages/SystemUI/shared/Android.mk
@@ -0,0 +1,42 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := SystemUISharedLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAR_EXCLUDE_FILES := none
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := SharedDummyLib
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := SystemUISharedLib
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/packages/SystemUI/shared/AndroidManifest.xml b/packages/SystemUI/shared/AndroidManifest.xml
new file mode 100644
index 0000000..43b9c75
--- /dev/null
+++ b/packages/SystemUI/shared/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.shared">
+
+    <uses-sdk
+        android:minSdkVersion="26" />
+
+</manifest>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java
new file mode 100644
index 0000000..ddd27b0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/BackgroundTaskLoader.java
@@ -0,0 +1,186 @@
+/*
+ * 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.shared.recents.model;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+/**
+ * Background task resource loader
+ */
+class BackgroundTaskLoader implements Runnable {
+    static String TAG = "BackgroundTaskLoader";
+    static boolean DEBUG = false;
+
+    private Context mContext;
+    private final HandlerThread mLoadThread;
+    private final Handler mLoadThreadHandler;
+    private final Handler mMainThreadHandler;
+
+    private final TaskResourceLoadQueue mLoadQueue;
+    private final TaskKeyLruCache<Drawable> mIconCache;
+    private final BitmapDrawable mDefaultIcon;
+
+    private boolean mStarted;
+    private boolean mCancelled;
+    private boolean mWaitingOnLoadQueue;
+
+    private final OnIdleChangedListener mOnIdleChangedListener;
+
+    /** Constructor, creates a new loading thread that loads task resources in the background */
+    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
+            TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
+            OnIdleChangedListener onIdleChangedListener) {
+        mLoadQueue = loadQueue;
+        mIconCache = iconCache;
+        mDefaultIcon = defaultIcon;
+        mMainThreadHandler = new Handler();
+        mOnIdleChangedListener = onIdleChangedListener;
+        mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
+                android.os.Process.THREAD_PRIORITY_BACKGROUND);
+        mLoadThread.start();
+        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
+    }
+
+    /** Restarts the loader thread */
+    void start(Context context) {
+        mContext = context;
+        mCancelled = false;
+        if (!mStarted) {
+            // Start loading on the load thread
+            mStarted = true;
+            mLoadThreadHandler.post(this);
+        } else {
+            // Notify the load thread to start loading again
+            synchronized (mLoadThread) {
+                mLoadThread.notifyAll();
+            }
+        }
+    }
+
+    /** Requests the loader thread to stop after the current iteration */
+    void stop() {
+        // Mark as cancelled for the thread to pick up
+        mCancelled = true;
+        // If we are waiting for the load queue for more tasks, then we can just reset the
+        // Context now, since nothing is using it
+        if (mWaitingOnLoadQueue) {
+            mContext = null;
+        }
+    }
+
+    @Override
+    public void run() {
+        while (true) {
+            if (mCancelled) {
+                // We have to unset the context here, since the background thread may be using it
+                // when we call stop()
+                mContext = null;
+                // If we are cancelled, then wait until we are started again
+                synchronized(mLoadThread) {
+                    try {
+                        mLoadThread.wait();
+                    } catch (InterruptedException ie) {
+                        ie.printStackTrace();
+                    }
+                }
+            } else {
+                // If we've stopped the loader, then fall through to the above logic to wait on
+                // the load thread
+                processLoadQueueItem();
+
+                // If there are no other items in the list, then just wait until something is added
+                if (!mCancelled && mLoadQueue.isEmpty()) {
+                    synchronized(mLoadQueue) {
+                        try {
+                            mWaitingOnLoadQueue = true;
+                            mMainThreadHandler.post(
+                                    () -> mOnIdleChangedListener.onIdleChanged(true));
+                            mLoadQueue.wait();
+                            mMainThreadHandler.post(
+                                    () -> mOnIdleChangedListener.onIdleChanged(false));
+                            mWaitingOnLoadQueue = false;
+                        } catch (InterruptedException ie) {
+                            ie.printStackTrace();
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * This needs to be in a separate method to work around an surprising interpreter behavior:
+     * The register will keep the local reference to cachedThumbnailData even if it falls out of
+     * scope. Putting it into a method fixes this issue.
+     */
+    private void processLoadQueueItem() {
+        // Load the next item from the queue
+        final Task t = mLoadQueue.nextTask();
+        if (t != null) {
+            Drawable cachedIcon = mIconCache.get(t.key);
+
+            // Load the icon if it is stale or we haven't cached one yet
+            if (cachedIcon == null) {
+                cachedIcon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(
+                        mContext, t.taskDescription, t.key.userId, mContext.getResources());
+
+                if (cachedIcon == null) {
+                    ActivityInfo info = PackageManagerWrapper.getInstance().getActivityInfo(
+                            t.key.getComponent(), t.key.userId);
+                    if (info != null) {
+                        if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
+                        cachedIcon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(
+                                info, t.key.userId);
+                    }
+                }
+
+                if (cachedIcon == null) {
+                    cachedIcon = mDefaultIcon;
+                }
+
+                // At this point, even if we can't load the icon, we will set the
+                // default icon.
+                mIconCache.put(t.key, cachedIcon);
+            }
+
+            if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
+            final ThumbnailData thumbnailData =
+                    ActivityManagerWrapper.getInstance().getTaskThumbnail(t.key.id,
+                            true /* reducedResolution */);
+
+            if (!mCancelled) {
+                // Notify that the task data has changed
+                final Drawable finalIcon = cachedIcon;
+                mMainThreadHandler.post(
+                        () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
+            }
+        }
+    }
+
+    interface OnIdleChangedListener {
+        void onIdleChanged(boolean idle);
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java
new file mode 100644
index 0000000..898d64a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/FilteredTaskList.java
@@ -0,0 +1,124 @@
+/*
+ * 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.shared.recents.model;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list of filtered tasks.
+ */
+class FilteredTaskList {
+
+    private final ArrayList<Task> mTasks = new ArrayList<>();
+    private final ArrayList<Task> mFilteredTasks = new ArrayList<>();
+    private final ArrayMap<TaskKey, Integer> mFilteredTaskIndices = new ArrayMap<>();
+    private TaskFilter mFilter;
+
+    /** Sets the task filter, and returns whether the set of filtered tasks have changed. */
+    boolean setFilter(TaskFilter filter) {
+        ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
+        mFilter = filter;
+        updateFilteredTasks();
+        return !prevFilteredTasks.equals(mFilteredTasks);
+    }
+
+    /** Adds a new task to the task list */
+    void add(Task t) {
+        mTasks.add(t);
+        updateFilteredTasks();
+    }
+
+    /** Sets the list of tasks */
+    void set(List<Task> tasks) {
+        mTasks.clear();
+        mTasks.addAll(tasks);
+        updateFilteredTasks();
+    }
+
+    /** Removes a task from the base list only if it is in the filtered list */
+    boolean remove(Task t) {
+        if (mFilteredTasks.contains(t)) {
+            boolean removed = mTasks.remove(t);
+            updateFilteredTasks();
+            return removed;
+        }
+        return false;
+    }
+
+    /** Returns the index of this task in the list of filtered tasks */
+    int indexOf(Task t) {
+        if (t != null && mFilteredTaskIndices.containsKey(t.key)) {
+            return mFilteredTaskIndices.get(t.key);
+        }
+        return -1;
+    }
+
+    /** Returns the size of the list of filtered tasks */
+    int size() {
+        return mFilteredTasks.size();
+    }
+
+    /** Returns whether the filtered list contains this task */
+    boolean contains(Task t) {
+        return mFilteredTaskIndices.containsKey(t.key);
+    }
+
+    /** Updates the list of filtered tasks whenever the base task list changes */
+    private void updateFilteredTasks() {
+        mFilteredTasks.clear();
+        if (mFilter != null) {
+            // Create a sparse array from task id to Task
+            SparseArray<Task> taskIdMap = new SparseArray<>();
+            int taskCount = mTasks.size();
+            for (int i = 0; i < taskCount; i++) {
+                Task t = mTasks.get(i);
+                taskIdMap.put(t.key.id, t);
+            }
+
+            for (int i = 0; i < taskCount; i++) {
+                Task t = mTasks.get(i);
+                if (mFilter.acceptTask(taskIdMap, t, i)) {
+                    mFilteredTasks.add(t);
+                }
+            }
+        } else {
+            mFilteredTasks.addAll(mTasks);
+        }
+        updateFilteredTaskIndices();
+    }
+
+    /** Updates the mapping of tasks to indices. */
+    private void updateFilteredTaskIndices() {
+        int taskCount = mFilteredTasks.size();
+        mFilteredTaskIndices.clear();
+        for (int i = 0; i < taskCount; i++) {
+            Task t = mFilteredTasks.get(i);
+            mFilteredTaskIndices.put(t.key, i);
+        }
+    }
+
+    /** Returns the list of filtered tasks */
+    ArrayList<Task> getTasks() {
+        return mFilteredTasks;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
new file mode 100644
index 0000000..24ba998
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/HighResThumbnailLoader.java
@@ -0,0 +1,236 @@
+/*
+ * 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.shared.recents.model;
+
+import static android.os.Process.setThreadPriority;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+/**
+ * Loader class that loads full-resolution thumbnails when appropriate.
+ */
+public class HighResThumbnailLoader implements TaskCallbacks {
+
+    private final ActivityManagerWrapper mActivityManager;
+
+    @GuardedBy("mLoadQueue")
+    private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
+    @GuardedBy("mLoadQueue")
+    private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
+    @GuardedBy("mLoadQueue")
+    private boolean mLoaderIdling;
+
+    private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
+
+    private final Thread mLoadThread;
+    private final Handler mMainThreadHandler;
+    private final boolean mIsLowRamDevice;
+    private boolean mLoading;
+    private boolean mVisible;
+    private boolean mFlingingFast;
+    private boolean mTaskLoadQueueIdle;
+
+    public HighResThumbnailLoader(ActivityManagerWrapper activityManager, Looper looper,
+            boolean isLowRamDevice) {
+        mActivityManager = activityManager;
+        mMainThreadHandler = new Handler(looper);
+        mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
+        mLoadThread.start();
+        mIsLowRamDevice = isLowRamDevice;
+    }
+
+    public void setVisible(boolean visible) {
+        if (mIsLowRamDevice) {
+            return;
+        }
+        mVisible = visible;
+        updateLoading();
+    }
+
+    public void setFlingingFast(boolean flingingFast) {
+        if (mFlingingFast == flingingFast || mIsLowRamDevice) {
+            return;
+        }
+        mFlingingFast = flingingFast;
+        updateLoading();
+    }
+
+    /**
+     * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not
+     * starting this queue until the other queue is idling.
+     */
+    public void setTaskLoadQueueIdle(boolean idle) {
+        if (mIsLowRamDevice) {
+            return;
+        }
+        mTaskLoadQueueIdle = idle;
+        updateLoading();
+    }
+
+    @VisibleForTesting
+    boolean isLoading() {
+        return mLoading;
+    }
+
+    private void updateLoading() {
+        setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle);
+    }
+
+    private void setLoading(boolean loading) {
+        if (loading == mLoading) {
+            return;
+        }
+        synchronized (mLoadQueue) {
+            mLoading = loading;
+            if (!loading) {
+                stopLoading();
+            } else {
+                startLoading();
+            }
+        }
+    }
+
+    @GuardedBy("mLoadQueue")
+    private void startLoading() {
+        for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
+            Task t = mVisibleTasks.get(i);
+            if ((t.thumbnail == null || t.thumbnail.reducedResolution)
+                    && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
+                mLoadQueue.add(t);
+            }
+        }
+        mLoadQueue.notifyAll();
+    }
+
+    @GuardedBy("mLoadQueue")
+    private void stopLoading() {
+        mLoadQueue.clear();
+        mLoadQueue.notifyAll();
+    }
+
+    /**
+     * Needs to be called when a task becomes visible. Note that this is different from
+     * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
+     * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
+     * has been updated.
+     */
+    public void onTaskVisible(Task t) {
+        t.addCallback(this);
+        mVisibleTasks.add(t);
+        if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
+            synchronized (mLoadQueue) {
+                mLoadQueue.add(t);
+                mLoadQueue.notifyAll();
+            }
+        }
+    }
+
+    /**
+     * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
+     * different from {@link TaskCallbacks#onTaskDataUnloaded()}
+     */
+    public void onTaskInvisible(Task t) {
+        t.removeCallback(this);
+        mVisibleTasks.remove(t);
+        synchronized (mLoadQueue) {
+            mLoadQueue.remove(t);
+        }
+    }
+
+    @VisibleForTesting
+    void waitForLoaderIdle() {
+        while (true) {
+            synchronized (mLoadQueue) {
+                if (mLoadQueue.isEmpty() && mLoaderIdling) {
+                    return;
+                }
+            }
+            SystemClock.sleep(100);
+        }
+    }
+
+    @Override
+    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
+        if (thumbnailData != null && !thumbnailData.reducedResolution) {
+            synchronized (mLoadQueue) {
+                mLoadQueue.remove(task);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskDataUnloaded() {
+    }
+
+    @Override
+    public void onTaskWindowingModeChanged() {
+    }
+
+    private final Runnable mLoader = new Runnable() {
+
+        @Override
+        public void run() {
+            setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
+            while (true) {
+                Task next = null;
+                synchronized (mLoadQueue) {
+                    if (!mLoading || mLoadQueue.isEmpty()) {
+                        try {
+                            mLoaderIdling = true;
+                            mLoadQueue.wait();
+                            mLoaderIdling = false;
+                        } catch (InterruptedException e) {
+                            // Don't care.
+                        }
+                    } else {
+                        next = mLoadQueue.poll();
+                        if (next != null) {
+                            mLoadingTasks.add(next);
+                        }
+                    }
+                }
+                if (next != null) {
+                    loadTask(next);
+                }
+            }
+        }
+
+        private void loadTask(Task t) {
+            ThumbnailData thumbnail = mActivityManager.getTaskThumbnail(t.key.id,
+                    false /* reducedResolution */);
+            mMainThreadHandler.post(() -> {
+                synchronized (mLoadQueue) {
+                    mLoadingTasks.remove(t);
+                }
+                if (mVisibleTasks.contains(t)) {
+                    t.notifyTaskDataLoaded(thumbnail, t.icon);
+                }
+            });
+        }
+    };
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
new file mode 100644
index 0000000..806a073
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.SparseBooleanArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+/**
+ * This class stores the loading state as it goes through multiple stages of loading:
+ *   1) preloadRawTasks() will load the raw set of recents tasks from the system
+ *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
+ *      thumbnails that are currently in the cache
+ *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
+ *      options specified, such that we can transition into the Recents activity seamlessly
+ */
+public class RecentsTaskLoadPlan {
+
+    /** The set of conditions to load tasks. */
+    public static class Options {
+        public int runningTaskId = -1;
+        public boolean loadIcons = true;
+        public boolean loadThumbnails = false;
+        public boolean onlyLoadForCache = false;
+        public boolean onlyLoadPausedActivities = false;
+        public int numVisibleTasks = 0;
+        public int numVisibleTaskThumbnails = 0;
+    }
+
+    private final Context mContext;
+    private final KeyguardManager mKeyguardManager;
+
+    private List<ActivityManager.RecentTaskInfo> mRawTasks;
+    private TaskStack mStack;
+
+    private final SparseBooleanArray mTmpLockedUsers = new SparseBooleanArray();
+
+    public RecentsTaskLoadPlan(Context context) {
+        mContext = context;
+        mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+    }
+
+    /**
+     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
+     * to most-recent order.
+     *
+     * Note: Do not lock, callers should synchronize on the loader before making this call.
+     */
+    void preloadRawTasks() {
+        int currentUserId = ActivityManagerWrapper.getInstance().getCurrentUserId();
+        mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
+                ActivityManager.getMaxRecentTasksStatic(), currentUserId);
+
+        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
+        Collections.reverse(mRawTasks);
+    }
+
+    /**
+     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
+     * have a list of all the recent tasks with their metadata, not including icons or
+     * thumbnails which were not cached and have to be loaded.
+     *
+     * The tasks will be ordered by:
+     * - least-recent to most-recent stack tasks
+     *
+     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
+     * this call (callers should synchronize on the loader before making this call).
+     */
+    void preloadPlan(RecentsTaskLoader loader, int runningTaskId) {
+        Resources res = mContext.getResources();
+        ArrayList<Task> allTasks = new ArrayList<>();
+        if (mRawTasks == null) {
+            preloadRawTasks();
+        }
+
+        int taskCount = mRawTasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
+
+            // Compose the task key
+            final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
+            TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
+                    t.userId, t.lastActiveTime);
+
+            boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
+            boolean isStackTask = !isFreeformTask;
+            boolean isLaunchTarget = taskKey.id == runningTaskId;
+
+            ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
+            if (info == null) {
+                continue;
+            }
+
+            // Load the title, icon, and color
+            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
+            String titleDescription = loader.getAndUpdateContentDescription(taskKey,
+                    t.taskDescription);
+            Drawable icon = isStackTask
+                    ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
+                    : null;
+            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                    false /* loadIfNotCached */, false /* storeInCache */);
+            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
+            int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
+            boolean isSystemApp = (info != null) &&
+                    ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+
+            // TODO: Refactor to not do this every preload
+            if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
+                mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
+            }
+            boolean isLocked = mTmpLockedUsers.get(t.userId);
+
+            // Add the task to the stack
+            Task task = new Task(taskKey, icon,
+                    thumbnail, title, titleDescription, activityColor, backgroundColor,
+                    isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
+                    t.taskDescription, t.resizeMode, t.topActivity, isLocked);
+
+            allTasks.add(task);
+        }
+
+        // Initialize the stacks
+        mStack = new TaskStack();
+        mStack.setTasks(allTasks, false /* notifyStackChanges */);
+    }
+
+    /**
+     * Called to apply the actual loading based on the specified conditions.
+     *
+     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
+     * this call (callers should synchronize on the loader before making this call).
+     */
+    void executePlan(Options opts, RecentsTaskLoader loader) {
+        Resources res = mContext.getResources();
+
+        // Iterate through each of the tasks and load them according to the load conditions.
+        ArrayList<Task> tasks = mStack.getStackTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            TaskKey taskKey = task.key;
+
+            boolean isRunningTask = (task.key.id == opts.runningTaskId);
+            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
+            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
+
+            // If requested, skip the running task
+            if (opts.onlyLoadPausedActivities && isRunningTask) {
+                continue;
+            }
+
+            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
+                if (task.icon == null) {
+                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
+                            true);
+                }
+            }
+            if (opts.loadThumbnails && isVisibleThumbnail) {
+                task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
+                        true /* loadIfNotCached */, true /* storeInCache */);
+            }
+        }
+    }
+
+    /**
+     * Returns the TaskStack from the preloaded list of recent tasks.
+     */
+    public TaskStack getTaskStack() {
+        return mStack;
+    }
+
+    /** Returns whether there are any tasks in any stacks. */
+    public boolean hasTasks() {
+        if (mStack != null) {
+            return mStack.getTaskCount() > 0;
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
new file mode 100644
index 0000000..de4c72c
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+
+/**
+ * Recents task loader
+ */
+public class RecentsTaskLoader {
+    private static final String TAG = "RecentsTaskLoader";
+    private static final boolean DEBUG = false;
+
+    /** Levels of svelte in increasing severity/austerity. */
+    // No svelting.
+    public static final int SVELTE_NONE = 0;
+    // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
+    // caching thumbnails as you scroll.
+    public static final int SVELTE_LIMIT_CACHE = 1;
+    // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
+    // evict all thumbnails when hidden.
+    public static final int SVELTE_DISABLE_CACHE = 2;
+    // Disable all thumbnail loading.
+    public static final int SVELTE_DISABLE_LOADING = 3;
+
+    private final Context mContext;
+
+    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
+    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
+    // below, this is per-package so we can't invalidate the items in the cache based on the last
+    // active time.  Instead, we rely on the PackageMonitor to keep us informed whenever a
+    // package in the cache has been updated, so that we may remove it.
+    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
+    private final TaskKeyLruCache<Drawable> mIconCache;
+    private final TaskKeyLruCache<String> mActivityLabelCache;
+    private final TaskKeyLruCache<String> mContentDescriptionCache;
+    private final TaskResourceLoadQueue mLoadQueue;
+    private final BackgroundTaskLoader mLoader;
+    private final HighResThumbnailLoader mHighResThumbnailLoader;
+    @GuardedBy("this")
+    private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
+    @GuardedBy("this")
+    private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
+    private final int mMaxThumbnailCacheSize;
+    private final int mMaxIconCacheSize;
+    private int mNumVisibleTasksLoaded;
+    private int mSvelteLevel;
+
+    private int mDefaultTaskBarBackgroundColor;
+    private int mDefaultTaskViewBackgroundColor;
+    private final BitmapDrawable mDefaultIcon;
+
+    private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() {
+        @Override
+        public void onEntryEvicted(TaskKey key) {
+            if (key != null) {
+                mActivityInfoCache.remove(key.getComponent());
+            }
+        }
+    };
+
+    public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize,
+            int svelteLevel) {
+        mContext = context;
+        mMaxThumbnailCacheSize = maxThumbnailCacheSize;
+        mMaxIconCacheSize = maxIconCacheSize;
+        mSvelteLevel = svelteLevel;
+
+        // Create the default assets
+        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+        icon.eraseColor(0);
+        mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
+
+        // Initialize the proxy, cache and loaders
+        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
+        mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(),
+                Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic());
+        mLoadQueue = new TaskResourceLoadQueue();
+        mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction);
+        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
+        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
+                mClearActivityInfoOnEviction);
+        mActivityInfoCache = new LruCache<>(numRecentTasks);
+        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
+                mHighResThumbnailLoader::setTaskLoadQueueIdle);
+    }
+
+    /**
+     * Sets the default task bar/view colors if none are provided by the app.
+     */
+    public void setDefaultColors(int defaultTaskBarBackgroundColor,
+            int defaultTaskViewBackgroundColor) {
+        mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor;
+        mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor;
+    }
+
+    /** Returns the size of the app icon cache. */
+    public int getIconCacheSize() {
+        return mMaxIconCacheSize;
+    }
+
+    /** Returns the size of the thumbnail cache. */
+    public int getThumbnailCacheSize() {
+        return mMaxThumbnailCacheSize;
+    }
+
+    public HighResThumbnailLoader getHighResThumbnailLoader() {
+        return mHighResThumbnailLoader;
+    }
+
+    /** Preloads recents tasks using the specified plan to store the output. */
+    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
+        try {
+            Trace.beginSection("preloadPlan");
+            plan.preloadPlan(this, runningTaskId);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    /** Begins loading the heavy task data according to the specified options. */
+    public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) {
+        if (opts == null) {
+            throw new RuntimeException("Requires load options");
+        }
+        if (opts.onlyLoadForCache && opts.loadThumbnails) {
+            // If we are loading for the cache, we'd like to have the real cache only include the
+            // visible thumbnails. However, we also don't want to reload already cached thumbnails.
+            // Thus, we copy over the current entries into a second cache, and clear the real cache,
+            // such that the real cache only contains visible thumbnails.
+            mTempCache.copyEntries(mThumbnailCache);
+            mThumbnailCache.evictAll();
+        }
+        plan.executePlan(opts, this);
+        mTempCache.evictAll();
+        if (!opts.onlyLoadForCache) {
+            mNumVisibleTasksLoaded = opts.numVisibleTasks;
+        }
+    }
+
+    /**
+     * Acquires the task resource data directly from the cache, loading if necessary.
+     */
+    public void loadTaskData(Task t) {
+        Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
+        icon = icon != null ? icon : mDefaultIcon;
+        mLoadQueue.addTask(t);
+        t.notifyTaskDataLoaded(t.thumbnail, icon);
+    }
+
+    /** Releases the task resource data back into the pool. */
+    public void unloadTaskData(Task t) {
+        mLoadQueue.removeTask(t);
+        t.notifyTaskDataUnloaded(mDefaultIcon);
+    }
+
+    /** Completely removes the resource data from the pool. */
+    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
+        mLoadQueue.removeTask(t);
+        mIconCache.remove(t.key);
+        mActivityLabelCache.remove(t.key);
+        mContentDescriptionCache.remove(t.key);
+        if (notifyTaskDataUnloaded) {
+            t.notifyTaskDataUnloaded(mDefaultIcon);
+        }
+    }
+
+    /**
+     * Handles signals from the system, trimming memory when requested to prevent us from running
+     * out of memory.
+     */
+    public synchronized void onTrimMemory(int level) {
+        switch (level) {
+            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
+                // Stop the loader immediately when the UI is no longer visible
+                stopLoader();
+                mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
+                        mMaxIconCacheSize / 2));
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
+            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
+                // We are leaving recents, so trim the data a bit
+                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 2));
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
+            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+                // We are going to be low on memory
+                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
+                mActivityInfoCache.trimToSize(Math.max(1,
+                        ActivityManager.getMaxRecentTasksStatic() / 4));
+                break;
+            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
+            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+                // We are low on memory, so release everything
+                mIconCache.evictAll();
+                mActivityInfoCache.evictAll();
+                // The cache is small, only clear the label cache when we are critical
+                mActivityLabelCache.evictAll();
+                mContentDescriptionCache.evictAll();
+                mThumbnailCache.evictAll();
+                break;
+            default:
+                break;
+        }
+    }
+
+    public void onPackageChanged(String packageName) {
+        // Remove all the cached activity infos for this package.  The other caches do not need to
+        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
+        // cached contents are requested
+        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
+        for (ComponentName cn : activityInfoCache.keySet()) {
+            if (cn.getPackageName().equals(packageName)) {
+                if (DEBUG) {
+                    Log.d(TAG, "Removing activity info from cache: " + cn);
+                }
+                mActivityInfoCache.remove(cn);
+            }
+        }
+    }
+
+    /**
+     * Returns the cached task label if the task key is not expired, updating the cache if it is.
+     */
+    String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) {
+        // Return the task description label if it exists
+        if (td != null && td.getLabel() != null) {
+            return td.getLabel();
+        }
+        // Return the cached activity label if it exists
+        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+        if (activityInfo != null) {
+            label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo,
+                    taskKey.userId);
+            mActivityLabelCache.put(taskKey, label);
+            return label;
+        }
+        // If the activity info does not exist or fails to load, return an empty label for now,
+        // but do not cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task content description if the task key is not expired, updating the
+     * cache if it is.
+     */
+    String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) {
+        // Return the cached content description if it exists
+        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
+        if (label != null) {
+            return label;
+        }
+
+        // All short paths failed, load the label from the activity info and cache it
+        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+        if (activityInfo != null) {
+            label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(
+                    activityInfo, taskKey.userId, td);
+            if (td == null) {
+                // Only add to the cache if the task description is null, otherwise, it is possible
+                // for the task description to change between calls without the last active time
+                // changing (ie. between preloading and Overview starting) which would lead to stale
+                // content descriptions
+                // TODO: Investigate improving this
+                mContentDescriptionCache.put(taskKey, label);
+            }
+            return label;
+        }
+        // If the content description does not exist, return an empty label for now, but do not
+        // cache it
+        return "";
+    }
+
+    /**
+     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
+     */
+    Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td,
+            Resources res, boolean loadIfNotCached) {
+        // Return the cached activity icon if it exists
+        Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
+        if (icon != null) {
+            return icon;
+        }
+
+        if (loadIfNotCached) {
+            // Return and cache the task description icon if it exists
+            icon = ActivityManagerWrapper.getInstance().getBadgedTaskDescriptionIcon(mContext, td,
+                    taskKey.userId, res);
+            if (icon != null) {
+                mIconCache.put(taskKey, icon);
+                return icon;
+            }
+
+            // Load the icon from the activity info and cache it
+            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
+            if (activityInfo != null) {
+                icon = ActivityManagerWrapper.getInstance().getBadgedActivityIcon(activityInfo,
+                        taskKey.userId);
+                if (icon != null) {
+                    mIconCache.put(taskKey, icon);
+                    return icon;
+                }
+            }
+        }
+        // We couldn't load any icon
+        return null;
+    }
+
+    /**
+     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
+     */
+    synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached,
+            boolean storeInCache) {
+        ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
+        if (cached != null) {
+            return cached;
+        }
+
+        cached = mTempCache.getAndInvalidateIfModified(taskKey);
+        if (cached != null) {
+            mThumbnailCache.put(taskKey, cached);
+            return cached;
+        }
+
+        if (loadIfNotCached) {
+            if (mSvelteLevel < SVELTE_DISABLE_LOADING) {
+                // Load the thumbnail from the system
+                ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
+                        taskKey.id, true /* reducedResolution */);
+                if (thumbnailData.thumbnail != null) {
+                    if (storeInCache) {
+                        mThumbnailCache.put(taskKey, thumbnailData);
+                    }
+                    return thumbnailData;
+                }
+            }
+        }
+
+        // We couldn't load any thumbnail
+        return null;
+    }
+
+    /**
+     * Returns the task's primary color if possible, defaulting to the default color if there is
+     * no specified primary color.
+     */
+    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
+        if (td != null && td.getPrimaryColor() != 0) {
+            return td.getPrimaryColor();
+        }
+        return mDefaultTaskBarBackgroundColor;
+    }
+
+    /**
+     * Returns the task's background color if possible.
+     */
+    int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
+        if (td != null && td.getBackgroundColor() != 0) {
+            return td.getBackgroundColor();
+        }
+        return mDefaultTaskViewBackgroundColor;
+    }
+
+    /**
+     * Returns the activity info for the given task key, retrieving one from the system if the
+     * task key is expired.
+     */
+    ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) {
+        ComponentName cn = taskKey.getComponent();
+        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
+        if (activityInfo == null) {
+            activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, taskKey.userId);
+            if (cn == null || activityInfo == null) {
+                Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
+                        activityInfo);
+                return null;
+            }
+            mActivityInfoCache.put(cn, activityInfo);
+        }
+        return activityInfo;
+    }
+
+    /**
+     * Starts loading tasks.
+     */
+    public void startLoader(Context ctx) {
+        mLoader.start(ctx);
+    }
+
+    /**
+     * Stops the task loader and clears all queued, pending task loads.
+     */
+    private void stopLoader() {
+        mLoader.stop();
+        mLoadQueue.clearTasks();
+    }
+
+    public synchronized void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+
+        writer.print(prefix); writer.println(TAG);
+        writer.print(prefix); writer.println("Icon Cache");
+        mIconCache.dump(innerPrefix, writer);
+        writer.print(prefix); writer.println("Thumbnail Cache");
+        mThumbnailCache.dump(innerPrefix, writer);
+        writer.print(prefix); writer.println("Temp Thumbnail Cache");
+        mTempCache.dump(innerPrefix, writer);
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
new file mode 100644
index 0000000..6bddbe0
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import android.app.ActivityManager.TaskDescription;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.view.ViewDebug;
+
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * A task represents the top most task in the system's task stack.
+ */
+public class Task {
+
+    public static final String TAG = "Task";
+
+    /* Task callbacks */
+    public interface TaskCallbacks {
+        /* Notifies when a task has been bound */
+        void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
+        /* Notifies when a task has been unbound */
+        void onTaskDataUnloaded();
+        /* Notifies when a task's windowing mode has changed. */
+        void onTaskWindowingModeChanged();
+    }
+
+    /* The Task Key represents the unique primary key for the task */
+    public static class TaskKey {
+        @ViewDebug.ExportedProperty(category="recents")
+        public final int id;
+        @ViewDebug.ExportedProperty(category="recents")
+        public int windowingMode;
+        @ViewDebug.ExportedProperty(category="recents")
+        public final Intent baseIntent;
+        @ViewDebug.ExportedProperty(category="recents")
+        public final int userId;
+        @ViewDebug.ExportedProperty(category="recents")
+        public long lastActiveTime;
+
+        private int mHashCode;
+
+        public TaskKey(int id, int windowingMode, Intent intent, int userId, long lastActiveTime) {
+            this.id = id;
+            this.windowingMode = windowingMode;
+            this.baseIntent = intent;
+            this.userId = userId;
+            this.lastActiveTime = lastActiveTime;
+            updateHashCode();
+        }
+
+        public void setWindowingMode(int windowingMode) {
+            this.windowingMode = windowingMode;
+            updateHashCode();
+        }
+
+        public ComponentName getComponent() {
+            return this.baseIntent.getComponent();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof TaskKey)) {
+                return false;
+            }
+            TaskKey otherKey = (TaskKey) o;
+            return id == otherKey.id
+                    && windowingMode == otherKey.windowingMode
+                    && userId == otherKey.userId;
+        }
+
+        @Override
+        public int hashCode() {
+            return mHashCode;
+        }
+
+        @Override
+        public String toString() {
+            return "id=" + id + " windowingMode=" + windowingMode + " user=" + userId
+                    + " lastActiveTime=" + lastActiveTime;
+        }
+
+        private void updateHashCode() {
+            mHashCode = Objects.hash(id, windowingMode, userId);
+        }
+    }
+
+    @ViewDebug.ExportedProperty(deepExport=true, prefix="key_")
+    public TaskKey key;
+
+    /**
+     * The temporary sort index in the stack, used when ordering the stack.
+     */
+    public int temporarySortIndexInStack;
+
+    /**
+     * The icon is the task description icon (if provided), which falls back to the activity icon,
+     * which can then fall back to the application icon.
+     */
+    public Drawable icon;
+    public ThumbnailData thumbnail;
+    @ViewDebug.ExportedProperty(category="recents")
+    public String title;
+    @ViewDebug.ExportedProperty(category="recents")
+    public String titleDescription;
+    @ViewDebug.ExportedProperty(category="recents")
+    public int colorPrimary;
+    @ViewDebug.ExportedProperty(category="recents")
+    public int colorBackground;
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean useLightOnPrimaryColor;
+
+    /**
+     * The task description for this task, only used to reload task icons.
+     */
+    public TaskDescription taskDescription;
+
+    /**
+     * The state isLaunchTarget will be set for the correct task upon launching Recents.
+     */
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean isLaunchTarget;
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean isStackTask;
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean isSystemApp;
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean isDockable;
+
+    /**
+     * Resize mode. See {@link ActivityInfo#resizeMode}.
+     */
+    @ViewDebug.ExportedProperty(category="recents")
+    public int resizeMode;
+
+    @ViewDebug.ExportedProperty(category="recents")
+    public ComponentName topActivity;
+
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean isLocked;
+
+    private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
+
+    public Task() {
+        // Do nothing
+    }
+
+    public Task(TaskKey key, Drawable icon, ThumbnailData thumbnail, String title,
+            String titleDescription, int colorPrimary, int colorBackground, boolean isLaunchTarget,
+            boolean isStackTask, boolean isSystemApp, boolean isDockable,
+            TaskDescription taskDescription, int resizeMode, ComponentName topActivity,
+            boolean isLocked) {
+        this.key = key;
+        this.icon = icon;
+        this.thumbnail = thumbnail;
+        this.title = title;
+        this.titleDescription = titleDescription;
+        this.colorPrimary = colorPrimary;
+        this.colorBackground = colorBackground;
+        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
+                Color.WHITE) > 3f;
+        this.taskDescription = taskDescription;
+        this.isLaunchTarget = isLaunchTarget;
+        this.isStackTask = isStackTask;
+        this.isSystemApp = isSystemApp;
+        this.isDockable = isDockable;
+        this.resizeMode = resizeMode;
+        this.topActivity = topActivity;
+        this.isLocked = isLocked;
+    }
+
+    /**
+     * Copies the metadata from another task, but retains the current callbacks.
+     */
+    public void copyFrom(Task o) {
+        this.key = o.key;
+        this.icon = o.icon;
+        this.thumbnail = o.thumbnail;
+        this.title = o.title;
+        this.titleDescription = o.titleDescription;
+        this.colorPrimary = o.colorPrimary;
+        this.colorBackground = o.colorBackground;
+        this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
+        this.taskDescription = o.taskDescription;
+        this.isLaunchTarget = o.isLaunchTarget;
+        this.isStackTask = o.isStackTask;
+        this.isSystemApp = o.isSystemApp;
+        this.isDockable = o.isDockable;
+        this.resizeMode = o.resizeMode;
+        this.isLocked = o.isLocked;
+        this.topActivity = o.topActivity;
+    }
+
+    /**
+     * Add a callback.
+     */
+    public void addCallback(TaskCallbacks cb) {
+        if (!mCallbacks.contains(cb)) {
+            mCallbacks.add(cb);
+        }
+    }
+
+    /**
+     * Remove a callback.
+     */
+    public void removeCallback(TaskCallbacks cb) {
+        mCallbacks.remove(cb);
+    }
+
+    /** Updates the task's windowing mode. */
+    public void setWindowingMode(int windowingMode) {
+        key.setWindowingMode(windowingMode);
+        int callbackCount = mCallbacks.size();
+        for (int i = 0; i < callbackCount; i++) {
+            mCallbacks.get(i).onTaskWindowingModeChanged();
+        }
+    }
+
+    /** Notifies the callback listeners that this task has been loaded */
+    public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
+        this.icon = applicationIcon;
+        this.thumbnail = thumbnailData;
+        int callbackCount = mCallbacks.size();
+        for (int i = 0; i < callbackCount; i++) {
+            mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
+        }
+    }
+
+    /** Notifies the callback listeners that this task has been unloaded */
+    public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
+        icon = defaultApplicationIcon;
+        thumbnail = null;
+        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+            mCallbacks.get(i).onTaskDataUnloaded();
+        }
+    }
+
+    /**
+     * Returns the top activity component.
+     */
+    public ComponentName getTopComponent() {
+        return topActivity != null
+                ? topActivity
+                : key.baseIntent.getComponent();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        // Check that the id matches
+        Task t = (Task) o;
+        return key.equals(t.key);
+    }
+
+    @Override
+    public String toString() {
+        return "[" + key.toString() + "] " + title;
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        writer.print(prefix); writer.print(key);
+        if (!isDockable) {
+            writer.print(" dockable=N");
+        }
+        if (isLaunchTarget) {
+            writer.print(" launchTarget=Y");
+        }
+        if (isLocked) {
+            writer.print(" locked=Y");
+        }
+        writer.print(" "); writer.print(title);
+        writer.println();
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java
new file mode 100644
index 0000000..9a1ff54
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskFilter.java
@@ -0,0 +1,27 @@
+/*
+ * 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.shared.recents.model;
+
+import android.util.SparseArray;
+
+/**
+ * An interface for a task filter to query whether a particular task should show in a stack.
+ */
+interface TaskFilter {
+    /** Returns whether the filter accepts the specified task */
+    boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java
new file mode 100644
index 0000000..4bf3500
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyCache.java
@@ -0,0 +1,89 @@
+/*
+ * 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.shared.recents.model;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+/**
+ * Base class for both strong and LRU task key cache.
+ */
+public abstract class TaskKeyCache<V> {
+
+    protected static final String TAG = "TaskKeyCache";
+
+    protected final SparseArray<TaskKey> mKeys = new SparseArray<>();
+
+    /**
+     * Gets a specific entry in the cache with the specified key, regardless of whether the cached
+     * value is valid or not.
+     */
+    final V get(TaskKey key) {
+        return getCacheEntry(key.id);
+    }
+
+    /**
+     * Returns the value only if the key is valid (has not been updated since the last time it was
+     * in the cache)
+     */
+    final V getAndInvalidateIfModified(TaskKey key) {
+        TaskKey lastKey = mKeys.get(key.id);
+        if (lastKey != null) {
+            if ((lastKey.windowingMode != key.windowingMode) ||
+                    (lastKey.lastActiveTime != key.lastActiveTime)) {
+                // The task has updated (been made active since the last time it was put into the
+                // LRU cache) or the stack id for the task has changed, invalidate that cache item
+                remove(key);
+                return null;
+            }
+        }
+        // Either the task does not exist in the cache, or the last active time is the same as
+        // the key specified, so return what is in the cache
+        return getCacheEntry(key.id);
+    }
+
+    /** Puts an entry in the cache for a specific key. */
+    final void put(TaskKey key, V value) {
+        if (key == null || value == null) {
+            Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
+            return;
+        }
+        mKeys.put(key.id, key);
+        putCacheEntry(key.id, value);
+    }
+
+
+    /** Removes a cache entry for a specific key. */
+    final void remove(TaskKey key) {
+        // Remove the key after the cache value because we need it to make the callback
+        removeCacheEntry(key.id);
+        mKeys.remove(key.id);
+    }
+
+    /** Removes all the entries in the cache. */
+    final void evictAll() {
+        evictAllCache();
+        mKeys.clear();
+    }
+
+    protected abstract V getCacheEntry(int id);
+    protected abstract void putCacheEntry(int id, V value);
+    protected abstract void removeCacheEntry(int id);
+    protected abstract void evictAllCache();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java
new file mode 100644
index 0000000..0ba2c3b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyLruCache.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import android.util.LruCache;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.io.PrintWriter;
+
+/**
+ * A mapping of {@link TaskKey} to value, with additional LRU functionality where the least
+ * recently referenced key/values will be evicted as more values than the given cache size are
+ * inserted.
+ *
+ * In addition, this also allows the caller to invalidate cached values for keys that have since
+ * changed.
+ */
+public class TaskKeyLruCache<V> extends TaskKeyCache<V> {
+
+    public interface EvictionCallback {
+        void onEntryEvicted(TaskKey key);
+    }
+
+    private final LruCache<Integer, V> mCache;
+    private final EvictionCallback mEvictionCallback;
+
+    public TaskKeyLruCache(int cacheSize) {
+        this(cacheSize, null);
+    }
+
+    public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) {
+        mEvictionCallback = evictionCallback;
+        mCache = new LruCache<Integer, V>(cacheSize) {
+
+            @Override
+            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
+                if (mEvictionCallback != null) {
+                    mEvictionCallback.onEntryEvicted(mKeys.get(taskId));
+                }
+                mKeys.remove(taskId);
+            }
+        };
+    }
+
+    /** Trims the cache to a specific size */
+    final void trimToSize(int cacheSize) {
+        mCache.trimToSize(cacheSize);
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+
+        writer.print(prefix); writer.print(TAG);
+        writer.print(" numEntries="); writer.print(mKeys.size());
+        writer.println();
+        int keyCount = mKeys.size();
+        for (int i = 0; i < keyCount; i++) {
+            writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
+        }
+    }
+
+    @Override
+    protected V getCacheEntry(int id) {
+        return mCache.get(id);
+    }
+
+    @Override
+    protected void putCacheEntry(int id, V value) {
+        mCache.put(id, value);
+    }
+
+    @Override
+    protected void removeCacheEntry(int id) {
+        mCache.remove(id);
+    }
+
+    @Override
+    protected void evictAllCache() {
+        mCache.evictAll();
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
new file mode 100644
index 0000000..4408ece
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskKeyStrongCache.java
@@ -0,0 +1,71 @@
+/*
+ * 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.shared.recents.model;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+
+import java.io.PrintWriter;
+
+/**
+ * Like {@link TaskKeyLruCache}, but without LRU functionality.
+ */
+public class TaskKeyStrongCache<V> extends TaskKeyCache<V> {
+
+    private static final String TAG = "TaskKeyCache";
+
+    private final ArrayMap<Integer, V> mCache = new ArrayMap<>();
+
+    final void copyEntries(TaskKeyStrongCache<V> other) {
+        for (int i = other.mKeys.size() - 1; i >= 0; i--) {
+            TaskKey key = other.mKeys.valueAt(i);
+            put(key, other.mCache.get(key.id));
+        }
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+        writer.print(prefix); writer.print(TAG);
+        writer.print(" numEntries="); writer.print(mKeys.size());
+        writer.println();
+        int keyCount = mKeys.size();
+        for (int i = 0; i < keyCount; i++) {
+            writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
+        }
+    }
+
+    @Override
+    protected V getCacheEntry(int id) {
+        return mCache.get(id);
+    }
+
+    @Override
+    protected void putCacheEntry(int id, V value) {
+        mCache.put(id, value);
+    }
+
+    @Override
+    protected void removeCacheEntry(int id) {
+        mCache.remove(id);
+    }
+
+    @Override
+    protected void evictAllCache() {
+        mCache.clear();
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java
new file mode 100644
index 0000000..fbb6ace
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskResourceLoadQueue.java
@@ -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
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A Task load queue
+ */
+class TaskResourceLoadQueue {
+
+    private final ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<>();
+
+    /** Adds a new task to the load queue */
+    void addTask(Task t) {
+        if (!mQueue.contains(t)) {
+            mQueue.add(t);
+        }
+        synchronized(this) {
+            notifyAll();
+        }
+    }
+
+    /**
+     * Retrieves the next task from the load queue, as well as whether we want that task to be
+     * force reloaded.
+     */
+    Task nextTask() {
+        return mQueue.poll();
+    }
+
+    /** Removes a task from the load queue */
+    void removeTask(Task t) {
+        mQueue.remove(t);
+    }
+
+    /** Clears all the tasks from the load queue */
+    void clearTasks() {
+        mQueue.clear();
+    }
+
+    /** Returns whether the load queue is empty */
+    boolean isEmpty() {
+        return mQueue.isEmpty();
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java
new file mode 100644
index 0000000..693379d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/TaskStack.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.model;
+
+import android.content.ComponentName;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * The task stack contains a list of multiple tasks.
+ */
+public class TaskStack {
+
+    private static final String TAG = "TaskStack";
+
+    /** Task stack callbacks */
+    public interface TaskStackCallbacks {
+        /**
+         * Notifies when a new task has been added to the stack.
+         */
+        void onStackTaskAdded(TaskStack stack, Task newTask);
+
+        /**
+         * Notifies when a task has been removed from the stack.
+         */
+        void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
+                AnimationProps animation, boolean fromDockGesture,
+                boolean dismissRecentsIfAllRemoved);
+
+        /**
+         * Notifies when all tasks have been removed from the stack.
+         */
+        void onStackTasksRemoved(TaskStack stack);
+
+        /**
+         * Notifies when tasks in the stack have been updated.
+         */
+        void onStackTasksUpdated(TaskStack stack);
+    }
+
+    private final ArrayList<Task> mRawTaskList = new ArrayList<>();
+    private final FilteredTaskList mStackTaskList = new FilteredTaskList();
+    private TaskStackCallbacks mCb;
+
+    public TaskStack() {
+        // Ensure that we only show stack tasks
+        mStackTaskList.setFilter((taskIdMap, t, index) -> t.isStackTask);
+    }
+
+    /** Sets the callbacks for this task stack. */
+    public void setCallbacks(TaskStackCallbacks cb) {
+        mCb = cb;
+    }
+
+    /**
+     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+     * how they should update themselves.
+     */
+    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
+        removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
+    }
+
+    /**
+     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+     * how they should update themselves.
+     */
+    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
+            boolean dismissRecentsIfAllRemoved) {
+        if (mStackTaskList.contains(t)) {
+            mStackTaskList.remove(t);
+            Task newFrontMostTask = getStackFrontMostTask();
+            if (mCb != null) {
+                // Notify that a task has been removed
+                mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
+                        fromDockGesture, dismissRecentsIfAllRemoved);
+            }
+        }
+        mRawTaskList.remove(t);
+    }
+
+    /**
+     * Removes all tasks from the stack.
+     */
+    public void removeAllTasks(boolean notifyStackChanges) {
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        for (int i = tasks.size() - 1; i >= 0; i--) {
+            Task t = tasks.get(i);
+            mStackTaskList.remove(t);
+            mRawTaskList.remove(t);
+        }
+        if (mCb != null && notifyStackChanges) {
+            // Notify that all tasks have been removed
+            mCb.onStackTasksRemoved(this);
+        }
+    }
+
+
+    /**
+     * @see #setTasks(List, boolean)
+     */
+    public void setTasks(TaskStack stack, boolean notifyStackChanges) {
+        setTasks(stack.mRawTaskList, notifyStackChanges);
+    }
+
+    /**
+     * Sets a few tasks in one go, without calling any callbacks.
+     *
+     * @param tasks the new set of tasks to replace the current set.
+     * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
+     */
+    public void setTasks(List<Task> tasks, boolean notifyStackChanges) {
+        // Compute a has set for each of the tasks
+        ArrayMap<TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
+        ArrayMap<TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
+        ArrayList<Task> addedTasks = new ArrayList<>();
+        ArrayList<Task> removedTasks = new ArrayList<>();
+        ArrayList<Task> allTasks = new ArrayList<>();
+
+        // Disable notifications if there are no callbacks
+        if (mCb == null) {
+            notifyStackChanges = false;
+        }
+
+        // Remove any tasks that no longer exist
+        int taskCount = mRawTaskList.size();
+        for (int i = taskCount - 1; i >= 0; i--) {
+            Task task = mRawTaskList.get(i);
+            if (!newTasksMap.containsKey(task.key)) {
+                if (notifyStackChanges) {
+                    removedTasks.add(task);
+                }
+            }
+        }
+
+        // Add any new tasks
+        taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task newTask = tasks.get(i);
+            Task currentTask = currentTasksMap.get(newTask.key);
+            if (currentTask == null && notifyStackChanges) {
+                addedTasks.add(newTask);
+            } else if (currentTask != null) {
+                // The current task has bound callbacks, so just copy the data from the new task
+                // state and add it back into the list
+                currentTask.copyFrom(newTask);
+                newTask = currentTask;
+            }
+            allTasks.add(newTask);
+        }
+
+        // Sort all the tasks to ensure they are ordered correctly
+        for (int i = allTasks.size() - 1; i >= 0; i--) {
+            allTasks.get(i).temporarySortIndexInStack = i;
+        }
+
+        mStackTaskList.set(allTasks);
+        mRawTaskList.clear();
+        mRawTaskList.addAll(allTasks);
+
+        // Only callback for the removed tasks after the stack has updated
+        int removedTaskCount = removedTasks.size();
+        Task newFrontMostTask = getStackFrontMostTask();
+        for (int i = 0; i < removedTaskCount; i++) {
+            mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
+                    AnimationProps.IMMEDIATE, false /* fromDockGesture */,
+                    true /* dismissRecentsIfAllRemoved */);
+        }
+
+        // Only callback for the newly added tasks after this stack has been updated
+        int addedTaskCount = addedTasks.size();
+        for (int i = 0; i < addedTaskCount; i++) {
+            mCb.onStackTaskAdded(this, addedTasks.get(i));
+        }
+
+        // Notify that the task stack has been updated
+        if (notifyStackChanges) {
+            mCb.onStackTasksUpdated(this);
+        }
+    }
+
+    /**
+     * Gets the front-most task in the stack.
+     */
+    public Task getStackFrontMostTask() {
+        ArrayList<Task> stackTasks = mStackTaskList.getTasks();
+        if (stackTasks.isEmpty()) {
+            return null;
+        }
+        return stackTasks.get(stackTasks.size() - 1);
+    }
+
+    /** Gets the task keys */
+    public ArrayList<TaskKey> getTaskKeys() {
+        ArrayList<TaskKey> taskKeys = new ArrayList<>();
+        ArrayList<Task> tasks = computeAllTasksList();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            taskKeys.add(task.key);
+        }
+        return taskKeys;
+    }
+
+    /**
+     * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
+     */
+    public ArrayList<Task> getStackTasks() {
+        return mStackTaskList.getTasks();
+    }
+
+    /**
+     * Computes a set of all the active and historical tasks.
+     */
+    public ArrayList<Task> computeAllTasksList() {
+        ArrayList<Task> tasks = new ArrayList<>();
+        tasks.addAll(mStackTaskList.getTasks());
+        return tasks;
+    }
+
+    /**
+     * Returns the number of stacktasks.
+     */
+    public int getTaskCount() {
+        return mStackTaskList.size();
+    }
+
+    /**
+     * Returns the number of stack tasks.
+     */
+    public int getStackTaskCount() {
+        return mStackTaskList.size();
+    }
+
+    /**
+     * Returns the task in stack tasks which is the launch target.
+     */
+    public Task getLaunchTarget() {
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.isLaunchTarget) {
+                return task;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the next launch target should actually be the PiP task.
+     */
+    public boolean isNextLaunchTargetPip(long lastPipTime) {
+        Task launchTarget = getLaunchTarget();
+        Task nextLaunchTarget = getNextLaunchTargetRaw();
+        if (nextLaunchTarget != null && lastPipTime > 0) {
+            // If the PiP time is more recent than the next launch target, then launch the PiP task
+            return lastPipTime > nextLaunchTarget.key.lastActiveTime;
+        } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
+            // Otherwise, if there is no next launch target, but there is a PiP, then launch
+            // the PiP task
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the task in stack tasks which should be launched next if Recents are toggled
+     * again, or null if there is no task to be launched. Callers should check
+     * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
+     * stack.
+     */
+    public Task getNextLaunchTarget() {
+        Task nextLaunchTarget = getNextLaunchTargetRaw();
+        if (nextLaunchTarget != null) {
+            return nextLaunchTarget;
+        }
+        return getStackTasks().get(getTaskCount() - 1);
+    }
+
+    private Task getNextLaunchTargetRaw() {
+        int taskCount = getTaskCount();
+        if (taskCount == 0) {
+            return null;
+        }
+        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+        if (launchTaskIndex != -1 && launchTaskIndex > 0) {
+            return getStackTasks().get(launchTaskIndex - 1);
+        }
+        return null;
+    }
+
+    /** Returns the index of this task in this current task stack */
+    public int indexOfStackTask(Task t) {
+        return mStackTaskList.indexOf(t);
+    }
+
+    /** Finds the task with the specified task id. */
+    public Task findTaskWithId(int taskId) {
+        ArrayList<Task> tasks = computeAllTasksList();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            if (task.key.id == taskId) {
+                return task;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Computes the components of tasks in this stack that have been removed as a result of a change
+     * in the specified package.
+     */
+    public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
+        // Identify all the tasks that should be removed as a result of the package being removed.
+        // Using a set to ensure that we callback once per unique component.
+        ArraySet<ComponentName> existingComponents = new ArraySet<>();
+        ArraySet<ComponentName> removedComponents = new ArraySet<>();
+        ArrayList<TaskKey> taskKeys = getTaskKeys();
+        int taskKeyCount = taskKeys.size();
+        for (int i = 0; i < taskKeyCount; i++) {
+            TaskKey t = taskKeys.get(i);
+
+            // Skip if this doesn't apply to the current user
+            if (t.userId != userId) continue;
+
+            ComponentName cn = t.getComponent();
+            if (cn.getPackageName().equals(packageName)) {
+                if (existingComponents.contains(cn)) {
+                    // If we know that the component still exists in the package, then skip
+                    continue;
+                }
+                if (PackageManagerWrapper.getInstance().getActivityInfo(cn, userId) != null) {
+                    existingComponents.add(cn);
+                } else {
+                    removedComponents.add(cn);
+                }
+            }
+        }
+        return removedComponents;
+    }
+
+    @Override
+    public String toString() {
+        String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            str += "    " + tasks.get(i).toString() + "\n";
+        }
+        return str;
+    }
+
+    /**
+     * Given a list of tasks, returns a map of each task's key to the task.
+     */
+    private ArrayMap<TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
+        ArrayMap<TaskKey, Task> map = new ArrayMap<>(tasks.size());
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            Task task = tasks.get(i);
+            map.put(task.key, task);
+        }
+        return map;
+    }
+
+    public void dump(String prefix, PrintWriter writer) {
+        String innerPrefix = prefix + "  ";
+
+        writer.print(prefix); writer.print(TAG);
+        writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
+        writer.println();
+        ArrayList<Task> tasks = mStackTaskList.getTasks();
+        int taskCount = tasks.size();
+        for (int i = 0; i < taskCount; i++) {
+            tasks.get(i).dump(innerPrefix, writer);
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
new file mode 100644
index 0000000..dd1763b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 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.shared.recents.model;
+
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+/**
+ * Data for a single thumbnail.
+ */
+public class ThumbnailData {
+
+    public final Bitmap thumbnail;
+    public int orientation;
+    public Rect insets;
+    public boolean reducedResolution;
+    public float scale;
+
+    public ThumbnailData() {
+        thumbnail = null;
+        orientation = ORIENTATION_UNDEFINED;
+        insets = new Rect();
+        reducedResolution = false;
+        scale = 1f;
+    }
+
+    public ThumbnailData(TaskSnapshot snapshot) {
+        thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
+        insets = new Rect(snapshot.getContentInsets());
+        orientation = snapshot.getOrientation();
+        reducedResolution = snapshot.isReducedResolution();
+        scale = snapshot.getScale();
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java
new file mode 100644
index 0000000..2de7f74
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/AnimationProps.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016 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.shared.recents.utilities;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+import android.view.View;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * The generic set of animation properties to animate a {@link View}. The animation can have
+ * different interpolators, start delays and durations for each of the different properties.
+ */
+public class AnimationProps {
+
+    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    public static final AnimationProps IMMEDIATE = new AnimationProps(0, LINEAR_INTERPOLATOR);
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
+    public @interface PropType {}
+
+    public static final int ALL = 0;
+    public static final int TRANSLATION_X = 1;
+    public static final int TRANSLATION_Y = 2;
+    public static final int TRANSLATION_Z = 3;
+    public static final int ALPHA = 4;
+    public static final int SCALE = 5;
+    public static final int BOUNDS = 6;
+    public static final int DIM_ALPHA = 7;
+
+    private SparseLongArray mPropStartDelay;
+    private SparseLongArray mPropDuration;
+    private SparseArray<Interpolator> mPropInterpolators;
+    private Animator.AnimatorListener mListener;
+
+    /**
+     * The builder constructor.
+     */
+    public AnimationProps() {}
+
+    /**
+     * Creates an animation with a default {@param duration} and {@param interpolator} for all
+     * properties in this animation.
+     */
+    public AnimationProps(int duration, Interpolator interpolator) {
+        this(0, duration, interpolator, null);
+    }
+
+    /**
+     * Creates an animation with a default {@param duration} and {@param interpolator} for all
+     * properties in this animation.
+     */
+    public AnimationProps(int duration, Interpolator interpolator,
+            Animator.AnimatorListener listener) {
+        this(0, duration, interpolator, listener);
+    }
+
+    /**
+     * Creates an animation with a default {@param startDelay}, {@param duration} and
+     * {@param interpolator} for all properties in this animation.
+     */
+    public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
+        this(startDelay, duration, interpolator, null);
+    }
+
+    /**
+     * Creates an animation with a default {@param startDelay}, {@param duration} and
+     * {@param interpolator} for all properties in this animation.
+     */
+    public AnimationProps(int startDelay, int duration, Interpolator interpolator,
+            Animator.AnimatorListener listener) {
+        setStartDelay(ALL, startDelay);
+        setDuration(ALL, duration);
+        setInterpolator(ALL, interpolator);
+        setListener(listener);
+    }
+
+    /**
+     * Creates a new {@link AnimatorSet} that will animate the given animators.  Callers need to
+     * manually apply the individual animation properties for each of the animators respectively.
+     */
+    public AnimatorSet createAnimator(List<Animator> animators) {
+        AnimatorSet anim = new AnimatorSet();
+        if (mListener != null) {
+            anim.addListener(mListener);
+        }
+        anim.playTogether(animators);
+        return anim;
+    }
+
+    /**
+     * Applies the specific start delay, duration and interpolator to the given {@param animator}
+     * for the specified {@param propertyType}.
+     */
+    public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) {
+        animator.setStartDelay(getStartDelay(propertyType));
+        animator.setDuration(getDuration(propertyType));
+        animator.setInterpolator(getInterpolator(propertyType));
+        return animator;
+    }
+
+    /**
+     * Sets a start delay for a specific property.
+     */
+    public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
+        if (mPropStartDelay == null) {
+            mPropStartDelay = new SparseLongArray();
+        }
+        mPropStartDelay.append(propertyType, startDelay);
+        return this;
+    }
+
+    /**
+     * Returns the start delay for a specific property.
+     */
+    public long getStartDelay(@PropType int propertyType) {
+        if (mPropStartDelay != null) {
+            long startDelay = mPropStartDelay.get(propertyType, -1);
+            if (startDelay != -1) {
+                return startDelay;
+            }
+            return mPropStartDelay.get(ALL, 0);
+        }
+        return 0;
+    }
+
+    /**
+     * Sets a duration for a specific property.
+     */
+    public AnimationProps setDuration(@PropType int propertyType, int duration) {
+        if (mPropDuration == null) {
+            mPropDuration = new SparseLongArray();
+        }
+        mPropDuration.append(propertyType, duration);
+        return this;
+    }
+
+    /**
+     * Returns the duration for a specific property.
+     */
+    public long getDuration(@PropType int propertyType) {
+        if (mPropDuration != null) {
+            long duration = mPropDuration.get(propertyType, -1);
+            if (duration != -1) {
+                return duration;
+            }
+            return mPropDuration.get(ALL, 0);
+        }
+        return 0;
+    }
+
+    /**
+     * Sets an interpolator for a specific property.
+     */
+    public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
+        if (mPropInterpolators == null) {
+            mPropInterpolators = new SparseArray<>();
+        }
+        mPropInterpolators.append(propertyType, interpolator);
+        return this;
+    }
+
+    /**
+     * Returns the interpolator for a specific property, falling back to the general interpolator
+     * if there is no specific property interpolator.
+     */
+    public Interpolator getInterpolator(@PropType int propertyType) {
+        if (mPropInterpolators != null) {
+            Interpolator interp = mPropInterpolators.get(propertyType);
+            if (interp != null) {
+                return interp;
+            }
+            return mPropInterpolators.get(ALL, LINEAR_INTERPOLATOR);
+        }
+        return LINEAR_INTERPOLATOR;
+    }
+
+    /**
+     * Sets an animator listener for this animation.
+     */
+    public AnimationProps setListener(Animator.AnimatorListener listener) {
+        mListener = listener;
+        return this;
+    }
+
+    /**
+     * Returns the animator listener for this animation.
+     */
+    public Animator.AnimatorListener getListener() {
+        return mListener;
+    }
+
+    /**
+     * Returns whether this animation has any duration.
+     */
+    public boolean isImmediate() {
+        int count = mPropDuration.size();
+        for (int i = 0; i < count; i++) {
+            if (mPropDuration.valueAt(i) > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java
new file mode 100644
index 0000000..51c1b5a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/RectFEvaluator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 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.shared.recents.utilities;
+
+import android.animation.TypeEvaluator;
+import android.graphics.RectF;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>RectF</code> values.
+ */
+public class RectFEvaluator implements TypeEvaluator<RectF> {
+
+    private final RectF mRect = new RectF();
+
+    /**
+     * This function returns the result of linearly interpolating the start and
+     * end Rect values, with <code>fraction</code> representing the proportion
+     * between the start and end values. The calculation is a simple parametric
+     * calculation on each of the separate components in the Rect objects
+     * (left, top, right, and bottom).
+     *
+     * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start Rect
+     * @param endValue   The end Rect
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    @Override
+    public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
+        float left = startValue.left + ((endValue.left - startValue.left) * fraction);
+        float top = startValue.top + ((endValue.top - startValue.top) * fraction);
+        float right = startValue.right + ((endValue.right - startValue.right) * fraction);
+        float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
+        mRect.set(left, top, right, bottom);
+        return mRect;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
new file mode 100644
index 0000000..a5d1963
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.recents.utilities;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.RectEvaluator;
+import android.annotation.FloatRange;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Trace;
+import android.util.ArraySet;
+import android.util.IntProperty;
+import android.util.Property;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewStub;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/* Common code */
+public class Utilities {
+
+    public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
+            new IntProperty<Drawable>("drawableAlpha") {
+                @Override
+                public void setValue(Drawable object, int alpha) {
+                    object.setAlpha(alpha);
+                }
+
+                @Override
+                public Integer get(Drawable object) {
+                    return object.getAlpha();
+                }
+            };
+
+    public static final Property<Drawable, Rect> DRAWABLE_RECT =
+            new Property<Drawable, Rect>(Rect.class, "drawableBounds") {
+                @Override
+                public void set(Drawable object, Rect bounds) {
+                    object.setBounds(bounds);
+                }
+
+                @Override
+                public Rect get(Drawable object) {
+                    return object.getBounds();
+                }
+            };
+
+    public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
+    public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
+
+    /**
+     * @return the first parent walking up the view hierarchy that has the given class type.
+     *
+     * @param parentClass must be a class derived from {@link View}
+     */
+    public static <T extends View> T findParent(View v, Class<T> parentClass) {
+        ViewParent parent = v.getParent();
+        while (parent != null) {
+            if (parentClass.isAssignableFrom(parent.getClass())) {
+                return (T) parent;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
+    /**
+     * Initializes the {@param setOut} with the given object.
+     */
+    public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) {
+        setOut.clear();
+        if (obj != null) {
+            setOut.add(obj);
+        }
+        return setOut;
+    }
+
+    /**
+     * Replaces the contents of {@param setOut} with the contents of the {@param array}.
+     */
+    public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) {
+        setOut.clear();
+        if (array != null) {
+            Collections.addAll(setOut, array);
+        }
+        return setOut;
+    }
+
+    /**
+     * @return the clamped {@param value} between the provided {@param min} and {@param max}.
+     */
+    public static float clamp(float value, float min, float max) {
+        return Math.max(min, Math.min(max, value));
+    }
+
+    /**
+     * @return the clamped {@param value} between the provided {@param min} and {@param max}.
+     */
+    public static int clamp(int value, int min, int max) {
+        return Math.max(min, Math.min(max, value));
+    }
+
+    /**
+     * @return the clamped {@param value} between 0 and 1.
+     */
+    public static float clamp01(float value) {
+        return Math.max(0f, Math.min(1f, value));
+    }
+
+    /**
+     * Scales the {@param value} to be proportionally between the {@param min} and
+     * {@param max} values.
+     *
+     * @param value must be between 0 and 1
+     */
+    public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+
+    /**
+     * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1.
+     *
+     * @param value must be between {@param min} and {@param max}
+     */
+    public static float unmapRange(float value, float min, float max) {
+        return (value - min) / (max - min);
+    }
+
+    /** Scales a rect about its centroid */
+    public static void scaleRectAboutCenter(RectF r, float scale) {
+        if (scale != 1.0f) {
+            float cx = r.centerX();
+            float cy = r.centerY();
+            r.offset(-cx, -cy);
+            r.left *= scale;
+            r.top *= scale;
+            r.right *= scale;
+            r.bottom *= scale;
+            r.offset(cx, cy);
+        }
+    }
+
+    /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
+    public static float computeContrastBetweenColors(int bg, int fg) {
+        float bgR = Color.red(bg) / 255f;
+        float bgG = Color.green(bg) / 255f;
+        float bgB = Color.blue(bg) / 255f;
+        bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
+        bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
+        bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
+        float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
+        
+        float fgR = Color.red(fg) / 255f;
+        float fgG = Color.green(fg) / 255f;
+        float fgB = Color.blue(fg) / 255f;
+        fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
+        fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
+        fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
+        float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
+
+        return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
+    }
+
+    /** Returns the base color overlaid with another overlay color with a specified alpha. */
+    public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
+        return Color.rgb(
+            (int) (overlayAlpha * Color.red(baseColor) +
+                    (1f - overlayAlpha) * Color.red(overlayColor)),
+            (int) (overlayAlpha * Color.green(baseColor) +
+                    (1f - overlayAlpha) * Color.green(overlayColor)),
+            (int) (overlayAlpha * Color.blue(baseColor) +
+                    (1f - overlayAlpha) * Color.blue(overlayColor)));
+    }
+
+    /**
+     * Cancels an animation ensuring that if it has listeners, onCancel and onEnd
+     * are not called.
+     */
+    public static void cancelAnimationWithoutCallbacks(Animator animator) {
+        if (animator != null && animator.isStarted()) {
+            removeAnimationListenersRecursive(animator);
+            animator.cancel();
+        }
+    }
+
+    /**
+     * Recursively removes all the listeners of all children of this animator
+     */
+    public static void removeAnimationListenersRecursive(Animator animator) {
+        if (animator instanceof AnimatorSet) {
+            ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations();
+            for (int i = animators.size() - 1; i >= 0; i--) {
+                removeAnimationListenersRecursive(animators.get(i));
+            }
+        }
+        animator.removeAllListeners();
+    }
+
+    /**
+     * Sets the given {@link View}'s frame from its current translation.
+     */
+    public static void setViewFrameFromTranslation(View v) {
+        RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+        taskViewRect.offset(v.getTranslationX(), v.getTranslationY());
+        v.setTranslationX(0);
+        v.setTranslationY(0);
+        v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top,
+                (int) taskViewRect.right, (int) taskViewRect.bottom);
+    }
+
+    /**
+     * Returns a view stub for the given view id.
+     */
+    public static ViewStub findViewStubById(View v, int stubId) {
+        return (ViewStub) v.findViewById(stubId);
+    }
+
+    /**
+     * Returns a view stub for the given view id.
+     */
+    public static ViewStub findViewStubById(Activity a, int stubId) {
+        return (ViewStub) a.findViewById(stubId);
+    }
+
+    /**
+     * Used for debugging, converts DP to PX.
+     */
+    public static float dpToPx(Resources res, float dp) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
+    }
+
+    /**
+     * Adds a trace event for debugging.
+     */
+    public static void addTraceEvent(String event) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, event);
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+    }
+
+    /**
+     * Returns whether this view, or one of its descendants have accessibility focus.
+     */
+    public static boolean isDescendentAccessibilityFocused(View v) {
+        if (v.isAccessibilityFocused()) {
+            return true;
+        }
+
+        if (v instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) v;
+            int childCount = vg.getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                if (isDescendentAccessibilityFocused(vg.getChildAt(i))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the application configuration, which is independent of the activity's current
+     * configuration in multiwindow.
+     */
+    public static Configuration getAppConfiguration(Context context) {
+        return context.getApplicationContext().getResources().getConfiguration();
+    }
+
+    /**
+     * Returns a lightweight dump of a rect.
+     */
+    public static String dumpRect(Rect r) {
+        if (r == null) {
+            return "N:0,0-0,0";
+        }
+        return r.left + "," + r.top + "-" + r.right + "," + r.bottom;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
new file mode 100644
index 0000000..3f93f76
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2015 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.shared.system;
+
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
+import android.util.Log;
+
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActivityManagerWrapper {
+
+    private static final String TAG = "ActivityManagerWrapper";
+
+    private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper();
+
+    private final PackageManager mPackageManager;
+    private final IconDrawableFactory mDrawableFactory;
+
+    private ActivityManagerWrapper() {
+        final Context context = AppGlobals.getInitialApplication();
+        mPackageManager = context.getPackageManager();
+        mDrawableFactory = IconDrawableFactory.newInstance(context);
+    }
+
+    public static ActivityManagerWrapper getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * @return the current user's id.
+     */
+    public int getCurrentUserId() {
+        UserInfo ui;
+        try {
+            ui = ActivityManager.getService().getCurrentUser();
+            return ui != null ? ui.id : 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @return a list of the recents tasks.
+     */
+    public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
+        try {
+            return ActivityManager.getService().getRecentTasks(numTasks,
+                            RECENT_IGNORE_UNAVAILABLE, userId).getList();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get recent tasks", e);
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * @return the task snapshot for the given {@param taskId}.
+     */
+    public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean reducedResolution) {
+        ActivityManager.TaskSnapshot snapshot = null;
+        try {
+            snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to retrieve task snapshot", e);
+        }
+        if (snapshot != null) {
+            return new ThumbnailData(snapshot);
+        } else {
+            return new ThumbnailData();
+        }
+    }
+
+    /**
+     * @return the task description icon, loading and badging it if it necessary.
+     */
+    public Drawable getBadgedTaskDescriptionIcon(Context context,
+            ActivityManager.TaskDescription taskDescription, int userId, Resources res) {
+        Bitmap tdIcon = taskDescription.getInMemoryIcon();
+        Drawable dIcon = null;
+        if (tdIcon != null) {
+            dIcon = new BitmapDrawable(res, tdIcon);
+        } else if (taskDescription.getIconResource() != 0) {
+            try {
+                dIcon = context.getDrawable(taskDescription.getIconResource());
+            } catch (NotFoundException e) {
+                Log.e(TAG, "Could not find icon drawable from resource", e);
+            }
+        } else {
+            tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
+                    taskDescription.getIconFilename(), userId);
+            if (tdIcon != null) {
+                dIcon = new BitmapDrawable(res, tdIcon);
+            }
+        }
+        if (dIcon != null) {
+            return getBadgedIcon(dIcon, userId);
+        }
+        return null;
+    }
+
+    /**
+     * @return the given icon for a user, badging if necessary.
+     */
+    private Drawable getBadgedIcon(Drawable icon, int userId) {
+        if (userId != UserHandle.myUserId()) {
+            icon = mPackageManager.getUserBadgedIcon(icon, new UserHandle(userId));
+        }
+        return icon;
+    }
+
+    /**
+     * @return the activity icon for the ActivityInfo for a user, badging if necessary.
+     */
+    public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
+        return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
+    }
+
+    /**
+     * @return the application icon for the ApplicationInfo for a user, badging if necessary.
+     */
+    public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
+        return mDrawableFactory.getBadgedIcon(appInfo, userId);
+    }
+
+    /**
+     * @return the activity label, badging if necessary.
+     */
+    public String getBadgedActivityLabel(ActivityInfo info, int userId) {
+        return getBadgedLabel(info.loadLabel(mPackageManager).toString(), userId);
+    }
+
+    /**
+     * @return the application label, badging if necessary.
+     */
+    public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
+        return getBadgedLabel(appInfo.loadLabel(mPackageManager).toString(), userId);
+    }
+
+    /**
+     * @return the content description for a given task, badging it if necessary.  The content
+     * description joins the app and activity labels.
+     */
+    public String getBadgedContentDescription(ActivityInfo info, int userId,
+            ActivityManager.TaskDescription td) {
+        String activityLabel;
+        if (td != null && td.getLabel() != null) {
+            activityLabel = td.getLabel();
+        } else {
+            activityLabel = info.loadLabel(mPackageManager).toString();
+        }
+        String applicationLabel = info.applicationInfo.loadLabel(mPackageManager).toString();
+        String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
+        return applicationLabel.equals(activityLabel)
+                ? badgedApplicationLabel
+                : badgedApplicationLabel + " " + activityLabel;
+    }
+
+    /**
+     * @return the given label for a user, badging if necessary.
+     */
+    private String getBadgedLabel(String label, int userId) {
+        if (userId != UserHandle.myUserId()) {
+            label = mPackageManager.getUserBadgedLabel(label, new UserHandle(userId)).toString();
+        }
+        return label;
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java
new file mode 100644
index 0000000..d5e6e6e
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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.shared.system;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+
+public class PackageManagerWrapper {
+
+    private static final String TAG = "PackageManagerWrapper";
+
+    private static final PackageManagerWrapper sInstance = new PackageManagerWrapper();
+
+    private static final IPackageManager mIPackageManager = AppGlobals.getPackageManager();
+
+    public static PackageManagerWrapper getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * @return the activity info for a given {@param componentName} and {@param userId}.
+     */
+    public ActivityInfo getActivityInfo(ComponentName componentName, int userId) {
+        try {
+            return mIPackageManager.getActivityInfo(componentName, PackageManager.GET_META_DATA,
+                    userId);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/tests/Android.mk b/packages/SystemUI/shared/tests/Android.mk
new file mode 100644
index 0000000..ce3b442
--- /dev/null
+++ b/packages/SystemUI/shared/tests/Android.mk
@@ -0,0 +1,53 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_JACK_FLAGS := --multi-dex native
+LOCAL_DX_FLAGS := --multi-dex
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/..
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
+
+LOCAL_PACKAGE_NAME := SystemUISharedLibTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+# Add local path sources as well as shared lib sources
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    $(call all-java-files-under, ../src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    metrics-helper-lib \
+    android-support-test \
+    mockito-target-minus-junit4 \
+    SystemUI-proto \
+    SystemUI-tags \
+    legacy-android-test \
+    testables \
+    truth-prebuilt \
+
+LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car
+
+# sign this with platform cert, so this test is allowed to inject key events into
+# UI it doesn't own. This is necessary to allow screenshots to be taken
+LOCAL_CERTIFICATE := platform
+
+ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
+    include $(BUILD_PACKAGE)
+endif
\ No newline at end of file
diff --git a/packages/SystemUI/shared/tests/AndroidManifest.xml b/packages/SystemUI/shared/tests/AndroidManifest.xml
new file mode 100644
index 0000000..3e1de49
--- /dev/null
+++ b/packages/SystemUI/shared/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.shared.tests">
+
+    <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.testing.TestableInstrumentation"
+        android:targetPackage="com.android.systemui.shared.tests"
+        android:label="Tests for SystemUISharedLib">
+    </instrumentation>
+</manifest>
diff --git a/packages/SystemUI/shared/tests/AndroidTest.xml b/packages/SystemUI/shared/tests/AndroidTest.xml
new file mode 100644
index 0000000..b3de836
--- /dev/null
+++ b/packages/SystemUI/shared/tests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Tests for SystemUISharedLib.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="SystemUISharedLibTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="SystemUISharedLibTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.systemui.shared.tests" />
+        <option name="runner" value="android.testing.TestableInstrumentation" />
+    </test>
+</configuration>
diff --git a/packages/SystemUI/shared/tests/src/com/android/systemui/shared/SysuiSharedLibTestCase.java b/packages/SystemUI/shared/tests/src/com/android/systemui/shared/SysuiSharedLibTestCase.java
new file mode 100644
index 0000000..04b341e
--- /dev/null
+++ b/packages/SystemUI/shared/tests/src/com/android/systemui/shared/SysuiSharedLibTestCase.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * Base class that does System UI Shared Lib specific setup.
+ */
+public abstract class SysuiSharedLibTestCase {
+
+    private static final String TAG = "SysuiSharedLibTestCase";
+
+    private Handler mHandler;
+    private Context mContext = InstrumentationRegistry.getContext();
+
+    @Before
+    public void SysuiSetup() throws Exception {
+        // Enable shared class loader to test package-private classes/methods
+        System.setProperty("dexmaker.share_classloader", "true");
+    }
+
+    @After
+    public void SysuiTeardown() {
+        // Do nothing
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    protected void waitForIdleSync() {
+        if (mHandler == null) {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
+        waitForIdleSync(mHandler);
+    }
+
+    public static void waitForIdleSync(Handler h) {
+        validateThread(h.getLooper());
+        Idler idler = new Idler(null);
+        h.getLooper().getQueue().addIdleHandler(idler);
+        // Ensure we are non-idle, so the idle handler can run.
+        h.post(new EmptyRunnable());
+        idler.waitForIdle();
+    }
+
+    private static final void validateThread(Looper l) {
+        if (Looper.myLooper() == l) {
+            throw new RuntimeException(
+                "This method can not be called from the looper being synced");
+        }
+    }
+
+    public static final class EmptyRunnable implements Runnable {
+        public void run() {
+        }
+    }
+
+    public static final class Idler implements MessageQueue.IdleHandler {
+        private final Runnable mCallback;
+        private boolean mIdle;
+
+        public Idler(Runnable callback) {
+            mCallback = callback;
+            mIdle = false;
+        }
+
+        @Override
+        public boolean queueIdle() {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+            synchronized (this) {
+                mIdle = true;
+                notifyAll();
+            }
+            return false;
+        }
+
+        public void waitForIdle() {
+            synchronized (this) {
+                while (!mIdle) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/shared/tests/src/com/android/systemui/shared/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/shared/tests/src/com/android/systemui/shared/recents/model/HighResThumbnailLoaderTest.java
new file mode 100644
index 0000000..b03ea90
--- /dev/null
+++ b/packages/SystemUI/shared/tests/src/com/android/systemui/shared/recents/model/HighResThumbnailLoaderTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.shared.recents.model;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.systemui.shared.SysuiSharedLibTestCase;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * runtest --path frameworks/base/packages/SystemUI/shared/tests/src/com/android/systemui/shared/recents/model/HighResThumbnailLoaderTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HighResThumbnailLoaderTest extends SysuiSharedLibTestCase {
+
+    private HighResThumbnailLoader mLoader;
+
+    @Mock
+    private ActivityManagerWrapper mMockActivityManagerWrapper;
+    @Mock
+    private Task mTask;
+
+    private ThumbnailData mThumbnailData = new ThumbnailData();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mLoader = new HighResThumbnailLoader(mMockActivityManagerWrapper, Looper.getMainLooper(),
+                false /* reducedResolution */);
+        mTask.key = new TaskKey(0, WINDOWING_MODE_UNDEFINED, null, 0, 0);
+        when(mMockActivityManagerWrapper.getTaskThumbnail(anyInt(), anyBoolean()))
+                .thenReturn(mThumbnailData);
+        mLoader.setVisible(true);
+        mLoader.setTaskLoadQueueIdle(true);
+    }
+
+    @Test
+    public void testLoading() throws Exception {
+        mLoader.setVisible(true);
+        assertTrue(mLoader.isLoading());
+        mLoader.setVisible(false);
+        assertFalse(mLoader.isLoading());
+        mLoader.setVisible(true);
+        mLoader.setFlingingFast(true);
+        assertFalse(mLoader.isLoading());
+        mLoader.setFlingingFast(false);
+        assertTrue(mLoader.isLoading());
+        mLoader.setFlingingFast(false);
+        mLoader.setTaskLoadQueueIdle(false);
+        assertFalse(mLoader.isLoading());
+        mLoader.setTaskLoadQueueIdle(true);
+        assertTrue(mLoader.isLoading());
+    }
+
+    @Test
+    public void testLoad() throws Exception {
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    @Test
+    public void testFlinging_notLoaded() throws Exception {
+        mLoader.setFlingingFast(true);
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    /**
+     * Tests whether task is loaded after stopping to fling
+     */
+    @Test
+    public void testAfterFlinging() throws Exception {
+        mLoader.setFlingingFast(true);
+        mLoader.onTaskVisible(mTask);
+        mLoader.setFlingingFast(false);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+
+    @Test
+    public void testAlreadyLoaded() throws Exception {
+        mTask.thumbnail = new ThumbnailData();
+        mTask.thumbnail.reducedResolution = false;
+        mLoader.onTaskVisible(mTask);
+        mLoader.waitForLoaderIdle();
+        waitForIdleSync();
+        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index 159ac4c..13c48d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -39,6 +39,7 @@
 import com.android.internal.telephony.IccCardConstants.State;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.settingslib.WirelessUtils;
+import android.telephony.TelephonyManager;
 
 public class CarrierText extends TextView {
     private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -52,6 +53,8 @@
 
     private WifiManager mWifiManager;
 
+    private boolean[] mSimErrorState = new boolean[TelephonyManager.getDefault().getPhoneCount()];
+
     private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onRefreshCarrierInfo() {
@@ -65,6 +68,22 @@
         public void onStartedWakingUp() {
             setSelected(true);
         };
+
+        public void onSimStateChanged(int subId, int slotId, IccCardConstants.State simState) {
+            if (slotId < 0) {
+                Log.d(TAG, "onSimStateChanged() - slotId invalid: " + slotId);
+                return;
+            }
+
+            if (DEBUG) Log.d(TAG,"onSimStateChanged: " + getStatusForIccState(simState));
+            if (getStatusForIccState(simState) == StatusMode.SimIoError) {
+                mSimErrorState[slotId] = true;
+                updateCarrierText();
+            } else if (mSimErrorState[slotId]) {
+                mSimErrorState[slotId] = false;
+                updateCarrierText();
+            }
+        };
     };
     /**
      * The status of this lock screen. Primarily used for widgets on LockScreen.
@@ -77,7 +96,8 @@
         SimPukLocked, // SIM card is PUK locked because SIM entered wrong too many times
         SimLocked, // SIM card is currently locked
         SimPermDisabled, // SIM card is permanently disabled due to PUK unlock failure
-        SimNotReady; // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimNotReady, // SIM is not ready yet. May never be on devices w/o a SIM.
+        SimIoError; // SIM card is faulty
     }
 
     public CarrierText(Context context) {
@@ -101,6 +121,35 @@
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
     }
 
+    /**
+     * Checks if there are faulty cards. Adds the text depending on the slot of the card
+     * @param text: current carrier text based on the sim state
+     * @param noSims: whether a valid sim card is inserted
+     * @return text
+    */
+    private CharSequence updateCarrierTextWithSimIoError(CharSequence text, boolean noSims) {
+        final CharSequence carrier = "";
+        CharSequence carrierTextForSimIOError = getCarrierTextForSimState(
+            IccCardConstants.State.CARD_IO_ERROR, carrier);
+        for (int index = 0; index < mSimErrorState.length; index++) {
+            if (mSimErrorState[index]) {
+                // In the case when no sim cards are detected but a faulty card is inserted
+                // overwrite the text and only show "Invalid card"
+                if (noSims) {
+                    return concatenate(carrierTextForSimIOError,
+                        getContext().getText(com.android.internal.R.string.emergency_calls_only));
+                } else if (index == 0) {
+                    // prepend "Invalid card" when faulty card is inserted in slot 0
+                    text = concatenate(carrierTextForSimIOError, text);
+                } else {
+                    // concatenate "Invalid card" when faulty card is inserted in slot 1
+                    text = concatenate(text, carrierTextForSimIOError);
+                }
+            }
+        }
+        return text;
+    }
+
     protected void updateCarrierText() {
         boolean allSimsMissing = true;
         boolean anySimReadyAndInService = false;
@@ -179,6 +228,7 @@
             }
         }
 
+        displayText = updateCarrierTextWithSimIoError(displayText, allSimsMissing);
         // APM (airplane mode) != no carrier state. There are carrier services
         // (e.g. WFC = Wi-Fi calling) which may operate in APM.
         if (!anySimReadyAndInService && WirelessUtils.isAirplaneModeOn(mContext)) {
@@ -270,6 +320,11 @@
                         getContext().getText(R.string.keyguard_sim_puk_locked_message),
                         text);
                 break;
+            case SimIoError:
+                carrierText = makeCarrierStringOnEmergencyCapable(
+                        getContext().getText(R.string.keyguard_sim_error_message_short),
+                        text);
+                break;
         }
 
         return carrierText;
@@ -319,6 +374,8 @@
                 return StatusMode.SimPermDisabled;
             case UNKNOWN:
                 return StatusMode.SimMissing;
+            case CARD_IO_ERROR:
+                return StatusMode.SimIoError;
         }
         return StatusMode.SimMissing;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
index 7baa57e..0cb6423 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityModel.java
@@ -57,16 +57,16 @@
     SecurityMode getSecurityMode(int userId) {
         KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
 
-        if (SubscriptionManager.isValidSubscriptionId(
-                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
-            return SecurityMode.SimPin;
-        }
-
         if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                 monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
             return SecurityMode.SimPuk;
         }
 
+        if (SubscriptionManager.isValidSubscriptionId(
+                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
+            return SecurityMode.SimPin;
+        }
+
         final int security = mLockPatternUtils.getActivePasswordQuality(userId);
         switch (security) {
             case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 7225ba9..432b406 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -66,7 +66,11 @@
                 // again when the PUK locked SIM is re-entered.
                 case ABSENT: {
                     KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
-                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    // onSimStateChanged callback can fire when the SIM PIN lock is not currently
+                    // active and mCallback is null.
+                    if (mCallback != null) {
+                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    }
                     break;
                 }
                 default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 171cf23..7f79008 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -72,7 +72,11 @@
                 // move into the READY state and the PUK lock keyguard should be removed.
                 case READY: {
                     KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
-                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    // mCallback can be null if onSimStateChanged callback is called when keyguard
+                    // isn't active.
+                    if (mCallback != null) {
+                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    }
                     break;
                 }
                 default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d95402c..282a71b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
 import static android.os.BatteryManager.BATTERY_STATUS_FULL;
@@ -50,7 +52,6 @@
 import android.os.BatteryManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Message;
 import android.os.RemoteException;
@@ -76,7 +77,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import com.google.android.collect.Lists;
 
@@ -452,6 +453,7 @@
      */
     public void setKeyguardGoingAway(boolean goingAway) {
         mKeyguardGoingAway = goingAway;
+        updateFingerprintListeningState();
     }
 
     /**
@@ -899,6 +901,8 @@
                 }
             } else if (IccCardConstants.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
                 state = IccCardConstants.State.NETWORK_LOCKED;
+            } else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) {
+                state = IccCardConstants.State.CARD_IO_ERROR;
             } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(stateExtra)
                         || IccCardConstants.INTENT_VALUE_ICC_IMSI.equals(stateExtra)) {
                 // This is required because telephony doesn't return to "READY" after
@@ -1069,6 +1073,7 @@
                 cb.onDreamingStateChanged(mIsDreaming);
             }
         }
+        updateFingerprintListeningState();
     }
 
     /**
@@ -1733,6 +1738,10 @@
         mFailedAttempts.delete(sCurrentUser);
     }
 
+    public ServiceState getServiceState(int subId) {
+        return mServiceStates.get(subId);
+    }
+
     public int getFailedUnlockAttempts(int userId) {
         return mFailedAttempts.get(userId, 0);
     }
@@ -1767,12 +1776,12 @@
         }
     }
 
-    private final TaskStackListener mTaskStackListener = new TaskStackListener() {
+    private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChangedBackground() {
             try {
                 ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo(
-                        ActivityManager.StackId.ASSISTANT_STACK_ID);
+                        WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
                 if (info == null) {
                     return;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 2b31967..2fe66a1 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui;
 
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 
 import android.animation.ArgbEvaluator;
@@ -52,6 +54,7 @@
 import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 import java.text.NumberFormat;
 
@@ -101,6 +104,9 @@
 
         mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
 
+        addOnAttachStateChangeListener(
+                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
+
         mSlotBattery = context.getString(
                 com.android.internal.R.string.status_bar_battery);
         mBatteryIconView = new ImageView(context);
diff --git a/packages/SystemUI/src/com/android/systemui/DemoMode.java b/packages/SystemUI/src/com/android/systemui/DemoMode.java
index 11996d0..5c39715 100644
--- a/packages/SystemUI/src/com/android/systemui/DemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/DemoMode.java
@@ -37,4 +37,5 @@
     public static final String COMMAND_STATUS = "status";
     public static final String COMMAND_NOTIFICATIONS = "notifications";
     public static final String COMMAND_VOLUME = "volume";
+    public static final String COMMAND_OPERATOR = "operator";
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 2937a25..d8a47c5 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -22,6 +22,8 @@
 import android.os.Looper;
 import android.os.Process;
 import android.util.ArrayMap;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.NightDisplayController;
@@ -304,6 +306,8 @@
 
         mProviders.put(LightBarController.class, () -> new LightBarController(mContext));
 
+        mProviders.put(IWindowManager.class, () -> WindowManagerGlobal.getWindowManagerService());
+
         // Put all dependencies above here so the factory can override them if it wants.
         SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
index c930d56..3714c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceControllerImpl.java
@@ -34,6 +34,10 @@
  */
 public class ForegroundServiceControllerImpl
         implements ForegroundServiceController {
+  
+    // shelf life of foreground services before they go bad
+    public static final long FG_SERVICE_GRACE_MILLIS = 5000;
+
     private static final String TAG = "FgServiceController";
     private static final boolean DBG = false;
 
@@ -72,7 +76,7 @@
             if (isDungeonNotification(sbn)) {
                 // if you remove the dungeon entirely, we take that to mean there are
                 // no running services
-                userServices.setRunningServices(null);
+                userServices.setRunningServices(null, 0);
                 return true;
             } else {
                 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
@@ -94,7 +98,7 @@
                 final Bundle extras = sbn.getNotification().extras;
                 if (extras != null) {
                     final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
-                    userServices.setRunningServices(svcs); // null ok
+                    userServices.setRunningServices(svcs, sbn.getNotification().when);
                 }
             } else {
                 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
@@ -118,9 +122,11 @@
      */
     private static class UserServices {
         private String[] mRunning = null;
+        private long mServiceStartTime = 0;
         private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
-        public void setRunningServices(String[] pkgs) {
+        public void setRunningServices(String[] pkgs, long serviceStartTime) {
             mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
+            mServiceStartTime = serviceStartTime;
         }
         public void addNotification(String pkg, String key) {
             if (mNotifications.get(pkg) == null) {
@@ -142,7 +148,9 @@
             return found;
         }
         public boolean isDungeonNeeded() {
-            if (mRunning != null) {
+            if (mRunning != null
+                && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
+
                 for (String pkg : mRunning) {
                     final ArraySet<String> set = mNotifications.get(pkg);
                     if (set == null || set.size() == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 907a79e..593bb50 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,11 +16,6 @@
 
 package com.android.systemui;
 
-import static android.opengl.GLES20.*;
-
-import static javax.microedition.khronos.egl.EGL10.*;
-
-import android.app.ActivityManager;
 import android.app.WallpaperManager;
 import android.content.ComponentCallbacks2;
 import android.graphics.Bitmap;
@@ -28,31 +23,18 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region.Op;
-import android.opengl.GLUtils;
 import android.os.AsyncTask;
-import android.os.SystemProperties;
 import android.os.Trace;
-import android.renderscript.Matrix4f;
 import android.service.wallpaper.WallpaperService;
 import android.util.Log;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
 
 /**
  * Default built-in wallpaper that simply shows a static image.
@@ -64,24 +46,13 @@
     private static final boolean DEBUG = false;
     private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
 
-    static final boolean FIXED_SIZED_SURFACE = true;
-    static final boolean USE_OPENGL = true;
-
-    WallpaperManager mWallpaperManager;
-
-    DrawableEngine mEngine;
-
-    boolean mIsHwAccelerated;
+    private WallpaperManager mWallpaperManager;
+    private DrawableEngine mEngine;
 
     @Override
     public void onCreate() {
         super.onCreate();
-        mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
-
-        //noinspection PointlessBooleanExpression,ConstantConditions
-        if (FIXED_SIZED_SURFACE && USE_OPENGL) {
-            mIsHwAccelerated = ActivityManager.isHighEndGfx();
-        }
+        mWallpaperManager = getSystemService(WallpaperManager.class);
     }
 
     @Override
@@ -98,15 +69,12 @@
     }
 
     class DrawableEngine extends Engine {
-        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-        static final int EGL_OPENGL_ES2_BIT = 4;
-
         Bitmap mBackground;
         int mBackgroundWidth = -1, mBackgroundHeight = -1;
         int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
         int mLastRotation = -1;
-        float mXOffset = 0.5f;
-        float mYOffset = 0.5f;
+        float mXOffset = 0f;
+        float mYOffset = 0f;
         float mScale = 1f;
 
         private Display mDefaultDisplay;
@@ -117,34 +85,6 @@
         int mLastXTranslation;
         int mLastYTranslation;
 
-        private EGL10 mEgl;
-        private EGLDisplay mEglDisplay;
-        private EGLConfig mEglConfig;
-        private EGLContext mEglContext;
-        private EGLSurface mEglSurface;
-
-        private static final String sSimpleVS =
-                "attribute vec4 position;\n" +
-                "attribute vec2 texCoords;\n" +
-                "varying vec2 outTexCoords;\n" +
-                "uniform mat4 projection;\n" +
-                "\nvoid main(void) {\n" +
-                "    outTexCoords = texCoords;\n" +
-                "    gl_Position = projection * position;\n" +
-                "}\n\n";
-        private static final String sSimpleFS =
-                "precision mediump float;\n\n" +
-                "varying vec2 outTexCoords;\n" +
-                "uniform sampler2D texture;\n" +
-                "\nvoid main(void) {\n" +
-                "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
-                "}\n\n";
-
-        private static final int FLOAT_SIZE_BYTES = 4;
-        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-
         private int mRotationAtLastSurfaceSizeUpdate = -1;
         private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
         private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
@@ -154,13 +94,14 @@
         private AsyncTask<Void, Void, Bitmap> mLoader;
         private boolean mNeedsDrawAfterLoadingWallpaper;
         private boolean mSurfaceValid;
+        private boolean mSurfaceRedrawNeeded;
 
-        public DrawableEngine() {
+        DrawableEngine() {
             super();
             setFixedSizeAllowed(true);
         }
 
-        public void trimMemory(int level) {
+        void trimMemory(int level) {
             if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
                     && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
                     && mBackground != null) {
@@ -179,6 +120,7 @@
 
             super.onCreate(surfaceHolder);
 
+            //noinspection ConstantConditions
             mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
             setOffsetNotificationsEnabled(false);
 
@@ -199,7 +141,7 @@
             // Load background image dimensions, if we haven't saved them yet
             if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
                 // Need to load the image to get dimensions
-                loadWallpaper(forDraw, false /* needsReset */);
+                loadWallpaper(forDraw);
                 if (DEBUG) {
                     Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
                 }
@@ -210,16 +152,13 @@
             int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
             int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
 
-            if (FIXED_SIZED_SURFACE) {
-                // Used a fixed size surface, because we are special.  We can do
-                // this because we know the current design of window animations doesn't
-                // cause this to break.
-                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
-                mLastRequestedWidth = surfaceWidth;
-                mLastRequestedHeight = surfaceHeight;
-            } else {
-                surfaceHolder.setSizeFromLayout();
-            }
+            // Used a fixed size surface, because we are special.  We can do
+            // this because we know the current design of window animations doesn't
+            // cause this to break.
+            surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
+            mLastRequestedWidth = surfaceWidth;
+            mLastRequestedHeight = surfaceHeight;
+
             return hasWallpaper;
         }
 
@@ -300,6 +239,13 @@
                 Log.d(TAG, "onSurfaceRedrawNeeded");
             }
             super.onSurfaceRedrawNeeded(holder);
+            // At the end of this method we should have drawn into the surface.
+            // This means that the bitmap should be loaded synchronously if
+            // it was already unloaded.
+            if (mBackground == null) {
+                updateBitmap(mWallpaperManager.getBitmap(true /* hardware */));
+            }
+            mSurfaceRedrawNeeded = true;
             drawFrame();
         }
 
@@ -336,7 +282,8 @@
                 boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
                         || dh != mLastSurfaceHeight;
 
-                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
+                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation
+                        || mSurfaceRedrawNeeded;
                 if (!redrawNeeded && !mOffsetsChanged) {
                     if (DEBUG) {
                         Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
@@ -345,40 +292,24 @@
                     return;
                 }
                 mLastRotation = newRotation;
+                mSurfaceRedrawNeeded = false;
 
                 // Load bitmap if it is not yet loaded
                 if (mBackground == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
-                                mBackground + ", " +
-                                ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
-                                ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
-                                dw + ", " + dh);
-                    }
-                    loadWallpaper(true /* needDraw */, true /* needReset */);
+                    loadWallpaper(true);
                     if (DEBUG) {
                         Log.d(TAG, "Reloading, resuming draw later");
                     }
                     return;
                 }
 
-                // Center the scaled image
+                // Left align the scaled image
                 mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
                         dh / (float) mBackground.getHeight()));
-                final int availw = dw - (int) (mBackground.getWidth() * mScale);
-                final int availh = dh - (int) (mBackground.getHeight() * mScale);
-                int xPixels = availw / 2;
-                int yPixels = availh / 2;
-
-                // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
-                // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
-                // will remain unchanged
-                final int availwUnscaled = dw - mBackground.getWidth();
-                final int availhUnscaled = dh - mBackground.getHeight();
-                if (availwUnscaled < 0)
-                    xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
-                if (availhUnscaled < 0)
-                    yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
+                final int availw = (int) (mBackground.getWidth() * mScale) - dw;
+                final int availh = (int) (mBackground.getHeight() * mScale) - dh;
+                int xPixels = (int) (availw * mXOffset);
+                int yPixels = (int) (availh * mYOffset);
 
                 mOffsetsChanged = false;
                 if (surfaceDimensionsChanged) {
@@ -399,21 +330,7 @@
                     Log.d(TAG, "Redrawing wallpaper");
                 }
 
-                if (mIsHwAccelerated) {
-                    if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
-                        drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
-                    }
-                } else {
-                    drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
-                    if (FIXED_SIZED_SURFACE) {
-                        // If the surface is fixed-size, we should only need to
-                        // draw it once and then we'll let the window manager
-                        // position it appropriately.  As such, we no longer needed
-                        // the loaded bitmap.  Yay!
-                        // hw-accelerated renderer retains bitmap for faster rotation
-                        unloadWallpaper(false /* forgetSize */);
-                    }
-                }
+                drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
             }
@@ -428,28 +345,20 @@
          *
          * If {@param needsReset} is set also clears the cache in WallpaperManager first.
          */
-        private void loadWallpaper(boolean needsDraw, boolean needsReset) {
+        private void loadWallpaper(boolean needsDraw) {
             mNeedsDrawAfterLoadingWallpaper |= needsDraw;
             if (mLoader != null) {
-                if (needsReset) {
-                    mLoader.cancel(false /* interrupt */);
-                    mLoader = null;
-                } else {
-                    if (DEBUG) {
-                        Log.d(TAG, "Skipping loadWallpaper, already in flight ");
-                    }
-                    return;
+                if (DEBUG) {
+                    Log.d(TAG, "Skipping loadWallpaper, already in flight ");
                 }
+                return;
             }
             mLoader = new AsyncTask<Void, Void, Bitmap>() {
                 @Override
                 protected Bitmap doInBackground(Void... params) {
                     Throwable exception;
                     try {
-                        if (needsReset) {
-                            mWallpaperManager.forgetLoadedWallpaper();
-                        }
-                        return mWallpaperManager.getBitmap();
+                        return mWallpaperManager.getBitmap(true /* hardware */);
                     } catch (RuntimeException | OutOfMemoryError e) {
                         exception = e;
                     }
@@ -458,48 +367,33 @@
                         return null;
                     }
 
-                    if (exception != null) {
-                        // Note that if we do fail at this, and the default wallpaper can't
-                        // be loaded, we will go into a cycle.  Don't do a build where the
-                        // default wallpaper can't be loaded.
-                        Log.w(TAG, "Unable to load wallpaper!", exception);
-                        try {
-                            mWallpaperManager.clear();
-                        } catch (IOException ex) {
-                            // now we're really screwed.
-                            Log.w(TAG, "Unable reset to default wallpaper!", ex);
-                        }
+                    // Note that if we do fail at this, and the default wallpaper can't
+                    // be loaded, we will go into a cycle.  Don't do a build where the
+                    // default wallpaper can't be loaded.
+                    Log.w(TAG, "Unable to load wallpaper!", exception);
+                    try {
+                        mWallpaperManager.clear();
+                    } catch (IOException ex) {
+                        // now we're really screwed.
+                        Log.w(TAG, "Unable reset to default wallpaper!", ex);
+                    }
 
-                        if (isCancelled()) {
-                            return null;
-                        }
+                    if (isCancelled()) {
+                        return null;
+                    }
 
-                        try {
-                            return mWallpaperManager.getBitmap();
-                        } catch (RuntimeException | OutOfMemoryError e) {
-                            Log.w(TAG, "Unable to load default wallpaper!", e);
-                        }
+                    try {
+                        return mWallpaperManager.getBitmap(true /* hardware */);
+                    } catch (RuntimeException | OutOfMemoryError e) {
+                        Log.w(TAG, "Unable to load default wallpaper!", e);
                     }
                     return null;
                 }
 
                 @Override
                 protected void onPostExecute(Bitmap b) {
-                    mBackground = null;
-                    mBackgroundWidth = -1;
-                    mBackgroundHeight = -1;
+                    updateBitmap(b);
 
-                    if (b != null) {
-                        mBackground = b;
-                        mBackgroundWidth = mBackground.getWidth();
-                        mBackgroundHeight = mBackground.getHeight();
-                    }
-
-                    if (DEBUG) {
-                        Log.d(TAG, "Wallpaper loaded: " + mBackground);
-                    }
-                    updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
-                            false /* forDraw */);
                     if (mNeedsDrawAfterLoadingWallpaper) {
                         drawFrame();
                     }
@@ -510,6 +404,24 @@
             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
         }
 
+        private void updateBitmap(Bitmap bitmap) {
+            mBackground = null;
+            mBackgroundWidth = -1;
+            mBackgroundHeight = -1;
+
+            if (bitmap != null) {
+                mBackground = bitmap;
+                mBackgroundWidth = mBackground.getWidth();
+                mBackgroundHeight = mBackground.getHeight();
+            }
+
+            if (DEBUG) {
+                Log.d(TAG, "Wallpaper loaded: " + mBackground);
+            }
+            updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
+                    false /* forDraw */);
+        }
+
         private void unloadWallpaper(boolean forgetSize) {
             if (mLoader != null) {
                 mLoader.cancel(false);
@@ -564,7 +476,7 @@
         }
 
         private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
-            Canvas c = sh.lockCanvas();
+            Canvas c = sh.lockHardwareCanvas();
             if (c != null) {
                 try {
                     if (DEBUG) {
@@ -590,278 +502,5 @@
                 }
             }
         }
-
-        private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
-            if (!initGL(sh)) return false;
-
-            final float right = left + mBackground.getWidth() * mScale;
-            final float bottom = top + mBackground.getHeight() * mScale;
-
-            final Rect frame = sh.getSurfaceFrame();
-            final Matrix4f ortho = new Matrix4f();
-            ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
-
-            final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
-
-            final int texture = loadTexture(mBackground);
-            final int program = buildProgram(sSimpleVS, sSimpleFS);
-
-            final int attribPosition = glGetAttribLocation(program, "position");
-            final int attribTexCoords = glGetAttribLocation(program, "texCoords");
-            final int uniformTexture = glGetUniformLocation(program, "texture");
-            final int uniformProjection = glGetUniformLocation(program, "projection");
-
-            checkGlError();
-
-            glViewport(0, 0, frame.width(), frame.height());
-            glBindTexture(GL_TEXTURE_2D, texture);
-
-            glUseProgram(program);
-            glEnableVertexAttribArray(attribPosition);
-            glEnableVertexAttribArray(attribTexCoords);
-            glUniform1i(uniformTexture, 0);
-            glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
-
-            checkGlError();
-
-            if (w > 0 || h > 0) {
-                glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-                glClear(GL_COLOR_BUFFER_BIT);
-            }
-
-            // drawQuad
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-            glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-            glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
-            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
-            boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
-            checkEglError();
-
-            finishGL(texture, program);
-
-            return status;
-        }
-
-        private FloatBuffer createMesh(int left, int top, float right, float bottom) {
-            final float[] verticesData = {
-                    // X, Y, Z, U, V
-                     left,  bottom, 0.0f, 0.0f, 1.0f,
-                     right, bottom, 0.0f, 1.0f, 1.0f,
-                     left,  top,    0.0f, 0.0f, 0.0f,
-                     right, top,    0.0f, 1.0f, 0.0f,
-            };
-
-            final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
-            final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
-                    ByteOrder.nativeOrder()).asFloatBuffer();
-            triangleVertices.put(verticesData).position(0);
-            return triangleVertices;
-        }
-
-        private int loadTexture(Bitmap bitmap) {
-            int[] textures = new int[1];
-
-            glActiveTexture(GL_TEXTURE0);
-            glGenTextures(1, textures, 0);
-            checkGlError();
-
-            int texture = textures[0];
-            glBindTexture(GL_TEXTURE_2D, texture);
-            checkGlError();
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-            GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
-            checkGlError();
-
-            return texture;
-        }
-
-        private int buildProgram(String vertex, String fragment) {
-            int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
-            if (vertexShader == 0) return 0;
-
-            int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
-            if (fragmentShader == 0) return 0;
-
-            int program = glCreateProgram();
-            glAttachShader(program, vertexShader);
-            glAttachShader(program, fragmentShader);
-            glLinkProgram(program);
-            checkGlError();
-
-            glDeleteShader(vertexShader);
-            glDeleteShader(fragmentShader);
-
-            int[] status = new int[1];
-            glGetProgramiv(program, GL_LINK_STATUS, status, 0);
-            if (status[0] != GL_TRUE) {
-                String error = glGetProgramInfoLog(program);
-                Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
-                glDeleteProgram(program);
-                return 0;
-            }
-
-            return program;
-        }
-
-        private int buildShader(String source, int type) {
-            int shader = glCreateShader(type);
-
-            glShaderSource(shader, source);
-            checkGlError();
-
-            glCompileShader(shader);
-            checkGlError();
-
-            int[] status = new int[1];
-            glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
-            if (status[0] != GL_TRUE) {
-                String error = glGetShaderInfoLog(shader);
-                Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
-                glDeleteShader(shader);
-                return 0;
-            }
-
-            return shader;
-        }
-
-        private void checkEglError() {
-            int error = mEgl.eglGetError();
-            if (error != EGL_SUCCESS) {
-                Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
-            }
-        }
-
-        private void checkGlError() {
-            int error = glGetError();
-            if (error != GL_NO_ERROR) {
-                Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
-            }
-        }
-
-        private void finishGL(int texture, int program) {
-            int[] textures = new int[1];
-            textures[0] = texture;
-            glDeleteTextures(1, textures, 0);
-            glDeleteProgram(program);
-            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
-            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-            mEgl.eglTerminate(mEglDisplay);
-        }
-
-        private boolean initGL(SurfaceHolder surfaceHolder) {
-            mEgl = (EGL10) EGLContext.getEGL();
-
-            mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
-            if (mEglDisplay == EGL_NO_DISPLAY) {
-                throw new RuntimeException("eglGetDisplay failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            int[] version = new int[2];
-            if (!mEgl.eglInitialize(mEglDisplay, version)) {
-                throw new RuntimeException("eglInitialize failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            mEglConfig = chooseEglConfig();
-            if (mEglConfig == null) {
-                throw new RuntimeException("eglConfig not initialized");
-            }
-
-            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
-            if (mEglContext == EGL_NO_CONTEXT) {
-                throw new RuntimeException("createContext failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            int attribs[] = {
-                EGL_WIDTH, 1,
-                EGL_HEIGHT, 1,
-                EGL_NONE
-            };
-            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
-            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
-
-            int[] maxSize = new int[1];
-            Rect frame = surfaceHolder.getSurfaceFrame();
-            glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
-
-            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
-
-            if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
-                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
-                mEgl.eglTerminate(mEglDisplay);
-                Log.e(GL_LOG_TAG, "requested  texture size " +
-                    frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
-                    maxSize[0] + "x" + maxSize[0]);
-                return false;
-            }
-
-            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
-            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
-                int error = mEgl.eglGetError();
-                if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
-                    Log.e(GL_LOG_TAG, "createWindowSurface returned " +
-                                         GLUtils.getEGLErrorString(error) + ".");
-                    return false;
-                }
-                throw new RuntimeException("createWindowSurface failed " +
-                        GLUtils.getEGLErrorString(error));
-            }
-
-            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                throw new RuntimeException("eglMakeCurrent failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            }
-
-            return true;
-        }
-
-
-        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
-            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
-            return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
-        }
-
-        private EGLConfig chooseEglConfig() {
-            int[] configsCount = new int[1];
-            EGLConfig[] configs = new EGLConfig[1];
-            int[] configSpec = getConfig();
-            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
-                throw new IllegalArgumentException("eglChooseConfig failed " +
-                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
-            } else if (configsCount[0] > 0) {
-                return configs[0];
-            }
-            return null;
-        }
-
-        private int[] getConfig() {
-            return new int[] {
-                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-                    EGL_RED_SIZE, 8,
-                    EGL_GREEN_SIZE, 8,
-                    EGL_BLUE_SIZE, 8,
-                    EGL_ALPHA_SIZE, 0,
-                    EGL_DEPTH_SIZE, 0,
-                    EGL_STENCIL_SIZE, 0,
-                    EGL_CONFIG_CAVEAT, EGL_NONE,
-                    EGL_NONE
-            };
-        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 44a044b..880ae70 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -28,7 +28,7 @@
     /**
      * Docks the top-most task and opens recents.
      */
-    boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+    boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
             int metricsDockAction);
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 4b37715..592dda0 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -83,7 +83,6 @@
 
     private boolean mMenuRowIntercepting;
     private boolean mLongPressSent;
-    private LongPressListener mLongPressListener;
     private Runnable mWatchLongPress;
     private final long mLongPressTimeout;
 
@@ -115,10 +114,6 @@
         mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
     }
 
-    public void setLongPressListener(LongPressListener listener) {
-        mLongPressListener = listener;
-    }
-
     public void setDensityScale(float densityScale) {
         mDensityScale = densityScale;
     }
@@ -257,7 +252,7 @@
         }
     }
 
-    public void removeLongPressCallback() {
+    public void cancelLongPress() {
         if (mWatchLongPress != null) {
             mHandler.removeCallbacks(mWatchLongPress);
             mWatchLongPress = null;
@@ -288,33 +283,27 @@
                     mInitialTouchPos = getPos(ev);
                     mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
                     mTranslation = getTranslation(mCurrView);
-                    if (mLongPressListener != null) {
-                        if (mWatchLongPress == null) {
-                            mWatchLongPress = new Runnable() {
-                                @Override
-                                public void run() {
-                                    if (mCurrView != null && !mLongPressSent) {
-                                        mLongPressSent = true;
+                    if (mWatchLongPress == null) {
+                        mWatchLongPress = new Runnable() {
+                            @Override
+                            public void run() {
+                                if (mCurrView != null && !mLongPressSent) {
+                                    mLongPressSent = true;
+                                    mCurrView.getLocationOnScreen(mTmpPos);
+                                    final int x = (int) ev.getRawX() - mTmpPos[0];
+                                    final int y = (int) ev.getRawY() - mTmpPos[1];
+                                    if (mCurrView instanceof ExpandableNotificationRow) {
                                         mCurrView.sendAccessibilityEvent(
                                                 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
-                                        mCurrView.getLocationOnScreen(mTmpPos);
-                                        final int x = (int) ev.getRawX() - mTmpPos[0];
-                                        final int y = (int) ev.getRawY() - mTmpPos[1];
-                                        MenuItem menuItem = null;
-                                        if (mCurrView instanceof ExpandableNotificationRow) {
-                                            menuItem = ((ExpandableNotificationRow) mCurrView)
-                                                    .getProvider().getLongpressMenuItem(mContext);
-                                        }
-                                        if (menuItem != null) {
-                                            mLongPressListener.onLongPress(mCurrView, x, y,
-                                                    menuItem);
-                                        }
+                                        ExpandableNotificationRow currRow =
+                                                (ExpandableNotificationRow) mCurrView;
+                                        currRow.doLongClickCallback(x, y);
                                     }
                                 }
-                            };
-                        }
-                        mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
+                            }
+                        };
                     }
+                    mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
                 }
                 break;
 
@@ -331,7 +320,7 @@
                         mDragging = true;
                         mInitialTouchPos = getPos(ev);
                         mTranslation = getTranslation(mCurrView);
-                        removeLongPressCallback();
+                        cancelLongPress();
                     }
                 }
                 break;
@@ -343,7 +332,7 @@
                 mCurrView = null;
                 mLongPressSent = false;
                 mMenuRowIntercepting = false;
-                removeLongPressCallback();
+                cancelLongPress();
                 if (captured) return true;
                 break;
         }
@@ -586,7 +575,7 @@
 
                 // We are not doing anything, make sure the long press callback
                 // is not still ticking like a bomb waiting to go off.
-                removeLongPressCallback();
+                cancelLongPress();
                 return false;
             }
         }
@@ -734,15 +723,4 @@
          */
         float getFalsingThresholdFactor();
     }
-
-    /**
-     * Equivalent to View.OnLongClickListener with coordinates
-     */
-    public interface LongPressListener {
-        /**
-         * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
-         * @return whether the longpress was handled
-         */
-        boolean onLongPress(View v, int x, int y, MenuItem item);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index c5eebcc..8a8bafa 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -61,6 +61,7 @@
     private AssistOrbContainer mView;
     private final DeviceProvisionedController mDeviceProvisionedController;
     protected final AssistUtils mAssistUtils;
+    private final boolean mShouldEnableOrb;
 
     private IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -96,6 +97,7 @@
                 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
                 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
         onConfigurationChanged(context.getResources().getConfiguration());
+        mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
     }
 
     protected void registerVoiceInteractionSessionListener() {
@@ -179,7 +181,9 @@
 
     private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
         maybeSwapSearchIcon(assistComponent, isService);
-        mView.show(true /* show */, true /* animate */);
+        if (mShouldEnableOrb) {
+            mView.show(true /* show */, true /* animate */);
+        }
     }
 
     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index 5c99961..debda21 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.doze;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.util.KeyValueListParser;
@@ -34,6 +39,10 @@
 public class AlwaysOnDisplayPolicy {
     public static final String TAG = "AlwaysOnDisplayPolicy";
 
+    private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
     static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
     static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
     static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
@@ -46,7 +55,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_SCREEN_BRIGHTNESS_ARRAY
      */
-    public final int[] screenBrightnessArray;
+    public int[] screenBrightnessArray;
 
     /**
      * Integer array to map ambient brightness type to dimming scrim.
@@ -54,7 +63,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_DIMMING_SCRIM_ARRAY
      */
-    public final int[] dimmingScrimArray;
+    public int[] dimmingScrimArray;
 
     /**
      * Delay time(ms) from covering the prox to turning off the screen.
@@ -62,7 +71,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_SCREEN_OFF_DELAY_MS
      */
-    public final long proxScreenOffDelayMs;
+    public long proxScreenOffDelayMs;
 
     /**
      * The threshold time(ms) to trigger the cooldown timer, which will
@@ -71,7 +80,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_TRIGGER_MS
      */
-    public final long proxCooldownTriggerMs;
+    public long proxCooldownTriggerMs;
 
     /**
      * The period(ms) to turning off the prox sensor if
@@ -80,43 +89,78 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_PERIOD_MS
      */
-    public final long proxCooldownPeriodMs;
+    public long proxCooldownPeriodMs;
 
     private final KeyValueListParser mParser;
+    private final Context mContext;
+    private SettingsObserver mSettingsObserver;
 
     public AlwaysOnDisplayPolicy(Context context) {
-        final Resources resources = context.getResources();
+        mContext = context;
         mParser = new KeyValueListParser(',');
-
-        final String value = Settings.Global.getString(context.getContentResolver(),
-                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
-
-        try {
-            mParser.setString(value);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Bad AOD constants");
-        }
-
-        proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
-                10 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
-                2 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
-                5 * DateUtils.SECOND_IN_MILLIS);
-        screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
-        dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+        mSettingsObserver = new SettingsObserver(context.getMainThreadHandler());
+        mSettingsObserver.observe();
     }
 
     private int[] parseIntArray(final String key, final int[] defaultArray) {
         final String value = mParser.getString(key, null);
         if (value != null) {
-            return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
-                    Integer::parseInt).toArray();
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
         } else {
             return defaultArray;
         }
     }
 
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI
+                = Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void observe() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI,
+                    false, this, UserHandle.USER_ALL);
+            update(null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
+        }
+
+        public void update(Uri uri) {
+            if (uri == null || ALWAYS_ON_DISPLAY_CONSTANTS_URI.equals(uri)) {
+                final Resources resources = mContext.getResources();
+                final String value = Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Bad AOD constants");
+                }
+
+                proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+                        DEFAULT_PROX_SCREEN_OFF_DELAY_MS);
+                proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+                        DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
+                proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+                        DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+                screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_brightness));
+                dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_scrim_opacity));
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
index 76a1902..58f1448 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java
@@ -28,20 +28,21 @@
     public static final String TAG = DozePauser.class.getSimpleName();
     private final AlarmTimeout mPauseTimeout;
     private final DozeMachine mMachine;
-    private final long mTimeoutMs;
+    private final AlwaysOnDisplayPolicy mPolicy;
 
     public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
             AlwaysOnDisplayPolicy policy) {
         mMachine = machine;
         mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
-        mTimeoutMs = policy.proxScreenOffDelayMs;
+        mPolicy = policy;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case DOZE_AOD_PAUSING:
-                mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+                mPauseTimeout.schedule(mPolicy.proxScreenOffDelayMs,
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
                 break;
             default:
                 mPauseTimeout.cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 03407e2..4bb4e79 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Handler;
+import android.os.Trace;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -94,9 +95,14 @@
 
     @Override
     public void onSensorChanged(SensorEvent event) {
-        if (mRegistered) {
-            mLastSensorValue = (int) event.values[0];
-            updateBrightnessAndReady();
+        Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        try {
+            if (mRegistered) {
+                mLastSensorValue = (int) event.values[0];
+                updateBrightnessAndReady();
+            }
+        } finally {
+            Trace.endSection();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index dc626fb..851b78c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -84,9 +84,6 @@
             case DOZE_REQUEST_PULSE:
                 pulseWhileDozing(mMachine.getPulseReason());
                 break;
-            case DOZE_PULSE_DONE:
-                mHost.abortPulsing();
-                break;
             case INITIALIZED:
                 mHost.startDozing();
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 09a08f0..f06cda0 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -31,6 +31,7 @@
 
 public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
 
+    private GlobalActions mPlugin;
     private Extension<GlobalActions> mExtension;
     private IStatusBarService mBarService;
 
@@ -41,10 +42,19 @@
         mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
                 .withPlugin(GlobalActions.class)
                 .withDefault(() -> new GlobalActionsImpl(mContext))
+                .withCallback(this::onExtensionCallback)
                 .build();
+        mPlugin = mExtension.get();
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallbacks(this);
     }
 
+    private void onExtensionCallback(GlobalActions newPlugin) {
+        if (mPlugin != null) {
+            mPlugin.destroy();
+        }
+        mPlugin = newPlugin;
+    }
+
     @Override
     public void handleShowShutdownUi(boolean isReboot, String reason) {
         mExtension.get().showShutdownUi(isReboot, reason);
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 189badf..d82f9cd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -16,27 +16,6 @@
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 
-import com.android.internal.R;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.colorextraction.ColorExtractor.GradientColors;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.EmergencyAffordanceManager;
-import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.telephony.TelephonyProperties;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.HardwareUiLayout;
-import com.android.systemui.Interpolators;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
-import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
-import com.android.systemui.volume.VolumeDialogMotion.LogDecelerateInterpolator;
-
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.ActivityManager;
 import android.app.Dialog;
 import android.app.WallpaperManager;
@@ -69,7 +48,6 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.MathUtils;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -86,7 +64,23 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.internal.R;
+import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -96,7 +90,8 @@
  * may show depending on whether the keyguard is showing, and whether the device
  * is provisioned.
  */
-class GlobalActionsDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+class GlobalActionsDialog implements DialogInterface.OnDismissListener,
+        DialogInterface.OnClickListener {
 
     static public final String SYSTEM_DIALOG_REASON_KEY = "reason";
     static public final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -195,6 +190,14 @@
         }
     }
 
+    /**
+     * Dismiss the global actions dialog, if it's currently shown
+     */
+    public void dismissDialog() {
+        mHandler.removeMessages(MESSAGE_DISMISS);
+        mHandler.sendEmptyMessage(MESSAGE_DISMISS);
+    }
+
     private void awakenIfNecessary() {
         if (mDreamManager != null) {
             try {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 2cf230c..3563437 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -14,12 +14,12 @@
 
 package com.android.systemui.globalactions;
 
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
 import android.app.Dialog;
 import android.app.KeyguardManager;
-import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -32,12 +32,14 @@
 import com.android.internal.colorextraction.drawable.GradientDrawable;
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.plugins.GlobalActions;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 
-public class GlobalActionsImpl implements GlobalActions {
+public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
 
     private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
 
@@ -45,15 +47,23 @@
     private final KeyguardMonitor mKeyguardMonitor;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private GlobalActionsDialog mGlobalActions;
+    private boolean mDisabled;
 
     public GlobalActionsImpl(Context context) {
         mContext = context;
         mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
         mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
+        SysUiServiceProvider.getComponent(context, CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
+    public void destroy() {
+        SysUiServiceProvider.getComponent(mContext, CommandQueue.class).removeCallbacks(this);
     }
 
     @Override
     public void showGlobalActions(GlobalActionsManager manager) {
+        if (mDisabled) return;
         if (mGlobalActions == null) {
             mGlobalActions = new GlobalActionsDialog(mContext, manager);
         }
@@ -107,4 +117,14 @@
 
         d.show();
     }
+
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+        if (disabled == mDisabled) return;
+        mDisabled = disabled;
+        if (disabled && mGlobalActions != null) {
+            mGlobalActions.dismissDialog();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
index f198229..4c3d5ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivityController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IActivityManager;
@@ -29,9 +28,8 @@
 import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 public class WorkLockActivityController {
     private final Context mContext;
@@ -98,7 +96,7 @@
         }
     }
 
-    private final TaskStackListener mLockListener = new TaskStackListener() {
+    private final TaskStackChangeListener mLockListener = new TaskStackChangeListener() {
         @Override
         public void onTaskProfileLocked(int taskId, int userId) {
             startWorkChallengeInTask(taskId, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
index 50720e9..b5c0d53 100644
--- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java
@@ -29,6 +29,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.LinkedList;
 
 /**
@@ -57,8 +59,12 @@
         }
     }
 
-    private LinkedList<Command> mCmdQueue = new LinkedList();
+    private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
 
+    private final Object mCompletionHandlingLock = new Object();
+    @GuardedBy("mCompletionHandlingLock")
+    private CreationAndCompletionThread mCompletionThread;
+    @GuardedBy("mCompletionHandlingLock")
     private Looper mLooper;
 
     /*
@@ -76,7 +82,10 @@
 
         public void run() {
             Looper.prepare();
+            // ok to modify mLooper as here we are
+            // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd)
             mLooper = Looper.myLooper();
+            if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper);
             synchronized(this) {
                 AudioManager audioManager =
                     (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
@@ -97,7 +106,7 @@
                     if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
                             && (mCmd.uri.getEncodedPath().length() > 0)) {
                         if (!audioManager.isMusicActiveRemotely()) {
-                            synchronized(mQueueAudioFocusLock) {
+                            synchronized (mQueueAudioFocusLock) {
                                 if (mAudioManagerWithAudioFocus == null) {
                                     if (DEBUG) Log.d(mTag, "requesting AudioFocus");
                                     int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
@@ -129,7 +138,9 @@
                         Log.e(mTag, "Exception while sleeping to sync notification playback"
                                 + " with ducking", e);
                     }
+                    if (DEBUG) { Log.d(mTag, "player.start"); }
                     if (mPlayer != null) {
+                        if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
                         mPlayer.release();
                     }
                     mPlayer = player;
@@ -148,7 +159,7 @@
         // is playing, let it continue until we're done, so there
         // is less of a glitch.
         try {
-            if (DEBUG) Log.d(mTag, "Starting playback");
+            if (DEBUG) { Log.d(mTag, "startSound()"); }
             //-----------------------------------
             // This is were we deviate from the AsyncPlayer implementation and create the
             // MediaPlayer in a new thread with which we're synchronized
@@ -158,10 +169,11 @@
                 // matters
                 if((mLooper != null)
                         && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+                    if (DEBUG) { Log.d(mTag, "in startSound quitting looper " + mLooper); }
                     mLooper.quit();
                 }
                 mCompletionThread = new CreationAndCompletionThread(cmd);
-                synchronized(mCompletionThread) {
+                synchronized (mCompletionThread) {
                     mCompletionThread.start();
                     mCompletionThread.wait();
                 }
@@ -209,13 +221,18 @@
                         mPlayer = null;
                         synchronized(mQueueAudioFocusLock) {
                             if (mAudioManagerWithAudioFocus != null) {
+                                if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
                                 mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                                 mAudioManagerWithAudioFocus = null;
                             }
                         }
-                        if((mLooper != null)
-                                && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
-                            mLooper.quit();
+                        synchronized (mCompletionHandlingLock) {
+                            if ((mLooper != null) &&
+                                    (mLooper.getThread().getState() != Thread.State.TERMINATED))
+                            {
+                                if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
+                                mLooper.quit();
+                            }
                         }
                     } else {
                         Log.w(mTag, "STOP command without a player");
@@ -250,9 +267,11 @@
         }
         // if there are no more sounds to play, end the Looper to listen for media completion
         synchronized (mCmdQueue) {
-            if (mCmdQueue.size() == 0) {
-                synchronized(mCompletionHandlingLock) {
-                    if(mLooper != null) {
+            synchronized(mCompletionHandlingLock) {
+                if (DEBUG) { Log.d(mTag, "onCompletion queue size=" + mCmdQueue.size()); }
+                if ((mCmdQueue.size() == 0)) {
+                    if (mLooper != null) {
+                        if (DEBUG) { Log.d(mTag, "in onCompletion quitting looper " + mLooper); }
                         mLooper.quit();
                     }
                     mCompletionThread = null;
@@ -269,13 +288,20 @@
     }
 
     private String mTag;
+
+    @GuardedBy("mCmdQueue")
     private CmdThread mThread;
-    private CreationAndCompletionThread mCompletionThread;
-    private final Object mCompletionHandlingLock = new Object();
+
     private MediaPlayer mPlayer;
+
+
+    @GuardedBy("mCmdQueue")
     private PowerManager.WakeLock mWakeLock;
+
     private final Object mQueueAudioFocusLock = new Object();
-    private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+    @GuardedBy("mQueueAudioFocusLock")
+    private AudioManager mAudioManagerWithAudioFocus;
+
     private int mNotificationRampTimeMs = 0;
 
     // The current state according to the caller.  Reality lags behind
@@ -311,6 +337,7 @@
      */
     @Deprecated
     public void play(Context context, Uri uri, boolean looping, int stream) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
@@ -339,6 +366,7 @@
      *          (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
      */
     public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
         cmd.code = PLAY;
@@ -357,6 +385,7 @@
      * at this point.  Calling this multiple times has no ill effects.
      */
     public void stop() {
+        if (DEBUG) { Log.d(mTag, "stop"); }
         synchronized (mCmdQueue) {
             // This check allows stop to be called multiple times without starting
             // a thread that ends up doing nothing.
@@ -370,6 +399,7 @@
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void enqueueLocked(Command cmd) {
         mCmdQueue.add(cmd);
         if (mThread == null) {
@@ -393,22 +423,26 @@
      * @hide
      */
     public void setUsesWakeLock(Context context) {
-        if (mWakeLock != null || mThread != null) {
-            // if either of these has happened, we've already played something.
-            // and our releases will be out of sync.
-            throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
-                    + " mThread=" + mThread);
+        synchronized (mCmdQueue) {
+            if (mWakeLock != null || mThread != null) {
+                // if either of these has happened, we've already played something.
+                // and our releases will be out of sync.
+                throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+                        + " mThread=" + mThread);
+            }
+            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
         }
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
     }
 
+    @GuardedBy("mCmdQueue")
     private void acquireWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.acquire();
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void releaseWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.release();
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
index abc5667..db4f988 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java
@@ -18,6 +18,8 @@
 
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
@@ -28,8 +30,6 @@
 import android.view.IWindowManager;
 import android.view.MotionEvent;
 
-import com.android.systemui.recents.misc.Utilities;
-
 import java.io.PrintWriter;
 
 /**
@@ -79,7 +79,8 @@
         }
     }
 
-    private IWindowManager mWindowManager;
+    private final IWindowManager mWindowManager;
+    private final IBinder mToken;
 
     private PipInputEventReceiver mInputEventReceiver;
     private TouchListener mListener;
@@ -87,6 +88,7 @@
 
     public InputConsumerController(IWindowManager windowManager) {
         mWindowManager = windowManager;
+        mToken = new Binder();
         registerInputConsumer();
     }
 
@@ -124,7 +126,7 @@
             final InputChannel inputChannel = new InputChannel();
             try {
                 mWindowManager.destroyInputConsumer(INPUT_CONSUMER_PIP);
-                mWindowManager.createInputConsumer(INPUT_CONSUMER_PIP, inputChannel);
+                mWindowManager.createInputConsumer(mToken, INPUT_CONSUMER_PIP, inputChannel);
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to create PIP input consumer", e);
             }
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 bae9ef8a..7e87666 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
@@ -40,8 +41,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.component.ExpandPipEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 
@@ -69,9 +69,9 @@
     /**
      * Handler for system task stack changes.
      */
-    TaskStackListener mTaskStackListener = new TaskStackListener() {
+    TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
-        public void onActivityPinned(String packageName, int userId, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             mTouchHandler.onActivityPinned();
             mMediaController.onActivityPinned();
             mMenuController.onActivityPinned();
@@ -199,7 +199,8 @@
     public final void onBusEvent(ExpandPipEvent event) {
         if (event.clearThumbnailWindows) {
             try {
-                StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                StackInfo stackInfo = mActivityManager.getStackInfo(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (stackInfo != null && stackInfo.taskIds != null) {
                     SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
                     for (int taskId : stackInfo.taskIds) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 68743b3..9fb201b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
@@ -383,7 +384,8 @@
     private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
             boolean allowMenuTimeout, boolean willResizeMenu) {
         try {
-            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
                 Intent intent = new Intent(mContext, PipMenuActivity.class);
@@ -421,7 +423,8 @@
             // Fetch the pinned stack bounds
             Rect stackBounds = null;
             try {
-                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (pinnedStackInfo != null) {
                     stackBounds = pinnedStackInfo.bounds;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index cebb22f..21a836c 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
@@ -121,7 +123,8 @@
     void synchronizePinnedStackBounds() {
         cancelAnimations();
         try {
-            StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            StackInfo stackInfo =
+                    mActivityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (stackInfo != null) {
                 mBounds.set(stackInfo.bounds);
             }
@@ -158,13 +161,7 @@
         mMenuController.hideMenuWithoutResize();
         mHandler.post(() -> {
             try {
-                if (skipAnimation) {
-                    mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true /* onTop */);
-                } else {
-                    mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
-                            true /* allowResizeInDockedMode */, true /* preserveWindows */,
-                            true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION);
-                }
+                mActivityManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error expanding PiP activity", e);
             }
@@ -182,7 +179,7 @@
         mMenuController.hideMenuWithoutResize();
         mHandler.post(() -> {
             try {
-                mActivityManager.removeStack(PINNED_STACK_ID);
+                mActivityManager.removeStacksInWindowingModes(new int[]{ WINDOWING_MODE_PINNED });
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to remove PiP", e);
             }
@@ -529,14 +526,15 @@
                 Rect toBounds = (Rect) args.arg1;
                 int duration = args.argi1;
                 try {
-                    StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                    StackInfo stackInfo = mActivityManager.getStackInfo(
+                            WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                     if (stackInfo == null) {
                         // In the case where we've already re-expanded or dismissed the PiP, then
                         // just skip the resize
                         return true;
                     }
 
-                    mActivityManager.resizeStack(PINNED_STACK_ID, toBounds,
+                    mActivityManager.resizeStack(stackInfo.stackId, toBounds,
                             false /* allowResizeInDockedMode */, true /* preserveWindows */,
                             true /* animate */, duration);
                     mBounds.set(toBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
index 56275fd..2f53de9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipUtils.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
@@ -38,7 +39,8 @@
             IActivityManager activityManager) {
         try {
             final String sysUiPackageName = context.getPackageName();
-            final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+            final StackInfo pinnedStackInfo =
+                    activityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
                 for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 186de5c4..c92562b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -20,7 +20,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
-import android.app.RemoteAction;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -47,13 +46,15 @@
 import com.android.systemui.R;
 import com.android.systemui.pip.BasePipManager;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 /**
@@ -121,6 +122,7 @@
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
     private boolean mInitialized;
     private int mPipTaskId = TASK_ID_NO_PIP;
+    private int mPinnedStackId = INVALID_STACK_ID;
     private ComponentName mPipComponentName;
     private MediaController mPipMediaController;
     private String[] mLastPackagesResourceGranted;
@@ -336,9 +338,11 @@
         mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
         if (removePipStack) {
             try {
-                mActivityManager.removeStack(PINNED_STACK_ID);
+                mActivityManager.removeStack(mPinnedStackId);
             } catch (RemoteException e) {
                 Log.e(TAG, "removeStack failed", e);
+            } finally {
+                mPinnedStackId = INVALID_STACK_ID;
             }
         }
         for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -424,7 +428,7 @@
         }
         try {
             int animationDurationMs = -1;
-            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+            mActivityManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
                     true, true, true, animationDurationMs);
         } catch (RemoteException e) {
             Log.e(TAG, "resizeStack failed", e);
@@ -502,7 +506,8 @@
     private StackInfo getPinnedStackInfo() {
         StackInfo stackInfo = null;
         try {
-            stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            stackInfo = mActivityManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
         } catch (RemoteException e) {
             Log.e(TAG, "getStackInfo failed", e);
         }
@@ -594,8 +599,8 @@
     private boolean isSettingsShown() {
         List<RunningTaskInfo> runningTasks;
         try {
-            runningTasks = mActivityManager.getTasks(1, 0);
-            if (runningTasks == null || runningTasks.size() == 0) {
+            runningTasks = mActivityManager.getTasks(1);
+            if (runningTasks.isEmpty()) {
                 return false;
             }
         } catch (RemoteException e) {
@@ -615,7 +620,7 @@
         return false;
     }
 
-    private TaskStackListener mTaskStackListener = new TaskStackListener() {
+    private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
@@ -654,7 +659,7 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int userId, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
             if (!checkCurrentUserId(mContext, DEBUG)) {
                 return;
@@ -665,6 +670,7 @@
                 return;
             }
             if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+            mPinnedStackId = stackInfo.stackId;
             mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
             mPipComponentName = ComponentName.unflattenFromString(
                     stackInfo.taskNames[stackInfo.taskNames.length - 1]);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index f378268..c1a3623 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -28,8 +28,14 @@
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.HardwarePropertiesManager;
+import android.os.IBinder;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
@@ -75,6 +81,7 @@
     private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
     private int mNumTemps;
     private long mNextLogTime;
+    private IThermalService mThermalService;
 
     // We create a method reference here so that we are guaranteed that we can remove a callback
     // by using the same instance (method references are not guaranteed to be the same object
@@ -263,7 +270,7 @@
                 resources.getInteger(R.integer.config_warningTemperature));
 
         if (mThresholdTemp < 0f) {
-            // Get the throttling temperature. No need to check if we're not throttling.
+            // Get the shutdown temperature, adjust for warning tolerance.
             float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
                     HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
                     HardwarePropertiesManager.TEMPERATURE_SHUTDOWN);
@@ -276,6 +283,25 @@
                     resources.getInteger(R.integer.config_warningTemperatureTolerance);
         }
 
+        if (mThermalService == null) {
+            // Enable push notifications of throttling from vendor thermal
+            // management subsystem via thermalservice, in addition to our
+            // usual polling, to react to temperature jumps more quickly.
+            IBinder b = ServiceManager.getService("thermalservice");
+
+            if (b != null) {
+                mThermalService = IThermalService.Stub.asInterface(b);
+                try {
+                    mThermalService.registerThermalEventListener(
+                        new ThermalEventListener());
+                } catch (RemoteException e) {
+                    // Should never happen.
+                }
+            } else {
+                Slog.w(TAG, "cannot find thermalservice, no throttling push notifications");
+            }
+        }
+
         setNextLogTime();
 
         // This initialization method may be called on a configuration change. Only one set of
@@ -414,5 +440,15 @@
         void dump(PrintWriter pw);
         void userSwitched();
     }
-}
 
+    // Thermal event received from vendor thermal management subsystem
+    private final class ThermalEventListener extends IThermalEventListener.Stub {
+        @Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
+            // Trigger an update of the temperature warning.  Only one
+            // callback can be enabled at a time, so remove any existing
+            // callback; updateTemperatureWarning will schedule another one.
+            mHandler.removeCallbacks(mUpdateTempCallback);
+            updateTemperatureWarning();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index b4cc4b1..ddd9910 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -34,6 +34,7 @@
 import android.widget.TextView;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
 
 /**
  * Quick settings common detail view with line items.
@@ -184,10 +185,10 @@
             }
             view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
             final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
-            if (item.iconDrawable != null) {
-                iv.setImageDrawable(item.iconDrawable);
+            if (item.icon != null) {
+                iv.setImageDrawable(item.icon.getDrawable(iv.getContext()));
             } else {
-                iv.setImageResource(item.icon);
+                iv.setImageResource(item.iconResId);
             }
             iv.getOverlay().clear();
             if (item.overlay != null) {
@@ -257,8 +258,8 @@
     }
 
     public static class Item {
-        public int icon;
-        public Drawable iconDrawable;
+        public int iconResId;
+        public QSTile.Icon icon;
         public Drawable overlay;
         public CharSequence line1;
         public CharSequence line2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 3199dec..532ead1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs;
 
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
 
 import android.app.ActivityManager;
@@ -37,7 +39,6 @@
 import android.view.View.OnClickListener;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -51,10 +52,11 @@
 import com.android.systemui.R;
 import com.android.systemui.R.dimen;
 import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.qs.TouchAnimator.Listener;
 import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.ExpandableIndicator;
 import com.android.systemui.statusbar.phone.MultiUserSwitch;
 import com.android.systemui.statusbar.phone.SettingsButton;
@@ -70,7 +72,7 @@
 
 public class QSFooterImpl extends FrameLayout implements QSFooter,
         NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
-        SignalCallback {
+        SignalCallback, CommandQueue.Callbacks {
     private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
 
     private ActivityStarter mActivityStarter;
@@ -83,6 +85,7 @@
     private View mAlarmStatusCollapsed;
     private View mDate;
 
+    private boolean mQsDisabled;
     private QSPanel mQsPanel;
 
     private boolean mExpanded;
@@ -278,9 +281,16 @@
     }
 
     @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
     @VisibleForTesting
     public void onDetachedFromWindow() {
         setListening(false);
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
         super.onDetachedFromWindow();
     }
 
@@ -302,6 +312,14 @@
         return findViewById(R.id.expand_indicator);
     }
 
+    @Override
+    public void disable(int state1, int state2, boolean animate) {
+        final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+        if (disabled == mQsDisabled) return;
+        mQsDisabled = disabled;
+        updateEverything();
+    }
+
     public void updateEverything() {
         post(() -> {
             updateVisibilities();
@@ -311,8 +329,13 @@
 
     private void updateVisibilities() {
         updateAlarmVisibilities();
+
+        mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+
+        mExpandIndicator.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
+
         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
 
         mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 00b883a..947b23f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -25,7 +25,7 @@
 
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.*;
+import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.plugins.qs.QSTileView;
@@ -43,6 +43,7 @@
 
     public static final String NUM_QUICK_TILES = "sysui_qqs_count";
 
+    private boolean mDisabledByPolicy;
     private int mMaxTiles;
     protected QSPanel mFullPanel;
 
@@ -151,6 +152,30 @@
         return Dependency.get(TunerService.class).getValue(NUM_QUICK_TILES, 6);
     }
 
+    void setDisabledByPolicy(boolean disabled) {
+        if (disabled != mDisabledByPolicy) {
+            mDisabledByPolicy = disabled;
+            setVisibility(disabled ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    /**
+     * Sets the visibility of this {@link QuickQSPanel}. This method has no effect when this panel
+     * is disabled by policy through {@link #setDisabledByPolicy(boolean)}, and in this case the
+     * visibility will always be {@link View#GONE}. This method is called externally by
+     * {@link QSAnimator} only.
+     */
+    @Override
+    public void setVisibility(int visibility) {
+        if (mDisabledByPolicy) {
+            if (getVisibility() == View.GONE) {
+                return;
+            }
+            visibility = View.GONE;
+        }
+        super.setVisibility(visibility);
+    }
+
     private static class HeaderTileLayout extends LinearLayout implements QSTileLayout {
 
         protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 0709e22..398592a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -14,6 +14,9 @@
 
 package com.android.systemui.qs;
 
+import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -30,13 +33,14 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.R.id;
+import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSDetail.Callback;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
 
-
-public class QuickStatusBarHeader extends RelativeLayout {
+public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue.Callbacks {
 
     private ActivityStarter mActivityStarter;
 
@@ -44,6 +48,7 @@
 
     private boolean mExpanded;
     private boolean mListening;
+    private boolean mQsDisabled;
 
     protected QuickQSPanel mHeaderQsPanel;
     protected QSTileHost mHost;
@@ -119,9 +124,25 @@
     }
 
     @Override
+    public void disable(int state1, int state2, boolean animate) {
+        final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
+        if (disabled == mQsDisabled) return;
+        mQsDisabled = disabled;
+        mHeaderQsPanel.setDisabledByPolicy(disabled);
+        final int rawHeight = (int) getResources().getDimension(R.dimen.status_bar_header_height);
+        getLayoutParams().height = disabled ? (rawHeight - mHeaderQsPanel.getHeight()) : rawHeight;
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+    }
+
+    @Override
     @VisibleForTesting
     public void onDetachedFromWindow() {
         setListening(false);
+        SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
         super.onDetachedFromWindow();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 176112b..b3244a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -305,7 +305,15 @@
             state.state = Tile.STATE_UNAVAILABLE;
             drawable = mDefaultIcon.loadDrawable(mContext);
         }
-        state.icon = new DrawableIcon(drawable);
+
+        final Drawable drawableF = drawable;
+        state.iconSupplier = () -> {
+            Drawable.ConstantState cs = drawableF.getConstantState();
+            if (cs != null) {
+                return new DrawableIcon(cs.newDrawable());
+            }
+            return null;
+        };
         state.label = mTile.getLabel();
         if (mTile.getContentDescription() != null) {
             state.contentDescription = mTile.getContentDescription();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e8c8b90..c249e37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -87,14 +87,15 @@
     }
 
     protected void updateIcon(ImageView iv, State state) {
-        if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))
+        final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
+        if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))
                 || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) {
             boolean shouldAnimate = iv.isShown() && mAnimationEnabled
                     && iv.getDrawable() != null;
-            Drawable d = state.icon != null
-                    ? shouldAnimate ? state.icon.getDrawable(mContext)
-                    : state.icon.getInvisibleDrawable(mContext) : null;
-            int padding = state.icon != null ? state.icon.getPadding() : 0;
+            Drawable d = icon != null
+                    ? shouldAnimate ? icon.getDrawable(mContext)
+                    : icon.getInvisibleDrawable(mContext) : null;
+            int padding = icon != null ? icon.getPadding() : 0;
             if (d != null) {
                 d.setAutoMirrored(false);
                 d.setLayoutDirection(getLayoutDirection());
@@ -107,7 +108,7 @@
                 iv.setImageDrawable(d);
             }
 
-            iv.setTag(R.id.qs_icon_tag, state.icon);
+            iv.setTag(R.id.qs_icon_tag, icon);
             iv.setTag(R.id.qs_slash_tag, state.slash);
             iv.setPadding(0, padding, 0, padding);
             if (d instanceof Animatable2) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 8d62f2a..0e4a9fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
@@ -34,10 +35,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.R.drawable;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -135,11 +134,9 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        BluetoothDeviceLayerDrawable drawable = createLayerDrawable(mContext,
-                                R.drawable.ic_qs_bluetooth_connected, batteryLevel,
+                        state.icon = new BluetoothBatteryTileIcon(lastDevice,
                                 mContext.getResources().getFraction(
                                         R.fraction.bt_battery_scale_fraction, 1, 1));
-                        state.icon = new DrawableIcon(drawable);
                     }
                 }
                 state.contentDescription = mContext.getString(
@@ -215,6 +212,23 @@
         return new BluetoothDetailAdapter();
     }
 
+    private class BluetoothBatteryTileIcon extends Icon {
+        private float mIconScale;
+        private CachedBluetoothDevice mDevice;
+
+        BluetoothBatteryTileIcon(CachedBluetoothDevice device, float iconScale) {
+            mIconScale = iconScale;
+            mDevice = device;
+        }
+
+        @Override
+        public Drawable getDrawable(Context context) {
+            // This method returns Pair<Drawable, String> while first value is the drawable
+            return com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription(
+                    context, mDevice, mIconScale).first;
+        }
+    }
+
     protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
         // We probably won't ever have space in the UI for more than 20 devices, so don't
         // get info for them.
@@ -285,16 +299,16 @@
                 for (CachedBluetoothDevice device : devices) {
                     if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
                     final Item item = new Item();
-                    item.icon = R.drawable.ic_qs_bluetooth_on;
+                    item.iconResId = R.drawable.ic_qs_bluetooth_on;
                     item.line1 = device.getName();
                     item.tag = device;
                     int state = device.getMaxConnectionState();
                     if (state == BluetoothProfile.STATE_CONNECTED) {
-                        item.icon = R.drawable.ic_qs_bluetooth_connected;
+                        item.iconResId = R.drawable.ic_qs_bluetooth_connected;
                         int batteryLevel = device.getBatteryLevel();
                         if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                            item.iconDrawable = createLayerDrawable(mContext, item.icon,
-                                    batteryLevel);
+                            item.icon = new BluetoothBatteryTileIcon(device,
+                                    1 /* iconScale */);
                             item.line2 = mContext.getString(
                                     R.string.quick_settings_connected_battery_level,
                                     Utils.formatPercentage(batteryLevel));
@@ -305,7 +319,7 @@
                         items.add(connectedDevices, item);
                         connectedDevices++;
                     } else if (state == BluetoothProfile.STATE_CONNECTING) {
-                        item.icon = R.drawable.ic_qs_bluetooth_connecting;
+                        item.iconResId = R.drawable.ic_qs_bluetooth_connecting;
                         item.line2 = mContext.getString(R.string.quick_settings_connecting);
                         items.add(connectedDevices, item);
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index fb396b9..678aa71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -19,26 +19,17 @@
 import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
 
 import android.app.Dialog;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.MediaRouter;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.widget.Button;
 
-import com.android.internal.app.MediaRouteChooserDialog;
-import com.android.internal.app.MediaRouteControllerDialog;
 import com.android.internal.app.MediaRouteDialogPresenter;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -280,7 +271,7 @@
                 for (CastDevice device : devices) {
                     if (device.state == CastDevice.STATE_CONNECTED) {
                         final Item item = new Item();
-                        item.icon = R.drawable.ic_qs_cast_on;
+                        item.iconResId = R.drawable.ic_qs_cast_on;
                         item.line1 = getDeviceName(device);
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
                         item.tag = device;
@@ -300,7 +291,7 @@
                         final CastDevice device = mVisibleOrder.get(id);
                         if (!devices.contains(device)) continue;
                         final Item item = new Item();
-                        item.icon = R.drawable.ic_qs_cast_off;
+                        item.iconResId = R.drawable.ic_qs_cast_off;
                         item.line1 = getDeviceName(device);
                         if (device.state == CastDevice.STATE_CONNECTING) {
                             item.line2 = mContext.getString(R.string.quick_settings_connecting);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index 2e389ba..0ce3e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -266,7 +266,7 @@
         }
 
         @Override
-        public void setNoSims(boolean show) {
+        public void setNoSims(boolean show, boolean simDetected) {
             mInfo.noSim = show;
             if (mInfo.noSim) {
                 // Make sure signal gets cleared out when no sims.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 2370273..977a725 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -402,7 +402,7 @@
                     final AccessPoint ap = mAccessPoints[i];
                     final Item item = new Item();
                     item.tag = ap;
-                    item.icon = mWifiController.getIcon(ap);
+                    item.iconResId = mWifiController.getIcon(ap);
                     item.line1 = ap.getSsid();
                     item.line2 = ap.isActive() ? ap.getSummary() : null;
                     item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 9214eef..5ae7f22c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -31,7 +31,7 @@
     void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
     void toggleRecents(int recentsGrowTarget);
     void onConfigurationChanged();
-    void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+    void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
             in Rect initialBounds);
     void onDraggingInRecents(float distanceFromTop);
     void onDraggingInRecentsEnded(float velocity);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
index cc7798e..58d8d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -31,5 +31,6 @@
     void sendRecentsDrawnEvent();
     void sendDockingTopTaskEvent(int dragMode, in Rect initialRect);
     void sendLaunchRecentsEvent();
+    void sendDockedFirstAnimationFrameEvent();
     void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 406bcac..2250e41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.app.ActivityManager;
@@ -26,14 +29,13 @@
 import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.EventLog;
@@ -50,6 +52,7 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -59,7 +62,7 @@
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 
@@ -78,23 +81,15 @@
         implements RecentsComponent, CommandQueue.Callbacks {
 
     private final static String TAG = "Recents";
-    private final static boolean DEBUG = false;
 
     public final static int EVENT_BUS_PRIORITY = 1;
     public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
-    public final static int RECENTS_GROW_TARGET_INVALID = -1;
 
     public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>();
     static {
         RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
     }
 
-    // Purely for experimentation
-    private final static String RECENTS_OVERRIDE_SYSPROP_KEY = "persist.recents_override_pkg";
-    private final static String ACTION_SHOW_RECENTS = "com.android.systemui.recents.ACTION_SHOW";
-    private final static String ACTION_HIDE_RECENTS = "com.android.systemui.recents.ACTION_HIDE";
-    private final static String ACTION_TOGGLE_RECENTS = "com.android.systemui.recents.ACTION_TOGGLE";
-
     private static final String COUNTER_WINDOW_SUPPORTED = "window_enter_supported";
     private static final String COUNTER_WINDOW_UNSUPPORTED = "window_enter_unsupported";
     private static final String COUNTER_WINDOW_INCOMPATIBLE = "window_enter_incompatible";
@@ -104,11 +99,6 @@
     private static RecentsTaskLoader sTaskLoader;
     private static RecentsConfiguration sConfiguration;
 
-    // For experiments only, allows another package to handle recents if it is defined in the system
-    // properties.  This is limited to show/toggle/hide, and does not tie into the ActivityManager,
-    // and does not reside in the home stack.
-    private String mOverrideRecentsPackageName;
-
     private Handler mHandler;
     private RecentsImpl mImpl;
     private int mDraggingInRecentsCurrentUser;
@@ -201,21 +191,23 @@
 
     @Override
     public void start() {
-        sDebugFlags = new RecentsDebugFlags(mContext);
+        final Resources res = mContext.getResources();
+        final int defaultTaskBarBackgroundColor =
+                mContext.getColor(R.color.recents_task_bar_default_background_color);
+        final int defaultTaskViewBackgroundColor =
+                mContext.getColor(R.color.recents_task_view_default_background_color);
+        sDebugFlags = new RecentsDebugFlags();
         sSystemServicesProxy = SystemServicesProxy.getInstance(mContext);
         sConfiguration = new RecentsConfiguration(mContext);
-        sTaskLoader = new RecentsTaskLoader(mContext);
+        sTaskLoader = new RecentsTaskLoader(mContext,
+                // TODO: Once we start building the AAR, move these into the loader
+                res.getInteger(R.integer.config_recents_max_thumbnail_count),
+                res.getInteger(R.integer.config_recents_max_icon_count),
+                res.getInteger(R.integer.recents_svelte_level));
+        sTaskLoader.setDefaultColors(defaultTaskBarBackgroundColor, defaultTaskViewBackgroundColor);
         mHandler = new Handler();
         mImpl = new RecentsImpl(mContext);
 
-        // Check if there is a recents override package
-        if (Build.IS_USERDEBUG || Build.IS_ENG) {
-            String cnStr = SystemProperties.get(RECENTS_OVERRIDE_SYSPROP_KEY);
-            if (!cnStr.isEmpty()) {
-                mOverrideRecentsPackageName = cnStr;
-            }
-        }
-
         // Register with the event bus
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
         EventBus.getDefault().register(sSystemServicesProxy, EVENT_BUS_PRIORITY);
@@ -254,16 +246,8 @@
             return;
         }
 
-        if (proxyToOverridePackage(ACTION_SHOW_RECENTS)) {
-            return;
-        }
-        try {
-            ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_RECENT_APPS);
-        } catch (RemoteException e) {
-        }
-
+        sSystemServicesProxy.sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
         int recentsGrowTarget = getComponent(Divider.class).getView().growsRecents();
-
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */,
@@ -298,10 +282,6 @@
             return;
         }
 
-        if (proxyToOverridePackage(ACTION_HIDE_RECENTS)) {
-            return;
-        }
-
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
@@ -333,12 +313,7 @@
             return;
         }
 
-        if (proxyToOverridePackage(ACTION_TOGGLE_RECENTS)) {
-            return;
-        }
-
         int growTarget = getComponent(Divider.class).getView().growsRecents();
-
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
             mImpl.toggleRecents(growTarget);
@@ -419,7 +394,7 @@
     }
 
     @Override
-    public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
+    public boolean splitPrimaryTask(int dragMode, int stackCreateMode, Rect initialBounds,
             int metricsDockAction) {
         // Ensure the device has been provisioned before allowing the user to interact with
         // recents
@@ -437,9 +412,12 @@
         int currentUser = sSystemServicesProxy.getCurrentUser();
         SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        final int activityType = runningTask != null
+                ? runningTask.configuration.windowConfiguration.getActivityType()
+                : ACTIVITY_TYPE_UNDEFINED;
         boolean screenPinningActive = ssp.isScreenPinningActive();
-        boolean isRunningTaskInHomeOrRecentsStack = runningTask != null &&
-                ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
+        boolean isRunningTaskInHomeOrRecentsStack =
+                activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
         if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
             logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
             if (runningTask.supportsSplitScreenMultiWindow) {
@@ -448,15 +426,16 @@
                             runningTask.topActivity.flattenToShortString());
                 }
                 if (sSystemServicesProxy.isSystemUser(currentUser)) {
-                    mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
+                    mImpl.splitPrimaryTask(runningTask.id, dragMode, stackCreateMode,
+                            initialBounds);
                 } else {
                     if (mSystemToUserCallbacks != null) {
                         IRecentsNonSystemUserCallbacks callbacks =
                                 mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
                         if (callbacks != null) {
                             try {
-                                callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,
-                                        initialBounds);
+                                callbacks.splitPrimaryTask(runningTask.id, dragMode,
+                                        stackCreateMode, initialBounds);
                             } catch (RemoteException e) {
                                 Log.e(TAG, "Callback failed", e);
                             }
@@ -628,6 +607,23 @@
         }
     }
 
+    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        int processUser = ssp.getProcessUser();
+        if (!ssp.isSystemUser(processUser)) {
+            postToSystemUser(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mUserToSystemCallbacks.sendDockedFirstAnimationFrameEvent();
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                }
+            });
+        }
+    }
+
     /**
      * Handle screen pinning request.
      */
@@ -814,21 +810,6 @@
                 (Settings.Secure.getInt(cr, Settings.Secure.USER_SETUP_COMPLETE, 0) != 0);
     }
 
-    /**
-     * Attempts to proxy the following action to the override recents package.
-     * @return whether the proxying was successful
-     */
-    private boolean proxyToOverridePackage(String action) {
-        if (mOverrideRecentsPackageName != null) {
-            Intent intent = new Intent(action);
-            intent.setPackage(mOverrideRecentsPackageName);
-            intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            mContext.sendBroadcast(intent);
-            return true;
-        }
-        return false;
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Recents");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index f545556..b75a142 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -17,7 +17,6 @@
 package com.android.systemui.recents;
 
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.TaskStackBuilder;
 import android.app.WallpaperManager;
@@ -29,10 +28,10 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
@@ -42,6 +41,7 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.content.PackageMonitor;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.keyguard.LatencyTracker;
@@ -53,7 +53,6 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
@@ -61,10 +60,10 @@
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
@@ -79,28 +78,24 @@
 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsView;
 import com.android.systemui.recents.views.SystemBarScrimViews;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.List;
 
 /**
  * The main Recents activity that is started from RecentsComponent.
@@ -114,7 +109,23 @@
     public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
     public final static int INCOMPATIBLE_APP_ALPHA_DURATION = 150;
 
-    private RecentsPackageMonitor mPackageMonitor;
+    private PackageMonitor mPackageMonitor = new PackageMonitor() {
+            @Override
+            public void onPackageRemoved(String packageName, int uid) {
+                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+            }
+
+            @Override
+            public boolean onPackageChanged(String packageName, int uid, String[] components) {
+                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+                return true;
+            }
+
+            @Override
+            public void onPackageModified(String packageName) {
+                RecentsActivity.this.onPackageChanged(packageName, getChangingUserId());
+            }
+        };
     private Handler mHandler = new Handler();
     private long mLastTabKeyEventTime;
     private boolean mFinishedOnStartup;
@@ -133,7 +144,6 @@
 
     // The trigger to automatically launch the current task
     private int mFocusTimerDuration;
-    private DozeTrigger mIterateTrigger;
     private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
 
     // Theme and colors
@@ -188,41 +198,6 @@
             } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
                 // When switching users, dismiss Recents to Home similar to screen off
                 finish();
-            } else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
-                // If the time shifts but the currentTime >= lastStackActiveTime, then that boundary
-                // is still valid.  Otherwise, we need to reset the lastStackactiveTime to the
-                // currentTime and remove the old tasks in between which would not be previously
-                // visible, but currently would be in the new currentTime
-                int currentUser = SystemServicesProxy.getInstance(RecentsActivity.this)
-                        .getCurrentUser();
-                long oldLastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
-                        Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1, currentUser);
-                if (oldLastStackActiveTime != -1) {
-                    long currentTime = System.currentTimeMillis();
-                    if (currentTime < oldLastStackActiveTime) {
-                        // We are only removing tasks that are between the new current time
-                        // and the old last stack active time, they were not visible and in the
-                        // TaskStack so we don't need to remove any associated TaskViews but we do
-                        // need to load the task id's from the system
-                        RecentsTaskLoader loader = Recents.getTaskLoader();
-                        RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(ctx);
-                        loader.preloadRawTasks(loadPlan, false /* includeFrontMostExcludedTask */);
-                        List<ActivityManager.RecentTaskInfo> tasks = loadPlan.getRawTasks();
-                        for (int i = tasks.size() - 1; i >= 0; i--) {
-                            ActivityManager.RecentTaskInfo task = tasks.get(i);
-                            if (currentTime <= task.lastActiveTime && task.lastActiveTime <
-                                    oldLastStackActiveTime) {
-                                Recents.getSystemServices().removeTask(task.persistentId);
-                            }
-                        }
-                        Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
-                                currentTime, currentUser);
-
-                        // Clear the last PiP task time, it's an edge case and we'd rather it
-                        // not relaunch the PiP task if the user double taps
-                        RecentsImpl.clearLastPipTime();
-                    }
-                }
             }
         }
     };
@@ -340,8 +315,8 @@
         EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
 
         // Initialize the package monitor
-        mPackageMonitor = new RecentsPackageMonitor();
-        mPackageMonitor.register(this);
+        mPackageMonitor.register(this, Looper.getMainLooper(), UserHandle.ALL,
+                true /* externalStorage */);
 
         // Select theme based on wallpaper colors
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
@@ -358,15 +333,11 @@
         mScrimViews = new SystemBarScrimViews(this);
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+        if (Recents.getConfiguration().isLowRamDevice) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
 
         mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
-        mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
-        mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
-            @Override
-            public void run() {
-                dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT);
-            }
-        });
 
         // Set the window background
         mRecentsView.updateBackgroundScrim(getWindow(), isInMultiWindowMode());
@@ -380,7 +351,6 @@
         // Register the broadcast receiver to handle messages when the screen is turned off
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         registerReceiver(mSystemBroadcastReceiver, filter);
 
@@ -457,22 +427,21 @@
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan loadPlan = RecentsImpl.consumeInstanceLoadPlan();
         if (loadPlan == null) {
-            loadPlan = loader.createLoadPlan(this);
+            loadPlan = new RecentsTaskLoadPlan(this);
         }
 
         // Start loading tasks according to the load plan
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!loadPlan.hasTasks()) {
-            loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
-                    !launchState.launchedFromHome && !launchState.launchedViaDockGesture);
+            loader.preloadTasks(loadPlan, launchState.launchedToTaskId);
         }
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.runningTaskId = launchState.launchedToTaskId;
         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
-        loader.loadTasks(this, loadPlan, loadOpts);
+        loader.loadTasks(loadPlan, loadOpts);
         TaskStack stack = loadPlan.getTaskStack();
         mRecentsView.onReload(stack, mIsVisible);
 
@@ -537,7 +506,6 @@
         super.onPause();
 
         mIgnoreAltTabRelease = false;
-        mIterateTrigger.stopDozing();
     }
 
     @Override
@@ -640,8 +608,7 @@
                     if (backward) {
                         EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                     } else {
-                        EventBus.getDefault().send(
-                                new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+                        EventBus.getDefault().send(new FocusNextTaskViewEvent());
                     }
                     mLastTabKeyEventTime = SystemClock.elapsedRealtime();
 
@@ -699,38 +666,10 @@
         }
     }
 
-    public final void onBusEvent(IterateRecentsEvent event) {
-        final RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-
-        // Start dozing after the recents button is clicked
-        int timerIndicatorDuration = 0;
-        if (debugFlags.isFastToggleRecentsEnabled()) {
-            timerIndicatorDuration = getResources().getInteger(
-                    R.integer.recents_subsequent_auto_advance_duration);
-
-            mIterateTrigger.setDozeDuration(timerIndicatorDuration);
-            if (!mIterateTrigger.isDozing()) {
-                mIterateTrigger.startDozing();
-            } else {
-                mIterateTrigger.poke();
-            }
-        }
-
-        // Focus the next task
-        EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
-
-        MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
-    }
-
     public final void onBusEvent(RecentsActivityStartingEvent event) {
         mRecentsStartRequested = true;
     }
 
-    public final void onBusEvent(UserInteractionEvent event) {
-        // Stop the fast-toggle dozer
-        mIterateTrigger.stopDozing();
-    }
-
     public final void onBusEvent(HideRecentsEvent event) {
         if (event.triggeredFromAltTab) {
             // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
@@ -748,15 +687,11 @@
     }
 
     public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
-        EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
         mRecentsView.invalidate();
     }
 
     public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
-        if (mRecentsView.isLastTaskLaunchedFreeform()) {
-            EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
-        }
         mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
         mRecentsView.invalidate();
     }
@@ -856,11 +791,6 @@
         MetricsLogger.count(this, "overview_screen_pinned", 1);
     }
 
-    public final void onBusEvent(DebugFlagsChangedEvent event) {
-        // Just finish recents so that we can reload the flags anew on the next instantiation
-        finish();
-    }
-
     public final void onBusEvent(StackViewScrolledEvent event) {
         // Once the user has scrolled while holding alt-tab, then we should ignore the release of
         // the key
@@ -885,14 +815,13 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
-        loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
-                false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(this);
+        loader.preloadTasks(loadPlan, -1 /* runningTaskId */);
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
         loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
-        loader.loadTasks(this, loadPlan, loadOpts);
+        loader.loadTasks(loadPlan, loadOpts);
 
         TaskStack stack = loadPlan.getTaskStack();
         int numStackTasks = stack.getStackTaskCount();
@@ -921,6 +850,11 @@
         return true;
     }
 
+    public void onPackageChanged(String packageName, int userId) {
+        Recents.getTaskLoader().onPackageChanged(packageName);
+        EventBus.getDefault().send(new PackagesChangedEvent(packageName, userId));
+    }
+
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
@@ -928,13 +862,9 @@
         Recents.getTaskLoader().dump(prefix, writer);
 
         String id = Integer.toHexString(System.identityHashCode(this));
-        long lastStackActiveTime = Settings.Secure.getLongForUser(getContentResolver(),
-                Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, -1,
-                SystemServicesProxy.getInstance(this).getCurrentUser());
 
         writer.print(prefix); writer.print(TAG);
         writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
-        writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
         writer.print(" currentTime="); writer.print(System.currentTimeMillis());
         writer.print(" [0x"); writer.print(id); writer.print("]");
         writer.println();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 5b8ed94..d2326ce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -33,7 +33,6 @@
     public boolean launchedFromPipApp;
     // Set if the next activity that quick-switch will launch is the PiP activity
     public boolean launchedWithNextPipApp;
-    public boolean launchedFromBlacklistedApp;
     public boolean launchedFromHome;
     public boolean launchedViaDragGesture;
     public boolean launchedViaDockGesture;
@@ -44,7 +43,6 @@
     public void reset() {
         launchedFromHome = false;
         launchedFromApp = false;
-        launchedFromBlacklistedApp = false;
         launchedFromPipApp = false;
         launchedWithNextPipApp = false;
         launchedToTaskId = -1;
@@ -60,18 +58,6 @@
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         if (launchedFromApp) {
-            if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
-                // If fast toggling, focus the front most task so that the next tap will launch the
-                // task
-                return numTasks - 1;
-            }
-
-            if (launchState.launchedFromBlacklistedApp) {
-                // If we are launching from a blacklisted app, focus the front most task so that the
-                // next tap will launch the task
-                return numTasks - 1;
-            }
-
             if (useGridLayout) {
                 // If coming from another app to the grid layout, focus the front most task
                 return numTasks - 1;
@@ -80,12 +66,6 @@
             // If coming from another app, focus the next task
             return Math.max(0, numTasks - 2);
         } else {
-            if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
-                // If fast toggling, defer focusing until the next tap (which will automatically
-                // focus the front most task)
-                return -1;
-            }
-
             // If coming from home, focus the front most task
             return numTasks - 1;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 5dc6f31..68df1d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -20,31 +20,31 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Rect;
 
 import android.os.SystemProperties;
 
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.DockState;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 /**
  * Represents the dock regions for each orientation.
  */
 class DockRegion {
-    public static TaskStack.DockState[] PHONE_LANDSCAPE = {
+    public static DockState[] PHONE_LANDSCAPE = {
             // We only allow docking to the left in landscape for now on small devices
-            TaskStack.DockState.LEFT
+            DockState.LEFT
     };
-    public static TaskStack.DockState[] PHONE_PORTRAIT = {
+    public static DockState[] PHONE_PORTRAIT = {
             // We only allow docking to the top for now on small devices
-            TaskStack.DockState.TOP
+            DockState.TOP
     };
-    public static TaskStack.DockState[] TABLET_LANDSCAPE = {
-            TaskStack.DockState.LEFT,
-            TaskStack.DockState.RIGHT
+    public static DockState[] TABLET_LANDSCAPE = {
+            DockState.LEFT,
+            DockState.RIGHT
     };
-    public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
+    public static DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
 }
 
 /**
@@ -56,18 +56,6 @@
     private static final int LARGE_SCREEN_MIN_DP = 600;
     private static final int XLARGE_SCREEN_MIN_DP = 720;
 
-    /** Levels of svelte in increasing severity/austerity. */
-    // No svelting.
-    public static final int SVELTE_NONE = 0;
-    // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
-    // caching thumbnails as you scroll.
-    public static final int SVELTE_LIMIT_CACHE = 1;
-    // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
-    // evict all thumbnails when hidden.
-    public static final int SVELTE_DISABLE_CACHE = 2;
-    // Disable all thumbnail loading.
-    public static final int SVELTE_DISABLE_LOADING = 3;
-
     // Launch states
     public RecentsActivityLaunchState mLaunchState = new RecentsActivityLaunchState();
 
@@ -125,7 +113,7 @@
      * Returns the preferred dock states for the current orientation.
      * @return a list of dock states for device and its orientation
      */
-    public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
+    public DockState[] getDockStatesForCurrentOrientation() {
         boolean isLandscape = mAppContext.getResources().getConfiguration().orientation ==
                 Configuration.ORIENTATION_LANDSCAPE;
         RecentsConfiguration config = Recents.getConfiguration();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 0262a09..1918593 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -16,75 +16,14 @@
 
 package com.android.systemui.recents;
 
-import android.content.Context;
-
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.tuner.TunerService;
-
-/**
- * Tunable debug flags
- */
-public class RecentsDebugFlags implements TunerService.Tunable {
+public class RecentsDebugFlags {
 
     public static class Static {
         // Enables debug drawing for the transition thumbnail
         public static final boolean EnableTransitionThumbnailDebugMode = false;
-        // This disables the bitmap and icon caches
-        public static final boolean DisableBackgroundCache = false;
-        // Enables the task affiliations
-        public static final boolean EnableAffiliatedTaskGroups = false;
-        // Enables the button above the stack
-        public static final boolean EnableStackActionButton = true;
-        // Overrides the Tuner flags and enables the timeout
-        private static final boolean EnableFastToggleTimeout = false;
-        // Overrides the Tuner flags and enables the paging via the Recents button
-        private static final boolean EnablePaging = false;
+
         // Disables enter and exit transitions for other tasks for low ram devices
         public static final boolean DisableRecentsLowRamEnterExitAnimation = false;
 
-        // Enables us to create mock recents tasks
-        public static final boolean EnableMockTasks = false;
-        // Defines the number of mock recents packages to create
-        public static final int MockTasksPackageCount = 3;
-        // Defines the number of mock recents tasks to create
-        public static final int MockTaskCount = 100;
-        // Enables the simulated task affiliations
-        public static final boolean EnableMockTaskGroups = false;
-        // Defines the number of mock task affiliations per group
-        public static final int MockTaskGroupsTaskCount = 12;
-    }
-
-    /**
-     * We read the prefs once when we start the activity, then update them as the tuner changes
-     * the flags.
-     */
-    public RecentsDebugFlags(Context context) {
-        // Register all our flags, this will also call onTuningChanged() for each key, which will
-        // initialize the current state of each flag
-    }
-
-    /**
-     * @return whether we are enabling fast toggling.
-     */
-    public boolean isFastToggleRecentsEnabled() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport() || ssp.isTouchExplorationEnabled()) {
-            return false;
-        }
-        return Static.EnableFastToggleTimeout;
-    }
-
-    /**
-     * @return whether we are enabling paging.
-     */
-    public boolean isPagingEnabled() {
-        return Static.EnablePaging;
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        EventBus.getDefault().send(new DebugFlagsChangedEvent());
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 43227ab..1533e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -16,9 +16,8 @@
 
 package com.android.systemui.recents;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.view.View.MeasureSpec;
 
 import android.app.ActivityManager;
@@ -56,7 +55,6 @@
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
@@ -72,20 +70,18 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.Task.TaskKey;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.recents.views.RecentsTransitionHelper;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
 import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewScroller;
 import com.android.systemui.recents.views.TaskViewHeader;
 import com.android.systemui.recents.views.TaskViewTransform;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
@@ -117,10 +113,10 @@
     public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
 
     /**
-     * An implementation of TaskStackListener, that allows us to listen for changes to the system
+     * An implementation of TaskStackChangeListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
      */
-    class TaskStackListenerImpl extends TaskStackListener {
+    class TaskStackListenerImpl extends TaskStackChangeListener {
 
         @Override
         public void onTaskStackChangedBackground() {
@@ -131,7 +127,7 @@
 
             // Preloads the next task
             RecentsConfiguration config = Recents.getConfiguration();
-            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+            if (config.svelteLevel == RecentsTaskLoader.SVELTE_NONE) {
                 Rect windowRect = getWindowRect(null /* windowRectOverride */);
                 if (windowRect.isEmpty()) {
                     return;
@@ -141,8 +137,8 @@
                 SystemServicesProxy ssp = Recents.getSystemServices();
                 ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
                 RecentsTaskLoader loader = Recents.getTaskLoader();
-                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+                RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+                loader.preloadTasks(plan, -1);
                 TaskStack stack = plan.getTaskStack();
                 RecentsActivityLaunchState launchState = new RecentsActivityLaunchState();
                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
@@ -168,12 +164,12 @@
                     launchOpts.onlyLoadPausedActivities = true;
                     launchOpts.loadThumbnails = true;
                 }
-                loader.loadTasks(mContext, plan, launchOpts);
+                loader.loadTasks(plan, launchOpts);
             }
         }
 
         @Override
-        public void onActivityPinned(String packageName, int userId, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             // Check this is for the right user
             if (!checkCurrentUserId(mContext, false /* debug */)) {
                 return;
@@ -207,7 +203,7 @@
             }
 
             EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId,
-                    ThumbnailData.createFromTaskSnapshot(snapshot)));
+                    new ThumbnailData(snapshot)));
         }
     }
 
@@ -282,13 +278,13 @@
         // When we start, preload the data associated with the previous recent tasks.
         // We can use a new plan since the caches will be the same.
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+        loader.preloadTasks(plan, -1);
         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
         launchOpts.numVisibleTasks = loader.getIconCacheSize();
         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
         launchOpts.onlyLoadForCache = true;
-        loader.loadTasks(mContext, plan, launchOpts);
+        loader.loadTasks(plan, launchOpts);
     }
 
     public void onConfigurationChanged() {
@@ -409,22 +405,17 @@
                 RecentsConfiguration config = Recents.getConfiguration();
                 RecentsActivityLaunchState launchState = config.getLaunchState();
                 if (!launchState.launchedWithAltTab) {
-                    // Has the user tapped quickly?
-                    boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
                     if (Recents.getConfiguration().isGridEnabled) {
+                        // Has the user tapped quickly?
+                        boolean isQuickTap = elapsedTime < ViewConfiguration.getDoubleTapTimeout();
                         if (isQuickTap) {
                             EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
                         } else {
                             EventBus.getDefault().post(new LaunchMostRecentTaskRequestEvent());
                         }
                     } else {
-                        if (!debugFlags.isPagingEnabled() || isQuickTap) {
-                            // Launch the next focused task
-                            EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
-                        } else {
-                            // Notify recents to move onto the next task
-                            EventBus.getDefault().post(new IterateRecentsEvent());
-                        }
+                        // Launch the next focused task
+                        EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
                     }
                 } else {
                     // If the user has toggled it too quickly, then just eat up the event here (it's
@@ -473,16 +464,15 @@
         // RecentsActivity) only if there is a task to animate to.  Post this to ensure that we
         // don't block the touch feedback on the nav bar button which triggers this.
         mHandler.post(() -> {
-            MutableBoolean isHomeStackVisible = new MutableBoolean(true);
-            if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
+            if (!ssp.isRecentsActivityVisible(null)) {
                 ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
                 if (runningTask == null) {
                     return;
                 }
 
                 RecentsTaskLoader loader = Recents.getTaskLoader();
-                sInstanceLoadPlan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
+                sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
+                loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
                 TaskStack stack = sInstanceLoadPlan.getTaskStack();
                 if (stack.getTaskCount() > 0) {
                     // Only preload the icon (but not the thumbnail since it may not have been taken
@@ -521,8 +511,8 @@
     public void showNextTask() {
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+        loader.preloadTasks(plan, -1);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -533,7 +523,9 @@
         if (runningTask == null) return;
 
         // Find the task in the recents list
-        boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
+        boolean isRunningTaskInHomeStack =
+                runningTask.configuration.windowConfiguration.getActivityType()
+                        == ACTIVITY_TYPE_HOME;
         ArrayList<Task> tasks = focusedStack.getStackTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
@@ -574,8 +566,8 @@
     public void showRelativeAffiliatedTask(boolean showNextTask) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(mContext);
+        loader.preloadTasks(plan, -1);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -583,52 +575,48 @@
 
         // Return early if there is no running task (can't determine affiliated tasks in this case)
         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
         if (runningTask == null) return;
         // Return early if the running task is in the home/recents stack (optimization)
-        if (isHomeOrRecentsStack(runningTask.stackId)) return;
+        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
 
         // Find the task in the recents list
         ArrayList<Task> tasks = focusedStack.getStackTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
         int taskCount = tasks.size();
-        int numAffiliatedTasks = 0;
         for (int i = 0; i < taskCount; i++) {
             Task task = tasks.get(i);
             if (task.key.id == runningTask.id) {
-                TaskGrouping group = task.group;
-                Task.TaskKey toTaskKey;
                 if (showNextTask) {
-                    toTaskKey = group.getNextTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_next_affiliated_task_target,
-                            R.anim.recents_launch_next_affiliated_task_source);
+                    if ((i + 1) < taskCount) {
+                        toTask = tasks.get(i + 1);
+                        launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                                R.anim.recents_launch_next_affiliated_task_target,
+                                R.anim.recents_launch_next_affiliated_task_source);
+                    }
                 } else {
-                    toTaskKey = group.getPrevTaskInGroup(task);
-                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
-                            R.anim.recents_launch_prev_affiliated_task_target,
-                            R.anim.recents_launch_prev_affiliated_task_source);
+                    if ((i - 1) >= 0) {
+                        toTask = tasks.get(i - 1);
+                        launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+                                R.anim.recents_launch_prev_affiliated_task_target,
+                                R.anim.recents_launch_prev_affiliated_task_source);
+                    }
                 }
-                if (toTaskKey != null) {
-                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
-                }
-                numAffiliatedTasks = group.getTaskCount();
                 break;
             }
         }
 
         // Return early if there is no next task
         if (toTask == null) {
-            if (numAffiliatedTasks > 1) {
-                if (showNextTask) {
-                    ssp.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_next_affiliated_task_bounce));
-                } else {
-                    ssp.startInPlaceAnimationOnFrontMostApplication(
-                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
-                                    R.anim.recents_launch_prev_affiliated_task_bounce));
-                }
+            if (showNextTask) {
+                ssp.startInPlaceAnimationOnFrontMostApplication(
+                        ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                R.anim.recents_launch_next_affiliated_task_bounce));
+            } else {
+                ssp.startInPlaceAnimationOnFrontMostApplication(
+                        ActivityOptions.makeCustomInPlaceAnimation(mContext,
+                                R.anim.recents_launch_prev_affiliated_task_bounce));
             }
             return;
         }
@@ -653,13 +641,13 @@
         showRelativeAffiliatedTask(false);
     }
 
-    public void dockTopTask(int topTaskId, int dragMode,
-            int stackCreateMode, Rect initialBounds) {
+    public void splitPrimaryTask(int taskId, int dragMode, int stackCreateMode,
+            Rect initialBounds) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Make sure we inform DividerView before we actually start the activity so we can change
         // the resize mode already.
-        if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
+        if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
             showRecents(
                     false /* triggeredFromAltTab */,
@@ -750,8 +738,7 @@
             stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
                     systemInsets.left, systemInsets.right, mTmpBounds);
             stackLayout.reset();
-            stackLayout.initialize(displayRect, windowRect, mTmpBounds,
-                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
+            stackLayout.initialize(displayRect, windowRect, mTmpBounds);
         }
     }
 
@@ -840,7 +827,7 @@
         launchOpts.runningTaskId = runningTaskId;
         launchOpts.loadThumbnails = false;
         launchOpts.onlyLoadForCache = true;
-        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+        Recents.getTaskLoader().loadTasks(sInstanceLoadPlan, launchOpts);
     }
 
     /**
@@ -870,59 +857,29 @@
             getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
                     Rect windowOverrideRect) {
         final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
-        if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
-            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
-            ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
-            TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
-            TaskStackViewScroller stackScroller = mDummyStackView.getScroller();
 
-            mDummyStackView.updateLayoutAlgorithm(true /* boundScroll */);
-            mDummyStackView.updateToInitialState();
+        // Update the destination rect
+        Task toTask = new Task();
+        TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
+                windowOverrideRect);
 
-            for (int i = tasks.size() - 1; i >= 0; i--) {
-                Task task = tasks.get(i);
-                if (task.isFreeformTask()) {
-                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
-                            stackScroller.getStackScroll(), mTmpTransform, null,
-                            windowOverrideRect);
-                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform);
-                    Rect toTaskRect = new Rect();
-                    mTmpTransform.rect.round(toTaskRect);
-                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
-                }
-            }
-            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
-            specs.toArray(specsArray);
+        RectF toTaskRect = toTransform.rect;
+        AppTransitionAnimationSpecsFuture future =
+                new RecentsTransitionHelper(mContext).getAppTransitionFuture(
+                        () -> {
+                    Rect rect = new Rect();
+                    toTaskRect.round(rect);
+                    GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
+                            toTransform);
+                    return Lists.newArrayList(new AppTransitionAnimationSpec(
+                            toTask.key.id, thumbnail, rect));
+                });
 
-            // For low end ram devices, wait for transition flag is reset when Recents entrance
-            // animation is complete instead of when the transition animation starts
-            return new Pair<>(ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    specsArray, mHandler, isLowRamDevice ? null : mResetToggleFlagListener, this),
-                    null);
-        } else {
-            // Update the destination rect
-            Task toTask = new Task();
-            TaskViewTransform toTransform = getThumbnailTransitionTransform(mDummyStackView, toTask,
-                    windowOverrideRect);
-
-            RectF toTaskRect = toTransform.rect;
-            AppTransitionAnimationSpecsFuture future =
-                    new RecentsTransitionHelper(mContext).getAppTransitionFuture(
-                            () -> {
-                        Rect rect = new Rect();
-                        toTaskRect.round(rect);
-                        GraphicBuffer thumbnail = drawThumbnailTransitionBitmap(toTask,
-                                toTransform);
-                        return Lists.newArrayList(new AppTransitionAnimationSpec(
-                                toTask.key.id, thumbnail, rect));
-                    });
-
-            // For low end ram devices, wait for transition flag is reset when Recents entrance
-            // animation is complete instead of when the transition animation starts
-            return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
-                    mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
-                    false /* scaleUp */), future);
-        }
+        // For low end ram devices, wait for transition flag is reset when Recents entrance
+        // animation is complete instead of when the transition animation starts
+        return new Pair<>(ActivityOptions.makeMultiThumbFutureAspectScaleAnimation(mContext,
+                mHandler, future.getFuture(), isLowRamDevice ? null : mResetToggleFlagListener,
+                false /* scaleUp */), future);
     }
 
     /**
@@ -937,7 +894,7 @@
             runningTaskOut.copyFrom(launchTask);
         } else {
             // If no task is specified or we can not find the task just use the front most one
-            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
+            launchTask = stack.getStackFrontMostTask();
             runningTaskOut.copyFrom(launchTask);
         }
 
@@ -990,12 +947,8 @@
             boolean isHomeStackVisible, boolean animate, int growTarget) {
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        boolean isBlacklisted = (runningTask != null)
-                ? ssp.isBlackListedActivity(runningTask.baseActivity.getClassName())
-                : false;
 
-        int runningTaskId = !mLaunchedWhileDocking && !isBlacklisted && (runningTask != null)
+        int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
                 ? runningTask.id
                 : -1;
 
@@ -1004,10 +957,10 @@
         // the stacks might have changed.
         if (mLaunchedWhileDocking || mTriggeredFromAltTab || sInstanceLoadPlan == null) {
             // Create a new load plan if preloadRecents() was never triggered
-            sInstanceLoadPlan = loader.createLoadPlan(mContext);
+            sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
         }
         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
+            loader.preloadTasks(sInstanceLoadPlan, runningTaskId);
         }
 
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -1018,7 +971,6 @@
         // Update the launch state that we need in updateHeaderBarLayout()
         launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
         launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
-        launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
         launchState.launchedFromPipApp = false;
         launchState.launchedWithNextPipApp =
                 stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
@@ -1054,9 +1006,7 @@
         }
 
         Pair<ActivityOptions, AppTransitionAnimationSpecsFuture> pair;
-        if (isBlacklisted) {
-            pair = new Pair<>(getUnknownTransitionActivityOptions(), null);
-        } else if (useThumbnailTransition) {
+        if (useThumbnailTransition) {
             // Try starting with a thumbnail transition
             pair = getThumbnailTransitionActivityOptions(runningTask, windowOverrideRect);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
index ff9e89e9..9493c78 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImplProxy.java
@@ -90,7 +90,7 @@
     }
 
     @Override
-    public void dockTopTask(int topTaskId, int dragMode, int stackCreateMode,
+    public void splitPrimaryTask(int topTaskId, int dragMode, int stackCreateMode,
             Rect initialBounds) throws RemoteException {
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = topTaskId;
@@ -144,7 +144,7 @@
                     break;
                 case MSG_DOCK_TOP_TASK:
                     args = (SomeArgs) msg.obj;
-                    mImpl.dockTopTask(args.argi1, args.argi2, args.argi3 = 0,
+                    mImpl.splitPrimaryTask(args.argi1, args.argi2, args.argi3 = 0,
                             (Rect) args.arg1);
                     break;
                 case MSG_ON_DRAGGING_IN_RECENTS:
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
index 1285626..ff1f7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -27,6 +27,7 @@
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
@@ -108,6 +109,11 @@
     }
 
     @Override
+    public void sendDockedFirstAnimationFrameEvent() throws RemoteException {
+        EventBus.getDefault().post(new DockedFirstAnimationFrameEvent());
+    }
+
+    @Override
     public void setWaitingForTransitionStartEvent(boolean waitingForTransitionStart) {
         EventBus.getDefault().post(new SetWaitingForTransitionStartEvent(
                 waitingForTransitionStart));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
index 7604de1..fec34e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/CancelEnterRecentsWindowAnimationEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * This is sent when we want to cancel the enter-recents window animation for the launch task.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
deleted file mode 100644
index fe3bf26..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/DebugFlagsChangedEvent.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the SystemUI tuner changes a flag.
- */
-public class DebugFlagsChangedEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
index e02fb14..e4a4f59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -22,5 +22,15 @@
  * This is sent when the stack action button should be hidden.
  */
 public class HideStackActionButtonEvent extends EventBus.Event {
-    // Simple event
+
+    // Whether or not to translate the stack action button when hiding it
+    public final boolean translate;
+
+    public HideStackActionButtonEvent() {
+        this(true);
+    }
+
+    public HideStackActionButtonEvent(boolean translate) {
+        this.translate = translate;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
deleted file mode 100644
index f7b2706..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/IterateRecentsEvent.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2015 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.recents.events.activity;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent when the user taps on the Overview button to iterate to the next item in the
- * Recents list.
- */
-public class IterateRecentsEvent extends EventBus.Event {
-    // Simple event
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 862a1ee..2409f39 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -22,7 +22,7 @@
 import android.graphics.Rect;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
index 64eeafa..e4972b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/MultiWindowStateChangedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 /**
  * This is sent by the activity whenever the multi-window state has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
index 3b68574..47670e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
@@ -17,22 +17,20 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.RecentsActivity;
 
 /**
- * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes.
+ * This event is sent by {@link RecentsActivity} when a package on the the system changes.
  * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
  * packages.
  */
 public class PackagesChangedEvent extends EventBus.Event {
 
-    public final RecentsPackageMonitor monitor;
     public final String packageName;
     public final int userId;
 
-    public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) {
-        this.monitor = monitor;
+    public PackagesChangedEvent(String packageName, int userId) {
         this.packageName = packageName;
         this.userId = userId;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
index 0d614e8c..51d02b5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/TaskStackUpdatedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.activity;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 /**
  * This is sent by the activity whenever the task stach has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
index 4ed0270..b52e83b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DeleteTaskDataEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * This is sent when the data associated with a given {@link Task} should be deleted from the
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
index 40c30b8..da19384 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/ShowApplicationInfoEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 
 /**
  * This is sent when a user wants to show the application info for a {@link Task}.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
index e0ed7a9..f082928 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskSnapshotChangedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 /**
  * Sent when a task snapshot has changed.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
index 0628c50..881a64a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/TaskViewDismissedEvent.java
@@ -17,8 +17,8 @@
 package com.android.systemui.recents.events.ui;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.AnimationProps;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
deleted file mode 100644
index b42da9c..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 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.recents.events.ui;
-
-import com.android.systemui.recents.events.EventBus;
-
-/**
- * This is sent to update the visibility of all visible freeform task views.
- */
-public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
-
-    public final boolean visible;
-
-    public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
-        this.visible = visible;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
index 216be61..cf61b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDropTargetChangedEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.DropTarget;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
index edd7995..297afc5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndCancelledEvent.java
@@ -17,9 +17,8 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.DropTarget;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 73c282f..73cbde9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.DropTarget;
 import com.android.systemui.recents.views.TaskView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index e57fa2d..021be77 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -19,7 +19,7 @@
 import android.graphics.Point;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskView;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
index 7030729..64ba574 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartInitializeDropTargetsEvent.java
@@ -17,7 +17,7 @@
 package com.android.systemui.recents.events.ui.dragndrop;
 
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.RecentsViewTouchHandler;
 import com.android.systemui.recents.views.TaskView;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
index a1e4957..171ab5e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/focus/FocusNextTaskViewEvent.java
@@ -22,10 +22,5 @@
  * Focuses the next task view in the stack.
  */
 public class FocusNextTaskViewEvent extends EventBus.Event {
-
-    public final int timerIndicatorDuration;
-
-    public FocusNextTaskViewEvent(int timerIndicatorDuration) {
-        this.timerIndicatorDuration = timerIndicatorDuration;
-    }
+    // Simple event
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java b/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
deleted file mode 100644
index ec3c39c..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/NamedCounter.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-/**
- * Used to generate successive incremented names.
- */
-public class NamedCounter {
-
-    int mCount;
-    String mPrefix = "";
-    String mSuffix = "";
-
-    public NamedCounter(String prefix, String suffix) {
-        mPrefix = prefix;
-        mSuffix = suffix;
-    }
-
-    /** Returns the next name. */
-    public String nextName() {
-        String name = mPrefix + mCount + mSuffix;
-        mCount++;
-        return name;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
deleted file mode 100644
index 72511de..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 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.recents.misc;
-
-import android.animation.TypeEvaluator;
-import android.graphics.RectF;
-
-/**
- * This evaluator can be used to perform type interpolation between <code>RectF</code> values.
- */
-public class RectFEvaluator implements TypeEvaluator<RectF> {
-
-    private RectF mRect = new RectF();
-
-    /**
-     * This function returns the result of linearly interpolating the start and
-     * end Rect values, with <code>fraction</code> representing the proportion
-     * between the start and end values. The calculation is a simple parametric
-     * calculation on each of the separate components in the Rect objects
-     * (left, top, right, and bottom).
-     *
-     * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p>
-     *
-     * @param fraction   The fraction from the starting to the ending values
-     * @param startValue The start Rect
-     * @param endValue   The end Rect
-     * @return A linear interpolation between the start and end values, given the
-     *         <code>fraction</code> parameter.
-     */
-    @Override
-    public RectF evaluate(float fraction, RectF startValue, RectF endValue) {
-        float left = startValue.left + ((endValue.left - startValue.left) * fraction);
-        float top = startValue.top + ((endValue.top - startValue.top) * fraction);
-        float right = startValue.right + ((endValue.right - startValue.right) * fraction);
-        float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction);
-        mRect.set(left, top, right, bottom);
-        return mRect;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 5a5251e..c4ac52b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,37 +16,29 @@
 
 package com.android.systemui.recents.misc;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
-import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
-import android.app.KeyguardManager;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -56,24 +48,18 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.IRemoteCallback;
-import android.os.Message;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.provider.Settings.Secure;
 import android.service.dreams.DreamService;
 import android.service.dreams.IDreamManager;
-import android.util.ArraySet;
-import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.MutableBoolean;
 import android.view.Display;
@@ -90,19 +76,12 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
-import com.android.systemui.pip.tv.PipMenuActivity;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Random;
 
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
@@ -118,42 +97,32 @@
         sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
     }
 
-    final static List<String> sRecentsBlacklist;
-    static {
-        sRecentsBlacklist = new ArrayList<>();
-        sRecentsBlacklist.add(PipMenuActivity.class.getName());
-    }
-
     private static SystemServicesProxy sSystemServicesProxy;
 
     AccessibilityManager mAccm;
     ActivityManager mAm;
     IActivityManager mIam;
     PackageManager mPm;
-    IconDrawableFactory mDrawableFactory;
     IPackageManager mIpm;
     private final IDreamManager mDreamManager;
     private final Context mContext;
     AssistUtils mAssistUtils;
     WindowManager mWm;
     IWindowManager mIwm;
-    KeyguardManager mKgm;
     UserManager mUm;
     Display mDisplay;
     String mRecentsPackage;
-    ComponentName mAssistComponent;
+    private TaskStackChangeListeners mTaskStackChangeListeners;
     private int mCurrentUserId;
 
     boolean mIsSafeMode;
-    boolean mHasFreeformWorkspaceSupport;
 
-    Bitmap mDummyIcon;
     int mDummyThumbnailWidth;
     int mDummyThumbnailHeight;
     Paint mBgProtectionPaint;
     Canvas mBgProtectionCanvas;
 
-    private final Handler mHandler = new H();
+    private final Handler mHandler = new Handler();
     private final Runnable mGcRunnable = new Runnable() {
         @Override
         public void run() {
@@ -164,143 +133,10 @@
 
     private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
 
-    /**
-     * An abstract class to track task stack changes.
-     * Classes should implement this instead of {@link android.app.ITaskStackListener}
-     * to reduce IPC calls from system services. These callbacks will be called on the main thread.
-     */
-    public abstract static class TaskStackListener {
-        /**
-         * NOTE: This call is made of the thread that the binder call comes in on.
-         */
-        public void onTaskStackChangedBackground() { }
-        public void onTaskStackChanged() { }
-        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
-        public void onActivityPinned(String packageName, int userId, int taskId) { }
-        public void onActivityUnpinned() { }
-        public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
-        public void onPinnedStackAnimationStarted() { }
-        public void onPinnedStackAnimationEnded() { }
-        public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
-        public void onActivityDismissingDockedStack() { }
-        public void onActivityLaunchOnSecondaryDisplayFailed() { }
-        public void onTaskProfileLocked(int taskId, int userId) { }
-
-        /**
-         * Checks that the current user matches the user's SystemUI process. Since
-         * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
-         * TaskStackListener should make this call to verify that we don't act on events from other
-         * user's processes.
-         */
-        protected final boolean checkCurrentUserId(Context context, boolean debug) {
-            int processUserId = UserHandle.myUserId();
-            int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
-            if (processUserId != currentUserId) {
-                if (debug) {
-                    Log.d(TAG, "UID mismatch. SystemUI is running uid=" + processUserId
-                            + " and the current user is uid=" + currentUserId);
-                }
-                return false;
-            }
-            return true;
-        }
-    }
-
-    /**
-     * Implementation of {@link android.app.ITaskStackListener} to listen task stack changes from
-     * ActivityManagerService.
-     * This simply passes callbacks to listeners through {@link H}.
-     * */
-    private android.app.TaskStackListener mTaskStackListener = new android.app.TaskStackListener() {
-
-        private final List<SystemServicesProxy.TaskStackListener> mTmpListeners = new ArrayList<>();
-
-        @Override
-        public void onTaskStackChanged() throws RemoteException {
-            // Call the task changed callback for the non-ui thread listeners first
-            synchronized (mTaskStackListeners) {
-                mTmpListeners.clear();
-                mTmpListeners.addAll(mTaskStackListeners);
-            }
-            for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
-                mTmpListeners.get(i).onTaskStackChangedBackground();
-            }
-
-            mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
-            mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
-        }
-
-        @Override
-        public void onActivityPinned(String packageName, int userId, int taskId)
-                throws RemoteException {
-            mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
-            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, userId, taskId, packageName).sendToTarget();
-        }
-
-        @Override
-        public void onActivityUnpinned() throws RemoteException {
-            mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
-        }
-
-        @Override
-        public void onPinnedActivityRestartAttempt(boolean clearedTask)
-                throws RemoteException{
-            mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
-            mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
-                    .sendToTarget();
-        }
-
-        @Override
-        public void onPinnedStackAnimationStarted() throws RemoteException {
-            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
-            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
-        }
-
-        @Override
-        public void onPinnedStackAnimationEnded() throws RemoteException {
-            mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
-            mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
-        }
-
-        @Override
-        public void onActivityForcedResizable(String packageName, int taskId, int reason)
-                throws RemoteException {
-            mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
-                    .sendToTarget();
-        }
-
-        @Override
-        public void onActivityDismissingDockedStack() throws RemoteException {
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
-        }
-
-        @Override
-        public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
-            mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
-        }
-
-        @Override
-        public void onTaskProfileLocked(int taskId, int userId) {
-            mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
-        }
-
-        @Override
-        public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
-                throws RemoteException {
-            mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
-        }
-    };
-
     private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
             (String name, Drawable picture, String userAccount) ->
                     mCurrentUserId = mAm.getCurrentUser();
 
-    /**
-     * List of {@link TaskStackListener} registered from {@link #registerTaskStackListener}.
-     */
-    private List<TaskStackListener> mTaskStackListeners = new ArrayList<>();
-
     /** Private constructor */
     private SystemServicesProxy(Context context) {
         mContext = context.getApplicationContext();
@@ -308,23 +144,18 @@
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
         mIam = ActivityManager.getService();
         mPm = context.getPackageManager();
-        mDrawableFactory = IconDrawableFactory.newInstance(context);
         mIpm = AppGlobals.getPackageManager();
         mAssistUtils = new AssistUtils(context);
         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mIwm = WindowManagerGlobal.getWindowManagerService();
-        mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
         mUm = UserManager.get(context);
         mDreamManager = IDreamManager.Stub.asInterface(
                 ServiceManager.checkService(DreamService.DREAM_SERVICE));
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
-        mHasFreeformWorkspaceSupport =
-                mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT) ||
-                        Settings.Global.getInt(context.getContentResolver(),
-                                DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
         mIsSafeMode = mPm.isSafeMode();
         mCurrentUserId = mAm.getCurrentUser();
+        mTaskStackChangeListeners = new TaskStackChangeListeners(Looper.getMainLooper());
 
         // Get the dummy thumbnail width/heights
         Resources res = context.getResources();
@@ -339,23 +170,11 @@
         mBgProtectionPaint.setColor(0xFFffffff);
         mBgProtectionCanvas = new Canvas();
 
-        // Resolve the assist intent
-        mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId());
-
         // Since SystemServicesProxy can be accessed from a per-SysUI process component, create a
         // per-process listener to keep track of the current user id to reduce the number of binder
         // calls to fetch it.
         UserInfoController userInfoController = Dependency.get(UserInfoController.class);
         userInfoController.addCallback(mOnUserInfoChangedListener);
-
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            // Create a dummy icon
-            mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
-            mDummyIcon.eraseColor(0xFF999999);
-        }
-
-        Collections.addAll(sRecentsBlacklist,
-                res.getStringArray(R.array.recents_blacklist_array));
     }
 
     /**
@@ -377,127 +196,21 @@
     }
 
     /**
-     * @return whether the provided {@param className} is blacklisted
-     */
-    public boolean isBlackListedActivity(String className) {
-        return sRecentsBlacklist.contains(className);
-    }
-
-    /**
-     * Returns a list of the recents tasks.
-     *
-     * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
-     *                                     will be visible, otherwise no excluded tasks will be
-     *                                     visible.
-     */
-    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
-            boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
-        if (mAm == null) return null;
-
-        // If we are mocking, then create some recent tasks
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            ArrayList<ActivityManager.RecentTaskInfo> tasks =
-                    new ArrayList<ActivityManager.RecentTaskInfo>();
-            int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount);
-            for (int i = 0; i < count; i++) {
-                // Create a dummy component name
-                int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount;
-                ComponentName cn = new ComponentName("com.android.test" + packageIndex,
-                        "com.android.test" + i + ".Activity");
-                String description = "" + i + " - " +
-                        Long.toString(Math.abs(new Random().nextLong()), 36);
-                // Create the recent task info
-                ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
-                rti.id = rti.persistentId = rti.affiliatedTaskId = i;
-                rti.baseIntent = new Intent();
-                rti.baseIntent.setComponent(cn);
-                rti.description = description;
-                rti.firstActiveTime = rti.lastActiveTime = i;
-                if (i % 2 == 0) {
-                    rti.taskDescription = new ActivityManager.TaskDescription(description,
-                            Bitmap.createBitmap(mDummyIcon), null,
-                            0xFF000000 | (0xFFFFFF & new Random().nextInt()),
-                            0xFF000000 | (0xFFFFFF & new Random().nextInt()),
-                            0, 0);
-                } else {
-                    rti.taskDescription = new ActivityManager.TaskDescription();
-                }
-                tasks.add(rti);
-            }
-            return tasks;
-        }
-
-        // Remove home/recents/excluded tasks
-        int minNumTasksToQuery = 10;
-        int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
-        int flags = ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS |
-                ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
-                ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
-                ActivityManager.RECENT_IGNORE_UNAVAILABLE |
-                ActivityManager.RECENT_INCLUDE_PROFILES;
-        if (includeFrontMostExcludedTask) {
-            flags |= ActivityManager.RECENT_WITH_EXCLUDED;
-        }
-        List<ActivityManager.RecentTaskInfo> tasks = null;
-        try {
-            tasks = mAm.getRecentTasksForUser(numTasksToQuery, flags, userId);
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to get recent tasks", e);
-        }
-
-        // Break early if we can't get a valid set of tasks
-        if (tasks == null) {
-            return new ArrayList<>();
-        }
-
-        boolean isFirstValidTask = true;
-        Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
-        while (iter.hasNext()) {
-            ActivityManager.RecentTaskInfo t = iter.next();
-
-            // NOTE: The order of these checks happens in the expected order of the traversal of the
-            // tasks
-
-            // Remove the task if it or it's package are blacklsited
-            if (sRecentsBlacklist.contains(t.realActivity.getClassName()) ||
-                    sRecentsBlacklist.contains(t.realActivity.getPackageName())) {
-                iter.remove();
-                continue;
-            }
-
-            // Remove the task if it is marked as excluded, unless it is the first most task and we
-            // are requested to include it
-            boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-                    == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
-            isExcluded |= quietProfileIds.contains(t.userId);
-            if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
-                iter.remove();
-            }
-
-            isFirstValidTask = false;
-        }
-
-        return tasks.subList(0, Math.min(tasks.size(), numLatestTasks));
-    }
-
-    /**
      * Returns the top running task.
      */
     public ActivityManager.RunningTaskInfo getRunningTask() {
         // Note: The set of running tasks from the system is ordered by recency
-        List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
-        if (tasks != null && !tasks.isEmpty()) {
-            // Find the first task in a valid stack, we ignore everything from the Recents and PiP
-            // stacks
-            for (int i = 0; i < tasks.size(); i++) {
-                ActivityManager.RunningTaskInfo task = tasks.get(i);
-                int stackId = task.stackId;
-                if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
-                    return task;
-                }
+        try {
+            List<ActivityManager.RunningTaskInfo> tasks = mIam.getFilteredTasks(1,
+                    ACTIVITY_TYPE_RECENTS /* ignoreActivityType */,
+                    WINDOWING_MODE_PINNED /* ignoreWindowingMode */);
+            if (tasks.isEmpty()) {
+                return null;
             }
+            return tasks.get(0);
+        } catch (RemoteException e) {
+            return null;
         }
-        return null;
     }
 
     /**
@@ -522,12 +235,17 @@
             ActivityManager.StackInfo fullscreenStackInfo = null;
             ActivityManager.StackInfo recentsStackInfo = null;
             for (int i = 0; i < stackInfos.size(); i++) {
-                StackInfo stackInfo = stackInfos.get(i);
-                if (stackInfo.stackId == HOME_STACK_ID) {
+                final StackInfo stackInfo = stackInfos.get(i);
+                final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
+                final int activityType = winConfig.getActivityType();
+                final int windowingMode = winConfig.getWindowingMode();
+                if (activityType == ACTIVITY_TYPE_HOME) {
                     homeStackInfo = stackInfo;
-                } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                } else if (activityType == ACTIVITY_TYPE_STANDARD
+                        && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                     fullscreenStackInfo = stackInfo;
-                } else if (stackInfo.stackId == RECENTS_STACK_ID) {
+                } else if (activityType == ACTIVITY_TYPE_RECENTS) {
                     recentsStackInfo = stackInfo;
                 }
             }
@@ -561,13 +279,6 @@
     }
 
     /**
-     * Returns whether this device has freeform workspaces.
-     */
-    public boolean hasFreeformWorkspaceSupport() {
-        return mHasFreeformWorkspaceSupport;
-    }
-
-    /**
      * Returns whether this device is in the safe mode.
      */
     public boolean isInSafeMode() {
@@ -580,7 +291,7 @@
 
         try {
             final ActivityOptions options = ActivityOptions.makeBasic();
-            options.setDockCreateMode(createMode);
+            options.setSplitScreenCreateMode(createMode);
             options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             mIam.startActivityFromRecents(taskId, options.toBundle());
             return true;
@@ -590,14 +301,15 @@
         return false;
     }
 
-    /** Docks an already resumed task to the side of the screen. */
-    public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
+    /** Moves an already resumed task to the side of the screen to initiate split screen. */
+    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
+            Rect initialBounds) {
         if (mIam == null) {
             return false;
         }
 
         try {
-            return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
+            return mIam.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
                     false /* animate */, initialBounds);
         } catch (RemoteException e) {
             e.printStackTrace();
@@ -606,34 +318,6 @@
     }
 
     /**
-     * Returns whether the given stack id is the home stack id.
-     */
-    public static boolean isHomeStack(int stackId) {
-        return stackId == HOME_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the pinned stack id.
-     */
-    public static boolean isPinnedStack(int stackId){
-        return stackId == PINNED_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the docked stack id.
-     */
-    public static boolean isDockedStack(int stackId) {
-        return stackId == DOCKED_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the freeform workspace stack id.
-     */
-    public static boolean isFreeformStack(int stackId) {
-        return stackId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    /**
      * @return whether there are any docked tasks for the current user.
      */
     public boolean hasDockedTask() {
@@ -641,7 +325,8 @@
 
         ActivityManager.StackInfo stackInfo = null;
         try {
-            stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
+            stackInfo =
+                    mIam.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -662,7 +347,7 @@
      */
     public boolean hasSoftNavigationBar() {
         try {
-            return WindowManagerGlobal.getWindowManagerService().hasNavigationBar();
+            return mIwm.hasNavigationBar();
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -705,51 +390,12 @@
         }
     }
 
-    /** Returns the top task thumbnail for the given task id */
-    public ThumbnailData getTaskThumbnail(int taskId, boolean reduced) {
-        if (mAm == null) return null;
-
-        // If we are mocking, then just return a dummy thumbnail
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            ThumbnailData thumbnailData = new ThumbnailData();
-            thumbnailData.thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth,
-                    mDummyThumbnailHeight, Bitmap.Config.ARGB_8888);
-            thumbnailData.thumbnail.eraseColor(0xff333333);
-            return thumbnailData;
-        }
-
-        return getThumbnail(taskId, reduced);
-    }
-
-    /**
-     * Returns a task thumbnail from the activity manager
-     */
-    public @NonNull ThumbnailData getThumbnail(int taskId, boolean reducedResolution) {
-        if (mAm == null) {
-            return new ThumbnailData();
-        }
-
-        ActivityManager.TaskSnapshot snapshot = null;
-        try {
-            snapshot = ActivityManager.getService().getTaskSnapshot(taskId, reducedResolution);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to retrieve snapshot", e);
-        }
-        if (snapshot != null) {
-            return ThumbnailData.createFromTaskSnapshot(snapshot);
-        } else {
-            return new ThumbnailData();
-        }
-    }
-
-    /**
-     * Moves a task into another stack.
-     */
-    public void moveTaskToStack(int taskId, int stackId) {
+    /** Set the task's windowing mode. */
+    public void setTaskWindowingMode(int taskId, int windowingMode) {
         if (mIam == null) return;
 
         try {
-            mIam.positionTaskInStack(taskId, stackId, 0);
+            mIam.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
         } catch (RemoteException | IllegalArgumentException e) {
             e.printStackTrace();
         }
@@ -758,11 +404,14 @@
     /** Removes the task */
     public void removeTask(final int taskId) {
         if (mAm == null) return;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return;
 
         // Remove the task.
         mUiOffloadThread.submit(() -> {
-            mAm.removeTask(taskId);
+            try {
+                mIam.removeTask(taskId);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
         });
     }
 
@@ -778,145 +427,6 @@
         });
     }
 
-    /**
-     * Returns the activity info for a given component name.
-     *
-     * @param cn The component name of the activity.
-     * @param userId The userId of the user that this is for.
-     */
-    public ActivityInfo getActivityInfo(ComponentName cn, int userId) {
-        if (mIpm == null) return null;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
-        try {
-            return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId);
-        } catch (RemoteException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Returns the activity info for a given component name.
-     *
-     * @param cn The component name of the activity.
-     */
-    public ActivityInfo getActivityInfo(ComponentName cn) {
-        if (mPm == null) return null;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo();
-
-        try {
-            return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA);
-        } catch (PackageManager.NameNotFoundException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Returns the activity label, badging if necessary.
-     */
-    public String getBadgedActivityLabel(ActivityInfo info, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return "Recent Task: " + userId;
-        }
-
-        return getBadgedLabel(info.loadLabel(mPm).toString(), userId);
-    }
-
-    /**
-     * Returns the application label, badging if necessary.
-     */
-    public String getBadgedApplicationLabel(ApplicationInfo appInfo, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return "Recent Task App: " + userId;
-        }
-
-        return getBadgedLabel(appInfo.loadLabel(mPm).toString(), userId);
-    }
-
-    /**
-     * Returns the content description for a given task, badging it if necessary.  The content
-     * description joins the app and activity labels.
-     */
-    public String getBadgedContentDescription(ActivityInfo info, int userId,
-            ActivityManager.TaskDescription td, Resources res) {
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return "Recent Task Content Description: " + userId;
-        }
-
-        String activityLabel;
-        if (td != null && td.getLabel() != null) {
-            activityLabel = td.getLabel();
-        } else {
-            activityLabel = info.loadLabel(mPm).toString();
-        }
-        String applicationLabel = info.applicationInfo.loadLabel(mPm).toString();
-        String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId);
-        return applicationLabel.equals(activityLabel) ? badgedApplicationLabel
-                : res.getString(R.string.accessibility_recents_task_header,
-                        badgedApplicationLabel, activityLabel);
-    }
-
-    /**
-     * Returns the activity icon for the ActivityInfo for a user, badging if
-     * necessary.
-     */
-    public Drawable getBadgedActivityIcon(ActivityInfo info, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        return mDrawableFactory.getBadgedIcon(info, info.applicationInfo, userId);
-    }
-
-    /**
-     * Returns the application icon for the ApplicationInfo for a user, badging if
-     * necessary.
-     */
-    public Drawable getBadgedApplicationIcon(ApplicationInfo appInfo, int userId) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        return mDrawableFactory.getBadgedIcon(appInfo, userId);
-    }
-
-    /**
-     * Returns the task description icon, loading and badging it if it necessary.
-     */
-    public Drawable getBadgedTaskDescriptionIcon(ActivityManager.TaskDescription taskDescription,
-            int userId, Resources res) {
-
-        // If we are mocking, then return a mock label
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        Bitmap tdIcon = taskDescription.getInMemoryIcon();
-        if (tdIcon == null) {
-            tdIcon = ActivityManager.TaskDescription.loadTaskDescriptionIcon(
-                    taskDescription.getIconFilename(), userId);
-        }
-        if (tdIcon != null) {
-            return getBadgedIcon(new BitmapDrawable(res, tdIcon), userId);
-        }
-        return null;
-    }
-
     public ActivityManager.TaskDescription getTaskDescription(int taskId) {
         try {
             return mIam.getTaskDescription(taskId);
@@ -926,85 +436,6 @@
     }
 
     /**
-     * Returns the given icon for a user, badging if necessary.
-     */
-    private Drawable getBadgedIcon(Drawable icon, int userId) {
-        if (userId != UserHandle.myUserId()) {
-            icon = mPm.getUserBadgedIcon(icon, new UserHandle(userId));
-        }
-        return icon;
-    }
-
-    /**
-     * Returns a banner used on TV for the specified Activity.
-     */
-    public Drawable getActivityBanner(ActivityInfo info) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock banner
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        Drawable banner = info.loadBanner(mPm);
-        return banner;
-    }
-
-    /**
-     * Returns a logo used on TV for the specified Activity.
-     */
-    public Drawable getActivityLogo(ActivityInfo info) {
-        if (mPm == null) return null;
-
-        // If we are mocking, then return a mock logo
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            return new ColorDrawable(0xFF666666);
-        }
-
-        Drawable logo = info.loadLogo(mPm);
-        return logo;
-    }
-
-
-    /**
-     * Returns the given label for a user, badging if necessary.
-     */
-    private String getBadgedLabel(String label, int userId) {
-        if (userId != UserHandle.myUserId()) {
-            label = mPm.getUserBadgedLabel(label, new UserHandle(userId)).toString();
-        }
-        return label;
-    }
-
-    /**
-     * Returns whether the provided {@param userId} is currently locked (and showing Keyguard).
-     */
-    public boolean isDeviceLocked(int userId) {
-        if (mKgm == null) {
-            return false;
-        }
-        return mKgm.isDeviceLocked(userId);
-    }
-
-    /** Returns the package name of the home activity. */
-    public String getHomeActivityPackageName() {
-        if (mPm == null) return null;
-        if (RecentsDebugFlags.Static.EnableMockTasks) return null;
-
-        ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
-        ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities);
-        if (defaultHomeActivity != null) {
-            return defaultHomeActivity.getPackageName();
-        } else if (homeActivities.size() == 1) {
-            ResolveInfo info = homeActivities.get(0);
-            if (info.activityInfo != null) {
-                return info.activityInfo.packageName;
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns whether the provided {@param userId} represents the system user.
      */
     public boolean isSystemUser(int userId) {
@@ -1106,9 +537,10 @@
 
         try {
             // Use the recents stack bounds, fallback to fullscreen stack if it is null
-            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID);
+            ActivityManager.StackInfo stackInfo =
+                    mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
             if (stackInfo == null) {
-                stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
+                stackInfo = mIam.getStackInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
             }
             if (stackInfo != null) {
                 windowRect.set(stackInfo.bounds);
@@ -1139,7 +571,7 @@
         if (mIam == null) {
             return;
         }
-        if (taskKey.stackId == DOCKED_STACK_ID) {
+        if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // We show non-visible docked tasks in Recents, but we always want to launch
             // them in the fullscreen stack.
             if (options == null) {
@@ -1191,19 +623,11 @@
      * Registers a task stack listener with the system.
      * This should be called on the main thread.
      */
-    public void registerTaskStackListener(TaskStackListener listener) {
+    public void registerTaskStackListener(TaskStackChangeListener listener) {
         if (mIam == null) return;
 
-        synchronized (mTaskStackListeners) {
-            mTaskStackListeners.add(listener);
-            if (mTaskStackListeners.size() == 1) {
-                // Register mTaskStackListener to IActivityManager only once if needed.
-                try {
-                    mIam.registerTaskStackListener(mTaskStackListener);
-                } catch (Exception e) {
-                    Log.w(TAG, "Failed to call registerTaskStackListener", e);
-                }
-            }
+        synchronized (mTaskStackChangeListeners) {
+            mTaskStackChangeListeners.addListener(mIam, listener);
         }
     }
 
@@ -1212,7 +636,7 @@
             return;
         }
         try {
-            WindowManagerGlobal.getWindowManagerService().endProlongedAnimations();
+            mIwm.endProlongedAnimations();
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -1222,7 +646,7 @@
         if (mWm == null) return;
 
         try {
-            WindowManagerGlobal.getWindowManagerService().registerDockedStackListener(listener);
+            mIwm.registerDockedStackListener(listener);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -1249,8 +673,7 @@
         if (mWm == null) return;
 
         try {
-            WindowManagerGlobal.getWindowManagerService().getStableInsets(Display.DEFAULT_DISPLAY,
-                    outStableInsets);
+            mIwm.getStableInsets(Display.DEFAULT_DISPLAY, outStableInsets);
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -1260,9 +683,7 @@
             IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
             boolean scaleUp) {
         try {
-            WindowManagerGlobal.getWindowManagerService()
-                    .overridePendingAppTransitionMultiThumbFuture(future, animStartedListener,
-                            scaleUp);
+            mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to override transition: " + e);
         }
@@ -1271,23 +692,27 @@
     /**
      * Updates the visibility of recents.
      */
-    public void setRecentsVisibility(boolean visible) {
-        try {
-            mIwm.setRecentsVisibility(visible);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to reach window manager", e);
-        }
+    public void setRecentsVisibility(final boolean visible) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mIwm.setRecentsVisibility(visible);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to reach window manager", e);
+            }
+        });
     }
 
     /**
      * Updates the visibility of the picture-in-picture.
      */
-    public void setPipVisibility(boolean visible) {
-        try {
-            mIwm.setPipVisibility(visible);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to reach window manager", e);
-        }
+    public void setPipVisibility(final boolean visible) {
+        mUiOffloadThread.submit(() -> {
+            try {
+                mIwm.setPipVisibility(visible);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to reach window manager", e);
+            }
+        });
     }
 
     public boolean isDreaming() {
@@ -1309,111 +734,7 @@
         });
     }
 
-    public void updateOverviewLastStackActiveTimeAsync(long newLastStackActiveTime,
-            int currentUserId) {
-        mUiOffloadThread.submit(() -> {
-            Settings.Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, newLastStackActiveTime, currentUserId);
-        });
-    }
-
     public interface StartActivityFromRecentsResultListener {
         void onStartActivityResult(boolean succeeded);
     }
-
-    private final class H extends Handler {
-        private static final int ON_TASK_STACK_CHANGED = 1;
-        private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
-        private static final int ON_ACTIVITY_PINNED = 3;
-        private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
-        private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
-        private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
-        private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
-        private static final int ON_TASK_PROFILE_LOCKED = 8;
-        private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
-        private static final int ON_ACTIVITY_UNPINNED = 10;
-        private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mTaskStackListeners) {
-                switch (msg.what) {
-                    case ON_TASK_STACK_CHANGED: {
-                    Trace.beginSection("onTaskStackChanged");
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskStackChanged();
-                        }
-                    Trace.endSection();
-                        break;
-                    }
-                    case ON_TASK_SNAPSHOT_CHANGED: {
-                    Trace.beginSection("onTaskSnapshotChanged");
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
-                                    (TaskSnapshot) msg.obj);
-                        }
-                    Trace.endSection();
-                        break;
-                    }
-                    case ON_ACTIVITY_PINNED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1,
-                                    msg.arg2);
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_UNPINNED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityUnpinned();
-                        }
-                        break;
-                    }
-                    case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
-                                    msg.arg1 != 0);
-                        }
-                        break;
-                    }
-                    case ON_PINNED_STACK_ANIMATION_STARTED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
-                        }
-                        break;
-                    }
-                    case ON_PINNED_STACK_ANIMATION_ENDED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_FORCED_RESIZABLE: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityForcedResizable(
-                                    (String) msg.obj, msg.arg1, msg.arg2);
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityDismissingDockedStack();
-                        }
-                        break;
-                    }
-                    case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
-                        }
-                        break;
-                    }
-                    case ON_TASK_PROFILE_LOCKED: {
-                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
-                        }
-                        break;
-                    }
-                }
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java b/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java
new file mode 100644
index 0000000..6d0952a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListener.java
@@ -0,0 +1,65 @@
+/*
+ * 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.recents.misc;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * An abstract class to track task stack changes.
+ * Classes should implement this instead of {@link android.app.ITaskStackListener}
+ * to reduce IPC calls from system services. These callbacks will be called on the main thread.
+ */
+public abstract class TaskStackChangeListener {
+
+    /**
+     * NOTE: This call is made of the thread that the binder call comes in on.
+     */
+    public void onTaskStackChangedBackground() { }
+    public void onTaskStackChanged() { }
+    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
+    public void onActivityUnpinned() { }
+    public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
+    public void onPinnedStackAnimationStarted() { }
+    public void onPinnedStackAnimationEnded() { }
+    public void onActivityForcedResizable(String packageName, int taskId, int reason) { }
+    public void onActivityDismissingDockedStack() { }
+    public void onActivityLaunchOnSecondaryDisplayFailed() { }
+    public void onTaskProfileLocked(int taskId, int userId) { }
+
+    /**
+     * Checks that the current user matches the user's SystemUI process. Since
+     * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
+     * TaskStackChangeListener should make this call to verify that we don't act on events from other
+     * user's processes.
+     */
+    protected final boolean checkCurrentUserId(Context context, boolean debug) {
+        int processUserId = UserHandle.myUserId();
+        int currentUserId = SystemServicesProxy.getInstance(context).getCurrentUser();
+        if (processUserId != currentUserId) {
+            if (debug) {
+                Log.d(SystemServicesProxy.TAG, "UID mismatch. SystemUI is running uid=" + processUserId
+                        + " and the current user is uid=" + currentUserId);
+            }
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java b/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java
new file mode 100644
index 0000000..8eb70f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/TaskStackChangeListeners.java
@@ -0,0 +1,254 @@
+/*
+ * 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.recents.misc;
+
+import android.app.ActivityManager.TaskSnapshot;
+import android.app.IActivityManager;
+import android.app.TaskStackListener;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.Trace;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks all the task stack listeners
+ */
+public class TaskStackChangeListeners extends TaskStackListener {
+
+    private static final String TAG = TaskStackChangeListeners.class.getSimpleName();
+
+    /**
+     * List of {@link TaskStackChangeListener} registered from {@link #addListener}.
+     */
+    private final List<TaskStackChangeListener> mTaskStackListeners = new ArrayList<>();
+    private final List<TaskStackChangeListener> mTmpListeners = new ArrayList<>();
+
+    private final Handler mHandler;
+
+    public TaskStackChangeListeners(Looper looper) {
+        mHandler = new H(looper);
+    }
+
+    public void addListener(IActivityManager am, TaskStackChangeListener listener) {
+        mTaskStackListeners.add(listener);
+        if (mTaskStackListeners.size() == 1) {
+            // Register mTaskStackListener to IActivityManager only once if needed.
+            try {
+                am.registerTaskStackListener(this);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to call registerTaskStackListener", e);
+            }
+        }
+    }
+
+    @Override
+    public void onTaskStackChanged() throws RemoteException {
+        // Call the task changed callback for the non-ui thread listeners first
+        synchronized (mTaskStackListeners) {
+            mTmpListeners.clear();
+            mTmpListeners.addAll(mTaskStackListeners);
+        }
+        for (int i = mTmpListeners.size() - 1; i >= 0; i--) {
+            mTmpListeners.get(i).onTaskStackChangedBackground();
+        }
+
+        mHandler.removeMessages(H.ON_TASK_STACK_CHANGED);
+        mHandler.sendEmptyMessage(H.ON_TASK_STACK_CHANGED);
+    }
+
+    @Override
+    public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
+            throws RemoteException {
+        mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
+        mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
+                new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
+    }
+
+    @Override
+    public void onActivityUnpinned() throws RemoteException {
+        mHandler.removeMessages(H.ON_ACTIVITY_UNPINNED);
+        mHandler.sendEmptyMessage(H.ON_ACTIVITY_UNPINNED);
+    }
+
+    @Override
+    public void onPinnedActivityRestartAttempt(boolean clearedTask)
+            throws RemoteException{
+        mHandler.removeMessages(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT);
+        mHandler.obtainMessage(H.ON_PINNED_ACTIVITY_RESTART_ATTEMPT, clearedTask ? 1 : 0, 0)
+                .sendToTarget();
+    }
+
+    @Override
+    public void onPinnedStackAnimationStarted() throws RemoteException {
+        mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
+        mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
+    }
+
+    @Override
+    public void onPinnedStackAnimationEnded() throws RemoteException {
+        mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
+        mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
+    }
+
+    @Override
+    public void onActivityForcedResizable(String packageName, int taskId, int reason)
+            throws RemoteException {
+        mHandler.obtainMessage(H.ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName)
+                .sendToTarget();
+    }
+
+    @Override
+    public void onActivityDismissingDockedStack() throws RemoteException {
+        mHandler.sendEmptyMessage(H.ON_ACTIVITY_DISMISSING_DOCKED_STACK);
+    }
+
+    @Override
+    public void onActivityLaunchOnSecondaryDisplayFailed() throws RemoteException {
+        mHandler.sendEmptyMessage(H.ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED);
+    }
+
+    @Override
+    public void onTaskProfileLocked(int taskId, int userId) throws RemoteException {
+        mHandler.obtainMessage(H.ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget();
+    }
+
+    @Override
+    public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot)
+            throws RemoteException {
+        mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
+    }
+
+    private final class H extends Handler {
+        private static final int ON_TASK_STACK_CHANGED = 1;
+        private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
+        private static final int ON_ACTIVITY_PINNED = 3;
+        private static final int ON_PINNED_ACTIVITY_RESTART_ATTEMPT = 4;
+        private static final int ON_PINNED_STACK_ANIMATION_ENDED = 5;
+        private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
+        private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
+        private static final int ON_TASK_PROFILE_LOCKED = 8;
+        private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
+        private static final int ON_ACTIVITY_UNPINNED = 10;
+        private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (mTaskStackListeners) {
+                switch (msg.what) {
+                    case ON_TASK_STACK_CHANGED: {
+                        Trace.beginSection("onTaskStackChanged");
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onTaskStackChanged();
+                        }
+                        Trace.endSection();
+                        break;
+                    }
+                    case ON_TASK_SNAPSHOT_CHANGED: {
+                        Trace.beginSection("onTaskSnapshotChanged");
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1,
+                                    (TaskSnapshot) msg.obj);
+                        }
+                        Trace.endSection();
+                        break;
+                    }
+                    case ON_ACTIVITY_PINNED: {
+                        final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityPinned(
+                                    info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_UNPINNED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityUnpinned();
+                        }
+                        break;
+                    }
+                    case ON_PINNED_ACTIVITY_RESTART_ATTEMPT: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onPinnedActivityRestartAttempt(
+                                    msg.arg1 != 0);
+                        }
+                        break;
+                    }
+                    case ON_PINNED_STACK_ANIMATION_STARTED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
+                        }
+                        break;
+                    }
+                    case ON_PINNED_STACK_ANIMATION_ENDED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_FORCED_RESIZABLE: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityForcedResizable(
+                                    (String) msg.obj, msg.arg1, msg.arg2);
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_DISMISSING_DOCKED_STACK: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityDismissingDockedStack();
+                        }
+                        break;
+                    }
+                    case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onActivityLaunchOnSecondaryDisplayFailed();
+                        }
+                        break;
+                    }
+                    case ON_TASK_PROFILE_LOCKED: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private static class PinnedActivityInfo {
+        final String mPackageName;
+        final int mUserId;
+        final int mTaskId;
+        final int mStackId;
+
+        PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) {
+            mPackageName = packageName;
+            mUserId = userId;
+            mTaskId = taskId;
+            mStackId = stackId;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
deleted file mode 100644
index 4349e30..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.misc;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.RectEvaluator;
-import android.annotation.FloatRange;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Trace;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.Property;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.ViewStub;
-
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.TaskViewTransform;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/* Common code */
-public class Utilities {
-
-    public static final Property<Drawable, Integer> DRAWABLE_ALPHA =
-            new IntProperty<Drawable>("drawableAlpha") {
-                @Override
-                public void setValue(Drawable object, int alpha) {
-                    object.setAlpha(alpha);
-                }
-
-                @Override
-                public Integer get(Drawable object) {
-                    return object.getAlpha();
-                }
-            };
-
-    public static final Property<Drawable, Rect> DRAWABLE_RECT =
-            new Property<Drawable, Rect>(Rect.class, "drawableBounds") {
-                @Override
-                public void set(Drawable object, Rect bounds) {
-                    object.setBounds(bounds);
-                }
-
-                @Override
-                public Rect get(Drawable object) {
-                    return object.getBounds();
-                }
-            };
-
-    public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
-    public static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect());
-    public static final Rect EMPTY_RECT = new Rect();
-
-    /**
-     * @return the first parent walking up the view hierarchy that has the given class type.
-     *
-     * @param parentClass must be a class derived from {@link View}
-     */
-    public static <T extends View> T findParent(View v, Class<T> parentClass) {
-        ViewParent parent = v.getParent();
-        while (parent != null) {
-            if (parentClass.isAssignableFrom(parent.getClass())) {
-                return (T) parent;
-            }
-            parent = parent.getParent();
-        }
-        return null;
-    }
-
-    /**
-     * Initializes the {@param setOut} with the given object.
-     */
-    public static <T> ArraySet<T> objectToSet(T obj, ArraySet<T> setOut) {
-        setOut.clear();
-        if (obj != null) {
-            setOut.add(obj);
-        }
-        return setOut;
-    }
-
-    /**
-     * Replaces the contents of {@param setOut} with the contents of the {@param array}.
-     */
-    public static <T> ArraySet<T> arrayToSet(T[] array, ArraySet<T> setOut) {
-        setOut.clear();
-        if (array != null) {
-            Collections.addAll(setOut, array);
-        }
-        return setOut;
-    }
-
-    /**
-     * @return the clamped {@param value} between the provided {@param min} and {@param max}.
-     */
-    public static float clamp(float value, float min, float max) {
-        return Math.max(min, Math.min(max, value));
-    }
-
-    /**
-     * @return the clamped {@param value} between the provided {@param min} and {@param max}.
-     */
-    public static int clamp(int value, int min, int max) {
-        return Math.max(min, Math.min(max, value));
-    }
-
-    /**
-     * @return the clamped {@param value} between 0 and 1.
-     */
-    public static float clamp01(float value) {
-        return Math.max(0f, Math.min(1f, value));
-    }
-
-    /**
-     * Scales the {@param value} to be proportionally between the {@param min} and
-     * {@param max} values.
-     *
-     * @param value must be between 0 and 1
-     */
-    public static float mapRange(@FloatRange(from=0.0,to=1.0) float value, float min, float max) {
-        return min + (value * (max - min));
-    }
-
-    /**
-     * Scales the {@param value} proportionally from {@param min} and {@param max} to 0 and 1.
-     *
-     * @param value must be between {@param min} and {@param max}
-     */
-    public static float unmapRange(float value, float min, float max) {
-        return (value - min) / (max - min);
-    }
-
-    /** Scales a rect about its centroid */
-    public static void scaleRectAboutCenter(RectF r, float scale) {
-        if (scale != 1.0f) {
-            float cx = r.centerX();
-            float cy = r.centerY();
-            r.offset(-cx, -cy);
-            r.left *= scale;
-            r.top *= scale;
-            r.right *= scale;
-            r.bottom *= scale;
-            r.offset(cx, cy);
-        }
-    }
-
-    /** Calculates the constrast between two colors, using the algorithm provided by the WCAG v2. */
-    public static float computeContrastBetweenColors(int bg, int fg) {
-        float bgR = Color.red(bg) / 255f;
-        float bgG = Color.green(bg) / 255f;
-        float bgB = Color.blue(bg) / 255f;
-        bgR = (bgR < 0.03928f) ? bgR / 12.92f : (float) Math.pow((bgR + 0.055f) / 1.055f, 2.4f);
-        bgG = (bgG < 0.03928f) ? bgG / 12.92f : (float) Math.pow((bgG + 0.055f) / 1.055f, 2.4f);
-        bgB = (bgB < 0.03928f) ? bgB / 12.92f : (float) Math.pow((bgB + 0.055f) / 1.055f, 2.4f);
-        float bgL = 0.2126f * bgR + 0.7152f * bgG + 0.0722f * bgB;
-        
-        float fgR = Color.red(fg) / 255f;
-        float fgG = Color.green(fg) / 255f;
-        float fgB = Color.blue(fg) / 255f;
-        fgR = (fgR < 0.03928f) ? fgR / 12.92f : (float) Math.pow((fgR + 0.055f) / 1.055f, 2.4f);
-        fgG = (fgG < 0.03928f) ? fgG / 12.92f : (float) Math.pow((fgG + 0.055f) / 1.055f, 2.4f);
-        fgB = (fgB < 0.03928f) ? fgB / 12.92f : (float) Math.pow((fgB + 0.055f) / 1.055f, 2.4f);
-        float fgL = 0.2126f * fgR + 0.7152f * fgG + 0.0722f * fgB;
-
-        return Math.abs((fgL + 0.05f) / (bgL + 0.05f));
-    }
-
-    /** Returns the base color overlaid with another overlay color with a specified alpha. */
-    public static int getColorWithOverlay(int baseColor, int overlayColor, float overlayAlpha) {
-        return Color.rgb(
-            (int) (overlayAlpha * Color.red(baseColor) +
-                    (1f - overlayAlpha) * Color.red(overlayColor)),
-            (int) (overlayAlpha * Color.green(baseColor) +
-                    (1f - overlayAlpha) * Color.green(overlayColor)),
-            (int) (overlayAlpha * Color.blue(baseColor) +
-                    (1f - overlayAlpha) * Color.blue(overlayColor)));
-    }
-
-    /**
-     * Cancels an animation ensuring that if it has listeners, onCancel and onEnd
-     * are not called.
-     */
-    public static void cancelAnimationWithoutCallbacks(Animator animator) {
-        if (animator != null && animator.isStarted()) {
-            removeAnimationListenersRecursive(animator);
-            animator.cancel();
-        }
-    }
-
-    /**
-     * Recursively removes all the listeners of all children of this animator
-     */
-    public static void removeAnimationListenersRecursive(Animator animator) {
-        if (animator instanceof AnimatorSet) {
-            ArrayList<Animator> animators = ((AnimatorSet) animator).getChildAnimations();
-            for (int i = animators.size() - 1; i >= 0; i--) {
-                removeAnimationListenersRecursive(animators.get(i));
-            }
-        }
-        animator.removeAllListeners();
-    }
-
-    /**
-     * Sets the given {@link View}'s frame from its current translation.
-     */
-    public static void setViewFrameFromTranslation(View v) {
-        RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
-        taskViewRect.offset(v.getTranslationX(), v.getTranslationY());
-        v.setTranslationX(0);
-        v.setTranslationY(0);
-        v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top,
-                (int) taskViewRect.right, (int) taskViewRect.bottom);
-    }
-
-    /**
-     * Returns a view stub for the given view id.
-     */
-    public static ViewStub findViewStubById(View v, int stubId) {
-        return (ViewStub) v.findViewById(stubId);
-    }
-
-    /**
-     * Returns a view stub for the given view id.
-     */
-    public static ViewStub findViewStubById(Activity a, int stubId) {
-        return (ViewStub) a.findViewById(stubId);
-    }
-
-    /**
-     * Updates {@param transforms} to be the same size as {@param tasks}.
-     */
-    public static void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
-        // We can reuse the task transforms where possible to reduce object allocation
-        int taskTransformCount = transforms.size();
-        int taskCount = tasks.size();
-        if (taskTransformCount < taskCount) {
-            // If there are less transforms than tasks, then add as many transforms as necessary
-            for (int i = taskTransformCount; i < taskCount; i++) {
-                transforms.add(new TaskViewTransform());
-            }
-        } else if (taskTransformCount > taskCount) {
-            // If there are more transforms than tasks, then just subset the transform list
-            transforms.subList(taskCount, taskTransformCount).clear();
-        }
-    }
-
-    /**
-     * Used for debugging, converts DP to PX.
-     */
-    public static float dpToPx(Resources res, float dp) {
-        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, res.getDisplayMetrics());
-    }
-
-    /**
-     * Adds a trace event for debugging.
-     */
-    public static void addTraceEvent(String event) {
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW, event);
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-    }
-
-    /**
-     * Returns whether this view, or one of its descendants have accessibility focus.
-     */
-    public static boolean isDescendentAccessibilityFocused(View v) {
-        if (v.isAccessibilityFocused()) {
-            return true;
-        }
-
-        if (v instanceof ViewGroup) {
-            ViewGroup vg = (ViewGroup) v;
-            int childCount = vg.getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                if (isDescendentAccessibilityFocused(vg.getChildAt(i))) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns the application configuration, which is independent of the activity's current
-     * configuration in multiwindow.
-     */
-    public static Configuration getAppConfiguration(Context context) {
-        return context.getApplicationContext().getResources().getConfiguration();
-    }
-
-    /**
-     * Returns a lightweight dump of a rect.
-     */
-    public static String dumpRect(Rect r) {
-        if (r == null) {
-            return "N:0,0-0,0";
-        }
-        return r.left + "," + r.top + "-" + r.right + "," + r.bottom;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
deleted file mode 100644
index 48fa6c3..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ /dev/null
@@ -1,235 +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.recents.model;
-
-import static android.os.Process.setThreadPriority;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task.TaskCallbacks;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-
-/**
- * Loader class that loads full-resolution thumbnails when appropriate.
- */
-public class HighResThumbnailLoader implements TaskCallbacks {
-
-    @GuardedBy("mLoadQueue")
-    private final ArrayDeque<Task> mLoadQueue = new ArrayDeque<>();
-    @GuardedBy("mLoadQueue")
-    private final ArraySet<Task> mLoadingTasks = new ArraySet<>();
-    @GuardedBy("mLoadQueue")
-    private boolean mLoaderIdling;
-
-    private final ArrayList<Task> mVisibleTasks = new ArrayList<>();
-    private final Thread mLoadThread;
-    private final Handler mMainThreadHandler;
-    private final SystemServicesProxy mSystemServicesProxy;
-    private final boolean mIsLowRamDevice;
-    private boolean mLoading;
-    private boolean mVisible;
-    private boolean mFlingingFast;
-    private boolean mTaskLoadQueueIdle;
-
-    public HighResThumbnailLoader(SystemServicesProxy ssp, Looper looper, boolean isLowRamDevice) {
-        mMainThreadHandler = new Handler(looper);
-        mLoadThread = new Thread(mLoader, "Recents-HighResThumbnailLoader");
-        mLoadThread.start();
-        mSystemServicesProxy = ssp;
-        mIsLowRamDevice = isLowRamDevice;
-    }
-
-    public void setVisible(boolean visible) {
-        if (mIsLowRamDevice) {
-            return;
-        }
-        mVisible = visible;
-        updateLoading();
-    }
-
-    public void setFlingingFast(boolean flingingFast) {
-        if (mFlingingFast == flingingFast || mIsLowRamDevice) {
-            return;
-        }
-        mFlingingFast = flingingFast;
-        updateLoading();
-    }
-
-    /**
-     * Sets whether the other task load queue is idling. Avoid double-loading bitmaps by not
-     * starting this queue until the other queue is idling.
-     */
-    public void setTaskLoadQueueIdle(boolean idle) {
-        if (mIsLowRamDevice) {
-            return;
-        }
-        mTaskLoadQueueIdle = idle;
-        updateLoading();
-    }
-
-    @VisibleForTesting
-    boolean isLoading() {
-        return mLoading;
-    }
-
-    private void updateLoading() {
-        setLoading(mVisible && !mFlingingFast && mTaskLoadQueueIdle);
-    }
-
-    private void setLoading(boolean loading) {
-        if (loading == mLoading) {
-            return;
-        }
-        synchronized (mLoadQueue) {
-            mLoading = loading;
-            if (!loading) {
-                stopLoading();
-            } else {
-                startLoading();
-            }
-        }
-    }
-
-    @GuardedBy("mLoadQueue")
-    private void startLoading() {
-        for (int i = mVisibleTasks.size() - 1; i >= 0; i--) {
-            Task t = mVisibleTasks.get(i);
-            if ((t.thumbnail == null || t.thumbnail.reducedResolution)
-                    && !mLoadQueue.contains(t) && !mLoadingTasks.contains(t)) {
-                mLoadQueue.add(t);
-            }
-        }
-        mLoadQueue.notifyAll();
-    }
-
-    @GuardedBy("mLoadQueue")
-    private void stopLoading() {
-        mLoadQueue.clear();
-        mLoadQueue.notifyAll();
-    }
-
-    /**
-     * Needs to be called when a task becomes visible. Note that this is different from
-     * {@link TaskCallbacks#onTaskDataLoaded} as this method should only be called once when it
-     * becomes visible, whereas onTaskDataLoaded can be called multiple times whenever some data
-     * has been updated.
-     */
-    public void onTaskVisible(Task t) {
-        t.addCallback(this);
-        mVisibleTasks.add(t);
-        if ((t.thumbnail == null || t.thumbnail.reducedResolution) && mLoading) {
-            synchronized (mLoadQueue) {
-                mLoadQueue.add(t);
-                mLoadQueue.notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Needs to be called when a task becomes visible. See {@link #onTaskVisible} why this is
-     * different from {@link TaskCallbacks#onTaskDataUnloaded()}
-     */
-    public void onTaskInvisible(Task t) {
-        t.removeCallback(this);
-        mVisibleTasks.remove(t);
-        synchronized (mLoadQueue) {
-            mLoadQueue.remove(t);
-        }
-    }
-
-    @VisibleForTesting
-    void waitForLoaderIdle() {
-        while (true) {
-            synchronized (mLoadQueue) {
-                if (mLoadQueue.isEmpty() && mLoaderIdling) {
-                    return;
-                }
-            }
-            SystemClock.sleep(100);
-        }
-    }
-
-    @Override
-    public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
-        if (thumbnailData != null && !thumbnailData.reducedResolution) {
-            synchronized (mLoadQueue) {
-                mLoadQueue.remove(task);
-            }
-        }
-    }
-
-    @Override
-    public void onTaskDataUnloaded() {
-    }
-
-    @Override
-    public void onTaskStackIdChanged() {
-    }
-
-    private final Runnable mLoader = new Runnable() {
-
-        @Override
-        public void run() {
-            setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND + 1);
-            while (true) {
-                Task next = null;
-                synchronized (mLoadQueue) {
-                    if (!mLoading || mLoadQueue.isEmpty()) {
-                        try {
-                            mLoaderIdling = true;
-                            mLoadQueue.wait();
-                            mLoaderIdling = false;
-                        } catch (InterruptedException e) {
-                            // Don't care.
-                        }
-                    } else {
-                        next = mLoadQueue.poll();
-                        if (next != null) {
-                            mLoadingTasks.add(next);
-                        }
-                    }
-                }
-                if (next != null) {
-                    loadTask(next);
-                }
-            }
-        }
-
-        private void loadTask(Task t) {
-            ThumbnailData thumbnail = mSystemServicesProxy.getTaskThumbnail(t.key.id,
-                    false /* reducedResolution */);
-            mMainThreadHandler.post(() -> {
-                synchronized (mLoadQueue) {
-                    mLoadingTasks.remove(t);
-                }
-                if (mVisibleTasks.contains(t)) {
-                    t.notifyTaskDataLoaded(thumbnail, t.icon);
-                }
-            });
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
deleted file mode 100644
index 308cece..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.ForegroundThread;
-
-/**
- * The package monitor listens for changes from PackageManager to update the contents of the
- * Recents list.
- */
-public class RecentsPackageMonitor extends PackageMonitor {
-
-    /** Registers the broadcast receivers with the specified callbacks. */
-    public void register(Context context) {
-        try {
-            // We register for events from all users, but will cross-reference them with
-            // packages for the current user and any profiles they have.  Ensure that events are
-            // handled in a background thread.
-            register(context, ForegroundThread.get().getLooper(), UserHandle.ALL, true);
-        } catch (IllegalStateException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /** Unregisters the broadcast receivers. */
-    @Override
-    public void unregister() {
-        try {
-            super.unregister();
-        } catch (IllegalStateException e) {
-            e.printStackTrace();
-        }
-    }
-
-    @Override
-    public void onPackageRemoved(String packageName, int uid) {
-        // Notify callbacks on the main thread that a package has changed
-        final int eventUserId = getChangingUserId();
-        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
-    }
-
-    @Override
-    public boolean onPackageChanged(String packageName, int uid, String[] components) {
-        onPackageModified(packageName);
-        return true;
-    }
-
-    @Override
-    public void onPackageModified(String packageName) {
-        // Notify callbacks on the main thread that a package has changed
-        final int eventUserId = getChangingUserId();
-        EventBus.getDefault().post(new PackagesChangedEvent(this, packageName, eventUserId));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
deleted file mode 100644
index 8d31730..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.util.ArraySet;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
-import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-
-/**
- * This class stores the loading state as it goes through multiple stages of loading:
- *   1) preloadRawTasks() will load the raw set of recents tasks from the system
- *   2) preloadPlan() will construct a new task stack with all metadata and only icons and
- *      thumbnails that are currently in the cache
- *   3) executePlan() will actually load and fill in the icons and thumbnails according to the load
- *      options specified, such that we can transition into the Recents activity seamlessly
- */
-public class RecentsTaskLoadPlan {
-
-    private static int MIN_NUM_TASKS = 5;
-    private static int SESSION_BEGIN_TIME = 1000 /* ms/s */ * 60 /* s/min */ * 60 /* min/hr */ *
-            6 /* hrs */;
-
-    /** The set of conditions to load tasks. */
-    public static class Options {
-        public int runningTaskId = -1;
-        public boolean loadIcons = true;
-        public boolean loadThumbnails = false;
-        public boolean onlyLoadForCache = false;
-        public boolean onlyLoadPausedActivities = false;
-        public int numVisibleTasks = 0;
-        public int numVisibleTaskThumbnails = 0;
-    }
-
-    Context mContext;
-
-    int mPreloadedUserId;
-    List<ActivityManager.RecentTaskInfo> mRawTasks;
-    TaskStack mStack;
-    ArraySet<Integer> mCurrentQuietProfiles = new ArraySet<Integer>();
-
-    /** Package level ctor */
-    RecentsTaskLoadPlan(Context context) {
-        mContext = context;
-    }
-
-    private void updateCurrentQuietProfilesCache(int currentUserId) {
-        mCurrentQuietProfiles.clear();
-
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        List<UserInfo> profiles = userManager.getProfiles(currentUserId);
-        if (profiles != null) {
-            for (int i = 0; i < profiles.size(); i++) {
-                UserInfo user  = profiles.get(i);
-                if (user.isManagedProfile() && user.isQuietModeEnabled()) {
-                    mCurrentQuietProfiles.add(user.id);
-                }
-            }
-        }
-    }
-
-    /**
-     * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
-     * to most-recent order.
-     *
-     * Note: Do not lock, callers should synchronize on the loader before making this call.
-     */
-    void preloadRawTasks(boolean includeFrontMostExcludedTask) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        int currentUserId = ssp.getCurrentUser();
-        updateCurrentQuietProfilesCache(currentUserId);
-        mPreloadedUserId = currentUserId;
-        mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
-                currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
-
-        // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
-        Collections.reverse(mRawTasks);
-    }
-
-    /**
-     * Preloads the list of recent tasks from the system. After this call, the TaskStack will
-     * have a list of all the recent tasks with their metadata, not including icons or
-     * thumbnails which were not cached and have to be loaded.
-     *
-     * The tasks will be ordered by:
-     * - least-recent to most-recent stack tasks
-     * - least-recent to most-recent freeform tasks
-     *
-     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
-     * this call (callers should synchronize on the loader before making this call).
-     */
-    void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
-            boolean includeFrontMostExcludedTask) {
-        Resources res = mContext.getResources();
-        ArrayList<Task> allTasks = new ArrayList<>();
-        if (mRawTasks == null) {
-            preloadRawTasks(includeFrontMostExcludedTask);
-        }
-
-        SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
-        SparseIntArray affiliatedTaskCounts = new SparseIntArray();
-        SparseBooleanArray lockedUsers = new SparseBooleanArray();
-        String dismissDescFormat = mContext.getString(
-                R.string.accessibility_recents_item_will_be_dismissed);
-        String appInfoDescFormat = mContext.getString(
-                R.string.accessibility_recents_item_open_app_info);
-        int currentUserId = mPreloadedUserId;
-        long legacyLastStackActiveTime = migrateLegacyLastStackActiveTime(currentUserId);
-        long lastStackActiveTime = Settings.Secure.getLongForUser(mContext.getContentResolver(),
-                Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, legacyLastStackActiveTime, currentUserId);
-        if (RecentsDebugFlags.Static.EnableMockTasks) {
-            lastStackActiveTime = 0;
-        }
-        long newLastStackActiveTime = -1;
-        int taskCount = mRawTasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
-
-            // Compose the task key
-            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
-                    t.userId, t.firstActiveTime, t.lastActiveTime);
-
-            // This task is only shown in the stack if it satisfies the historical time or min
-            // number of tasks constraints. Freeform tasks are also always shown.
-            boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
-            boolean isStackTask;
-            if (Recents.getConfiguration().isGridEnabled) {
-                // When grid layout is enabled, we only show the first
-                // TaskGridLayoutAlgorithm.MAX_LAYOUT_FROM_HOME_TASK_COUNT} tasks.
-                isStackTask = t.lastActiveTime >= lastStackActiveTime &&
-                    i >= taskCount - TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
-            } else if (Recents.getConfiguration().isLowRamDevice) {
-                // Show a max of 3 items
-                isStackTask = t.lastActiveTime >= lastStackActiveTime &&
-                        i >= taskCount - TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT;
-            } else {
-                isStackTask = isFreeformTask || !isHistoricalTask(t) ||
-                    (t.lastActiveTime >= lastStackActiveTime && i >= (taskCount - MIN_NUM_TASKS));
-            }
-            boolean isLaunchTarget = taskKey.id == runningTaskId;
-
-            // The last stack active time is the baseline for which we show visible tasks.  Since
-            // the system will store all the tasks, we don't want to show the tasks prior to the
-            // last visible ones, otherwise, as you dismiss them, the previous tasks may satisfy
-            // the other stack-task constraints.
-            if (isStackTask && newLastStackActiveTime < 0) {
-                newLastStackActiveTime = t.lastActiveTime;
-            }
-
-            // Load the title, icon, and color
-            ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
-            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
-            String titleDescription = loader.getAndUpdateContentDescription(taskKey,
-                    t.taskDescription, res);
-            String dismissDescription = String.format(dismissDescFormat, titleDescription);
-            String appInfoDescription = String.format(appInfoDescFormat, titleDescription);
-            Drawable icon = isStackTask
-                    ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
-                    : null;
-            ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                    false /* loadIfNotCached */, false /* storeInCache */);
-            int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
-            int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
-            boolean isSystemApp = (info != null) &&
-                    ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
-            if (lockedUsers.indexOfKey(t.userId) < 0) {
-                lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId));
-            }
-            boolean isLocked = lockedUsers.get(t.userId);
-
-            // Add the task to the stack
-            Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
-                    thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
-                    activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
-                    t.supportsSplitScreenMultiWindow, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
-                    isLocked);
-
-            allTasks.add(task);
-            affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
-            affiliatedTasks.put(taskKey.id, taskKey);
-        }
-        if (newLastStackActiveTime != -1) {
-            Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
-                    newLastStackActiveTime, currentUserId);
-        }
-
-        // Initialize the stacks
-        mStack = new TaskStack();
-        mStack.setTasks(mContext, allTasks, false /* notifyStackChanges */);
-    }
-
-    /**
-     * Called to apply the actual loading based on the specified conditions.
-     *
-     * Note: Do not lock, since this can be calling back to the loader, which separately also drives
-     * this call (callers should synchronize on the loader before making this call).
-     */
-    void executePlan(Options opts, RecentsTaskLoader loader) {
-        Resources res = mContext.getResources();
-
-        // Iterate through each of the tasks and load them according to the load conditions.
-        ArrayList<Task> tasks = mStack.getStackTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            Task.TaskKey taskKey = task.key;
-
-            boolean isRunningTask = (task.key.id == opts.runningTaskId);
-            boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
-            boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
-
-            // If requested, skip the running task
-            if (opts.onlyLoadPausedActivities && isRunningTask) {
-                continue;
-            }
-
-            if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
-                if (task.icon == null) {
-                    task.icon = loader.getAndUpdateActivityIcon(taskKey, task.taskDescription, res,
-                            true);
-                }
-            }
-            if (opts.loadThumbnails && isVisibleThumbnail) {
-                task.thumbnail = loader.getAndUpdateThumbnail(taskKey,
-                        true /* loadIfNotCached */, true /* storeInCache */);
-            }
-        }
-    }
-
-    /**
-     * Returns the TaskStack from the preloaded list of recent tasks.
-     */
-    public TaskStack getTaskStack() {
-        return mStack;
-    }
-
-    /**
-     * Returns the raw list of recent tasks.
-     */
-    public List<ActivityManager.RecentTaskInfo> getRawTasks() {
-        return mRawTasks;
-    }
-
-    /** Returns whether there are any tasks in any stacks. */
-    public boolean hasTasks() {
-        if (mStack != null) {
-            return mStack.getTaskCount() > 0;
-        }
-        return false;
-    }
-
-    /**
-     * Returns whether this task is too old to be shown.
-     */
-    private boolean isHistoricalTask(ActivityManager.RecentTaskInfo t) {
-        return t.lastActiveTime < (System.currentTimeMillis() - SESSION_BEGIN_TIME);
-    }
-
-
-    /**
-     * Migrate the last active time from the prefs to the secure settings.
-     *
-     * The first time this runs, it will:
-     * 1) fetch the last stack active time from the prefs
-     * 2) set the prefs to the last stack active time for all users
-     * 3) clear the pref
-     * 4) return the last stack active time
-     *
-     * Subsequent calls to this will return zero.
-     */
-    private long migrateLegacyLastStackActiveTime(int currentUserId) {
-        long legacyLastStackActiveTime = Prefs.getLong(mContext,
-                Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
-        if (legacyLastStackActiveTime != -1) {
-            Prefs.remove(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME);
-            UserManager userMgr = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-            List<UserInfo> users = userMgr.getUsers();
-            for (int i = 0; i < users.size(); i++) {
-                int userId = users.get(i).id;
-                if (userId != currentUserId) {
-                    Recents.getSystemServices().updateOverviewLastStackActiveTimeAsync(
-                            legacyLastStackActiveTime, userId);
-                }
-            }
-            return legacyLastStackActiveTime;
-        }
-        return 0;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
deleted file mode 100644
index 1b89386..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Trace;
-import android.util.Log;
-import android.util.LruCache;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.events.activity.PackagesChangedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-
-import java.io.PrintWriter;
-import java.util.Map;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-
-/**
- * A Task load queue
- */
-class TaskResourceLoadQueue {
-
-    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
-
-    /** Adds a new task to the load queue */
-    void addTask(Task t) {
-        if (!mQueue.contains(t)) {
-            mQueue.add(t);
-        }
-        synchronized(this) {
-            notifyAll();
-        }
-    }
-
-    /**
-     * Retrieves the next task from the load queue, as well as whether we want that task to be
-     * force reloaded.
-     */
-    Task nextTask() {
-        return mQueue.poll();
-    }
-
-    /** Removes a task from the load queue */
-    void removeTask(Task t) {
-        mQueue.remove(t);
-    }
-
-    /** Clears all the tasks from the load queue */
-    void clearTasks() {
-        mQueue.clear();
-    }
-
-    /** Returns whether the load queue is empty */
-    boolean isEmpty() {
-        return mQueue.isEmpty();
-    }
-}
-
-/**
- * Task resource loader
- */
-class BackgroundTaskLoader implements Runnable {
-    static String TAG = "TaskResourceLoader";
-    static boolean DEBUG = false;
-
-    Context mContext;
-    HandlerThread mLoadThread;
-    Handler mLoadThreadHandler;
-    Handler mMainThreadHandler;
-
-    TaskResourceLoadQueue mLoadQueue;
-    TaskKeyLruCache<Drawable> mIconCache;
-    BitmapDrawable mDefaultIcon;
-
-    boolean mStarted;
-    boolean mCancelled;
-    boolean mWaitingOnLoadQueue;
-
-    private final OnIdleChangedListener mOnIdleChangedListener;
-
-    /** Constructor, creates a new loading thread that loads task resources in the background */
-    public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
-            TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
-            OnIdleChangedListener onIdleChangedListener) {
-        mLoadQueue = loadQueue;
-        mIconCache = iconCache;
-        mDefaultIcon = defaultIcon;
-        mMainThreadHandler = new Handler();
-        mOnIdleChangedListener = onIdleChangedListener;
-        mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
-                android.os.Process.THREAD_PRIORITY_BACKGROUND);
-        mLoadThread.start();
-        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
-    }
-
-    /** Restarts the loader thread */
-    void start(Context context) {
-        mContext = context;
-        mCancelled = false;
-        if (!mStarted) {
-            // Start loading on the load thread
-            mStarted = true;
-            mLoadThreadHandler.post(this);
-        } else {
-            // Notify the load thread to start loading again
-            synchronized (mLoadThread) {
-                mLoadThread.notifyAll();
-            }
-        }
-    }
-
-    /** Requests the loader thread to stop after the current iteration */
-    void stop() {
-        // Mark as cancelled for the thread to pick up
-        mCancelled = true;
-        // If we are waiting for the load queue for more tasks, then we can just reset the
-        // Context now, since nothing is using it
-        if (mWaitingOnLoadQueue) {
-            mContext = null;
-        }
-    }
-
-    @Override
-    public void run() {
-        while (true) {
-            if (mCancelled) {
-                // We have to unset the context here, since the background thread may be using it
-                // when we call stop()
-                mContext = null;
-                // If we are cancelled, then wait until we are started again
-                synchronized(mLoadThread) {
-                    try {
-                        mLoadThread.wait();
-                    } catch (InterruptedException ie) {
-                        ie.printStackTrace();
-                    }
-                }
-            } else {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                // If we've stopped the loader, then fall through to the above logic to wait on
-                // the load thread
-                if (ssp != null) {
-                    processLoadQueueItem(ssp);
-                }
-
-                // If there are no other items in the list, then just wait until something is added
-                if (!mCancelled && mLoadQueue.isEmpty()) {
-                    synchronized(mLoadQueue) {
-                        try {
-                            mWaitingOnLoadQueue = true;
-                            mMainThreadHandler.post(
-                                    () -> mOnIdleChangedListener.onIdleChanged(true));
-                            mLoadQueue.wait();
-                            mMainThreadHandler.post(
-                                    () -> mOnIdleChangedListener.onIdleChanged(false));
-                            mWaitingOnLoadQueue = false;
-                        } catch (InterruptedException ie) {
-                            ie.printStackTrace();
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * This needs to be in a separate method to work around an surprising interpreter behavior:
-     * The register will keep the local reference to cachedThumbnailData even if it falls out of
-     * scope. Putting it into a method fixes this issue.
-     */
-    private void processLoadQueueItem(SystemServicesProxy ssp) {
-        // Load the next item from the queue
-        final Task t = mLoadQueue.nextTask();
-        if (t != null) {
-            Drawable cachedIcon = mIconCache.get(t.key);
-
-            // Load the icon if it is stale or we haven't cached one yet
-            if (cachedIcon == null) {
-                cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription,
-                        t.key.userId, mContext.getResources());
-
-                if (cachedIcon == null) {
-                    ActivityInfo info = ssp.getActivityInfo(
-                            t.key.getComponent(), t.key.userId);
-                    if (info != null) {
-                        if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
-                        cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId);
-                    }
-                }
-
-                if (cachedIcon == null) {
-                    cachedIcon = mDefaultIcon;
-                }
-
-                // At this point, even if we can't load the icon, we will set the
-                // default icon.
-                mIconCache.put(t.key, cachedIcon);
-            }
-
-            if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
-            final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id,
-                    true /* reducedResolution */);
-
-            if (!mCancelled) {
-                // Notify that the task data has changed
-                final Drawable finalIcon = cachedIcon;
-                mMainThreadHandler.post(
-                        () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
-            }
-        }
-    }
-
-    interface OnIdleChangedListener {
-        void onIdleChanged(boolean idle);
-    }
-}
-
-/**
- * Recents task loader
- */
-public class RecentsTaskLoader {
-
-    private static final String TAG = "RecentsTaskLoader";
-    private static final boolean DEBUG = false;
-
-    // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
-    // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
-    // below, this is per-package so we can't invalidate the items in the cache based on the last
-    // active time.  Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
-    // package in the cache has been updated, so that we may remove it.
-    private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
-    private final TaskKeyLruCache<Drawable> mIconCache;
-    private final TaskKeyLruCache<String> mActivityLabelCache;
-    private final TaskKeyLruCache<String> mContentDescriptionCache;
-    private final TaskResourceLoadQueue mLoadQueue;
-    private final BackgroundTaskLoader mLoader;
-    private final HighResThumbnailLoader mHighResThumbnailLoader;
-    @GuardedBy("this")
-    private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
-    @GuardedBy("this")
-    private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
-    private final int mMaxThumbnailCacheSize;
-    private final int mMaxIconCacheSize;
-    private int mNumVisibleTasksLoaded;
-
-    int mDefaultTaskBarBackgroundColor;
-    int mDefaultTaskViewBackgroundColor;
-    BitmapDrawable mDefaultIcon;
-
-    private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
-            new TaskKeyLruCache.EvictionCallback() {
-        @Override
-        public void onEntryEvicted(Task.TaskKey key) {
-            if (key != null) {
-                mActivityInfoCache.remove(key.getComponent());
-            }
-        }
-    };
-
-    public RecentsTaskLoader(Context context) {
-        Resources res = context.getResources();
-        mDefaultTaskBarBackgroundColor =
-                context.getColor(R.color.recents_task_bar_default_background_color);
-        mDefaultTaskViewBackgroundColor =
-                context.getColor(R.color.recents_task_view_default_background_color);
-        mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
-        mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
-        int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
-                mMaxIconCacheSize;
-
-        // Create the default assets
-        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
-        icon.eraseColor(0);
-        mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
-
-        // Initialize the proxy, cache and loaders
-        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
-        mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
-                Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice);
-        mLoadQueue = new TaskResourceLoadQueue();
-        mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
-        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
-        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
-                mClearActivityInfoOnEviction);
-        mActivityInfoCache = new LruCache(numRecentTasks);
-        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
-                mHighResThumbnailLoader::setTaskLoadQueueIdle);
-    }
-
-    /** Returns the size of the app icon cache. */
-    public int getIconCacheSize() {
-        return mMaxIconCacheSize;
-    }
-
-    /** Returns the size of the thumbnail cache. */
-    public int getThumbnailCacheSize() {
-        return mMaxThumbnailCacheSize;
-    }
-
-    public HighResThumbnailLoader getHighResThumbnailLoader() {
-        return mHighResThumbnailLoader;
-    }
-
-    /** Creates a new plan for loading the recent tasks. */
-    public RecentsTaskLoadPlan createLoadPlan(Context context) {
-        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
-        return plan;
-    }
-
-    /** Preloads raw recents tasks using the specified plan to store the output. */
-    public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan,
-            boolean includeFrontMostExcludedTask) {
-        plan.preloadRawTasks(includeFrontMostExcludedTask);
-    }
-
-    /** Preloads recents tasks using the specified plan to store the output. */
-    public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
-            boolean includeFrontMostExcludedTask) {
-        try {
-            Trace.beginSection("preloadPlan");
-            plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    /** Begins loading the heavy task data according to the specified options. */
-    public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan,
-            RecentsTaskLoadPlan.Options opts) {
-        if (opts == null) {
-            throw new RuntimeException("Requires load options");
-        }
-        if (opts.onlyLoadForCache && opts.loadThumbnails) {
-
-            // If we are loading for the cache, we'd like to have the real cache only include the
-            // visible thumbnails. However, we also don't want to reload already cached thumbnails.
-            // Thus, we copy over the current entries into a second cache, and clear the real cache,
-            // such that the real cache only contains visible thumbnails.
-            mTempCache.copyEntries(mThumbnailCache);
-            mThumbnailCache.evictAll();
-        }
-        plan.executePlan(opts, this);
-        mTempCache.evictAll();
-        if (!opts.onlyLoadForCache) {
-            mNumVisibleTasksLoaded = opts.numVisibleTasks;
-        }
-    }
-
-    /**
-     * Acquires the task resource data directly from the cache, loading if necessary.
-     */
-    public void loadTaskData(Task t) {
-        Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
-        icon = icon != null ? icon : mDefaultIcon;
-        mLoadQueue.addTask(t);
-        t.notifyTaskDataLoaded(t.thumbnail, icon);
-    }
-
-    /** Releases the task resource data back into the pool. */
-    public void unloadTaskData(Task t) {
-        mLoadQueue.removeTask(t);
-        t.notifyTaskDataUnloaded(mDefaultIcon);
-    }
-
-    /** Completely removes the resource data from the pool. */
-    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
-        mLoadQueue.removeTask(t);
-        mIconCache.remove(t.key);
-        mActivityLabelCache.remove(t.key);
-        mContentDescriptionCache.remove(t.key);
-        if (notifyTaskDataUnloaded) {
-            t.notifyTaskDataUnloaded(mDefaultIcon);
-        }
-    }
-
-    /**
-     * Handles signals from the system, trimming memory when requested to prevent us from running
-     * out of memory.
-     */
-    public synchronized void onTrimMemory(int level) {
-        switch (level) {
-            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
-                // Stop the loader immediately when the UI is no longer visible
-                stopLoader();
-                mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
-                        mMaxIconCacheSize / 2));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
-            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
-                // We are leaving recents, so trim the data a bit
-                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
-                mActivityInfoCache.trimToSize(Math.max(1,
-                        ActivityManager.getMaxRecentTasksStatic() / 2));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
-            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
-                // We are going to be low on memory
-                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
-                mActivityInfoCache.trimToSize(Math.max(1,
-                        ActivityManager.getMaxRecentTasksStatic() / 4));
-                break;
-            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
-            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
-                // We are low on memory, so release everything
-                mIconCache.evictAll();
-                mActivityInfoCache.evictAll();
-                // The cache is small, only clear the label cache when we are critical
-                mActivityLabelCache.evictAll();
-                mContentDescriptionCache.evictAll();
-                mThumbnailCache.evictAll();
-                break;
-            default:
-                break;
-        }
-    }
-
-    /**
-     * Returns the cached task label if the task key is not expired, updating the cache if it is.
-     */
-    String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        // Return the task description label if it exists
-        if (td != null && td.getLabel() != null) {
-            return td.getLabel();
-        }
-        // Return the cached activity label if it exists
-        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-        // All short paths failed, load the label from the activity info and cache it
-        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-        if (activityInfo != null) {
-            label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
-            mActivityLabelCache.put(taskKey, label);
-            return label;
-        }
-        // If the activity info does not exist or fails to load, return an empty label for now,
-        // but do not cache it
-        return "";
-    }
-
-    /**
-     * Returns the cached task content description if the task key is not expired, updating the
-     * cache if it is.
-     */
-    String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
-            Resources res) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        // Return the cached content description if it exists
-        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
-        if (label != null) {
-            return label;
-        }
-
-        // All short paths failed, load the label from the activity info and cache it
-        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-        if (activityInfo != null) {
-            label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res);
-            if (td == null) {
-                // Only add to the cache if the task description is null, otherwise, it is possible
-                // for the task description to change between calls without the last active time
-                // changing (ie. between preloading and Overview starting) which would lead to stale
-                // content descriptions
-                // TODO: Investigate improving this
-                mContentDescriptionCache.put(taskKey, label);
-            }
-            return label;
-        }
-        // If the content description does not exist, return an empty label for now, but do not
-        // cache it
-        return "";
-    }
-
-    /**
-     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
-     */
-    Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
-            Resources res, boolean loadIfNotCached) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        // Return the cached activity icon if it exists
-        Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
-        if (icon != null) {
-            return icon;
-        }
-
-        if (loadIfNotCached) {
-            // Return and cache the task description icon if it exists
-            icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
-            if (icon != null) {
-                mIconCache.put(taskKey, icon);
-                return icon;
-            }
-
-            // Load the icon from the activity info and cache it
-            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
-            if (activityInfo != null) {
-                icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
-                if (icon != null) {
-                    mIconCache.put(taskKey, icon);
-                    return icon;
-                }
-            }
-        }
-        // We couldn't load any icon
-        return null;
-    }
-
-    /**
-     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
-     */
-    synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached,
-            boolean storeInCache) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-
-        ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
-        if (cached != null) {
-            return cached;
-        }
-
-        cached = mTempCache.getAndInvalidateIfModified(taskKey);
-        if (cached != null) {
-            mThumbnailCache.put(taskKey, cached);
-            return cached;
-        }
-
-        if (loadIfNotCached) {
-            RecentsConfiguration config = Recents.getConfiguration();
-            if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
-                // Load the thumbnail from the system
-                ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id,
-                        true /* reducedResolution */);
-                if (thumbnailData.thumbnail != null) {
-                    if (storeInCache) {
-                        mThumbnailCache.put(taskKey, thumbnailData);
-                    }
-                    return thumbnailData;
-                }
-            }
-        }
-
-        // We couldn't load any thumbnail
-        return null;
-    }
-
-    /**
-     * Returns the task's primary color if possible, defaulting to the default color if there is
-     * no specified primary color.
-     */
-    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
-        if (td != null && td.getPrimaryColor() != 0) {
-            return td.getPrimaryColor();
-        }
-        return mDefaultTaskBarBackgroundColor;
-    }
-
-    /**
-     * Returns the task's background color if possible.
-     */
-    int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
-        if (td != null && td.getBackgroundColor() != 0) {
-            return td.getBackgroundColor();
-        }
-        return mDefaultTaskViewBackgroundColor;
-    }
-
-    /**
-     * Returns the activity info for the given task key, retrieving one from the system if the
-     * task key is expired.
-     */
-    ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ComponentName cn = taskKey.getComponent();
-        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
-        if (activityInfo == null) {
-            activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
-            if (cn == null || activityInfo == null) {
-                Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
-                        activityInfo);
-                return null;
-            }
-            mActivityInfoCache.put(cn, activityInfo);
-        }
-        return activityInfo;
-    }
-
-    /**
-     * Starts loading tasks.
-     */
-    public void startLoader(Context ctx) {
-        mLoader.start(ctx);
-    }
-
-    /**
-     * Stops the task loader and clears all queued, pending task loads.
-     */
-    private void stopLoader() {
-        mLoader.stop();
-        mLoadQueue.clearTasks();
-    }
-
-    /**** Event Bus Events ****/
-
-    public final void onBusEvent(PackagesChangedEvent event) {
-        // Remove all the cached activity infos for this package.  The other caches do not need to
-        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
-        // cached contents are requested
-        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
-        for (ComponentName cn : activityInfoCache.keySet()) {
-            if (cn.getPackageName().equals(event.packageName)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Removing activity info from cache: " + cn);
-                }
-                mActivityInfoCache.remove(cn);
-            }
-        }
-    }
-
-    public synchronized void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.println(TAG);
-        writer.print(prefix); writer.println("Icon Cache");
-        mIconCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Thumbnail Cache");
-        mThumbnailCache.dump(innerPrefix, writer);
-        writer.print(prefix); writer.println("Temp Thumbnail Cache");
-        mTempCache.dump(innerPrefix, writer);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
deleted file mode 100644
index 9e6bf85..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.view.ViewDebug;
-
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Objects;
-
-/**
- * A task represents the top most task in the system's task stack.
- */
-public class Task {
-
-    public static final String TAG = "Task";
-
-    /* Task callbacks */
-    public interface TaskCallbacks {
-        /* Notifies when a task has been bound */
-        public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
-        /* Notifies when a task has been unbound */
-        public void onTaskDataUnloaded();
-        /* Notifies when a task's stack id has changed. */
-        public void onTaskStackIdChanged();
-    }
-
-    /* The Task Key represents the unique primary key for the task */
-    public static class TaskKey {
-        @ViewDebug.ExportedProperty(category="recents")
-        public final int id;
-        @ViewDebug.ExportedProperty(category="recents")
-        public int stackId;
-        @ViewDebug.ExportedProperty(category="recents")
-        public final Intent baseIntent;
-        @ViewDebug.ExportedProperty(category="recents")
-        public final int userId;
-        @ViewDebug.ExportedProperty(category="recents")
-        public long firstActiveTime;
-        @ViewDebug.ExportedProperty(category="recents")
-        public long lastActiveTime;
-
-        private int mHashCode;
-
-        public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
-                long lastActiveTime) {
-            this.id = id;
-            this.stackId = stackId;
-            this.baseIntent = intent;
-            this.userId = userId;
-            this.firstActiveTime = firstActiveTime;
-            this.lastActiveTime = lastActiveTime;
-            updateHashCode();
-        }
-
-        public void setStackId(int stackId) {
-            this.stackId = stackId;
-            updateHashCode();
-        }
-
-        public ComponentName getComponent() {
-            return this.baseIntent.getComponent();
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof TaskKey)) {
-                return false;
-            }
-            TaskKey otherKey = (TaskKey) o;
-            return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
-        }
-
-        @Override
-        public int hashCode() {
-            return mHashCode;
-        }
-
-        @Override
-        public String toString() {
-            return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
-                    lastActiveTime;
-        }
-
-        private void updateHashCode() {
-            mHashCode = Objects.hash(id, stackId, userId);
-        }
-    }
-
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="key_")
-    public TaskKey key;
-
-    /**
-     * The temporary sort index in the stack, used when ordering the stack.
-     */
-    public int temporarySortIndexInStack;
-
-    /**
-     * The group will be computed separately from the initialization of the task
-     */
-    @ViewDebug.ExportedProperty(deepExport=true, prefix="group_")
-    public TaskGrouping group;
-    /**
-     * The affiliationTaskId is the task id of the parent task or itself if it is not affiliated
-     * with any task.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    public int affiliationTaskId;
-    @ViewDebug.ExportedProperty(category="recents")
-    public int affiliationColor;
-
-    /**
-     * The icon is the task description icon (if provided), which falls back to the activity icon,
-     * which can then fall back to the application icon.
-     */
-    public Drawable icon;
-    public ThumbnailData thumbnail;
-    @ViewDebug.ExportedProperty(category="recents")
-    public String title;
-    @ViewDebug.ExportedProperty(category="recents")
-    public String titleDescription;
-    @ViewDebug.ExportedProperty(category="recents")
-    public String dismissDescription;
-    @ViewDebug.ExportedProperty(category="recents")
-    public String appInfoDescription;
-    @ViewDebug.ExportedProperty(category="recents")
-    public int colorPrimary;
-    @ViewDebug.ExportedProperty(category="recents")
-    public int colorBackground;
-    @ViewDebug.ExportedProperty(category="recents")
-    public boolean useLightOnPrimaryColor;
-
-    /**
-     * The bounds of the task, used only if it is a freeform task.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect bounds;
-
-    /**
-     * The task description for this task, only used to reload task icons.
-     */
-    public ActivityManager.TaskDescription taskDescription;
-
-    /**
-     * The state isLaunchTarget will be set for the correct task upon launching Recents.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    public boolean isLaunchTarget;
-    @ViewDebug.ExportedProperty(category="recents")
-    public boolean isStackTask;
-    @ViewDebug.ExportedProperty(category="recents")
-    public boolean isSystemApp;
-    @ViewDebug.ExportedProperty(category="recents")
-    public boolean isDockable;
-
-    /**
-     * Resize mode. See {@link ActivityInfo#resizeMode}.
-     */
-    @ViewDebug.ExportedProperty(category="recents")
-    public int resizeMode;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    public ComponentName topActivity;
-
-    @ViewDebug.ExportedProperty(category="recents")
-    public boolean isLocked;
-
-    private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
-
-    public Task() {
-        // Do nothing
-    }
-
-    public Task(TaskKey key, int affiliationTaskId, int affiliationColor, Drawable icon,
-            ThumbnailData thumbnail, String title, String titleDescription,
-            String dismissDescription, String appInfoDescription, int colorPrimary,
-            int colorBackground, boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
-            boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
-            int resizeMode, ComponentName topActivity, boolean isLocked) {
-        boolean isInAffiliationGroup = (affiliationTaskId != key.id);
-        boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
-        this.key = key;
-        this.affiliationTaskId = affiliationTaskId;
-        this.affiliationColor = affiliationColor;
-        this.icon = icon;
-        this.thumbnail = thumbnail;
-        this.title = title;
-        this.titleDescription = titleDescription;
-        this.dismissDescription = dismissDescription;
-        this.appInfoDescription = appInfoDescription;
-        this.colorPrimary = hasAffiliationGroupColor ? affiliationColor : colorPrimary;
-        this.colorBackground = colorBackground;
-        this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
-                Color.WHITE) > 3f;
-        this.bounds = bounds;
-        this.taskDescription = taskDescription;
-        this.isLaunchTarget = isLaunchTarget;
-        this.isStackTask = isStackTask;
-        this.isSystemApp = isSystemApp;
-        this.isDockable = isDockable;
-        this.resizeMode = resizeMode;
-        this.topActivity = topActivity;
-        this.isLocked = isLocked;
-    }
-
-    /**
-     * Copies the metadata from another task, but retains the current callbacks.
-     */
-    public void copyFrom(Task o) {
-        this.key = o.key;
-        this.group = o.group;
-        this.affiliationTaskId = o.affiliationTaskId;
-        this.affiliationColor = o.affiliationColor;
-        this.icon = o.icon;
-        this.thumbnail = o.thumbnail;
-        this.title = o.title;
-        this.titleDescription = o.titleDescription;
-        this.dismissDescription = o.dismissDescription;
-        this.appInfoDescription = o.appInfoDescription;
-        this.colorPrimary = o.colorPrimary;
-        this.colorBackground = o.colorBackground;
-        this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
-        this.bounds = o.bounds;
-        this.taskDescription = o.taskDescription;
-        this.isLaunchTarget = o.isLaunchTarget;
-        this.isStackTask = o.isStackTask;
-        this.isSystemApp = o.isSystemApp;
-        this.isDockable = o.isDockable;
-        this.resizeMode = o.resizeMode;
-        this.isLocked = o.isLocked;
-        this.topActivity = o.topActivity;
-    }
-
-    /**
-     * Add a callback.
-     */
-    public void addCallback(TaskCallbacks cb) {
-        if (!mCallbacks.contains(cb)) {
-            mCallbacks.add(cb);
-        }
-    }
-
-    /**
-     * Remove a callback.
-     */
-    public void removeCallback(TaskCallbacks cb) {
-        mCallbacks.remove(cb);
-    }
-
-    /** Set the grouping */
-    public void setGroup(TaskGrouping group) {
-        this.group = group;
-    }
-
-    /**
-     * Updates the stack id of this task.
-     */
-    public void setStackId(int stackId) {
-        key.setStackId(stackId);
-        int callbackCount = mCallbacks.size();
-        for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskStackIdChanged();
-        }
-    }
-
-    /**
-     * Returns whether this task is on the freeform task stack.
-     */
-    public boolean isFreeformTask() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
-    }
-
-    /** Notifies the callback listeners that this task has been loaded */
-    public void notifyTaskDataLoaded(ThumbnailData thumbnailData, Drawable applicationIcon) {
-        this.icon = applicationIcon;
-        this.thumbnail = thumbnailData;
-        int callbackCount = mCallbacks.size();
-        for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskDataLoaded(this, thumbnailData);
-        }
-    }
-
-    /** Notifies the callback listeners that this task has been unloaded */
-    public void notifyTaskDataUnloaded(Drawable defaultApplicationIcon) {
-        icon = defaultApplicationIcon;
-        thumbnail = null;
-        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-            mCallbacks.get(i).onTaskDataUnloaded();
-        }
-    }
-
-    /**
-     * Returns whether this task is affiliated with another task.
-     */
-    public boolean isAffiliatedTask() {
-        return key.id != affiliationTaskId;
-    }
-
-    /**
-     * Returns the top activity component.
-     */
-    public ComponentName getTopComponent() {
-        return topActivity != null
-                ? topActivity
-                : key.baseIntent.getComponent();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        // Check that the id matches
-        Task t = (Task) o;
-        return key.equals(t.key);
-    }
-
-    @Override
-    public String toString() {
-        return "[" + key.toString() + "] " + title;
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        writer.print(prefix); writer.print(key);
-        if (isAffiliatedTask()) {
-            writer.print(" "); writer.print("affTaskId=" + affiliationTaskId);
-        }
-        if (!isDockable) {
-            writer.print(" dockable=N");
-        }
-        if (isLaunchTarget) {
-            writer.print(" launchTarget=Y");
-        }
-        if (isFreeformTask()) {
-            writer.print(" freeform=Y");
-        }
-        if (isLocked) {
-            writer.print(" locked=Y");
-        }
-        writer.print(" "); writer.print(title);
-        writer.println();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
deleted file mode 100644
index 2109376..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.android.systemui.recents.model;
-
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-
-/** Represents a grouping of tasks witihin a stack. */
-public class TaskGrouping {
-
-    int affiliation;
-    long latestActiveTimeInGroup;
-
-    Task.TaskKey mFrontMostTaskKey;
-    ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
-    ArrayMap<Task.TaskKey, Integer> mTaskKeyIndices = new ArrayMap<>();
-
-    /** Creates a group with a specified affiliation. */
-    public TaskGrouping(int affiliation) {
-        this.affiliation = affiliation;
-    }
-
-    /** Adds a new task to this group. */
-    void addTask(Task t) {
-        mTaskKeys.add(t.key);
-        if (t.key.lastActiveTime > latestActiveTimeInGroup) {
-            latestActiveTimeInGroup = t.key.lastActiveTime;
-        }
-        t.setGroup(this);
-        updateTaskIndices();
-    }
-
-    /** Removes a task from this group. */
-    void removeTask(Task t) {
-        mTaskKeys.remove(t.key);
-        latestActiveTimeInGroup = 0;
-        int taskCount = mTaskKeys.size();
-        for (int i = 0; i < taskCount; i++) {
-            long lastActiveTime = mTaskKeys.get(i).lastActiveTime;
-            if (lastActiveTime > latestActiveTimeInGroup) {
-                latestActiveTimeInGroup = lastActiveTime;
-            }
-        }
-        t.setGroup(null);
-        updateTaskIndices();
-    }
-
-    /** Returns the key of the next task in the group. */
-    public Task.TaskKey getNextTaskInGroup(Task t) {
-        int i = indexOf(t);
-        if ((i + 1) < getTaskCount()) {
-            return mTaskKeys.get(i + 1);
-        }
-        return null;
-    }
-
-    /** Returns the key of the previous task in the group. */
-    public Task.TaskKey getPrevTaskInGroup(Task t) {
-        int i = indexOf(t);
-        if ((i - 1) >= 0) {
-            return mTaskKeys.get(i - 1);
-        }
-        return null;
-    }
-
-    /** Gets the front task */
-    public boolean isFrontMostTask(Task t) {
-        return (t.key == mFrontMostTaskKey);
-    }
-
-    /** Finds the index of a given task in a group. */
-    public int indexOf(Task t) {
-        return mTaskKeyIndices.get(t.key);
-    }
-
-    /** Returns whether a task is in this grouping. */
-    public boolean containsTask(Task t) {
-        return mTaskKeyIndices.containsKey(t.key);
-    }
-
-    /** Returns whether one task is above another in the group.  If they are not in the same group,
-     * this returns false. */
-    public boolean isTaskAboveTask(Task t, Task below) {
-        return mTaskKeyIndices.containsKey(t.key) && mTaskKeyIndices.containsKey(below.key) &&
-                mTaskKeyIndices.get(t.key) > mTaskKeyIndices.get(below.key);
-    }
-
-    /** Returns the number of tasks in this group. */
-    public int getTaskCount() { return mTaskKeys.size(); }
-
-    /** Updates the mapping of tasks to indices. */
-    private void updateTaskIndices() {
-        if (mTaskKeys.isEmpty()) {
-            mFrontMostTaskKey = null;
-            mTaskKeyIndices.clear();
-            return;
-        }
-
-        int taskCount = mTaskKeys.size();
-        mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
-        mTaskKeyIndices.clear();
-        for (int i = 0; i < taskCount; i++) {
-            Task.TaskKey k = mTaskKeys.get(i);
-            mTaskKeyIndices.put(k, i);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java
deleted file mode 100644
index be99f93..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyCache.java
+++ /dev/null
@@ -1,89 +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.recents.model;
-
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.systemui.recents.model.Task.TaskKey;
-
-/**
- * Base class for both strong and LRU task key cache.
- */
-public abstract class TaskKeyCache<V> {
-
-    protected static final String TAG = "TaskKeyCache";
-
-    protected final SparseArray<TaskKey> mKeys = new SparseArray<>();
-
-    /**
-     * Gets a specific entry in the cache with the specified key, regardless of whether the cached
-     * value is valid or not.
-     */
-    final V get(Task.TaskKey key) {
-        return getCacheEntry(key.id);
-    }
-
-    /**
-     * Returns the value only if the key is valid (has not been updated since the last time it was
-     * in the cache)
-     */
-    final V getAndInvalidateIfModified(Task.TaskKey key) {
-        Task.TaskKey lastKey = mKeys.get(key.id);
-        if (lastKey != null) {
-            if ((lastKey.stackId != key.stackId) ||
-                    (lastKey.lastActiveTime != key.lastActiveTime)) {
-                // The task has updated (been made active since the last time it was put into the
-                // LRU cache) or the stack id for the task has changed, invalidate that cache item
-                remove(key);
-                return null;
-            }
-        }
-        // Either the task does not exist in the cache, or the last active time is the same as
-        // the key specified, so return what is in the cache
-        return getCacheEntry(key.id);
-    }
-
-    /** Puts an entry in the cache for a specific key. */
-    final void put(Task.TaskKey key, V value) {
-        if (key == null || value == null) {
-            Log.e(TAG, "Unexpected null key or value: " + key + ", " + value);
-            return;
-        }
-        mKeys.put(key.id, key);
-        putCacheEntry(key.id, value);
-    }
-
-
-    /** Removes a cache entry for a specific key. */
-    final void remove(Task.TaskKey key) {
-        // Remove the key after the cache value because we need it to make the callback
-        removeCacheEntry(key.id);
-        mKeys.remove(key.id);
-    }
-
-    /** Removes all the entries in the cache. */
-    final void evictAll() {
-        evictAllCache();
-        mKeys.clear();
-    }
-
-    protected abstract V getCacheEntry(int id);
-    protected abstract void putCacheEntry(int id, V value);
-    protected abstract void removeCacheEntry(int id);
-    protected abstract void evictAllCache();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
deleted file mode 100644
index 778df6b..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyLruCache.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import android.util.LruCache;
-
-import java.io.PrintWriter;
-
-/**
- * A mapping of {@link Task.TaskKey} to value, with additional LRU functionality where the least
- * recently referenced key/values will be evicted as more values than the given cache size are
- * inserted.
- *
- * In addition, this also allows the caller to invalidate cached values for keys that have since
- * changed.
- */
-public class TaskKeyLruCache<V> extends TaskKeyCache<V> {
-
-    public interface EvictionCallback {
-        public void onEntryEvicted(Task.TaskKey key);
-    }
-
-    private final LruCache<Integer, V> mCache;
-    private final EvictionCallback mEvictionCallback;
-
-    public TaskKeyLruCache(int cacheSize) {
-        this(cacheSize, null);
-    }
-
-    public TaskKeyLruCache(int cacheSize, EvictionCallback evictionCallback) {
-        mEvictionCallback = evictionCallback;
-        mCache = new LruCache<Integer, V>(cacheSize) {
-
-            @Override
-            protected void entryRemoved(boolean evicted, Integer taskId, V oldV, V newV) {
-                if (mEvictionCallback != null) {
-                    mEvictionCallback.onEntryEvicted(mKeys.get(taskId));
-                }
-                mKeys.remove(taskId);
-            }
-        };
-    }
-
-    /** Trims the cache to a specific size */
-    final void trimToSize(int cacheSize) {
-        mCache.trimToSize(cacheSize);
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" numEntries="); writer.print(mKeys.size());
-        writer.println();
-        int keyCount = mKeys.size();
-        for (int i = 0; i < keyCount; i++) {
-            writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
-        }
-    }
-
-    @Override
-    protected V getCacheEntry(int id) {
-        return mCache.get(id);
-    }
-
-    @Override
-    protected void putCacheEntry(int id, V value) {
-        mCache.put(id, value);
-    }
-
-    @Override
-    protected void removeCacheEntry(int id) {
-        mCache.remove(id);
-    }
-
-    @Override
-    protected void evictAllCache() {
-        mCache.evictAll();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java
deleted file mode 100644
index c84df8a..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskKeyStrongCache.java
+++ /dev/null
@@ -1,73 +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.recents.model;
-
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.systemui.recents.model.Task.TaskKey;
-
-import java.io.PrintWriter;
-
-/**
- * Like {@link TaskKeyLruCache}, but without LRU functionality.
- */
-public class TaskKeyStrongCache<V> extends TaskKeyCache<V> {
-
-    private static final String TAG = "TaskKeyCache";
-
-    private final ArrayMap<Integer, V> mCache = new ArrayMap<>();
-
-    final void copyEntries(TaskKeyStrongCache<V> other) {
-        for (int i = other.mKeys.size() - 1; i >= 0; i--) {
-            TaskKey key = other.mKeys.valueAt(i);
-            put(key, other.mCache.get(key.id));
-        }
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" numEntries="); writer.print(mKeys.size());
-        writer.println();
-        int keyCount = mKeys.size();
-        for (int i = 0; i < keyCount; i++) {
-            writer.print(innerPrefix); writer.println(mKeys.get(mKeys.keyAt(i)));
-        }
-    }
-
-    @Override
-    protected V getCacheEntry(int id) {
-        return mCache.get(id);
-    }
-
-    @Override
-    protected void putCacheEntry(int id, V value) {
-        mCache.put(id, value);
-    }
-
-    @Override
-    protected void removeCacheEntry(int id) {
-        mCache.remove(id);
-    }
-
-    @Override
-    protected void evictAllCache() {
-        mCache.clear();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
deleted file mode 100644
index 6e3be09..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ /dev/null
@@ -1,1142 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.model;
-
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.IntProperty;
-import android.util.SparseArray;
-import android.view.animation.Interpolator;
-
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsDebugFlags;
-import com.android.systemui.recents.misc.NamedCounter;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.views.AnimationProps;
-import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
-
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Random;
-
-
-/**
- * An interface for a task filter to query whether a particular task should show in a stack.
- */
-interface TaskFilter {
-    /** Returns whether the filter accepts the specified task */
-    public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index);
-}
-
-/**
- * A list of filtered tasks.
- */
-class FilteredTaskList {
-
-    ArrayList<Task> mTasks = new ArrayList<>();
-    ArrayList<Task> mFilteredTasks = new ArrayList<>();
-    ArrayMap<Task.TaskKey, Integer> mTaskIndices = new ArrayMap<>();
-    TaskFilter mFilter;
-
-    /** Sets the task filter, saving the current touch state */
-    boolean setFilter(TaskFilter filter) {
-        ArrayList<Task> prevFilteredTasks = new ArrayList<>(mFilteredTasks);
-        mFilter = filter;
-        updateFilteredTasks();
-        if (!prevFilteredTasks.equals(mFilteredTasks)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /** Removes the task filter and returns the previous touch state */
-    void removeFilter() {
-        mFilter = null;
-        updateFilteredTasks();
-    }
-
-    /** Adds a new task to the task list */
-    void add(Task t) {
-        mTasks.add(t);
-        updateFilteredTasks();
-    }
-
-    /**
-     * Moves the given task.
-     */
-    public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
-        int taskIndex = indexOf(task);
-        if (taskIndex != insertIndex) {
-            mTasks.remove(taskIndex);
-            if (taskIndex < insertIndex) {
-                insertIndex--;
-            }
-            mTasks.add(insertIndex, task);
-        }
-
-        // Update the stack id now, after we've moved the task, and before we update the
-        // filtered tasks
-        task.setStackId(newStackId);
-        updateFilteredTasks();
-    }
-
-    /** Sets the list of tasks */
-    void set(List<Task> tasks) {
-        mTasks.clear();
-        mTasks.addAll(tasks);
-        updateFilteredTasks();
-    }
-
-    /** Removes a task from the base list only if it is in the filtered list */
-    boolean remove(Task t) {
-        if (mFilteredTasks.contains(t)) {
-            boolean removed = mTasks.remove(t);
-            updateFilteredTasks();
-            return removed;
-        }
-        return false;
-    }
-
-    /** Returns the index of this task in the list of filtered tasks */
-    int indexOf(Task t) {
-        if (t != null && mTaskIndices.containsKey(t.key)) {
-            return mTaskIndices.get(t.key);
-        }
-        return -1;
-    }
-
-    /** Returns the size of the list of filtered tasks */
-    int size() {
-        return mFilteredTasks.size();
-    }
-
-    /** Returns whether the filtered list contains this task */
-    boolean contains(Task t) {
-        return mTaskIndices.containsKey(t.key);
-    }
-
-    /** Updates the list of filtered tasks whenever the base task list changes */
-    private void updateFilteredTasks() {
-        mFilteredTasks.clear();
-        if (mFilter != null) {
-            // Create a sparse array from task id to Task
-            SparseArray<Task> taskIdMap = new SparseArray<>();
-            int taskCount = mTasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task t = mTasks.get(i);
-                taskIdMap.put(t.key.id, t);
-            }
-
-            for (int i = 0; i < taskCount; i++) {
-                Task t = mTasks.get(i);
-                if (mFilter.acceptTask(taskIdMap, t, i)) {
-                    mFilteredTasks.add(t);
-                }
-            }
-        } else {
-            mFilteredTasks.addAll(mTasks);
-        }
-        updateFilteredTaskIndices();
-    }
-
-    /** Updates the mapping of tasks to indices. */
-    private void updateFilteredTaskIndices() {
-        int taskCount = mFilteredTasks.size();
-        mTaskIndices.clear();
-        for (int i = 0; i < taskCount; i++) {
-            Task t = mFilteredTasks.get(i);
-            mTaskIndices.put(t.key, i);
-        }
-    }
-
-    /** Returns whether this task list is filtered */
-    boolean hasFilter() {
-        return (mFilter != null);
-    }
-
-    /** Returns the list of filtered tasks */
-    ArrayList<Task> getTasks() {
-        return mFilteredTasks;
-    }
-}
-
-/**
- * The task stack contains a list of multiple tasks.
- */
-public class TaskStack {
-
-    private static final String TAG = "TaskStack";
-
-    /** Task stack callbacks */
-    public interface TaskStackCallbacks {
-        /**
-         * Notifies when a new task has been added to the stack.
-         */
-        void onStackTaskAdded(TaskStack stack, Task newTask);
-
-        /**
-         * Notifies when a task has been removed from the stack.
-         */
-        void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
-                AnimationProps animation, boolean fromDockGesture,
-                boolean dismissRecentsIfAllRemoved);
-
-        /**
-         * Notifies when all tasks have been removed from the stack.
-         */
-        void onStackTasksRemoved(TaskStack stack);
-
-        /**
-         * Notifies when tasks in the stack have been updated.
-         */
-        void onStackTasksUpdated(TaskStack stack);
-    }
-
-    /**
-     * The various possible dock states when dragging and dropping a task.
-     */
-    public static class DockState implements DropTarget {
-
-        public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
-        public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
-
-        // The rotation to apply to the hint text
-        @Retention(RetentionPolicy.SOURCE)
-        @IntDef({HORIZONTAL, VERTICAL})
-        public @interface TextOrientation {}
-        private static final int HORIZONTAL = 0;
-        private static final int VERTICAL = 1;
-
-        private static final int DOCK_AREA_ALPHA = 80;
-        public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
-                null, null, null);
-        public static final DockState LEFT = new DockState(DOCKED_LEFT,
-                DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
-                new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
-                new RectF(0, 0, 0.5f, 1));
-        public static final DockState TOP = new DockState(DOCKED_TOP,
-                DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
-                new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
-                new RectF(0, 0, 1, 0.5f));
-        public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
-                DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
-                new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
-                new RectF(0.5f, 0, 1, 1));
-        public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
-                DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
-                new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
-                new RectF(0, 0.5f, 1, 1));
-
-        @Override
-        public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
-                boolean isCurrentTarget) {
-            if (isCurrentTarget) {
-                getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
-                return mTmpRect.contains(x, y);
-            } else {
-                getMappedRect(touchArea, width, height, mTmpRect);
-                updateBoundsWithSystemInsets(mTmpRect, insets);
-                return mTmpRect.contains(x, y);
-            }
-        }
-
-        // Represents the view state of this dock state
-        public static class ViewState {
-            private static final IntProperty<ViewState> HINT_ALPHA =
-                    new IntProperty<ViewState>("drawableAlpha") {
-                        @Override
-                        public void setValue(ViewState object, int alpha) {
-                            object.mHintTextAlpha = alpha;
-                            object.dockAreaOverlay.invalidateSelf();
-                        }
-
-                        @Override
-                        public Integer get(ViewState object) {
-                            return object.mHintTextAlpha;
-                        }
-                    };
-
-            public final int dockAreaAlpha;
-            public final ColorDrawable dockAreaOverlay;
-            public final int hintTextAlpha;
-            public final int hintTextOrientation;
-
-            private final int mHintTextResId;
-            private String mHintText;
-            private Paint mHintTextPaint;
-            private Point mHintTextBounds = new Point();
-            private int mHintTextAlpha = 255;
-            private AnimatorSet mDockAreaOverlayAnimator;
-            private Rect mTmpRect = new Rect();
-
-            private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
-                    int hintTextResId) {
-                dockAreaAlpha = areaAlpha;
-                dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
-                        ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
-                dockAreaOverlay.setAlpha(0);
-                hintTextAlpha = hintAlpha;
-                hintTextOrientation = hintOrientation;
-                mHintTextResId = hintTextResId;
-                mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-                mHintTextPaint.setColor(Color.WHITE);
-            }
-
-            /**
-             * Updates the view state with the given context.
-             */
-            public void update(Context context) {
-                Resources res = context.getResources();
-                mHintText = context.getString(mHintTextResId);
-                mHintTextPaint.setTextSize(res.getDimensionPixelSize(
-                        R.dimen.recents_drag_hint_text_size));
-                mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
-                mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
-            }
-
-            /**
-             * Draws the current view state.
-             */
-            public void draw(Canvas canvas) {
-                // Draw the overlay background
-                if (dockAreaOverlay.getAlpha() > 0) {
-                    dockAreaOverlay.draw(canvas);
-                }
-
-                // Draw the hint text
-                if (mHintTextAlpha > 0) {
-                    Rect bounds = dockAreaOverlay.getBounds();
-                    int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
-                    int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
-                    mHintTextPaint.setAlpha(mHintTextAlpha);
-                    if (hintTextOrientation == VERTICAL) {
-                        canvas.save();
-                        canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
-                    }
-                    canvas.drawText(mHintText, x, y, mHintTextPaint);
-                    if (hintTextOrientation == VERTICAL) {
-                        canvas.restore();
-                    }
-                }
-            }
-
-            /**
-             * Creates a new bounds and alpha animation.
-             */
-            public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
-                    Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
-                if (mDockAreaOverlayAnimator != null) {
-                    mDockAreaOverlayAnimator.cancel();
-                }
-
-                ObjectAnimator anim;
-                ArrayList<Animator> animators = new ArrayList<>();
-                if (dockAreaOverlay.getAlpha() != areaAlpha) {
-                    if (animateAlpha) {
-                        anim = ObjectAnimator.ofInt(dockAreaOverlay,
-                                Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
-                        anim.setDuration(duration);
-                        anim.setInterpolator(interpolator);
-                        animators.add(anim);
-                    } else {
-                        dockAreaOverlay.setAlpha(areaAlpha);
-                    }
-                }
-                if (mHintTextAlpha != hintAlpha) {
-                    if (animateAlpha) {
-                        anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
-                                hintAlpha);
-                        anim.setDuration(150);
-                        anim.setInterpolator(hintAlpha > mHintTextAlpha
-                                ? Interpolators.ALPHA_IN
-                                : Interpolators.ALPHA_OUT);
-                        animators.add(anim);
-                    } else {
-                        mHintTextAlpha = hintAlpha;
-                        dockAreaOverlay.invalidateSelf();
-                    }
-                }
-                if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
-                    if (animateBounds) {
-                        PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
-                                Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
-                                new Rect(dockAreaOverlay.getBounds()), bounds);
-                        anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
-                        anim.setDuration(duration);
-                        anim.setInterpolator(interpolator);
-                        animators.add(anim);
-                    } else {
-                        dockAreaOverlay.setBounds(bounds);
-                    }
-                }
-                if (!animators.isEmpty()) {
-                    mDockAreaOverlayAnimator = new AnimatorSet();
-                    mDockAreaOverlayAnimator.playTogether(animators);
-                    mDockAreaOverlayAnimator.start();
-                }
-            }
-        }
-
-        public final int dockSide;
-        public final int createMode;
-        public final ViewState viewState;
-        private final RectF touchArea;
-        private final RectF dockArea;
-        private final RectF expandedTouchDockArea;
-        private static final Rect mTmpRect = new Rect();
-
-        /**
-         * @param createMode used to pass to ActivityManager to dock the task
-         * @param touchArea the area in which touch will initiate this dock state
-         * @param dockArea the visible dock area
-         * @param expandedTouchDockArea the area in which touch will continue to dock after entering
-         *                              the initial touch area.  This is also the new dock area to
-         *                              draw.
-         */
-        DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
-                  @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
-                  RectF expandedTouchDockArea) {
-            this.dockSide = dockSide;
-            this.createMode = createMode;
-            this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
-                    R.string.recents_drag_hint_message);
-            this.dockArea = dockArea;
-            this.touchArea = touchArea;
-            this.expandedTouchDockArea = expandedTouchDockArea;
-        }
-
-        /**
-         * Updates the dock state with the given context.
-         */
-        public void update(Context context) {
-            viewState.update(context);
-        }
-
-        /**
-         * Returns the docked task bounds with the given {@param width} and {@param height}.
-         */
-        public Rect getPreDockedBounds(int width, int height, Rect insets) {
-            getMappedRect(dockArea, width, height, mTmpRect);
-            return updateBoundsWithSystemInsets(mTmpRect, insets);
-        }
-
-        /**
-         * Returns the expanded docked task bounds with the given {@param width} and
-         * {@param height}.
-         */
-        public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
-                Resources res) {
-            // Calculate the docked task bounds
-            boolean isHorizontalDivision =
-                    res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
-            int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                    insets, width, height, dividerSize);
-            Rect newWindowBounds = new Rect();
-            DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
-                    width, height, dividerSize);
-            return newWindowBounds;
-        }
-
-        /**
-         * Returns the task stack bounds with the given {@param width} and
-         * {@param height}.
-         */
-        public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
-                int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
-                Resources res, Rect windowRectOut) {
-            // Calculate the inverse docked task bounds
-            boolean isHorizontalDivision =
-                    res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
-            int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
-                    insets, width, height, dividerSize);
-            DockedDividerUtils.calculateBoundsForPosition(position,
-                    DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
-                    dividerSize);
-
-            // Calculate the task stack bounds from the new window bounds
-            Rect taskStackBounds = new Rect();
-            // If the task stack bounds is specifically under the dock area, then ignore the top
-            // inset
-            int top = dockArea.bottom < 1f
-                    ? 0
-                    : insets.top;
-            // For now, ignore the left insets since we always dock on the left and show Recents
-            // on the right
-            layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
-                    taskStackBounds);
-            return taskStackBounds;
-        }
-
-        /**
-         * Returns the expanded bounds in certain dock sides such that the bounds account for the
-         * system insets (namely the vertical nav bar).  This call modifies and returns the given
-         * {@param bounds}.
-         */
-        private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
-            if (dockSide == DOCKED_LEFT) {
-                bounds.right += insets.left;
-            } else if (dockSide == DOCKED_RIGHT) {
-                bounds.left -= insets.right;
-            }
-            return bounds;
-        }
-
-        /**
-         * Returns the mapped rect to the given dimensions.
-         */
-        private void getMappedRect(RectF bounds, int width, int height, Rect out) {
-            out.set((int) (bounds.left * width), (int) (bounds.top * height),
-                    (int) (bounds.right * width), (int) (bounds.bottom * height));
-        }
-    }
-
-    // A comparator that sorts tasks by their freeform state
-    private Comparator<Task> FREEFORM_COMPARATOR = new Comparator<Task>() {
-        @Override
-        public int compare(Task o1, Task o2) {
-            if (o1.isFreeformTask() && !o2.isFreeformTask()) {
-                return 1;
-            } else if (o2.isFreeformTask() && !o1.isFreeformTask()) {
-                return -1;
-            }
-            return Long.compare(o1.temporarySortIndexInStack, o2.temporarySortIndexInStack);
-        }
-    };
-
-
-    // The task offset to apply to a task id as a group affiliation
-    static final int IndividualTaskIdOffset = 1 << 16;
-
-    ArrayList<Task> mRawTaskList = new ArrayList<>();
-    FilteredTaskList mStackTaskList = new FilteredTaskList();
-    TaskStackCallbacks mCb;
-
-    ArrayList<TaskGrouping> mGroups = new ArrayList<>();
-    ArrayMap<Integer, TaskGrouping> mAffinitiesGroups = new ArrayMap<>();
-
-    public TaskStack() {
-        // Ensure that we only show non-docked tasks
-        mStackTaskList.setFilter(new TaskFilter() {
-            @Override
-            public boolean acceptTask(SparseArray<Task> taskIdMap, Task t, int index) {
-                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
-                    if (t.isAffiliatedTask()) {
-                        // If this task is affiliated with another parent in the stack, then the
-                        // historical state of this task depends on the state of the parent task
-                        Task parentTask = taskIdMap.get(t.affiliationTaskId);
-                        if (parentTask != null) {
-                            t = parentTask;
-                        }
-                    }
-                }
-                return t.isStackTask;
-            }
-        });
-    }
-
-    /** Sets the callbacks for this task stack. */
-    public void setCallbacks(TaskStackCallbacks cb) {
-        mCb = cb;
-    }
-
-    /**
-     * Moves the given task to either the front of the freeform workspace or the stack.
-     */
-    public void moveTaskToStack(Task task, int newStackId) {
-        // Find the index to insert into
-        ArrayList<Task> taskList = mStackTaskList.getTasks();
-        int taskCount = taskList.size();
-        if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
-            // Insert freeform tasks at the front
-            mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
-        } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
-            // Insert after the first stacked task
-            int insertIndex = 0;
-            for (int i = taskCount - 1; i >= 0; i--) {
-                if (!taskList.get(i).isFreeformTask()) {
-                    insertIndex = i + 1;
-                    break;
-                }
-            }
-            mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
-        }
-    }
-
-    /** Does the actual work associated with removing the task. */
-    void removeTaskImpl(FilteredTaskList taskList, Task t) {
-        // Remove the task from the list
-        taskList.remove(t);
-        // Remove it from the group as well, and if it is empty, remove the group
-        TaskGrouping group = t.group;
-        if (group != null) {
-            group.removeTask(t);
-            if (group.getTaskCount() == 0) {
-                removeGroup(group);
-            }
-        }
-    }
-
-    /**
-     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
-     * how they should update themselves.
-     */
-    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
-        removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
-    }
-
-    /**
-     * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
-     * how they should update themselves.
-     */
-    public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
-            boolean dismissRecentsIfAllRemoved) {
-        if (mStackTaskList.contains(t)) {
-            removeTaskImpl(mStackTaskList, t);
-            Task newFrontMostTask = getStackFrontMostTask(false  /* includeFreeform */);
-            if (mCb != null) {
-                // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
-                        fromDockGesture, dismissRecentsIfAllRemoved);
-            }
-        }
-        mRawTaskList.remove(t);
-    }
-
-    /**
-     * Removes all tasks from the stack.
-     */
-    public void removeAllTasks(boolean notifyStackChanges) {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            Task t = tasks.get(i);
-            removeTaskImpl(mStackTaskList, t);
-            mRawTaskList.remove(t);
-        }
-        if (mCb != null && notifyStackChanges) {
-            // Notify that all tasks have been removed
-            mCb.onStackTasksRemoved(this);
-        }
-    }
-
-
-    /**
-     * @see #setTasks(Context, List, boolean, boolean)
-     */
-    public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) {
-        setTasks(context, stack.mRawTaskList, notifyStackChanges);
-    }
-
-    /**
-     * Sets a few tasks in one go, without calling any callbacks.
-     *
-     * @param tasks the new set of tasks to replace the current set.
-     * @param notifyStackChanges whether or not to callback on specific changes to the list of tasks.
-     */
-    public void setTasks(Context context, List<Task> tasks, boolean notifyStackChanges) {
-        // Compute a has set for each of the tasks
-        ArrayMap<Task.TaskKey, Task> currentTasksMap = createTaskKeyMapFromList(mRawTaskList);
-        ArrayMap<Task.TaskKey, Task> newTasksMap = createTaskKeyMapFromList(tasks);
-        ArrayList<Task> addedTasks = new ArrayList<>();
-        ArrayList<Task> removedTasks = new ArrayList<>();
-        ArrayList<Task> allTasks = new ArrayList<>();
-
-        // Disable notifications if there are no callbacks
-        if (mCb == null) {
-            notifyStackChanges = false;
-        }
-
-        // Remove any tasks that no longer exist
-        int taskCount = mRawTaskList.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task task = mRawTaskList.get(i);
-            if (!newTasksMap.containsKey(task.key)) {
-                if (notifyStackChanges) {
-                    removedTasks.add(task);
-                }
-            }
-            task.setGroup(null);
-        }
-
-        // Add any new tasks
-        taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task newTask = tasks.get(i);
-            Task currentTask = currentTasksMap.get(newTask.key);
-            if (currentTask == null && notifyStackChanges) {
-                addedTasks.add(newTask);
-            } else if (currentTask != null) {
-                // The current task has bound callbacks, so just copy the data from the new task
-                // state and add it back into the list
-                currentTask.copyFrom(newTask);
-                newTask = currentTask;
-            }
-            allTasks.add(newTask);
-        }
-
-        // Sort all the tasks to ensure they are ordered correctly
-        for (int i = allTasks.size() - 1; i >= 0; i--) {
-            allTasks.get(i).temporarySortIndexInStack = i;
-        }
-        Collections.sort(allTasks, FREEFORM_COMPARATOR);
-
-        mStackTaskList.set(allTasks);
-        mRawTaskList = allTasks;
-
-        // Update the affiliated groupings
-        createAffiliatedGroupings(context);
-
-        // Only callback for the removed tasks after the stack has updated
-        int removedTaskCount = removedTasks.size();
-        Task newFrontMostTask = getStackFrontMostTask(false);
-        for (int i = 0; i < removedTaskCount; i++) {
-            mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
-                    AnimationProps.IMMEDIATE, false /* fromDockGesture */,
-                    true /* dismissRecentsIfAllRemoved */);
-        }
-
-        // Only callback for the newly added tasks after this stack has been updated
-        int addedTaskCount = addedTasks.size();
-        for (int i = 0; i < addedTaskCount; i++) {
-            mCb.onStackTaskAdded(this, addedTasks.get(i));
-        }
-
-        // Notify that the task stack has been updated
-        if (notifyStackChanges) {
-            mCb.onStackTasksUpdated(this);
-        }
-    }
-
-    /**
-     * Gets the front-most task in the stack.
-     */
-    public Task getStackFrontMostTask(boolean includeFreeformTasks) {
-        ArrayList<Task> stackTasks = mStackTaskList.getTasks();
-        if (stackTasks.isEmpty()) {
-            return null;
-        }
-        for (int i = stackTasks.size() - 1; i >= 0; i--) {
-            Task task = stackTasks.get(i);
-            if (!task.isFreeformTask() || includeFreeformTasks) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /** Gets the task keys */
-    public ArrayList<Task.TaskKey> getTaskKeys() {
-        ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
-        ArrayList<Task> tasks = computeAllTasksList();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            taskKeys.add(task.key);
-        }
-        return taskKeys;
-    }
-
-    /**
-     * Returns the set of "active" (non-historical) tasks in the stack that have been used recently.
-     */
-    public ArrayList<Task> getStackTasks() {
-        return mStackTaskList.getTasks();
-    }
-
-    /**
-     * Returns the set of "freeform" tasks in the stack.
-     */
-    public ArrayList<Task> getFreeformTasks() {
-        ArrayList<Task> freeformTasks = new ArrayList<>();
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isFreeformTask()) {
-                freeformTasks.add(task);
-            }
-        }
-        return freeformTasks;
-    }
-
-    /**
-     * Computes a set of all the active and historical tasks.
-     */
-    public ArrayList<Task> computeAllTasksList() {
-        ArrayList<Task> tasks = new ArrayList<>();
-        tasks.addAll(mStackTaskList.getTasks());
-        return tasks;
-    }
-
-    /**
-     * Returns the number of stack and freeform tasks.
-     */
-    public int getTaskCount() {
-        return mStackTaskList.size();
-    }
-
-    /**
-     * Returns the number of stack tasks.
-     */
-    public int getStackTaskCount() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int stackCount = 0;
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (!task.isFreeformTask()) {
-                stackCount++;
-            }
-        }
-        return stackCount;
-    }
-
-    /**
-     * Returns the number of freeform tasks.
-     */
-    public int getFreeformTaskCount() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int freeformCount = 0;
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isFreeformTask()) {
-                freeformCount++;
-            }
-        }
-        return freeformCount;
-    }
-
-    /**
-     * Returns the task in stack tasks which is the launch target.
-     */
-    public Task getLaunchTarget() {
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.isLaunchTarget) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns whether the next launch target should actually be the PiP task.
-     */
-    public boolean isNextLaunchTargetPip(long lastPipTime) {
-        Task launchTarget = getLaunchTarget();
-        Task nextLaunchTarget = getNextLaunchTargetRaw();
-        if (nextLaunchTarget != null && lastPipTime > 0) {
-            // If the PiP time is more recent than the next launch target, then launch the PiP task
-            return lastPipTime > nextLaunchTarget.key.lastActiveTime;
-        } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
-            // Otherwise, if there is no next launch target, but there is a PiP, then launch
-            // the PiP task
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns the task in stack tasks which should be launched next if Recents are toggled
-     * again, or null if there is no task to be launched. Callers should check
-     * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
-     * stack.
-     */
-    public Task getNextLaunchTarget() {
-        Task nextLaunchTarget = getNextLaunchTargetRaw();
-        if (nextLaunchTarget != null) {
-            return nextLaunchTarget;
-        }
-        return getStackTasks().get(getTaskCount() - 1);
-    }
-
-    private Task getNextLaunchTargetRaw() {
-        int taskCount = getTaskCount();
-        if (taskCount == 0) {
-            return null;
-        }
-        int launchTaskIndex = indexOfStackTask(getLaunchTarget());
-        if (launchTaskIndex != -1 && launchTaskIndex > 0) {
-            return getStackTasks().get(launchTaskIndex - 1);
-        }
-        return null;
-    }
-
-    /** Returns the index of this task in this current task stack */
-    public int indexOfStackTask(Task t) {
-        return mStackTaskList.indexOf(t);
-    }
-
-    /** Finds the task with the specified task id. */
-    public Task findTaskWithId(int taskId) {
-        ArrayList<Task> tasks = computeAllTasksList();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            if (task.key.id == taskId) {
-                return task;
-            }
-        }
-        return null;
-    }
-
-    /******** Grouping ********/
-
-    /** Adds a group to the set */
-    public void addGroup(TaskGrouping group) {
-        mGroups.add(group);
-        mAffinitiesGroups.put(group.affiliation, group);
-    }
-
-    public void removeGroup(TaskGrouping group) {
-        mGroups.remove(group);
-        mAffinitiesGroups.remove(group.affiliation);
-    }
-
-    /** Returns the group with the specified affiliation. */
-    public TaskGrouping getGroupWithAffiliation(int affiliation) {
-        return mAffinitiesGroups.get(affiliation);
-    }
-
-    /**
-     * Temporary: This method will simulate affiliation groups
-     */
-    void createAffiliatedGroupings(Context context) {
-        mGroups.clear();
-        mAffinitiesGroups.clear();
-
-        if (RecentsDebugFlags.Static.EnableMockTaskGroups) {
-            ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>();
-            // Sort all tasks by increasing firstActiveTime of the task
-            ArrayList<Task> tasks = mStackTaskList.getTasks();
-            Collections.sort(tasks, new Comparator<Task>() {
-                @Override
-                public int compare(Task task, Task task2) {
-                    return Long.compare(task.key.firstActiveTime, task2.key.firstActiveTime);
-                }
-            });
-            // Create groups when sequential packages are the same
-            NamedCounter counter = new NamedCounter("task-group", "");
-            int taskCount = tasks.size();
-            String prevPackage = "";
-            int prevAffiliation = -1;
-            Random r = new Random();
-            int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
-            for (int i = 0; i < taskCount; i++) {
-                Task t = tasks.get(i);
-                String packageName = t.key.getComponent().getPackageName();
-                packageName = "pkg";
-                TaskGrouping group;
-                if (packageName.equals(prevPackage) && groupCountDown > 0) {
-                    group = getGroupWithAffiliation(prevAffiliation);
-                    groupCountDown--;
-                } else {
-                    int affiliation = IndividualTaskIdOffset + t.key.id;
-                    group = new TaskGrouping(affiliation);
-                    addGroup(group);
-                    prevAffiliation = affiliation;
-                    prevPackage = packageName;
-                    groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount;
-                }
-                group.addTask(t);
-                taskMap.put(t.key, t);
-            }
-            // Sort groups by increasing latestActiveTime of the group
-            Collections.sort(mGroups, new Comparator<TaskGrouping>() {
-                @Override
-                public int compare(TaskGrouping taskGrouping, TaskGrouping taskGrouping2) {
-                    return Long.compare(taskGrouping.latestActiveTimeInGroup,
-                            taskGrouping2.latestActiveTimeInGroup);
-                }
-            });
-            // Sort group tasks by increasing firstActiveTime of the task, and also build a new list
-            // of tasks
-            int taskIndex = 0;
-            int groupCount = mGroups.size();
-            for (int i = 0; i < groupCount; i++) {
-                TaskGrouping group = mGroups.get(i);
-                Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
-                    @Override
-                    public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
-                        return Long.compare(taskKey.firstActiveTime, taskKey2.firstActiveTime);
-                    }
-                });
-                ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
-                int groupTaskCount = groupTasks.size();
-                for (int j = 0; j < groupTaskCount; j++) {
-                    tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
-                    taskIndex++;
-                }
-            }
-            mStackTaskList.set(tasks);
-        } else {
-            // Create the task groups
-            ArrayMap<Task.TaskKey, Task> tasksMap = new ArrayMap<>();
-            ArrayList<Task> tasks = mStackTaskList.getTasks();
-            int taskCount = tasks.size();
-            for (int i = 0; i < taskCount; i++) {
-                Task t = tasks.get(i);
-                TaskGrouping group;
-                if (RecentsDebugFlags.Static.EnableAffiliatedTaskGroups) {
-                    int affiliation = t.affiliationTaskId > 0 ? t.affiliationTaskId :
-                            IndividualTaskIdOffset + t.key.id;
-                    if (mAffinitiesGroups.containsKey(affiliation)) {
-                        group = getGroupWithAffiliation(affiliation);
-                    } else {
-                        group = new TaskGrouping(affiliation);
-                        addGroup(group);
-                    }
-                } else {
-                    group = new TaskGrouping(t.key.id);
-                    addGroup(group);
-                }
-                group.addTask(t);
-                tasksMap.put(t.key, t);
-            }
-            // Update the task colors for each of the groups
-            float minAlpha = context.getResources().getFloat(
-                    R.dimen.recents_task_affiliation_color_min_alpha_percentage);
-            int taskGroupCount = mGroups.size();
-            for (int i = 0; i < taskGroupCount; i++) {
-                TaskGrouping group = mGroups.get(i);
-                taskCount = group.getTaskCount();
-                // Ignore the groups that only have one task
-                if (taskCount <= 1) continue;
-                // Calculate the group color distribution
-                int affiliationColor = tasksMap.get(group.mTaskKeys.get(0)).affiliationColor;
-                float alphaStep = (1f - minAlpha) / taskCount;
-                float alpha = 1f;
-                for (int j = 0; j < taskCount; j++) {
-                    Task t = tasksMap.get(group.mTaskKeys.get(j));
-                    t.colorPrimary = Utilities.getColorWithOverlay(affiliationColor, Color.WHITE,
-                            alpha);
-                    alpha -= alphaStep;
-                }
-            }
-        }
-    }
-
-    /**
-     * Computes the components of tasks in this stack that have been removed as a result of a change
-     * in the specified package.
-     */
-    public ArraySet<ComponentName> computeComponentsRemoved(String packageName, int userId) {
-        // Identify all the tasks that should be removed as a result of the package being removed.
-        // Using a set to ensure that we callback once per unique component.
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ArraySet<ComponentName> existingComponents = new ArraySet<>();
-        ArraySet<ComponentName> removedComponents = new ArraySet<>();
-        ArrayList<Task.TaskKey> taskKeys = getTaskKeys();
-        int taskKeyCount = taskKeys.size();
-        for (int i = 0; i < taskKeyCount; i++) {
-            Task.TaskKey t = taskKeys.get(i);
-
-            // Skip if this doesn't apply to the current user
-            if (t.userId != userId) continue;
-
-            ComponentName cn = t.getComponent();
-            if (cn.getPackageName().equals(packageName)) {
-                if (existingComponents.contains(cn)) {
-                    // If we know that the component still exists in the package, then skip
-                    continue;
-                }
-                if (ssp.getActivityInfo(cn, userId) != null) {
-                    existingComponents.add(cn);
-                } else {
-                    removedComponents.add(cn);
-                }
-            }
-        }
-        return removedComponents;
-    }
-
-    @Override
-    public String toString() {
-        String str = "Stack Tasks (" + mStackTaskList.size() + "):\n";
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            str += "    " + tasks.get(i).toString() + "\n";
-        }
-        return str;
-    }
-
-    /**
-     * Given a list of tasks, returns a map of each task's key to the task.
-     */
-    private ArrayMap<Task.TaskKey, Task> createTaskKeyMapFromList(List<Task> tasks) {
-        ArrayMap<Task.TaskKey, Task> map = new ArrayMap<>(tasks.size());
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            Task task = tasks.get(i);
-            map.put(task.key, task);
-        }
-        return map;
-    }
-
-    public void dump(String prefix, PrintWriter writer) {
-        String innerPrefix = prefix + "  ";
-
-        writer.print(prefix); writer.print(TAG);
-        writer.print(" numStackTasks="); writer.print(mStackTaskList.size());
-        writer.println();
-        ArrayList<Task> tasks = mStackTaskList.getTasks();
-        int taskCount = tasks.size();
-        for (int i = 0; i < taskCount; i++) {
-            tasks.get(i).dump(innerPrefix, writer);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java b/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
deleted file mode 100644
index 33ff1b6..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/model/ThumbnailData.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2016 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.recents.model;
-
-import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
-
-/**
- * Data for a single thumbnail.
- */
-public class ThumbnailData {
-
-    // TODO: Make these final once the non-snapshot path is removed.
-    public Bitmap thumbnail;
-    public int orientation;
-    public final Rect insets = new Rect();
-    public boolean reducedResolution;
-    public float scale;
-
-    public static ThumbnailData createFromTaskSnapshot(TaskSnapshot snapshot) {
-        ThumbnailData out = new ThumbnailData();
-        out.thumbnail = Bitmap.createHardwareBitmap(snapshot.getSnapshot());
-        out.insets.set(snapshot.getContentInsets());
-        out.orientation = snapshot.getOrientation();
-        out.reducedResolution = snapshot.isReducedResolution();
-        out.scale = snapshot.getScale();
-        return out;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 7998ecb..b598ec6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -22,7 +22,7 @@
 import android.view.ViewDebug;
 import android.view.ViewOutlineProvider;
 
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.Utilities;
 
 /* An outline provider that has a clip and outline that can be animated. */
 public class AnimateableViewBounds extends ViewOutlineProvider {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
deleted file mode 100644
index 716d1bc..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2016 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.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.util.SparseArray;
-import android.util.SparseLongArray;
-import android.view.View;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.List;
-
-/**
- * The generic set of animation properties to animate a {@link View}. The animation can have
- * different interpolators, start delays and durations for each of the different properties.
- */
-public class AnimationProps {
-
-    public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
-    public @interface PropType {}
-
-    public static final int ALL = 0;
-    public static final int TRANSLATION_X = 1;
-    public static final int TRANSLATION_Y = 2;
-    public static final int TRANSLATION_Z = 3;
-    public static final int ALPHA = 4;
-    public static final int SCALE = 5;
-    public static final int BOUNDS = 6;
-    public static final int DIM_ALPHA = 7;
-    public static final int FOCUS_STATE = 8;
-
-    private SparseLongArray mPropStartDelay;
-    private SparseLongArray mPropDuration;
-    private SparseArray<Interpolator> mPropInterpolators;
-    private Animator.AnimatorListener mListener;
-
-    /**
-     * The builder constructor.
-     */
-    public AnimationProps() {}
-
-    /**
-     * Creates an animation with a default {@param duration} and {@param interpolator} for all
-     * properties in this animation.
-     */
-    public AnimationProps(int duration, Interpolator interpolator) {
-        this(0, duration, interpolator, null);
-    }
-
-    /**
-     * Creates an animation with a default {@param duration} and {@param interpolator} for all
-     * properties in this animation.
-     */
-    public AnimationProps(int duration, Interpolator interpolator,
-            Animator.AnimatorListener listener) {
-        this(0, duration, interpolator, listener);
-    }
-
-    /**
-     * Creates an animation with a default {@param startDelay}, {@param duration} and
-     * {@param interpolator} for all properties in this animation.
-     */
-    public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
-        this(startDelay, duration, interpolator, null);
-    }
-
-    /**
-     * Creates an animation with a default {@param startDelay}, {@param duration} and
-     * {@param interpolator} for all properties in this animation.
-     */
-    public AnimationProps(int startDelay, int duration, Interpolator interpolator,
-            Animator.AnimatorListener listener) {
-        setStartDelay(ALL, startDelay);
-        setDuration(ALL, duration);
-        setInterpolator(ALL, interpolator);
-        setListener(listener);
-    }
-
-    /**
-     * Creates a new {@link AnimatorSet} that will animate the given animators.  Callers need to
-     * manually apply the individual animation properties for each of the animators respectively.
-     */
-    public AnimatorSet createAnimator(List<Animator> animators) {
-        AnimatorSet anim = new AnimatorSet();
-        if (mListener != null) {
-            anim.addListener(mListener);
-        }
-        anim.playTogether(animators);
-        return anim;
-    }
-
-    /**
-     * Applies the specific start delay, duration and interpolator to the given {@param animator}
-     * for the specified {@param propertyType}.
-     */
-    public <T extends ValueAnimator> T apply(@PropType int propertyType, T animator) {
-        animator.setStartDelay(getStartDelay(propertyType));
-        animator.setDuration(getDuration(propertyType));
-        animator.setInterpolator(getInterpolator(propertyType));
-        return animator;
-    }
-
-    /**
-     * Sets a start delay for a specific property.
-     */
-    public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
-        if (mPropStartDelay == null) {
-            mPropStartDelay = new SparseLongArray();
-        }
-        mPropStartDelay.append(propertyType, startDelay);
-        return this;
-    }
-
-    /**
-     * Returns the start delay for a specific property.
-     */
-    public long getStartDelay(@PropType int propertyType) {
-        if (mPropStartDelay != null) {
-            long startDelay = mPropStartDelay.get(propertyType, -1);
-            if (startDelay != -1) {
-                return startDelay;
-            }
-            return mPropStartDelay.get(ALL, 0);
-        }
-        return 0;
-    }
-
-    /**
-     * Sets a duration for a specific property.
-     */
-    public AnimationProps setDuration(@PropType int propertyType, int duration) {
-        if (mPropDuration == null) {
-            mPropDuration = new SparseLongArray();
-        }
-        mPropDuration.append(propertyType, duration);
-        return this;
-    }
-
-    /**
-     * Returns the duration for a specific property.
-     */
-    public long getDuration(@PropType int propertyType) {
-        if (mPropDuration != null) {
-            long duration = mPropDuration.get(propertyType, -1);
-            if (duration != -1) {
-                return duration;
-            }
-            return mPropDuration.get(ALL, 0);
-        }
-        return 0;
-    }
-
-    /**
-     * Sets an interpolator for a specific property.
-     */
-    public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
-        if (mPropInterpolators == null) {
-            mPropInterpolators = new SparseArray<>();
-        }
-        mPropInterpolators.append(propertyType, interpolator);
-        return this;
-    }
-
-    /**
-     * Returns the interpolator for a specific property, falling back to the general interpolator
-     * if there is no specific property interpolator.
-     */
-    public Interpolator getInterpolator(@PropType int propertyType) {
-        if (mPropInterpolators != null) {
-            Interpolator interp = mPropInterpolators.get(propertyType);
-            if (interp != null) {
-                return interp;
-            }
-            return mPropInterpolators.get(ALL, Interpolators.LINEAR);
-        }
-        return Interpolators.LINEAR;
-    }
-
-    /**
-     * Sets an animator listener for this animation.
-     */
-    public AnimationProps setListener(Animator.AnimatorListener listener) {
-        mListener = listener;
-        return this;
-    }
-
-    /**
-     * Returns the animator listener for this animation.
-     */
-    public Animator.AnimatorListener getListener() {
-        return mListener;
-    }
-
-    /**
-     * Returns whether this animation has any duration.
-     */
-    public boolean isImmediate() {
-        int count = mPropDuration.size();
-        for (int i = 0; i < count; i++) {
-            if (mPropDuration.valueAt(i) > 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
new file mode 100644
index 0000000..65b96fb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DockState.java
@@ -0,0 +1,351 @@
+/*
+ * 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.recents.views;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
+import android.util.IntProperty;
+import android.view.animation.Interpolator;
+
+import com.android.internal.policy.DockedDividerUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.recents.utilities.Utilities;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * The various possible dock states when dragging and dropping a task.
+ */
+public class DockState implements DropTarget {
+
+    public static final int DOCK_AREA_BG_COLOR = 0xFFffffff;
+    public static final int DOCK_AREA_GRID_BG_COLOR = 0xFF000000;
+
+    // The rotation to apply to the hint text
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({HORIZONTAL, VERTICAL})
+    public @interface TextOrientation {}
+    private static final int HORIZONTAL = 0;
+    private static final int VERTICAL = 1;
+
+    private static final int DOCK_AREA_ALPHA = 80;
+    public static final DockState NONE = new DockState(DOCKED_INVALID, -1, 80, 255, HORIZONTAL,
+            null, null, null);
+    public static final DockState LEFT = new DockState(DOCKED_LEFT,
+            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, VERTICAL,
+            new RectF(0, 0, 0.125f, 1), new RectF(0, 0, 0.125f, 1),
+            new RectF(0, 0, 0.5f, 1));
+    public static final DockState TOP = new DockState(DOCKED_TOP,
+            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+            new RectF(0, 0, 1, 0.125f), new RectF(0, 0, 1, 0.125f),
+            new RectF(0, 0, 1, 0.5f));
+    public static final DockState RIGHT = new DockState(DOCKED_RIGHT,
+            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, VERTICAL,
+            new RectF(0.875f, 0, 1, 1), new RectF(0.875f, 0, 1, 1),
+            new RectF(0.5f, 0, 1, 1));
+    public static final DockState BOTTOM = new DockState(DOCKED_BOTTOM,
+            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, DOCK_AREA_ALPHA, 0, HORIZONTAL,
+            new RectF(0, 0.875f, 1, 1), new RectF(0, 0.875f, 1, 1),
+            new RectF(0, 0.5f, 1, 1));
+
+    @Override
+    public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
+            boolean isCurrentTarget) {
+        if (isCurrentTarget) {
+            getMappedRect(expandedTouchDockArea, width, height, mTmpRect);
+            return mTmpRect.contains(x, y);
+        } else {
+            getMappedRect(touchArea, width, height, mTmpRect);
+            updateBoundsWithSystemInsets(mTmpRect, insets);
+            return mTmpRect.contains(x, y);
+        }
+    }
+
+    // Represents the view state of this dock state
+    public static class ViewState {
+        private static final IntProperty<ViewState> HINT_ALPHA =
+                new IntProperty<ViewState>("drawableAlpha") {
+                    @Override
+                    public void setValue(ViewState object, int alpha) {
+                        object.mHintTextAlpha = alpha;
+                        object.dockAreaOverlay.invalidateSelf();
+                    }
+
+                    @Override
+                    public Integer get(ViewState object) {
+                        return object.mHintTextAlpha;
+                    }
+                };
+
+        public final int dockAreaAlpha;
+        public final ColorDrawable dockAreaOverlay;
+        public final int hintTextAlpha;
+        public final int hintTextOrientation;
+
+        private final int mHintTextResId;
+        private String mHintText;
+        private Paint mHintTextPaint;
+        private Point mHintTextBounds = new Point();
+        private int mHintTextAlpha = 255;
+        private AnimatorSet mDockAreaOverlayAnimator;
+        private Rect mTmpRect = new Rect();
+
+        private ViewState(int areaAlpha, int hintAlpha, @TextOrientation int hintOrientation,
+                int hintTextResId) {
+            dockAreaAlpha = areaAlpha;
+            dockAreaOverlay = new ColorDrawable(Recents.getConfiguration().isGridEnabled
+                    ? DOCK_AREA_GRID_BG_COLOR : DOCK_AREA_BG_COLOR);
+            dockAreaOverlay.setAlpha(0);
+            hintTextAlpha = hintAlpha;
+            hintTextOrientation = hintOrientation;
+            mHintTextResId = hintTextResId;
+            mHintTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mHintTextPaint.setColor(Color.WHITE);
+        }
+
+        /**
+         * Updates the view state with the given context.
+         */
+        public void update(Context context) {
+            Resources res = context.getResources();
+            mHintText = context.getString(mHintTextResId);
+            mHintTextPaint.setTextSize(res.getDimensionPixelSize(
+                    R.dimen.recents_drag_hint_text_size));
+            mHintTextPaint.getTextBounds(mHintText, 0, mHintText.length(), mTmpRect);
+            mHintTextBounds.set((int) mHintTextPaint.measureText(mHintText), mTmpRect.height());
+        }
+
+        /**
+         * Draws the current view state.
+         */
+        public void draw(Canvas canvas) {
+            // Draw the overlay background
+            if (dockAreaOverlay.getAlpha() > 0) {
+                dockAreaOverlay.draw(canvas);
+            }
+
+            // Draw the hint text
+            if (mHintTextAlpha > 0) {
+                Rect bounds = dockAreaOverlay.getBounds();
+                int x = bounds.left + (bounds.width() - mHintTextBounds.x) / 2;
+                int y = bounds.top + (bounds.height() + mHintTextBounds.y) / 2;
+                mHintTextPaint.setAlpha(mHintTextAlpha);
+                if (hintTextOrientation == VERTICAL) {
+                    canvas.save();
+                    canvas.rotate(-90f, bounds.centerX(), bounds.centerY());
+                }
+                canvas.drawText(mHintText, x, y, mHintTextPaint);
+                if (hintTextOrientation == VERTICAL) {
+                    canvas.restore();
+                }
+            }
+        }
+
+        /**
+         * Creates a new bounds and alpha animation.
+         */
+        public void startAnimation(Rect bounds, int areaAlpha, int hintAlpha, int duration,
+                Interpolator interpolator, boolean animateAlpha, boolean animateBounds) {
+            if (mDockAreaOverlayAnimator != null) {
+                mDockAreaOverlayAnimator.cancel();
+            }
+
+            ObjectAnimator anim;
+            ArrayList<Animator> animators = new ArrayList<>();
+            if (dockAreaOverlay.getAlpha() != areaAlpha) {
+                if (animateAlpha) {
+                    anim = ObjectAnimator.ofInt(dockAreaOverlay,
+                            Utilities.DRAWABLE_ALPHA, dockAreaOverlay.getAlpha(), areaAlpha);
+                    anim.setDuration(duration);
+                    anim.setInterpolator(interpolator);
+                    animators.add(anim);
+                } else {
+                    dockAreaOverlay.setAlpha(areaAlpha);
+                }
+            }
+            if (mHintTextAlpha != hintAlpha) {
+                if (animateAlpha) {
+                    anim = ObjectAnimator.ofInt(this, HINT_ALPHA, mHintTextAlpha,
+                            hintAlpha);
+                    anim.setDuration(150);
+                    anim.setInterpolator(hintAlpha > mHintTextAlpha
+                            ? Interpolators.ALPHA_IN
+                            : Interpolators.ALPHA_OUT);
+                    animators.add(anim);
+                } else {
+                    mHintTextAlpha = hintAlpha;
+                    dockAreaOverlay.invalidateSelf();
+                }
+            }
+            if (bounds != null && !dockAreaOverlay.getBounds().equals(bounds)) {
+                if (animateBounds) {
+                    PropertyValuesHolder prop = PropertyValuesHolder.ofObject(
+                            Utilities.DRAWABLE_RECT, Utilities.RECT_EVALUATOR,
+                            new Rect(dockAreaOverlay.getBounds()), bounds);
+                    anim = ObjectAnimator.ofPropertyValuesHolder(dockAreaOverlay, prop);
+                    anim.setDuration(duration);
+                    anim.setInterpolator(interpolator);
+                    animators.add(anim);
+                } else {
+                    dockAreaOverlay.setBounds(bounds);
+                }
+            }
+            if (!animators.isEmpty()) {
+                mDockAreaOverlayAnimator = new AnimatorSet();
+                mDockAreaOverlayAnimator.playTogether(animators);
+                mDockAreaOverlayAnimator.start();
+            }
+        }
+    }
+
+    public final int dockSide;
+    public final int createMode;
+    public final ViewState viewState;
+    private final RectF touchArea;
+    private final RectF dockArea;
+    private final RectF expandedTouchDockArea;
+    private static final Rect mTmpRect = new Rect();
+
+    /**
+     * @param createMode used to pass to ActivityManager to dock the task
+     * @param touchArea the area in which touch will initiate this dock state
+     * @param dockArea the visible dock area
+     * @param expandedTouchDockArea the area in which touch will continue to dock after entering
+     *                              the initial touch area.  This is also the new dock area to
+     *                              draw.
+     */
+    DockState(int dockSide, int createMode, int dockAreaAlpha, int hintTextAlpha,
+            @TextOrientation int hintTextOrientation, RectF touchArea, RectF dockArea,
+            RectF expandedTouchDockArea) {
+        this.dockSide = dockSide;
+        this.createMode = createMode;
+        this.viewState = new ViewState(dockAreaAlpha, hintTextAlpha, hintTextOrientation,
+                R.string.recents_drag_hint_message);
+        this.dockArea = dockArea;
+        this.touchArea = touchArea;
+        this.expandedTouchDockArea = expandedTouchDockArea;
+    }
+
+    /**
+     * Updates the dock state with the given context.
+     */
+    public void update(Context context) {
+        viewState.update(context);
+    }
+
+    /**
+     * Returns the docked task bounds with the given {@param width} and {@param height}.
+     */
+    public Rect getPreDockedBounds(int width, int height, Rect insets) {
+        getMappedRect(dockArea, width, height, mTmpRect);
+        return updateBoundsWithSystemInsets(mTmpRect, insets);
+    }
+
+    /**
+     * Returns the expanded docked task bounds with the given {@param width} and
+     * {@param height}.
+     */
+    public Rect getDockedBounds(int width, int height, int dividerSize, Rect insets,
+            Resources res) {
+        // Calculate the docked task bounds
+        boolean isHorizontalDivision =
+                res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                insets, width, height, dividerSize);
+        Rect newWindowBounds = new Rect();
+        DockedDividerUtils.calculateBoundsForPosition(position, dockSide, newWindowBounds,
+                width, height, dividerSize);
+        return newWindowBounds;
+    }
+
+    /**
+     * Returns the task stack bounds with the given {@param width} and
+     * {@param height}.
+     */
+    public Rect getDockedTaskStackBounds(Rect displayRect, int width, int height,
+            int dividerSize, Rect insets, TaskStackLayoutAlgorithm layoutAlgorithm,
+            Resources res, Rect windowRectOut) {
+        // Calculate the inverse docked task bounds
+        boolean isHorizontalDivision =
+                res.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
+        int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
+                insets, width, height, dividerSize);
+        DockedDividerUtils.calculateBoundsForPosition(position,
+                DockedDividerUtils.invertDockSide(dockSide), windowRectOut, width, height,
+                dividerSize);
+
+        // Calculate the task stack bounds from the new window bounds
+        Rect taskStackBounds = new Rect();
+        // If the task stack bounds is specifically under the dock area, then ignore the top
+        // inset
+        int top = dockArea.bottom < 1f
+                ? 0
+                : insets.top;
+        // For now, ignore the left insets since we always dock on the left and show Recents
+        // on the right
+        layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, 0, insets.right,
+                taskStackBounds);
+        return taskStackBounds;
+    }
+
+    /**
+     * Returns the expanded bounds in certain dock sides such that the bounds account for the
+     * system insets (namely the vertical nav bar).  This call modifies and returns the given
+     * {@param bounds}.
+     */
+    private Rect updateBoundsWithSystemInsets(Rect bounds, Rect insets) {
+        if (dockSide == DOCKED_LEFT) {
+            bounds.right += insets.left;
+        } else if (dockSide == DOCKED_RIGHT) {
+            bounds.left -= insets.right;
+        }
+        return bounds;
+    }
+
+    /**
+     * Returns the mapped rect to the given dimensions.
+     */
+    private void getMappedRect(RectF bounds, int width, int height, Rect out) {
+        out.set((int) (bounds.left * width), (int) (bounds.top * height),
+                (int) (bounds.right * width), (int) (bounds.bottom * height));
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
deleted file mode 100644
index 035c058..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.RectF;
-import android.util.ArrayMap;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * The layout logic for the contents of the freeform workspace.
- */
-public class FreeformWorkspaceLayoutAlgorithm {
-
-    // Optimization, allows for quick lookup of task -> rect
-    private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
-
-    private int mTaskPadding;
-
-    public FreeformWorkspaceLayoutAlgorithm(Context context) {
-        reloadOnConfigurationChange(context);
-    }
-
-    /**
-     * Reloads the layout for the current configuration.
-     */
-    public void reloadOnConfigurationChange(Context context) {
-        // This is applied to the edges of each task
-        mTaskPadding = context.getResources().getDimensionPixelSize(
-                R.dimen.recents_freeform_layout_task_padding) / 2;
-    }
-
-    /**
-     * Updates the layout for each of the freeform workspace tasks.  This is called after the stack
-     * layout is updated.
-     */
-    public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
-        Collections.reverse(freeformTasks);
-        mTaskRectMap.clear();
-
-        int numFreeformTasks = stackLayout.mNumFreeformTasks;
-        if (!freeformTasks.isEmpty()) {
-
-            // Normalize the widths so that we can calculate the best layout below
-            int workspaceWidth = stackLayout.mFreeformRect.width();
-            int workspaceHeight = stackLayout.mFreeformRect.height();
-            float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
-            float normalizedWorkspaceHeight = 1f;
-            float[] normalizedTaskWidths = new float[numFreeformTasks];
-            for (int i = 0; i < numFreeformTasks; i++) {
-                Task task = freeformTasks.get(i);
-                float rowTaskWidth;
-                if (task.bounds != null) {
-                    rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
-                } else {
-                    // If this is a stack task that was dragged into the freeform workspace, then
-                    // the task will not yet have an associated bounds, so assume the full workspace
-                    // width for the time being
-                    rowTaskWidth = normalizedWorkspaceWidth;
-                }
-                // Bound the task width to the workspace width so that at the worst case, it will
-                // fit its own row
-                normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
-            }
-
-            // Determine the scale to best fit each of the tasks in the workspace
-            float rowScale = 0.85f;
-            float rowWidth = 0f;
-            float maxRowWidth = 0f;
-            int rowCount = 1;
-            for (int i = 0; i < numFreeformTasks;) {
-                float width = normalizedTaskWidths[i] * rowScale;
-                if (rowWidth + width > normalizedWorkspaceWidth) {
-                    // That is too long for this row, create new row
-                    if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
-                        // The new row is too high, so we need to try fitting again.  Update the
-                        // scale to be the smaller of the scale needed to fit the task in the
-                        // previous row, or the scale needed to fit the new row
-                        rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
-                                normalizedWorkspaceHeight / (rowCount + 1));
-                        rowCount = 1;
-                        rowWidth = 0;
-                        i = 0;
-                    } else {
-                        // The new row fits, so continue
-                        rowWidth = width;
-                        rowCount++;
-                        i++;
-                    }
-                } else {
-                    // Task is OK in this row
-                    rowWidth += width;
-                    i++;
-                }
-                maxRowWidth = Math.max(rowWidth, maxRowWidth);
-            }
-
-            // Normalize each of the actual rects to that scale
-            float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
-                    workspaceWidth) / 2f;
-            float rowLeft = defaultRowLeft;
-            float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
-            float rowHeight = rowScale * workspaceHeight;
-            for (int i = 0; i < numFreeformTasks; i++) {
-                Task task = freeformTasks.get(i);
-                float width = rowHeight * normalizedTaskWidths[i];
-                if (rowLeft + width > workspaceWidth) {
-                    // This goes on the next line
-                    rowTop += rowHeight;
-                    rowLeft = defaultRowLeft;
-                }
-                RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight);
-                rect.inset(mTaskPadding, mTaskPadding);
-                rowLeft += width;
-                mTaskRectMap.put(task.key, rect);
-            }
-        }
-    }
-
-    /**
-     * Returns whether the transform is available for the given task.
-     */
-    public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
-        if (stackLayout.mNumFreeformTasks == 0 || task == null) {
-            return false;
-        }
-        return mTaskRectMap.containsKey(task.key);
-    }
-
-    /**
-     * Returns the transform for the given task.  Any rect returned will be offset by the actual
-     * transform for the freeform workspace.
-     */
-    public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
-            TaskStackLayoutAlgorithm stackLayout) {
-        if (mTaskRectMap.containsKey(task.key)) {
-            final RectF ffRect = mTaskRectMap.get(task.key);
-
-            transformOut.scale = 1f;
-            transformOut.alpha = 1f;
-            transformOut.translationZ = stackLayout.mMaxTranslationZ;
-            transformOut.dimAlpha = 0f;
-            transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
-            transformOut.rect.set(ffRect);
-            transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
-            transformOut.visible = true;
-            return transformOut;
-        }
-        return null;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 4d33216..7442904 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -16,15 +16,9 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -32,13 +26,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -65,8 +56,8 @@
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
@@ -194,20 +185,9 @@
         } else {
             LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
                     screenPinningRequested);
-            if (task.group != null && !task.group.isFrontMostTask(task)) {
-                launchStartedEvent.addPostAnimationCallback(new Runnable() {
-                    @Override
-                    public void run() {
-                        startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                                windowingMode, activityType);
-                    }
-                });
-                EventBus.getDefault().send(launchStartedEvent);
-            } else {
-                EventBus.getDefault().send(launchStartedEvent);
-                startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                        windowingMode, activityType);
-            }
+            EventBus.getDefault().send(launchStartedEvent);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture, windowingMode,
+                    activityType);
         }
         Recents.getSystemServices().sendCloseSystemWindows(
                 StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY);
@@ -322,11 +302,6 @@
      */
     private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
             final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
-        if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
-                || windowingMode == WINDOWING_MODE_PINNED) {
-            return null;
-        }
-
         // Calculate the offscreen task rect (for tasks that are not backed by views)
         TaskView taskView = stackView.getChildViewForTask(task);
         TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
@@ -335,7 +310,6 @@
 
         // If this is a full screen stack, the transition will be towards the single, full screen
         // task. We only need the transition spec for this task.
-        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
 
         // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
         // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
@@ -344,6 +318,7 @@
                 || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                 || activityType == ACTIVITY_TYPE_ASSISTANT
                 || windowingMode == WINDOWING_MODE_UNDEFINED) {
+            List<AppTransitionAnimationSpec> specs = new ArrayList<>();
             if (taskView == null) {
                 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
             } else {
@@ -357,34 +332,7 @@
             }
             return specs;
         }
-
-        // Otherwise, for freeform tasks, create a new animation spec for each task we have to
-        // launch
-        TaskStack stack = stackView.getStack();
-        ArrayList<Task> tasks = stack.getStackTasks();
-        int taskCount = tasks.size();
-        for (int i = taskCount - 1; i >= 0; i--) {
-            Task t = tasks.get(i);
-            if (t.isFreeformTask() || windowingMode == WINDOWING_MODE_FREEFORM) {
-                TaskView tv = stackView.getChildViewForTask(t);
-                if (tv == null) {
-                    // TODO: Create a different animation task rect for this case (though it should
-                    //       never happen)
-                    specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
-                } else {
-                    mTmpTransform.fillIn(taskView);
-                    stackLayout.transformToScreenCoordinates(mTmpTransform,
-                            null /* windowOverrideRect */);
-                    AppTransitionAnimationSpec spec = composeAnimationSpec(stackView, tv,
-                            mTmpTransform, true /* addHeaderBitmap */);
-                    if (spec != null) {
-                        specs.add(spec);
-                    }
-                }
-            }
-        }
-
-        return specs;
+        return Collections.emptyList();
     }
 
     /**
@@ -468,7 +416,7 @@
         // force the task thumbnail to full stackView height immediately causing the transition
         // jarring.
         if (!Recents.getConfiguration().isLowRamDevice && taskView.getTask() !=
-                stackView.getStack().getStackFrontMostTask(false /* includeFreeformTasks */)) {
+                stackView.getStack().getStackFrontMostTask()) {
             taskRect.bottom = taskRect.top + stackView.getMeasuredHeight();
         }
         return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index fd1b806..5f12a04 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,10 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.ActivityOptions.OnAnimationStartedListener;
@@ -57,7 +53,6 @@
 import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
 import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
@@ -78,9 +73,9 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AnimationSpecComposer;
 import com.android.systemui.recents.views.RecentsTransitionHelper.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.stackdivider.WindowManagerProxy;
@@ -114,7 +109,6 @@
     private final int mStackButtonShadowColor;
 
     private boolean mAwaitingFirstLayout = true;
-    private boolean mLastTaskLaunchedWasFreeform;
 
     @ViewDebug.ExportedProperty(category="recents")
     Rect mSystemInsets = new Rect();
@@ -165,24 +159,20 @@
         mEmptyView = (TextView) inflater.inflate(R.layout.recents_empty, this, false);
         addView(mEmptyView);
 
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            if (mStackActionButton != null) {
-                removeView(mStackActionButton);
-            }
-            mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
-                            .isLowRamDevice
-                        ? R.layout.recents_low_ram_stack_action_button
-                        : R.layout.recents_stack_action_button,
-                    this, false);
-            mStackActionButton.setOnClickListener(
-                    v -> EventBus.getDefault().send(new DismissAllTaskViewsEvent()));
-
-            mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
-            mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
-                    mStackActionButton.getShadowDy());
-            mStackButtonShadowColor = mStackActionButton.getShadowColor();
-            addView(mStackActionButton);
+        if (mStackActionButton != null) {
+            removeView(mStackActionButton);
         }
+        mStackActionButton = (TextView) inflater.inflate(Recents.getConfiguration()
+                        .isLowRamDevice
+                    ? R.layout.recents_low_ram_stack_action_button
+                    : R.layout.recents_stack_action_button,
+                this, false);
+
+        mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
+        mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
+                mStackActionButton.getShadowDy());
+        mStackButtonShadowColor = mStackActionButton.getShadowColor();
+        addView(mStackActionButton);
 
         reevaluateStyles();
     }
@@ -205,10 +195,6 @@
                         mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
                         mStackButtonShadowColor);
             }
-            if (Recents.getConfiguration().isLowRamDevice) {
-                int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
-                mStackActionButton.setBackgroundColor(bgColor);
-            }
         }
 
         // Let's also require dark status and nav bars if the text is dark
@@ -238,7 +224,6 @@
 
         // Reset the state
         mAwaitingFirstLayout = !isResumingFromVisible;
-        mLastTaskLaunchedWasFreeform = false;
 
         // Update the stack
         mTaskStackView.onReload(isResumingFromVisible);
@@ -325,13 +310,6 @@
         }
     }
 
-    /**
-     * Returns whether the last task launched was in the freeform stack or not.
-     */
-    public boolean isLastTaskLaunchedFreeform() {
-        return mLastTaskLaunchedWasFreeform;
-    }
-
     /** Launches the focused task from the first stack if possible */
     public boolean launchFocusedTask(int logEvent) {
         if (mTaskStackView != null) {
@@ -377,9 +355,7 @@
         mEmptyView.setText(msgResId);
         mEmptyView.setVisibility(View.VISIBLE);
         mEmptyView.bringToFront();
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButton.bringToFront();
-        }
+        mStackActionButton.bringToFront();
     }
 
     /**
@@ -389,9 +365,7 @@
         mEmptyView.setVisibility(View.INVISIBLE);
         mTaskStackView.setVisibility(View.VISIBLE);
         mTaskStackView.bringToFront();
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButton.bringToFront();
-        }
+        mStackActionButton.bringToFront();
     }
 
     /**
@@ -439,13 +413,11 @@
                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
         }
 
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            // Measure the stack action button within the constraints of the space above the stack
-            Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
-            measureChild(mStackActionButton,
-                    MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
-        }
+        // Measure the stack action button within the constraints of the space above the stack
+        Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect();
+        measureChild(mStackActionButton,
+                MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
+                MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
 
         setMeasuredDimension(width, height);
     }
@@ -479,13 +451,11 @@
         mBackgroundScrim.setBounds(left, top, right, bottom);
         mMultiWindowBackgroundScrim.setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
 
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            // Layout the stack action button such that its drawable is start-aligned with the
-            // stack, vertically centered in the available space above the stack
-            Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
-            mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
-                    buttonBounds.bottom);
-        }
+        // Layout the stack action button such that its drawable is start-aligned with the
+        // stack, vertically centered in the available space above the stack
+        Rect buttonBounds = getStackActionButtonBoundsFromStackLayout();
+        mStackActionButton.layout(buttonBounds.left, buttonBounds.top, buttonBounds.right,
+                buttonBounds.bottom);
 
         if (mAwaitingFirstLayout) {
             mAwaitingFirstLayout = false;
@@ -527,7 +497,7 @@
     public void onDrawForeground(Canvas canvas) {
         super.onDrawForeground(canvas);
 
-        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
             visDockStates.get(i).viewState.draw(canvas);
         }
@@ -535,7 +505,7 @@
 
     @Override
     protected boolean verifyDrawable(Drawable who) {
-        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
             Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
             if (d == who) {
@@ -548,21 +518,18 @@
     /**** EventBus Events ****/
 
     public final void onBusEvent(LaunchTaskEvent event) {
-        mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
         mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
                 event.taskView, event.screenPinningRequested, event.targetWindowingMode,
                 event.targetActivityType);
         if (Recents.getConfiguration().isLowRamDevice) {
-            hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, false /* translate */);
+            EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
         }
     }
 
     public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            // Hide the stack action button
-            hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
-        }
+        // Hide the stack action button
+        EventBus.getDefault().send(new HideStackActionButtonEvent());
         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
 
         if (Recents.getConfiguration().isLowRamDevice) {
@@ -572,8 +539,8 @@
 
     public final void onBusEvent(DragStartEvent event) {
         updateVisibleDockRegions(Recents.getConfiguration().getDockStatesForCurrentOrientation(),
-                true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
-                TaskStack.DockState.NONE.viewState.hintTextAlpha,
+                true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+                DockState.NONE.viewState.hintTextAlpha,
                 true /* animateAlpha */, false /* animateBounds */);
 
         // Temporarily hide the stack action button without changing visibility
@@ -587,15 +554,15 @@
     }
 
     public final void onBusEvent(DragDropTargetChangedEvent event) {
-        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
+        if (event.dropTarget == null || !(event.dropTarget instanceof DockState)) {
             updateVisibleDockRegions(
                     Recents.getConfiguration().getDockStatesForCurrentOrientation(),
-                    true /* isDefaultDockState */, TaskStack.DockState.NONE.viewState.dockAreaAlpha,
-                    TaskStack.DockState.NONE.viewState.hintTextAlpha,
+                    true /* isDefaultDockState */, DockState.NONE.viewState.dockAreaAlpha,
+                    DockState.NONE.viewState.hintTextAlpha,
                     true /* animateAlpha */, true /* animateBounds */);
         } else {
-            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
-            updateVisibleDockRegions(new TaskStack.DockState[] {dockState},
+            final DockState dockState = (DockState) event.dropTarget;
+            updateVisibleDockRegions(new DockState[] {dockState},
                     false /* isDefaultDockState */, -1, -1, true /* animateAlpha */,
                     true /* animateBounds */);
         }
@@ -614,8 +581,8 @@
 
     public final void onBusEvent(final DragEndEvent event) {
         // Handle the case where we drop onto a dock region
-        if (event.dropTarget instanceof TaskStack.DockState) {
-            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+        if (event.dropTarget instanceof DockState) {
+            final DockState dockState = (DockState) event.dropTarget;
 
             // Hide the dock region
             updateVisibleDockRegions(null, false /* isDefaultDockState */, -1, -1,
@@ -722,13 +689,10 @@
             animateBackgroundScrim(getOpaqueScrimAlpha(),
                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
         }
-        if (Recents.getConfiguration().isLowRamDevice && mEmptyView.getVisibility() != View.VISIBLE) {
-            showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, false /* translate */);
-        }
     }
 
     public final void onBusEvent(AllTaskViewsDismissedEvent event) {
-        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
+        EventBus.getDefault().send(new HideStackActionButtonEvent());
     }
 
     public final void onBusEvent(DismissAllTaskViewsEvent event) {
@@ -740,18 +704,10 @@
     }
 
     public final void onBusEvent(ShowStackActionButtonEvent event) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, event.translate);
     }
 
     public final void onBusEvent(HideStackActionButtonEvent event) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
     }
 
@@ -767,16 +723,13 @@
      * Shows the stack action button.
      */
     private void showStackActionButton(final int duration, final boolean translate) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
         if (mStackActionButton.getVisibility() == View.INVISIBLE) {
             mStackActionButton.setVisibility(View.VISIBLE);
             mStackActionButton.setAlpha(0f);
             if (translate) {
-                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
+                        (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
             } else {
                 mStackActionButton.setTranslationY(0f);
             }
@@ -802,10 +755,6 @@
      * Hides the stack action button.
      */
     private void hideStackActionButton(int duration, boolean translate) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         final ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger();
         hideStackActionButton(duration, translate, postAnimationTrigger);
         postAnimationTrigger.flushLastDecrementRunnables();
@@ -816,14 +765,10 @@
      */
     private void hideStackActionButton(int duration, boolean translate,
                                        final ReferenceCountedTrigger postAnimationTrigger) {
-        if (!RecentsDebugFlags.Static.EnableStackActionButton) {
-            return;
-        }
-
         if (mStackActionButton.getVisibility() == View.VISIBLE) {
             if (translate) {
-                mStackActionButton.animate()
-                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
+                        * (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
             }
             mStackActionButton.animate()
                     .alpha(0f)
@@ -866,15 +811,15 @@
     /**
      * Updates the dock region to match the specified dock state.
      */
-    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates,
+    private void updateVisibleDockRegions(DockState[] newDockStates,
             boolean isDefaultDockState, int overrideAreaAlpha, int overrideHintAlpha,
             boolean animateAlpha, boolean animateBounds) {
-        ArraySet<TaskStack.DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
-                new ArraySet<TaskStack.DockState>());
-        ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+        ArraySet<DockState> newDockStatesSet = Utilities.arrayToSet(newDockStates,
+                new ArraySet<DockState>());
+        ArrayList<DockState> visDockStates = mTouchHandler.getVisibleDockStates();
         for (int i = visDockStates.size() - 1; i >= 0; i--) {
-            TaskStack.DockState dockState = visDockStates.get(i);
-            TaskStack.DockState.ViewState viewState = dockState.viewState;
+            DockState dockState = visDockStates.get(i);
+            DockState.ViewState viewState = dockState.viewState;
             if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
                 // This is no longer visible, so hide it
                 viewState.startAnimation(null, 0, 0, TaskStackView.SLOW_SYNC_STACK_DURATION,
@@ -935,7 +880,7 @@
     /**
      * @return the bounds of the stack action button.
      */
-    private Rect getStackActionButtonBoundsFromStackLayout() {
+    Rect getStackActionButtonBoundsFromStackLayout() {
         Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
         int left, top;
         if (Recents.getConfiguration().isLowRamDevice) {
@@ -957,6 +902,16 @@
         return actionButtonRect;
     }
 
+    View getStackActionButton() {
+        return mStackActionButton;
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+        mTouchHandler.cancelStackActionButtonClick();
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "  ";
         String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 46619c2..0cfdbde 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -17,12 +17,12 @@
 package com.android.systemui.recents.views;
 
 import android.app.ActivityManager;
-import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 
@@ -31,6 +31,7 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -38,8 +39,8 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 import java.util.ArrayList;
 
@@ -69,7 +70,7 @@
     private DropTarget mLastDropTarget;
     private DividerSnapAlgorithm mDividerSnapAlgorithm;
     private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
-    private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
+    private ArrayList<DockState> mVisibleDockStates = new ArrayList<>();
 
     public RecentsViewTouchHandler(RecentsView rv) {
         mRv = rv;
@@ -93,14 +94,13 @@
     /**
      * Returns the set of visible dock states for this current drag.
      */
-    public ArrayList<TaskStack.DockState> getVisibleDockStates() {
+    public ArrayList<DockState> getVisibleDockStates() {
         return mVisibleDockStates;
     }
 
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        handleTouchEvent(ev);
-        return mDragRequested;
+        return handleTouchEvent(ev) || mDragRequested;
     }
 
     /** Handles touch events once we have intercepted them */
@@ -146,9 +146,9 @@
                 EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
             } else {
                 // Add the dock state drop targets (these take priority)
-                TaskStack.DockState[] dockStates = Recents.getConfiguration()
+                DockState[] dockStates = Recents.getConfiguration()
                         .getDockStatesForCurrentOrientation();
-                for (TaskStack.DockState dockState : dockStates) {
+                for (DockState dockState : dockStates) {
                     registerDropTargetForCurrentDrag(dockState);
                     dockState.update(mRv.getContext());
                     mVisibleDockStates.add(dockState);
@@ -183,22 +183,47 @@
         }
     }
 
+    void cancelStackActionButtonClick() {
+        mRv.getStackActionButton().setPressed(false);
+    }
+
+    private boolean isWithinStackActionButton(float x, float y) {
+        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+        return mRv.getStackActionButton().getVisibility() == View.VISIBLE &&
+                mRv.getStackActionButton().pointInView(x - rect.left, y - rect.top, 0 /* slop */);
+    }
+
+    private void changeStackActionButtonDrawableHotspot(float x, float y) {
+        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+        mRv.getStackActionButton().drawableHotspotChanged(x - rect.left, y - rect.top);
+    }
+
     /**
      * Handles dragging touch events
      */
-    private void handleTouchEvent(MotionEvent ev) {
+    private boolean handleTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
+        boolean consumed = false;
+        float evX = ev.getX();
+        float evY = ev.getY();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                mDownPos.set((int) ev.getX(), (int) ev.getY());
+                mDownPos.set((int) evX, (int) evY);
                 mDeviceId = ev.getDeviceId();
+
+                if (isWithinStackActionButton(evX, evY)) {
+                    changeStackActionButtonDrawableHotspot(evX, evY);
+                    mRv.getStackActionButton().setPressed(true);
+                }
                 break;
             case MotionEvent.ACTION_MOVE: {
-                float evX = ev.getX();
-                float evY = ev.getY();
                 float x = evX - mTaskViewOffset.x;
                 float y = evY - mTaskViewOffset.y;
 
+                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+                    changeStackActionButtonDrawableHotspot(evX, evY);
+                }
+
                 if (mDragRequested) {
                     if (!mIsDragging) {
                         mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
@@ -232,9 +257,7 @@
                             EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
                                     currentDropTarget));
                         }
-
                     }
-
                     mTaskView.setTranslationX(x);
                     mTaskView.setTranslationY(y);
                 }
@@ -242,6 +265,11 @@
             }
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL: {
+                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
+                    consumed = true;
+                }
+                cancelStackActionButtonClick();
                 if (mDragRequested) {
                     boolean cancelled = action == MotionEvent.ACTION_CANCEL;
                     if (cancelled) {
@@ -254,5 +282,6 @@
                 mDeviceId = -1;
             }
         }
+        return consumed;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
index 8f784b8..7827c59 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/SystemBarScrimViews.java
@@ -30,7 +30,7 @@
 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
 
 /** Manages the scrims for the various system bars. */
 public class SystemBarScrimViews {
@@ -159,7 +159,7 @@
 
     public final void onBusEvent(final DragEndEvent event) {
         // Hide the nav bar scrims once we drop to a dock region
-        if (event.dropTarget instanceof TaskStack.DockState) {
+        if (event.dropTarget instanceof DockState) {
             animateScrimToCurrentNavBarState(false /* hasStackTasks */);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 81bf6af..26db26f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -18,13 +18,11 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.util.Log;
-import android.view.View;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
@@ -37,9 +35,10 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -161,20 +160,12 @@
         for (int i = taskViews.size() - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
-                    launchTargetTask.group != null &&
-                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
-            boolean hideTask = launchTargetTask != null &&
-                    launchTargetTask.isFreeformTask() &&
-                    task.isFreeformTask();
 
             // Get the current transform for the task, which will be used to position it offscreen
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
                     null);
 
-            if (hideTask) {
-                tv.setVisibility(View.INVISIBLE);
-            } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
+            if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
                 if (task.isLaunchTarget) {
                     tv.onPrepareLaunchTargetForEnterAnimation();
                 } else if (isLowRamDevice && i >= taskViews.size() -
@@ -195,13 +186,6 @@
                     // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
                     mStackView.updateTaskViewToTransform(tv, mTmpTransform,
                             new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
-                } else if (currentTaskOccludesLaunchTarget) {
-                    // Move the task view slightly lower so we can animate it in
-                    mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
-                    mTmpTransform.alpha = 0f;
-                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
-                            AnimationProps.IMMEDIATE);
-                    tv.setClipViewInStack(false);
                 }
             } else if (launchState.launchedFromHome) {
                 if (isLowRamDevice) {
@@ -266,9 +250,6 @@
             int taskIndexFromBack = i;
             final TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
-                    launchTargetTask.group != null &&
-                    launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
 
             // Get the current transform for the task, which will be updated to the final transform
             // to animate to depending on how recents was invoked
@@ -280,21 +261,6 @@
                     tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
                             taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
                             postAnimationTrigger);
-                } else {
-                    // Animate the task up if it was occluding the launch target
-                    if (currentTaskOccludesLaunchTarget) {
-                        AnimationProps taskAnimation = new AnimationProps(
-                                taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
-                                new AnimatorListenerAdapter() {
-                                    @Override
-                                    public void onAnimationEnd(Animator animation) {
-                                        postAnimationTrigger.decrement();
-                                        tv.setClipViewInStack(true);
-                                    }
-                                });
-                        postAnimationTrigger.increment();
-                        mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
-                    }
                 }
 
             } else if (launchState.launchedFromHome) {
@@ -423,9 +389,6 @@
         for (int i = 0; i < taskViewCount; i++) {
             TaskView tv = taskViews.get(i);
             Task task = tv.getTask();
-            boolean currentTaskOccludesLaunchTarget = launchingTask != null &&
-                    launchingTask.group != null &&
-                    launchingTask.group.isTaskAboveTask(task, launchingTask);
 
             if (tv == launchingTaskView) {
                 tv.setClipViewInStack(false);
@@ -437,17 +400,6 @@
                 });
                 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
                         screenPinningRequested, postAnimationTrigger);
-            } else if (currentTaskOccludesLaunchTarget) {
-                // Animate this task out of view
-                AnimationProps taskAnimation = new AnimationProps(
-                        taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
-                        postAnimationTrigger.decrementOnAnimationEnd());
-                postAnimationTrigger.increment();
-
-                mTmpTransform.fillIn(tv);
-                mTmpTransform.alpha = 0f;
-                mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
-                mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
             }
         }
     }
@@ -611,7 +563,7 @@
                 false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
 
         // Hide the front most task view until the scroll is complete
-        Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
+        Task frontMostTask = newStack.getStackFrontMostTask();
         final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
         final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
                 stackTasks.indexOf(frontMostTask));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index eaa32ee..acb058c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.MutableFloat;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.ViewDebug;
@@ -36,9 +35,9 @@
 import com.android.systemui.recents.RecentsDebugFlags;
 import com.android.systemui.recents.misc.FreePathInterpolator;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 
@@ -147,75 +146,6 @@
     }
 
     /**
-     * The various stack/freeform states.
-     */
-    public static class StackState {
-
-        public static final StackState FREEFORM_ONLY = new StackState(1f, 255);
-        public static final StackState STACK_ONLY = new StackState(0f, 0);
-        public static final StackState SPLIT = new StackState(0.5f, 255);
-
-        public final float freeformHeightPct;
-        public final int freeformBackgroundAlpha;
-
-        /**
-         * @param freeformHeightPct the percentage of the stack height (not including paddings) to
-         *                          allocate to the freeform workspace
-         * @param freeformBackgroundAlpha the background alpha for the freeform workspace
-         */
-        private StackState(float freeformHeightPct, int freeformBackgroundAlpha) {
-            this.freeformHeightPct = freeformHeightPct;
-            this.freeformBackgroundAlpha = freeformBackgroundAlpha;
-        }
-
-        /**
-         * Resolves the stack state for the layout given a task stack.
-         */
-        public static StackState getStackStateForStack(TaskStack stack) {
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
-            int freeformCount = stack.getFreeformTaskCount();
-            int stackCount = stack.getStackTaskCount();
-            if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
-                return SPLIT;
-            } else if (hasFreeformWorkspaces && freeformCount > 0) {
-                return FREEFORM_ONLY;
-            } else {
-                return STACK_ONLY;
-            }
-        }
-
-        /**
-         * Computes the freeform and stack rect for this state.
-         *
-         * @param freeformRectOut the freeform rect to be written out
-         * @param stackRectOut the stack rect, we only write out the top of the stack
-         * @param taskStackBounds the full rect that the freeform rect can take up
-         */
-        public void computeRects(Rect freeformRectOut, Rect stackRectOut,
-                Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
-            // The freeform height is the visible height (not including system insets) - padding
-            // above freeform and below stack - gap between the freeform and stack
-            int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
-            int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
-            int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
-            freeformRectOut.set(taskStackBounds.left,
-                    taskStackBounds.top + topMargin,
-                    taskStackBounds.right,
-                    taskStackBounds.top + topMargin + ffHeight);
-            stackRectOut.set(taskStackBounds.left,
-                    taskStackBounds.top,
-                    taskStackBounds.right,
-                    taskStackBounds.bottom);
-            if (ffPaddedHeight > 0) {
-                stackRectOut.top += ffPaddedHeight;
-            } else {
-                stackRectOut.top += topMargin;
-            }
-        }
-    }
-
-    /**
      * @return True if we should use the grid layout.
      */
     boolean useGridLayout() {
@@ -234,15 +164,11 @@
     }
 
     Context mContext;
-    private StackState mState = StackState.SPLIT;
     private TaskStackLayoutAlgorithmCallbacks mCb;
 
     // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mTaskRect = new Rect();
-    // The freeform workspace bounds, inset by the top system insets and is a fixed height
-    @ViewDebug.ExportedProperty(category="recents")
-    public Rect mFreeformRect = new Rect();
     // The stack bounds, inset from the top system insets, and runs to the bottom of the screen
     @ViewDebug.ExportedProperty(category="recents")
     public Rect mStackRect = new Rect();
@@ -268,10 +194,6 @@
     private int mBaseBottomMargin;
     private int mMinMargin;
 
-    // The gap between the freeform and stack layouts
-    @ViewDebug.ExportedProperty(category="recents")
-    private int mFreeformStackGap;
-
     // The initial offset that the focused task is from the top
     @ViewDebug.ExportedProperty(category="recents")
     private int mInitialTopOffset;
@@ -331,8 +253,6 @@
     // The last computed task counts
     @ViewDebug.ExportedProperty(category="recents")
     int mNumStackTasks;
-    @ViewDebug.ExportedProperty(category="recents")
-    int mNumFreeformTasks;
 
     // The min/max z translations
     @ViewDebug.ExportedProperty(category="recents")
@@ -344,8 +264,6 @@
     private SparseIntArray mTaskIndexMap = new SparseIntArray();
     private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
 
-    // The freeform workspace layout
-    FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
     TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
     TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm;
 
@@ -356,7 +274,6 @@
     public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
         mContext = context;
         mCb = cb;
-        mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
         mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
         mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context);
         reloadOnConfigurationChange(context);
@@ -393,7 +310,6 @@
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet);
-        mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
         mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
         mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context);
         mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
@@ -408,8 +324,6 @@
                 R.dimen.recents_layout_side_margin_tablet_xlarge,
                 R.dimen.recents_layout_side_margin_tablet);
         mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
-        mFreeformStackGap =
-                res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
         mTitleBarHeight = getDimensionForDevice(mContext,
                 R.dimen.recents_task_view_header_height,
                 R.dimen.recents_task_view_header_height,
@@ -462,8 +376,7 @@
      * Computes the stack and task rects.  The given task stack bounds already has the top/right
      * insets and left/right padding already applied.
      */
-    public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds,
-            StackState state) {
+    public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) {
         Rect lastStackRect = new Rect(mStackRect);
 
         int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
@@ -474,10 +387,9 @@
         mInitialBottomOffset = mBaseInitialBottomOffset;
 
         // Compute the stack bounds
-        mState = state;
         mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
-        state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
-                mFreeformStackGap, mStackBottomOffset);
+        mStackRect.set(taskStackBounds);
+        mStackRect.top += topMargin;
 
         // The stack action button will take the full un-padded header space above the stack
         mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
@@ -530,26 +442,20 @@
         if (tasks.isEmpty()) {
             mFrontMostTaskP = 0;
             mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
-            mNumStackTasks = mNumFreeformTasks = 0;
+            mNumStackTasks = 0;
             return;
         }
 
-        // Filter the set of freeform and stack tasks
-        ArrayList<Task> freeformTasks = new ArrayList<>();
+        // Filter the set of stack tasks
         ArrayList<Task> stackTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
             Task task = tasks.get(i);
             if (ignoreTasksSet.contains(task.key)) {
                 continue;
             }
-            if (task.isFreeformTask()) {
-                freeformTasks.add(task);
-            } else {
-                stackTasks.add(task);
-            }
+            stackTasks.add(task);
         }
         mNumStackTasks = stackTasks.size();
-        mNumFreeformTasks = freeformTasks.size();
 
         // Put each of the tasks in the progress map at a fixed index (does not need to actually
         // map to a scroll position, just by index)
@@ -559,11 +465,6 @@
             mTaskIndexMap.put(task.key.id, i);
         }
 
-        // Update the freeform tasks
-        if (!freeformTasks.isEmpty()) {
-            mFreeformLayoutAlgorithm.update(freeformTasks, this);
-        }
-
         // Calculate the min/max/initial scroll
         Task launchTask = stack.getLaunchTarget();
         int launchTaskIndex = launchTask != null
@@ -582,7 +483,7 @@
             } else {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
             }
-        } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+        } else if (mNumStackTasks == 1) {
             // If there is one stack task, ignore the min/max/initial scroll positions
             mMinScrollP = 0;
             mMaxScrollP = 0;
@@ -603,9 +504,7 @@
             boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
                     || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
 
-            if (launchState.launchedFromBlacklistedApp) {
-                mInitialScrollP = mMaxScrollP;
-            } else if (launchState.launchedWithAltTab) {
+            if (launchState.launchedWithAltTab) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
             } else if (Recents.getConfiguration().isLowRamDevice) {
                 mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
@@ -633,7 +532,6 @@
         boolean scrollToFront = launchState.launchedFromHome ||
                 launchState.launchedFromPipApp ||
                 launchState.launchedWithNextPipApp ||
-                launchState.launchedFromBlacklistedApp ||
                 launchState.launchedViaDockGesture;
         if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
             if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
@@ -767,7 +665,7 @@
     public int getInitialFocusState() {
         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-        if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
+        if (launchState.launchedWithAltTab) {
             return STATE_FOCUSED;
         } else {
             return STATE_UNFOCUSED;
@@ -794,13 +692,6 @@
     }
 
     /**
-     * Returns the current stack state.
-     */
-    public StackState getStackState() {
-        return mState;
-    }
-
-    /**
      * Returns whether this stack layout has been initialized.
      */
     public boolean isInitialized() {
@@ -825,62 +716,44 @@
             return new VisibilityReport(1, 1);
         }
 
-        // Quick return when there are no stack tasks
-        if (mNumStackTasks == 0) {
-            return new VisibilityReport(mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0,
-                    mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0);
-        }
-
         // Otherwise, walk backwards in the stack and count the number of tasks and visible
-        // thumbnails and add that to the total freeform task count
+        // thumbnails and add that to the total task count
         TaskViewTransform tmpTransform = new TaskViewTransform();
         Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
         currentRange.offset(mInitialScrollP);
         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
                 R.dimen.recents_task_view_header_height);
-        int numVisibleTasks = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 1) : 0;
-        int numVisibleThumbnails = mNumFreeformTasks > 0 ? Math.max(mNumFreeformTasks, 0) : 0;
+        int numVisibleTasks = 0;
+        int numVisibleThumbnails = 0;
         float prevScreenY = Integer.MAX_VALUE;
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
 
-            // Skip freeform
-            if (task.isFreeformTask()) {
-                continue;
-            }
-
             // Skip invisible
             float taskProgress = getStackScrollForTask(task);
             if (!currentRange.isInRange(taskProgress)) {
                 continue;
             }
 
-            boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
-            if (isFrontMostTaskInGroup) {
-                getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
-                        tmpTransform, null, false /* ignoreSingleTaskCase */,
-                        false /* forceUpdate */);
-                float screenY = tmpTransform.rect.top;
-                boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
-                if (hasVisibleThumbnail) {
-                    numVisibleThumbnails++;
-                    numVisibleTasks++;
-                    prevScreenY = screenY;
-                } else {
-                    // Once we hit the next front most task that does not have a visible thumbnail,
-                    // walk through remaining visible set
-                    for (int j = i; j >= 0; j--) {
-                        taskProgress = getStackScrollForTask(tasks.get(j));
-                        if (!currentRange.isInRange(taskProgress)) {
-                            break;
-                        }
-                        numVisibleTasks++;
-                    }
-                    break;
-                }
-            } else {
-                // Affiliated task, no thumbnail
+            getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
+                    tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */);
+            float screenY = tmpTransform.rect.top;
+            boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
+            if (hasVisibleThumbnail) {
+                numVisibleThumbnails++;
                 numVisibleTasks++;
+                prevScreenY = screenY;
+            } else {
+                // Once we hit the next front most task that does not have a visible thumbnail,
+                // walk through remaining visible set
+                for (int j = i; j >= 0; j--) {
+                    taskProgress = getStackScrollForTask(tasks.get(j));
+                    if (!currentRange.isInRange(taskProgress)) {
+                        break;
+                    }
+                    numVisibleTasks++;
+                }
+                break;
             }
         }
         return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
@@ -906,10 +779,7 @@
     public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
             TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
             boolean ignoreTaskOverrides) {
-        if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
-            mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
-            return transformOut;
-        } else if (useGridLayout()) {
+        if (useGridLayout()) {
             int taskIndex = mTaskIndexMap.get(task.key.id);
             int taskCount = mTaskIndexMap.size();
             mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
@@ -1024,7 +894,7 @@
         float z;
         float dimAlpha;
         float viewOutlineAlpha;
-        if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
+        if (mNumStackTasks == 1 && !ignoreSingleTaskCase) {
             // When there is exactly one task, then decouple the task from the stack and just move
             // in screen space
             float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
@@ -1378,7 +1248,6 @@
         writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
         writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
         writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
-        writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
         writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
         writer.println();
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 74e9ef2..428113a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,23 +16,15 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.ArrayMap;
@@ -65,7 +57,6 @@
 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
-import com.android.systemui.recents.events.activity.IterateRecentsEvent;
 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
@@ -84,13 +75,11 @@
 import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
 import com.android.systemui.recents.events.ui.RecentsGrowingEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
 import com.android.systemui.recents.events.ui.UserInteractionEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
@@ -98,9 +87,10 @@
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.grid.GridTaskView;
 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import com.android.systemui.recents.views.grid.TaskViewFocusFrame;
@@ -154,8 +144,6 @@
     @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_")
     private TaskStackViewTouchHandler mTouchHandler;
     private TaskStackAnimationHelper mAnimationHelper;
-    private GradientDrawable mFreeformWorkspaceBackground;
-    private ObjectAnimator mFreeformWorkspaceBackgroundAnimator;
     private ViewPool<TaskView, Task> mViewPool;
 
     private ArrayList<TaskView> mTaskViews = new ArrayList<>();
@@ -240,20 +228,6 @@
                 }
             };
 
-    // The drop targets for a task drag
-    private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
-        @Override
-        public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
-                boolean isCurrentTarget) {
-            // This drop target has a fixed bounds and should be checked last, so just fall through
-            // if it is the current target
-            if (!isCurrentTarget) {
-                return mLayoutAlgorithm.mFreeformRect.contains(x, y);
-            }
-            return false;
-        }
-    };
-
     private DropTarget mStackDropTarget = new DropTarget() {
         @Override
         public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
@@ -313,17 +287,6 @@
             }
         });
         setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            setWillNotDraw(false);
-        }
-
-        mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
-                R.drawable.recents_freeform_workspace_bg);
-        mFreeformWorkspaceBackground.setCallback(this);
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            mFreeformWorkspaceBackground.setColor(
-                    getContext().getColor(R.color.recents_freeform_workspace_bg_color));
-        }
     }
 
     @Override
@@ -360,12 +323,7 @@
         readSystemFlags();
         mTaskViewsClipDirty = true;
         mUIDozeTrigger.stopDozing();
-        if (isResumingFromVisible) {
-            // Animate in the freeform workspace
-            int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
-            animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
-                    Interpolators.FAST_OUT_SLOW_IN));
-        } else {
+        if (!isResumingFromVisible) {
             mStackScroller.reset();
             mStableLayoutAlgorithm.reset();
             mLayoutAlgorithm.reset();
@@ -388,7 +346,7 @@
 
         // Only notify if we are already initialized, otherwise, everything will pick up all the
         // new and old tasks when we next layout
-        mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized);
+        mStack.setTasks(stack, allowNotifyStackChanges && isInitialized);
     }
 
     /** Returns the task stack. */
@@ -423,23 +381,13 @@
 
     /**
      * Returns the front most task view.
-     *
-     * @param stackTasksOnly if set, will return the front most task view in the stack (by default
-     *                       the front most task view will be freeform since they are placed above
-     *                       stack tasks)
      */
-    private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
+    private TaskView getFrontMostTaskView() {
         List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = taskViewCount - 1; i >= 0; i--) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            if (stackTasksOnly && task.isFreeformTask()) {
-                continue;
-            }
-            return tv;
+        if (taskViews.isEmpty()) {
+            return null;
         }
-        return null;
+        return taskViews.get(taskViews.size() - 1);
     }
 
     /**
@@ -501,8 +449,6 @@
      * visible range includes all tasks at the target stack scroll. This is useful for ensure that
      * all views necessary for a transition or animation will be visible at the start.
      *
-     * This call ignores freeform tasks.
-     *
      * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
      *                       match the size of {@param tasks}
      * @param tasks The set of tasks for which to generate transforms
@@ -525,7 +471,7 @@
         boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
 
         // We can reuse the task transforms where possible to reduce object allocation
-        Utilities.matchTaskListSize(tasks, taskTransforms);
+        matchTaskListSize(tasks, taskTransforms);
 
         // Update the stack transforms
         TaskViewTransform frontTransform = null;
@@ -555,12 +501,6 @@
                 continue;
             }
 
-            // For freeform tasks, only calculate the stack transform and skip the calculation of
-            // the visible stack indices
-            if (task.isFreeformTask()) {
-                continue;
-            }
-
             frontTransform = transform;
             frontTransformAtTarget = transformAtTarget;
             if (transform.visible) {
@@ -623,7 +563,7 @@
                 transform = mCurrentTaskTransforms.get(taskIndex);
             }
 
-            if (task.isFreeformTask() || (transform != null && transform.visible)) {
+            if (transform != null && transform.visible) {
                 mTmpTaskViewMap.put(task.key, tv);
             } else {
                 if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
@@ -644,24 +584,20 @@
                 continue;
             }
 
-            // Skip the invisible non-freeform stack tasks
-            if (!task.isFreeformTask() && !transform.visible) {
+            // Skip the invisible stack tasks
+            if (!transform.visible) {
                 continue;
             }
 
             TaskView tv = mTmpTaskViewMap.get(task.key);
             if (tv == null) {
                 tv = mViewPool.pickUpViewFromPool(task, task);
-                if (task.isFreeformTask()) {
-                    updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE);
+                if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
+                    updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
+                            AnimationProps.IMMEDIATE);
                 } else {
-                    if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
-                        updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
-                                AnimationProps.IMMEDIATE);
-                    } else {
-                        updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
-                                AnimationProps.IMMEDIATE);
-                    }
+                    updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
+                            AnimationProps.IMMEDIATE);
                 }
             } else {
                 // Reattach it in the right z order
@@ -765,7 +701,7 @@
      */
     public void getCurrentTaskTransforms(ArrayList<Task> tasks,
             ArrayList<TaskViewTransform> transformsOut) {
-        Utilities.matchTaskListSize(tasks, transformsOut);
+        matchTaskListSize(tasks, transformsOut);
         int focusState = mLayoutAlgorithm.getFocusState();
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
@@ -788,7 +724,7 @@
      */
     public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks,
             boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) {
-        Utilities.matchTaskListSize(tasks, transformsOut);
+        matchTaskListSize(tasks, transformsOut);
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
             TaskViewTransform transform = transformsOut.get(i);
@@ -888,13 +824,6 @@
         // Compute the min and max scroll values
         mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
 
-        // Update the freeform workspace background
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
-            mFreeformWorkspaceBackground.setBounds(mTmpRect);
-        }
-
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
         }
@@ -907,8 +836,7 @@
         mWindowRect.set(mStableWindowRect);
         mStackBounds.set(mStableStackBounds);
         mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
-        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
         updateLayoutAlgorithm(true /* boundScroll */);
     }
 
@@ -1029,21 +957,10 @@
         if (focusedTask != null) {
             if (stackTasksOnly) {
                 List<Task> tasks =  mStack.getStackTasks();
-                if (focusedTask.isFreeformTask()) {
-                    // Try and focus the front most stack task
-                    TaskView tv = getFrontMostTaskView(stackTasksOnly);
-                    if (tv != null) {
-                        newIndex = mStack.indexOfStackTask(tv.getTask());
-                    }
-                } else {
-                    // Try the next task if it is a stack task
-                    int tmpNewIndex = newIndex + (forward ? -1 : 1);
-                    if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
-                        Task t = tasks.get(tmpNewIndex);
-                        if (!t.isFreeformTask()) {
-                            newIndex = tmpNewIndex;
-                        }
-                    }
+                // Try the next task if it is a stack task
+                int tmpNewIndex = newIndex + (forward ? -1 : 1);
+                if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
+                    newIndex = tmpNewIndex;
                 }
             } else {
                 // No restrictions, lets just move to the new task (looping forward/backwards if
@@ -1128,7 +1045,7 @@
                 return tv.getTask();
             }
         }
-        TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */);
+        TaskView frontTv = getFrontMostTaskView();
         if (frontTv != null) {
             return frontTv.getTask();
         }
@@ -1279,10 +1196,8 @@
         }
 
         // Compute the rects in the stack algorithm
-        mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
-        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
-                TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+        mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds);
+        mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
         updateLayoutAlgorithm(false /* boundScroll */);
 
         // If this is the first layout, then scroll to the front of the stack, then update the
@@ -1405,11 +1320,6 @@
         // Setup the view for the enter animation
         mAnimationHelper.prepareForEnterAnimation();
 
-        // Animate in the freeform workspace
-        int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
-        animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
-                Interpolators.FAST_OUT_SLOW_IN));
-
         // Set the task focused state without requesting view focus, and leave the focus animations
         // until after the enter-animation
         RecentsConfiguration config = Recents.getConfiguration();
@@ -1457,43 +1367,6 @@
         return null;
     }
 
-    @Override
-    protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        // Draw the freeform workspace background
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            if (mFreeformWorkspaceBackground.getAlpha() > 0) {
-                mFreeformWorkspaceBackground.draw(canvas);
-            }
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        if (who == mFreeformWorkspaceBackground) {
-            return true;
-        }
-        return super.verifyDrawable(who);
-    }
-
-    /**
-     * Launches the freeform tasks.
-     */
-    public boolean launchFreeformTasks() {
-        ArrayList<Task> tasks = mStack.getFreeformTasks();
-        if (!tasks.isEmpty()) {
-            Task frontTask = tasks.get(tasks.size() - 1);
-            if (frontTask != null && frontTask.isFreeformTask()) {
-                EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
-                        frontTask, null, false));
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -1672,8 +1545,7 @@
         }
 
         // Restore the action button visibility if it is the front most task view
-        if (mScreenPinningEnabled && tv.getTask() ==
-                mStack.getStackFrontMostTask(false /* includeFreeform */)) {
+        if (mScreenPinningEnabled && tv.getTask() == mStack.getStackFrontMostTask()) {
             tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
         }
     }
@@ -1689,7 +1561,6 @@
 
         // If the doze trigger has already fired, then update the state for this task view
         if (mUIDozeTrigger.isAsleep() ||
-                Recents.getSystemServices().hasFreeformWorkspaceSupport() ||
                 useGridLayout() || Recents.getConfiguration().isLowRamDevice) {
             tv.setNoUserInteractionState();
         }
@@ -1768,8 +1639,17 @@
         }
 
         // In grid layout, the stack action button always remains visible.
-        if (mEnterAnimationComplete && !useGridLayout() &&
-                !Recents.getConfiguration().isLowRamDevice) {
+        if (mEnterAnimationComplete && !useGridLayout()) {
+            if (Recents.getConfiguration().isLowRamDevice) {
+                // Show stack button when user drags down to show older tasks on low ram devices
+                if (mStack.getTaskCount() > 0 && !mStackActionButtonVisible
+                        && mTouchHandler.mIsScrolling && curScroll - prevScroll < 0) {
+                    // Going up
+                    EventBus.getDefault().send(
+                            new ShowStackActionButtonEvent(true /* translate */));
+                }
+                return;
+            }
             if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     mStack.getTaskCount() > 0) {
@@ -1812,21 +1692,17 @@
 
     public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) {
         if (mStack.getTaskCount() > 0) {
-            Task mostRecentTask = mStack.getStackFrontMostTask(true /* includeFreefromTasks */);
+            Task mostRecentTask = mStack.getStackFrontMostTask();
             launchTask(mostRecentTask);
         }
     }
 
     public final void onBusEvent(ShowStackActionButtonEvent event) {
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButtonVisible = true;
-        }
+        mStackActionButtonVisible = true;
     }
 
     public final void onBusEvent(HideStackActionButtonEvent event) {
-        if (RecentsDebugFlags.Static.EnableStackActionButton) {
-            mStackActionButtonVisible = false;
-        }
+        mStackActionButtonVisible = false;
     }
 
     public final void onBusEvent(LaunchNextTaskRequestEvent event) {
@@ -1883,11 +1759,6 @@
         // Start the task animations
         mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
 
-        // Dismiss the freeform workspace background
-        int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
-        animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
-                Interpolators.FAST_OUT_SLOW_IN));
-
         // Dismiss the grid task view focus frame
         if (mTaskViewFocusFrame != null) {
             mTaskViewFocusFrame.moveGridTaskViewFocus(null);
@@ -1956,6 +1827,9 @@
         // Remove the task from the stack
         mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+        if (mStack.getTaskCount() > 0 && Recents.getConfiguration().isLowRamDevice) {
+            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+        }
 
         MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
                 event.task.key.getComponent().toString());
@@ -1966,8 +1840,7 @@
         mStackScroller.stopScroller();
         mStackScroller.stopBoundScrollAnimation();
 
-        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
-                event.timerIndicatorDuration);
+        setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0);
     }
 
     public final void onBusEvent(FocusPreviousTaskViewEvent event) {
@@ -1991,8 +1864,7 @@
                     EventBus.getDefault().send(new FocusPreviousTaskViewEvent());
                     break;
                 case DOWN:
-                    EventBus.getDefault().send(
-                        new FocusNextTaskViewEvent(0 /* timerIndicatorDuration */));
+                    EventBus.getDefault().send(new FocusNextTaskViewEvent());
                     break;
             }
         }
@@ -2003,7 +1875,7 @@
         mUIDozeTrigger.poke();
 
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
-        if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) {
+        if (mFocusedTask != null) {
             TaskView tv = getChildViewForTask(mFocusedTask);
             if (tv != null) {
                 tv.getHeaderView().cancelFocusTimerIndicator();
@@ -2015,11 +1887,6 @@
         // Ensure that the drag task is not animated
         addIgnoreTask(event.task);
 
-        if (event.task.isFreeformTask()) {
-            // Animate to the front of the stack
-            mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
-        }
-
         // Enlarge the dragged view slightly
         float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
@@ -2031,22 +1898,14 @@
                 new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
     }
 
-    public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
-            event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
-        }
-    }
-
     public final void onBusEvent(DragDropTargetChangedEvent event) {
         AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION,
                 Interpolators.FAST_OUT_SLOW_IN);
         boolean ignoreTaskOverrides = false;
-        if (event.dropTarget instanceof TaskStack.DockState) {
+        if (event.dropTarget instanceof DockState) {
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
-            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
+            final DockState dockState = (DockState) event.dropTarget;
             Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets);
             // When docked, the nav bar insets are consumed and the activity is measured without
             // insets.  However, the window bounds include the insets, so we need to subtract them
@@ -2058,8 +1917,7 @@
                     height, mDividerSize, systemInsets,
                     mLayoutAlgorithm, getResources(), mWindowRect));
             mLayoutAlgorithm.setSystemInsets(systemInsets);
-            mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
-                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
+            mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds);
             updateLayoutAlgorithm(true /* boundScroll */);
             ignoreTaskOverrides = true;
         } else {
@@ -2074,39 +1932,13 @@
 
     public final void onBusEvent(final DragEndEvent event) {
         // We don't handle drops on the dock regions
-        if (event.dropTarget instanceof TaskStack.DockState) {
+        if (event.dropTarget instanceof DockState) {
             // However, we do need to reset the overrides, since the last state of this task stack
             // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
             mLayoutAlgorithm.clearUnfocusedTaskOverrides();
             return;
         }
 
-        boolean isFreeformTask = event.task.isFreeformTask();
-        boolean hasChangedStacks =
-                (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
-                        (isFreeformTask && event.dropTarget == mStackDropTarget);
-
-        if (hasChangedStacks) {
-            // Move the task to the right position in the stack (ie. the front of the stack if
-            // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
-            // before we update their stack ids, otherwise, the keys will have changed.
-            if (event.dropTarget == mFreeformWorkspaceDropTarget) {
-                mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
-            } else if (event.dropTarget == mStackDropTarget) {
-                mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
-            }
-            updateLayoutAlgorithm(true /* boundScroll */);
-
-            // Move the task to the new stack in the system after the animation completes
-            event.addPostAnimationCallback(new Runnable() {
-                @Override
-                public void run() {
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
-                }
-            });
-        }
-
         // Restore the task, so that relayout will apply to it below
         removeIgnoreTask(event.task);
 
@@ -2141,13 +1973,6 @@
         event.getAnimationTrigger().increment();
     }
 
-    public final void onBusEvent(IterateRecentsEvent event) {
-        if (!mEnterAnimationComplete) {
-            // Cancel the previous task's window transition before animating the focused state
-            EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
-        }
-    }
-
     public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
         mEnterAnimationComplete = true;
         tryStartEnterAnimation();
@@ -2166,9 +1991,7 @@
             // Add a runnable to the post animation ref counter to clear all the views
             trigger.addLastDecrementRunnable(() -> {
                 // Start the dozer to trigger to trigger any UI that shows after a timeout
-                if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
-                    mUIDozeTrigger.startDozing();
-                }
+                mUIDozeTrigger.startDozing();
 
                 // Update the focused state here -- since we only set the focused task without
                 // requesting view focus in onFirstLayout(), actually request view focus and
@@ -2191,18 +2014,6 @@
         mStackReloaded = false;
     }
 
-    public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
-        List<TaskView> taskViews = getTaskViews();
-        int taskViewCount = taskViews.size();
-        for (int i = 0; i < taskViewCount; i++) {
-            TaskView tv = taskViews.get(i);
-            Task task = tv.getTask();
-            if (task.isFreeformTask()) {
-                tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
-            }
-        }
-    }
-
     public final void onBusEvent(final MultiWindowStateChangedEvent event) {
         if (event.inMultiWindow || !event.showDeferredAnimation) {
             setTasks(event.stack, true /* allowNotifyStackChanges */);
@@ -2304,27 +2115,6 @@
     }
 
     /**
-     * Starts an alpha animation on the freeform workspace background.
-     */
-    private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
-            AnimationProps animation) {
-        if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
-            return;
-        }
-
-        Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
-        mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
-                Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
-        mFreeformWorkspaceBackgroundAnimator.setStartDelay(
-                animation.getDuration(AnimationProps.ALPHA));
-        mFreeformWorkspaceBackgroundAnimator.setDuration(
-                animation.getDuration(AnimationProps.ALPHA));
-        mFreeformWorkspaceBackgroundAnimator.setInterpolator(
-                animation.getInterpolator(AnimationProps.ALPHA));
-        mFreeformWorkspaceBackgroundAnimator.start();
-    }
-
-    /**
      * Returns the insert index for the task in the current set of task views. If the given task
      * is already in the task view list, then this method returns the insert index assuming it
      * is first removed at the previous index.
@@ -2410,6 +2200,24 @@
         }
     }
 
+    /**
+     * Updates {@param transforms} to be the same size as {@param tasks}.
+     */
+    private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) {
+        // We can reuse the task transforms where possible to reduce object allocation
+        int taskTransformCount = transforms.size();
+        int taskCount = tasks.size();
+        if (taskTransformCount < taskCount) {
+            // If there are less transforms than tasks, then add as many transforms as necessary
+            for (int i = taskTransformCount; i < taskCount; i++) {
+                transforms.add(new TaskViewTransform());
+            }
+        } else if (taskTransformCount > taskCount) {
+            // If there are more transforms than tasks, then just subset the transform list
+            transforms.subList(taskCount, taskTransformCount).clear();
+        }
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "  ";
         String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 0b20b10..6b23977 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.util.FloatProperty;
 import android.util.Log;
-import android.util.MutableFloat;
 import android.util.Property;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
@@ -33,7 +32,8 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 32a249c..b9ca248 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Path;
-import android.graphics.Rect;
 import android.util.ArrayMap;
 import android.util.MutableBoolean;
 import android.view.InputDevice;
@@ -45,9 +44,9 @@
 import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
 import com.android.systemui.recents.misc.FreePathInterpolator;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 
 import java.util.ArrayList;
@@ -403,18 +402,6 @@
             return;
         }
 
-        // If tapping on the freeform workspace background, just launch the first freeform task
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            Rect freeformRect = mSv.mLayoutAlgorithm.mFreeformRect;
-            if (freeformRect.top <= y && y <= freeformRect.bottom) {
-                if (mSv.launchFreeformTasks()) {
-                    // TODO: Animate Recents away as we launch the freeform tasks
-                    return;
-                }
-            }
-        }
-
         // The user intentionally tapped on the background, which is like a tap on the "desktop".
         // Hide recents and transition to the launcher.
         EventBus.getDefault().send(new HideRecentsEvent(false, true));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index c64f6df..b440847 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -53,10 +51,10 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -196,9 +194,7 @@
      * Called from RecentsActivity when it is relaunched.
      */
     void onReload(boolean isResumingFromVisible) {
-        if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
-            resetNoUserInteractionState();
-        }
+        resetNoUserInteractionState();
         if (!isResumingFromVisible) {
             resetViewProperties();
         }
@@ -415,9 +411,7 @@
      * view.
      */
     boolean shouldClipViewInStack() {
-        // Never clip for freeform tasks or if invisible
-        if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE ||
-                Recents.getConfiguration().isLowRamDevice) {
+        if (getVisibility() != View.VISIBLE || Recents.getConfiguration().isLowRamDevice) {
             return false;
         }
         return mClipViewInStack;
@@ -647,7 +641,7 @@
     }
 
     @Override
-    public void onTaskStackIdChanged() {
+    public void onTaskWindowingModeChanged() {
         // Force rebind the header, the thumbnail does not change due to stack changes
         mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
         mHeaderView.onTaskDataLoaded();
@@ -715,7 +709,7 @@
     /**** Events ****/
 
     public final void onBusEvent(DragEndEvent event) {
-        if (!(event.dropTarget instanceof TaskStack.DockState)) {
+        if (!(event.dropTarget instanceof DockState)) {
             event.addPostAnimationCallback(() -> {
                 // Reset the clip state for the drag view after the end animation completes
                 setClipViewInStack(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
index 0c6b6b8..0fc507b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAccessibilityDelegate.java
@@ -28,11 +28,10 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.TaskStack;
 
 public class TaskViewAccessibilityDelegate extends View.AccessibilityDelegate {
     private static final String TAG = "TaskViewAccessibilityDelegate";
@@ -61,14 +60,14 @@
         super.onInitializeAccessibilityNodeInfo(host, info);
         if (ActivityManager.supportsSplitScreenMultiWindow(mTaskView.getContext())
                 && !Recents.getSystemServices().hasDockedTask()) {
-            TaskStack.DockState[] dockStates = Recents.getConfiguration()
+            DockState[] dockStates = Recents.getConfiguration()
                     .getDockStatesForCurrentOrientation();
-            for (TaskStack.DockState dockState: dockStates) {
-                if (dockState == TaskStack.DockState.TOP) {
+            for (DockState dockState: dockStates) {
+                if (dockState == DockState.TOP) {
                     info.addAction(mActions.get(SPLIT_TASK_TOP));
-                } else if (dockState == TaskStack.DockState.LEFT) {
+                } else if (dockState == DockState.LEFT) {
                     info.addAction(mActions.get(SPLIT_TASK_LEFT));
-                } else if (dockState == TaskStack.DockState.RIGHT) {
+                } else if (dockState == DockState.RIGHT) {
                     info.addAction(mActions.get(SPLIT_TASK_RIGHT));
                 }
             }
@@ -78,11 +77,11 @@
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
         if (action == SPLIT_TASK_TOP) {
-            simulateDragIntoMultiwindow(TaskStack.DockState.TOP);
+            simulateDragIntoMultiwindow(DockState.TOP);
         } else if (action == SPLIT_TASK_LEFT) {
-            simulateDragIntoMultiwindow(TaskStack.DockState.LEFT);
+            simulateDragIntoMultiwindow(DockState.LEFT);
         } else if (action == SPLIT_TASK_RIGHT) {
-            simulateDragIntoMultiwindow(TaskStack.DockState.RIGHT);
+            simulateDragIntoMultiwindow(DockState.RIGHT);
         } else {
             return super.performAccessibilityAction(host, action, args);
         }
@@ -90,8 +89,7 @@
     }
 
     /** Simulate a user drag event to split the screen to the respected side */
-    private void simulateDragIntoMultiwindow(TaskStack.DockState dockState) {
-        int orientation = Utilities.getAppConfiguration(mTaskView.getContext()).orientation;
+    private void simulateDragIntoMultiwindow(DockState dockState) {
         EventBus.getDefault().send(new DragStartEvent(mTaskView.getTask(), mTaskView,
                 new Point(0,0), false /* isUserTouchInitiated */));
         EventBus.getDefault().send(new DragEndEvent(mTaskView.getTask(), mTaskView, dockState));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 198ecae..0272a90 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -17,8 +17,6 @@
 package com.android.systemui.recents.views;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.animation.Animator;
@@ -33,7 +31,6 @@
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
@@ -59,8 +56,10 @@
 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 
 /* The task bar view */
 public class TaskViewHeader extends FrameLayout
@@ -164,8 +163,6 @@
     float mDimAlpha;
     Drawable mLightDismissDrawable;
     Drawable mDarkDismissDrawable;
-    Drawable mLightFreeformIcon;
-    Drawable mDarkFreeformIcon;
     Drawable mLightFullscreenIcon;
     Drawable mDarkFullscreenIcon;
     Drawable mLightInfoIcon;
@@ -173,6 +170,8 @@
     int mTaskBarViewLightTextColor;
     int mTaskBarViewDarkTextColor;
     int mDisabledTaskBarBackgroundColor;
+    String mDismissDescFormat;
+    String mAppInfoDescFormat;
     int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
 
     // Header background
@@ -215,14 +214,15 @@
         mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
         mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
         mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
-        mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
-        mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
         mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
         mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
         mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
         mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
         mDisabledTaskBarBackgroundColor =
                 context.getColor(R.color.recents_task_bar_disabled_background_color);
+        mDismissDescFormat = mContext.getString(
+                R.string.accessibility_recents_item_will_be_dismissed);
+        mAppInfoDescFormat = mContext.getString(R.string.accessibility_recents_item_open_app_info);
 
         // Configure the background and dim
         mBackground = new HighlightColorDrawable();
@@ -249,9 +249,6 @@
         mIconView.setOnLongClickListener(this);
         mTitleView = findViewById(R.id.title);
         mDismissButton = findViewById(R.id.dismiss_task);
-        if (ssp.hasFreeformWorkspaceSupport()) {
-            mMoveTaskButton = findViewById(R.id.move_task);
-        }
 
         onConfigurationChanged();
     }
@@ -341,20 +338,6 @@
         boolean showDismissIcon = true;
         int rightInset = width - getMeasuredWidth();
 
-        if (mTask != null && mTask.isFreeformTask()) {
-            // For freeform tasks, we always show the app icon, and only show the title, move-task
-            // icon, and the dismiss icon if there is room
-            int appIconWidth = mIconView.getMeasuredWidth();
-            int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
-            int dismissWidth = mDismissButton.getMeasuredWidth();
-            int moveTaskWidth = mMoveTaskButton != null
-                    ? mMoveTaskButton.getMeasuredWidth()
-                    : 0;
-            showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
-            showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
-            showDismissIcon = width >= (appIconWidth + dismissWidth);
-        }
-
         mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
         if (mMoveTaskButton != null) {
             mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
@@ -477,44 +460,14 @@
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
                 mLightDismissDrawable : mDarkDismissDrawable);
-        mDismissButton.setContentDescription(t.dismissDescription);
+        mDismissButton.setContentDescription(String.format(mDismissDescFormat, t.titleDescription));
         mDismissButton.setOnClickListener(this);
         mDismissButton.setClickable(false);
         ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
 
-        // When freeform workspaces are enabled, then update the move-task button depending on the
-        // current task
-        if (mMoveTaskButton != null) {
-            if (t.isFreeformTask()) {
-                mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN;
-                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
-                        ? mLightFullscreenIcon
-                        : mDarkFullscreenIcon);
-            } else {
-                mTaskWindowingMode = WINDOWING_MODE_FREEFORM;
-                mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
-                        ? mLightFreeformIcon
-                        : mDarkFreeformIcon);
-            }
-            mMoveTaskButton.setOnClickListener(this);
-            mMoveTaskButton.setClickable(false);
-            ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
-        }
-
-        if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
-            if (mFocusTimerIndicator == null) {
-                mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
-                        R.id.focus_timer_indicator_stub).inflate();
-            }
-            mFocusTimerIndicator.getProgressDrawable()
-                    .setColorFilter(
-                            getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
-                            PorterDuff.Mode.SRC_IN);
-        }
-
         // In accessibility, a single click on the focused app info button will show it
         if (touchExplorationEnabled) {
-            mIconView.setContentDescription(t.appInfoDescription);
+            mIconView.setContentDescription(String.format(mAppInfoDescFormat, t.titleDescription));
             mIconView.setOnClickListener(this);
             mIconView.setClickable(true);
         }
@@ -651,7 +604,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         ComponentName cn = mTask.key.getComponent();
         int userId = mTask.key.userId;
-        ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
+        ActivityInfo activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(cn, userId);
         if (activityInfo == null) {
             return;
         }
@@ -671,11 +624,12 @@
         }
 
         // Update the overlay contents for the current app
-        mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
+        mAppTitleView.setText(ActivityManagerWrapper.getInstance().getBadgedApplicationLabel(
+                activityInfo.applicationInfo, userId));
         mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
-        mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
-                userId));
+        mAppIconView.setImageDrawable(ActivityManagerWrapper.getInstance().getBadgedApplicationIcon(
+                activityInfo.applicationInfo, userId));
         mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
                 ? mLightInfoIcon
                 : mDarkInfoIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index a2190b3..4152b05 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -37,9 +37,9 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.ThumbnailData;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import java.io.PrintWriter;
 
 
@@ -245,10 +245,6 @@
     public void updateThumbnailMatrix() {
         mThumbnailScale = 1f;
         if (mBitmapShader != null && mThumbnailData != null) {
-            // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
-            // dragged into the stack from the freeform workspace
-            boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
-            int xOffset, yOffset = 0;
             if (mTaskViewRect.isEmpty()) {
                 // If we haven't measured , skip the thumbnail drawing and only draw the background
                 // color
@@ -266,7 +262,7 @@
                     mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
                             / (float) mThumbnailRect.height();
                 }
-            } else if (isStackTask) {
+            } else {
                 float invThumbnailScale = 1f / mFullscreenThumbnailScale;
                 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
                     if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -283,12 +279,6 @@
                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
                     mThumbnailScale = invThumbnailScale;
                 }
-            } else {
-                // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
-                // to fit the entire bitmap into the task bounds
-                mThumbnailScale = Math.min(
-                        (float) mTaskViewRect.width() / mThumbnailRect.width(),
-                        (float) mTaskViewRect.height() / mThumbnailRect.height());
             }
             mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
                     -mThumbnailData.insets.top * mFullscreenThumbnailScale);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 397f24e..9b717e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -21,11 +21,11 @@
 import android.animation.PropertyValuesHolder;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.util.IntProperty;
 import android.util.Property;
 import android.view.View;
 
-import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.shared.recents.utilities.AnimationProps;
+import com.android.systemui.shared.recents.utilities.Utilities;
 
 import java.util.ArrayList;
 
@@ -59,7 +59,7 @@
 
     public boolean visible = false;
 
-    // This is a window-space rect used for positioning the task in the stack and freeform workspace
+    // This is a window-space rect used for positioning the task in the stack
     public RectF rect = new RectF();
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
index bcf4f17..2d7cfb1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
@@ -26,8 +26,8 @@
 
 public class GridTaskViewThumbnail extends TaskViewThumbnail {
 
-    private Path mThumbnailOutline;
-    private Path mRestBackgroundOutline;
+    private final Path mThumbnailOutline = new Path();
+    private final Path mRestBackgroundOutline = new Path();
     // True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
     // be updated.
     private boolean mUpdateThumbnailOutline = true;
@@ -77,47 +77,38 @@
             (int) (mThumbnailRect.width() * mThumbnailScale));
         final int thumbnailHeight = Math.min(viewHeight,
             (int) (mThumbnailRect.height() * mThumbnailScale));
-        // Draw the thumbnail, we only round the bottom corners:
-        //
-        // outerLeft                outerRight
-        //    <----------------------->            mRestBackgroundOutline
-        //    _________________________            (thumbnailWidth < viewWidth)
-        //    |_______________________| outerTop     A ____ B
-        //    |                       |    ↑           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  | C
-        //    \_______________________/    ↓           |__/
-        //  mCornerRadius             outerBottom    E    D
-        //
-        //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
-        //  A _________________________ B
-        //    |                       | C
-        //  F \_______________________/
-        //    E                       D
-        final int outerLeft = 0;
-        final int outerTop = 0;
-        final int outerRight = outerLeft + thumbnailWidth;
-        final int outerBottom = outerTop + thumbnailHeight;
-        mThumbnailOutline = new Path();
-        mThumbnailOutline.moveTo(outerLeft, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerBottom - mCornerRadius);
-        mThumbnailOutline.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius,
-                outerRight, outerBottom, 0, 90, false);
-        mThumbnailOutline.lineTo(outerLeft + mCornerRadius, outerBottom);
-        mThumbnailOutline.arcTo(outerLeft, outerBottom - 2 * mCornerRadius,
-                outerLeft + 2 * mCornerRadius, outerBottom, 90, 90, false);
-        mThumbnailOutline.lineTo(outerLeft, outerTop);
-        mThumbnailOutline.close();
 
         if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+            // Draw the thumbnail, we only round the bottom corners:
+            //
+            // outerLeft                outerRight
+            //    <----------------------->            mRestBackgroundOutline
+            //    _________________________            (thumbnailWidth < viewWidth)
+            //    |_______________________| outerTop     A ____ B
+            //    |                       |    ↑           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  | C
+            //    \_______________________/    ↓           |__/
+            //  mCornerRadius             outerBottom    E    D
+            //
+            //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
+            //  A _________________________ B
+            //    |                       | C
+            //  F \_______________________/
+            //    E                       D
+            final int outerLeft = 0;
+            final int outerTop = 0;
+            final int outerRight = outerLeft + thumbnailWidth;
+            final int outerBottom = outerTop + thumbnailHeight;
+            createThumbnailPath(outerLeft, outerTop, outerRight, outerBottom, mThumbnailOutline);
+
             if (thumbnailWidth < viewWidth) {
                 final int l = Math.max(0, outerRight - mCornerRadius);
                 final int r = outerRight;
                 final int t = outerTop;
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -133,7 +124,7 @@
                 final int r = outerRight;
                 final int t = Math.max(0, thumbnailHeight - mCornerRadius);
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -145,9 +136,26 @@
                 mRestBackgroundOutline.lineTo(l, t); // A
                 mRestBackgroundOutline.close();
             }
+        } else {
+            createThumbnailPath(0, 0, viewWidth, viewHeight, mThumbnailOutline);
         }
     }
 
+    private void createThumbnailPath(int outerLeft, int outerTop, int outerRight, int outerBottom,
+            Path outPath) {
+        outPath.reset();
+        outPath.moveTo(outerLeft, outerTop);
+        outPath.lineTo(outerRight, outerTop);
+        outPath.lineTo(outerRight, outerBottom - mCornerRadius);
+        outPath.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius, outerRight,
+                outerBottom, 0, 90, false);
+        outPath.lineTo(outerLeft + mCornerRadius, outerBottom);
+        outPath.arcTo(outerLeft, outerBottom - 2 * mCornerRadius, outerLeft + 2 * mCornerRadius,
+                outerBottom, 90, 90, false);
+        outPath.lineTo(outerLeft, outerTop);
+        outPath.close();
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         final int titleHeight = getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
index c513202..ccda4b5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -25,10 +25,9 @@
 import android.view.WindowManager;
 
 import com.android.systemui.R;
-import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskViewTransform;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
index 86ed583..95f1d58 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskViewFocusFrame.java
@@ -23,7 +23,7 @@
 
 import android.view.ViewTreeObserver.OnGlobalFocusChangeListener;
 import com.android.systemui.R;
-import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.shared.recents.model.TaskStack;
 import com.android.systemui.recents.views.TaskStackView;
 
 public class TaskViewFocusFrame extends View implements OnGlobalFocusChangeListener {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
index 17e6b9e..49cac26 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/lowram/TaskStackLowRamLayoutAlgorithm.java
@@ -23,8 +23,8 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.Task;
+import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
 import com.android.systemui.recents.views.TaskViewTransform;
 
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 7699bb9..1cda301 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -16,37 +16,28 @@
 
 package com.android.systemui.shortcut;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.os.UserHandle.USER_CURRENT;
+
 import android.app.ActivityManager;
-import android.app.IActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.settingslib.accessibility.AccessibilityUtils;
-import com.android.systemui.R;
 import com.android.systemui.SystemUI;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 
 import java.util.List;
-import java.util.Set;
 
 /**
  * Dispatches shortcut to System UI components
@@ -58,7 +49,6 @@
 
     private ShortcutKeyServiceProxy mShortcutKeyServiceProxy = new ShortcutKeyServiceProxy(this);
     private IWindowManager mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
-    private IActivityManager mActivityManager = ActivityManager.getService();
 
     protected final long META_MASK = ((long) KeyEvent.META_META_ON) << Integer.SIZE;
     protected final long ALT_MASK = ((long) KeyEvent.META_ALT_ON) << Integer.SIZE;
@@ -102,11 +92,10 @@
                 // If there is no window docked, we dock the top-most window.
                 Recents recents = getComponent(Recents.class);
                 int dockMode = (shortcutCode == SC_DOCK_LEFT)
-                        ? ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                        : ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                        ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
                 List<ActivityManager.RecentTaskInfo> taskList =
-                        SystemServicesProxy.getInstance(mContext).getRecentTasks(1,
-                                UserHandle.USER_CURRENT, false, new ArraySet<>());
+                        ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
                 recents.showRecentApps(
                         false /* triggeredFromAltTab */,
                         false /* fromHome */);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 44e60ee..7bcef57 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.stackdivider;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 
@@ -23,7 +26,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -36,8 +38,6 @@
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.VelocityTracker;
@@ -62,8 +62,8 @@
 import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
@@ -661,7 +661,7 @@
         } else {
             mWindowManagerProxy.maximizeDockedStack();
         }
-        mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
+        mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
     }
 
     private void liftBackground() {
@@ -997,8 +997,7 @@
         SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
         float dimFraction = getDimFraction(position, closestDismissTarget);
         mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
-                getStackIdForDismissTarget(closestDismissTarget),
-                dimFraction);
+                getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
     }
 
     private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1132,13 +1131,13 @@
         }
     }
 
-    private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
+    private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
         if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
                         && dockSideBottomRight(mDockSide))) {
-            return StackId.DOCKED_STACK_ID;
+            return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
         } else {
-            return StackId.RECENTS_STACK_ID;
+            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
         }
     }
 
@@ -1192,6 +1191,10 @@
         }
     }
 
+    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+        saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
+    }
+
     public final void onBusEvent(DockedTopTaskEvent event) {
         if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
             mState.growAfterRecentsDrawn = false;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
index 578a18a..0997983 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/ForcedResizableInfoActivityController.java
@@ -32,7 +32,7 @@
 import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent;
 import com.android.systemui.recents.events.component.ShowUserToastEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.stackdivider.events.StartedDragingEvent;
 import com.android.systemui.stackdivider.events.StoppedDragingEvent;
 
@@ -76,7 +76,7 @@
         mContext = context;
         EventBus.getDefault().register(this);
         SystemServicesProxy.getInstance(context).registerTaskStackListener(
-                new TaskStackListener() {
+                new TaskStackChangeListener() {
                     @Override
                     public void onActivityForcedResizable(String packageName, int taskId,
                             int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 185f6e3..85a6062 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.stackdivider;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.view.WindowManager.DOCKED_INVALID;
 
 import android.app.ActivityManager;
@@ -56,7 +55,7 @@
     private final Rect mTouchableRegion = new Rect();
 
     private boolean mDimLayerVisible;
-    private int mDimLayerTargetStack;
+    private int mDimLayerTargetWindowingMode;
     private float mDimLayerAlpha;
 
     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@@ -88,8 +87,7 @@
         @Override
         public void run() {
             try {
-                ActivityManager.getService().moveTasksToFullscreenStack(
-                        DOCKED_STACK_ID, false /* onTop */);
+                ActivityManager.getService().dismissSplitScreenMode(false /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to remove stack: " + e);
             }
@@ -100,8 +98,7 @@
         @Override
         public void run() {
             try {
-                ActivityManager.getService().resizeStack(
-                        DOCKED_STACK_ID, null, true, true, false, -1);
+                ActivityManager.getService().dismissSplitScreenMode(true /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -113,7 +110,7 @@
         public void run() {
             try {
                 WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
-                        mDimLayerTargetStack, mDimLayerAlpha);
+                        mDimLayerTargetWindowingMode, mDimLayerAlpha);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -200,9 +197,9 @@
         return DOCKED_INVALID;
     }
 
-    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         mDimLayerVisible = visible;
-        mDimLayerTargetStack = targetStackId;
+        mDimLayerTargetWindowingMode = targetWindowingMode;
         mDimLayerAlpha = alpha;
         mExecutor.execute(mDimLayerRunnable);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 68fe9a8..84b7015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -29,7 +29,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewAnimationUtils;
-import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
@@ -173,12 +172,12 @@
     private int mOverrideTint;
     private float mOverrideAmount;
     private boolean mShadowHidden;
-    private boolean mWasActivatedOnDown;
     /**
      * Similar to mDimmed but is also true if it's not dimmable but should be
      */
     private boolean mNeedsDimming;
     private int mDimmedAlpha;
+    private boolean mBlockNextTouch;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -204,7 +203,7 @@
             } else {
                 makeInactive(true /* animate */);
             }
-        }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+        }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
     }
 
     @Override
@@ -241,9 +240,15 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN
+        if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
                 && disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
-            return true;
+            if (!mActivated) {
+                return true;
+            } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
+                mBlockNextTouch = true;
+                makeInactive(true /* animate */);
+                return true;
+            }
         }
         return super.onInterceptTouchEvent(ev);
     }
@@ -263,10 +268,11 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         boolean result;
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            mWasActivatedOnDown = mActivated;
+        if (mBlockNextTouch) {
+            mBlockNextTouch = false;
+            return false;
         }
-        if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) {
+        if (mNeedsDimming && !isTouchExplorationEnabled() && isInteractive()) {
             boolean wasActivated = mActivated;
             result = handleTouchEventDimmed(event);
             if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
@@ -312,7 +318,7 @@
 
     @Override
     public boolean performClick() {
-        if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) {
+        if (!mNeedsDimming || isTouchExplorationEnabled()) {
             return super.performClick();
         }
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 966e789..6c5f4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -36,6 +36,7 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Property;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.NotificationHeaderView;
@@ -174,6 +175,11 @@
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
+
+    // Listener will be called when receiving a long click event.
+    // Use #setLongPressPosition to optionally assign positional data with the long press.
+    private LongPressListener mLongPressListener;
+
     private boolean mGroupExpansionChanging;
 
     /**
@@ -788,6 +794,10 @@
         mOnExpandClickListener = onExpandClickListener;
     }
 
+    public void setLongPressListener(LongPressListener longPressListener) {
+        mLongPressListener = longPressListener;
+    }
+
     @Override
     public void setOnClickListener(@Nullable OnClickListener l) {
         super.setOnClickListener(l);
@@ -1338,6 +1348,47 @@
         }
     }
 
+    private void doLongClickCallback() {
+        doLongClickCallback(getWidth() / 2, getHeight() / 2);
+    }
+
+    public void doLongClickCallback(int x, int y) {
+        createMenu();
+        MenuItem menuItem = getProvider().getLongpressMenuItem(mContext);
+        if (mLongPressListener != null && menuItem != null) {
+            mLongPressListener.onLongPress(this, x, y, menuItem);
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            event.startTracking();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            if (!event.isCanceled()) {
+                performClick();
+            }
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        if (KeyEvent.isConfirmKey(keyCode)) {
+            doLongClickCallback();
+            return true;
+        }
+        return false;
+    }
+
     public void resetTranslation() {
         if (mTranslateAnim != null) {
             mTranslateAnim.cancel();
@@ -2205,6 +2256,7 @@
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
+        info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
         if (canViewBeDismissed()) {
             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
         }
@@ -2244,6 +2296,9 @@
             case AccessibilityNodeInfo.ACTION_EXPAND:
                 mExpandClickListener.onClick(this);
                 return true;
+            case AccessibilityNodeInfo.ACTION_LONG_CLICK:
+                doLongClickCallback();
+                return true;
         }
         return false;
     }
@@ -2332,4 +2387,15 @@
     protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
         mChildrenContainer = childrenContainer;
     }
+
+    /**
+     * Equivalent to View.OnLongClickListener with coordinates
+     */
+    public interface LongPressListener {
+        /**
+         * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+         * @return whether the longpress was handled
+         */
+        boolean onLongPress(View v, int x, int y, MenuItem item);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index d370a63..2d16d22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -433,24 +433,27 @@
         // Assist.
         final AssistUtils assistUtils = new AssistUtils(mContext);
         final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
-        PackageInfo assistPackageInfo = null;
-        try {
-            assistPackageInfo = mPackageManager.getPackageInfo(
-                    assistComponent.getPackageName(), 0, userId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "PackageManagerService is dead");
-        }
+        // Not all devices have an assist component.
+        if (assistComponent != null) {
+            PackageInfo assistPackageInfo = null;
+            try {
+                assistPackageInfo = mPackageManager.getPackageInfo(
+                        assistComponent.getPackageName(), 0, userId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManagerService is dead");
+            }
 
-        if (assistPackageInfo != null) {
-            final Icon assistIcon = Icon.createWithResource(
-                    assistPackageInfo.applicationInfo.packageName,
-                    assistPackageInfo.applicationInfo.icon);
+            if (assistPackageInfo != null) {
+                final Icon assistIcon = Icon.createWithResource(
+                        assistPackageInfo.applicationInfo.packageName,
+                        assistPackageInfo.applicationInfo.icon);
 
-            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
-                    mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
-                    assistIcon,
-                    KeyEvent.KEYCODE_UNKNOWN,
-                    KeyEvent.META_META_ON));
+                keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+                        mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
+                        assistIcon,
+                        KeyEvent.KEYCODE_UNKNOWN,
+                        KeyEvent.META_META_ON));
+            }
         }
 
         // Browser.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca54..492ab44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,13 @@
  */
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
@@ -28,12 +33,15 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Typeface;
+import android.metrics.LogMaker;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.SpannableString;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -51,11 +59,23 @@
 public class NotificationSnooze extends LinearLayout
         implements NotificationGuts.GutsContent, View.OnClickListener {
 
+    private static final String TAG = "NotificationSnooze";
     /**
      * If this changes more number increases, more assistant action resId's should be defined for
      * accessibility purposes, see {@link #setSnoozeOptions(List)}
      */
     private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+    private static final String KEY_DEFAULT_SNOOZE = "default";
+    private static final String KEY_OPTIONS = "options_array";
+    private static final LogMaker OPTIONS_OPEN_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_OPEN);
+    private static final LogMaker OPTIONS_CLOSE_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_CLOSE);
+    private static final LogMaker UNDO_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
+                    .setType(MetricsEvent.TYPE_ACTION);
     private NotificationGuts mGutsContainer;
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
@@ -72,9 +92,31 @@
     private boolean mSnoozing;
     private boolean mExpanded;
     private AnimatorSet mExpandAnimation;
+    private KeyValueListParser mParser;
+
+    private final static int[] sAccessibilityActions = {
+            R.id.action_snooze_shorter,
+            R.id.action_snooze_short,
+            R.id.action_snooze_long,
+            R.id.action_snooze_longer,
+    };
+
+    private MetricsLogger mMetricsLogger = new MetricsLogger();
 
     public NotificationSnooze(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mParser = new KeyValueListParser(',');
+    }
+
+    @VisibleForTesting
+    SnoozeOption getDefaultOption()
+    {
+        return mDefaultOption;
+    }
+
+    @VisibleForTesting
+    void setKeyValueListParser(KeyValueListParser parser) {
+        mParser = parser;
     }
 
     @Override
@@ -96,7 +138,13 @@
         mSnoozeOptions = getDefaultSnoozeOptions();
         createOptionViews();
 
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
     }
 
     @Override
@@ -136,7 +184,7 @@
             SnoozeOption so = mSnoozeOptions.get(i);
             if (so.getAccessibilityAction() != null
                     && so.getAccessibilityAction().getId() == action) {
-                setSelected(so);
+                setSelected(so, true);
                 return true;
             }
         }
@@ -172,17 +220,49 @@
         mSbn = sbn;
     }
 
-    private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+    @VisibleForTesting
+    ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+        final Resources resources = getContext().getResources();
         ArrayList<SnoozeOption> options = new ArrayList<>();
+        try {
+            final String config = Settings.Global.getString(getContext().getContentResolver(),
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+            mParser.setString(config);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Bad snooze constants");
+        }
 
-        options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
-        options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
-        mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
-        options.add(mDefaultOption);
-        options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+        final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+                resources.getInteger(R.integer.config_notification_snooze_time_default));
+        final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+                resources.getIntArray(R.array.config_notification_snooze_times));
+
+        for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+            int snoozeTime = snoozeTimes[i];
+            SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+            if (i == 0 || snoozeTime == defaultSnooze) {
+                mDefaultOption = option;
+            }
+            options.add(option);
+        }
         return options;
     }
 
+    @VisibleForTesting
+    int[] parseIntArray(final String key, final int[] defaultArray) {
+        final String value = mParser.getString(key, null);
+        if (value != null) {
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
+        } else {
+            return defaultArray;
+        }
+    }
+
     private SnoozeOption createOption(int minutes, int accessibilityActionId) {
         Resources res = getResources();
         boolean showInHours = minutes >= 60;
@@ -268,12 +348,24 @@
         mExpandAnimation.start();
     }
 
-    private void setSelected(SnoozeOption option) {
+    private void setSelected(SnoozeOption option, boolean userAction) {
         mSelectedOption = option;
         mSelectedOptionText.setText(option.getConfirmation());
         showSnoozeOptions(false);
         hideSelectedOption();
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        if (userAction) {
+            logOptionSelection(MetricsEvent.NOTIFICATION_SELECT_SNOOZE, option);
+        }
+    }
+
+    private void logOptionSelection(int category, SnoozeOption option) {
+        int index = mSnoozeOptions.indexOf(option);
+        long duration = TimeUnit.MINUTES.toMillis(option.getMinutesToSnoozeFor());
+        mMetricsLogger.write(new LogMaker(category)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_INDEX, index)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS, duration));
     }
 
     @Override
@@ -284,13 +376,15 @@
         final int id = v.getId();
         final SnoozeOption tag = (SnoozeOption) v.getTag();
         if (tag != null) {
-            setSelected(tag);
+            setSelected(tag, true);
         } else if (id == R.id.notification_snooze) {
             // Toggle snooze options
             showSnoozeOptions(!mExpanded);
+            mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
         } else {
             // Undo snooze was selected
             undoSnooze(v);
+            mMetricsLogger.write(UNDO_LOG);
         }
     }
 
@@ -321,7 +415,7 @@
     @Override
     public View getContentView() {
         // Reset the view before use
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
         return this;
     }
 
@@ -343,7 +437,7 @@
             return true;
         } else {
             // The view should actually be closed
-            setSelected(mSnoozeOptions.get(0));
+            setSelected(mSnoozeOptions.get(0), false);
             return false; // Return false here so that guts handles closing the view
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
new file mode 100644
index 0000000..5090f74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OperatorNameView.java
@@ -0,0 +1,155 @@
+/*
+ * 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.statusbar;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import com.android.internal.telephony.IccCardConstants.State;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DemoMode;
+import com.android.systemui.Dependency;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.IconState;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.List;
+
+public class OperatorNameView extends TextView implements DemoMode, DarkReceiver,
+        SignalCallback, Tunable {
+
+    private static final String KEY_SHOW_OPERATOR_NAME = "show_operator_name";
+
+    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private boolean mDemoMode;
+
+    private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+        @Override
+        public void onRefreshCarrierInfo() {
+            updateText();
+        }
+    };
+
+    public OperatorNameView(Context context) {
+        this(context, null);
+    }
+
+    public OperatorNameView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public OperatorNameView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+        mKeyguardUpdateMonitor.registerCallback(mCallback);
+        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(this);
+        Dependency.get(NetworkController.class).addCallback(this);
+        Dependency.get(TunerService.class).addTunable(this, KEY_SHOW_OPERATOR_NAME);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mKeyguardUpdateMonitor.removeCallback(mCallback);
+        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(this);
+        Dependency.get(NetworkController.class).removeCallback(this);
+        Dependency.get(TunerService.class).removeTunable(this);
+    }
+
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        setTextColor(DarkIconDispatcher.getTint(area, this, tint));
+    }
+
+    @Override
+    public void setIsAirplaneMode(IconState icon) {
+        update();
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        update();
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, Bundle args) {
+        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+            mDemoMode = true;
+        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+            mDemoMode = false;
+            update();
+        } else if (mDemoMode && command.equals(COMMAND_OPERATOR)) {
+            setText(args.getString("name"));
+        }
+    }
+
+    private void update() {
+        boolean showOperatorName = Dependency.get(TunerService.class)
+                .getValue(KEY_SHOW_OPERATOR_NAME, 1) != 0;
+        setVisibility(showOperatorName ? VISIBLE : GONE);
+
+        boolean hasMobile = ConnectivityManager.from(mContext)
+                .isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+        boolean airplaneMode = WirelessUtils.isAirplaneModeOn(mContext);
+        if (!hasMobile || airplaneMode) {
+            setText(null);
+            setVisibility(GONE);
+            return;
+        }
+
+        if (!mDemoMode) {
+            updateText();
+        }
+    }
+
+    private void updateText() {
+        CharSequence displayText = null;
+        List<SubscriptionInfo> subs = mKeyguardUpdateMonitor.getSubscriptionInfo(false);
+        final int N = subs.size();
+        for (int i = 0; i < N; i++) {
+            int subId = subs.get(i).getSubscriptionId();
+            State simState = mKeyguardUpdateMonitor.getSimState(subId);
+            CharSequence carrierName = subs.get(i).getCarrierName();
+            if (!TextUtils.isEmpty(carrierName) && simState == State.READY) {
+                ServiceState ss = mKeyguardUpdateMonitor.getServiceState(subId);
+                if (ss != null && ss.getState() == ServiceState.STATE_IN_SERVICE) {
+                    displayText = carrierName;
+                    break;
+                }
+            }
+        }
+
+        setText(displayText);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 759d2cf..a7fb61a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,14 +16,15 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
@@ -50,14 +51,15 @@
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
-        SecurityController.SecurityControllerCallback, Tunable,
-        DarkReceiver {
+        SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
 
     static final String TAG = "SignalClusterView";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -73,6 +75,7 @@
 
     private boolean mNoSimsVisible = false;
     private boolean mVpnVisible = false;
+    private boolean mSimDetected;
     private int mVpnIconId = 0;
     private int mLastVpnIconId = -1;
     private boolean mEthernetVisible = false;
@@ -148,6 +151,8 @@
         mIconScaleFactor = typedValue.getFloat();
         mNetworkController = Dependency.get(NetworkController.class);
         mSecurityController = Dependency.get(SecurityController.class);
+        addOnAttachStateChangeListener(
+                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
         updateActivityEnabled();
     }
 
@@ -327,8 +332,9 @@
     }
 
     @Override
-    public void setNoSims(boolean show) {
+    public void setNoSims(boolean show, boolean simDetected) {
         mNoSimsVisible = show && !mBlockMobile;
+        mSimDetected = simDetected;
         apply();
     }
 
@@ -548,6 +554,23 @@
         if (mNoSimsVisible) {
             mIconLogger.onIconShown(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.VISIBLE);
+            if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) {
+                mNoSimsCombo.setTag(mSimDetected);
+                if (mSimDetected) {
+                    SignalDrawable d = new SignalDrawable(mNoSims.getContext());
+                    d.setDarkIntensity(0);
+                    mNoSims.setImageDrawable(d);
+                    mNoSims.setImageLevel(SignalDrawable.getEmptyState(4));
+
+                    SignalDrawable dark = new SignalDrawable(mNoSims.getContext());
+                    dark.setDarkIntensity(1);
+                    mNoSimsDark.setImageDrawable(dark);
+                    mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4));
+                } else {
+                    mNoSims.setImageResource(R.drawable.stat_sys_no_sims);
+                    mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims);
+                }
+            }
         } else {
             mIconLogger.onIconHidden(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 2cff79d..6cfd42f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -43,6 +43,7 @@
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
@@ -142,6 +143,7 @@
     private float[] mMatrix;
     private ColorMatrixColorFilter mMatrixColorFilter;
     private boolean mIsInShelf;
+    private Runnable mLayoutRunnable;
 
     public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
         this(context, slot, sbn, false);
@@ -796,6 +798,24 @@
         }
     }
 
+    /**
+     * This method returns the drawing rect for the view which is different from the regular
+     * drawing rect, since we layout all children at position 0 and usually the translation is
+     * neglected. The standard implementation doesn't account for translation.
+     *
+     * @param outRect The (scrolled) drawing bounds of the view.
+     */
+    @Override
+    public void getDrawingRect(Rect outRect) {
+        super.getDrawingRect(outRect);
+        float translationX = getTranslationX();
+        float translationY = getTranslationY();
+        outRect.left += translationX;
+        outRect.right += translationX;
+        outRect.top += translationY;
+        outRect.bottom += translationY;
+    }
+
     public void setIsInShelf(boolean isInShelf) {
         mIsInShelf = isInShelf;
     }
@@ -804,6 +824,19 @@
         return mIsInShelf;
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mLayoutRunnable != null) {
+            mLayoutRunnable.run();
+            mLayoutRunnable = null;
+        }
+    }
+
+    public void executeOnLayout(Runnable runnable) {
+        mLayoutRunnable = runnable;
+    }
+
     public interface OnVisibilityChangedListener {
         void onVisibilityChanged(int newVisibility);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 9d2d71e..f5c77f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -21,7 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import android.app.ActivityManager.StackId;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -107,7 +107,7 @@
         }
     }
 
-    public void taskChanged(String packageName, int stackId) {
+    public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
         // If the package name belongs to a filter, then highlight appropriate button in
         // the navigation bar.
         if (mFacetPackageMap.containsKey(packageName)) {
@@ -121,8 +121,9 @@
         }
 
         // Set up the persistent docked task if needed.
-        if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask()
-                && stackId != StackId.HOME_STACK_ID) {
+        boolean isHomeTask =
+                taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
+        if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
             mStatusBar.startActivityOnStack(mPersistentTaskIntent,
                     WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 10fc496..fed2ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -36,14 +36,18 @@
 import android.view.WindowManager;
 import android.widget.LinearLayout;
 
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.SwipeHelper;
+import com.android.systemui.classifier.FalsingLog;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -51,10 +55,6 @@
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.classifier.FalsingLog;
-import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.Prefs;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -247,11 +247,12 @@
     }
 
     /**
-     * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be
-     * triggered when a notification card is long-pressed.
+     * Returns the
+     * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
+     * be triggered when a notification card is long-pressed.
      */
     @Override
-    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
         // For the automative use case, we do not want to the user to be able to interact with
         // a notification other than a regular click. As a result, just return null for the
         // long click listener.
@@ -304,17 +305,17 @@
     }
 
     /**
-     * An implementation of TaskStackListener, that listens for changes in the system task
+     * An implementation of TaskStackChangeListener, that listens for changes in the system task
      * stack and notifies the navigation bar.
      */
-    private class TaskStackListenerImpl extends TaskStackListener {
+    private class TaskStackListenerImpl extends TaskStackChangeListener {
         @Override
         public void onTaskStackChanged() {
             SystemServicesProxy ssp = Recents.getSystemServices();
             ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
             if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
                 mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
-                        runningTaskInfo.stackId);
+                        runningTaskInfo);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 2c3f452..61f3130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -60,6 +60,7 @@
     private StatusBar mStatusBarComponent;
     private DarkIconManager mDarkIconManager;
     private SignalClusterView mSignalClusterView;
+    private View mOperatorNameFrame;
 
     private SignalCallback mSignalCallback = new SignalCallback() {
         @Override
@@ -97,6 +98,7 @@
         // Default to showing until we know otherwise.
         showSystemIconArea(false);
         initEmergencyCryptkeeperText();
+        initOperatorName();
     }
 
     @Override
@@ -150,8 +152,10 @@
         if ((diff1 & DISABLE_SYSTEM_INFO) != 0) {
             if ((state1 & DISABLE_SYSTEM_INFO) != 0) {
                 hideSystemIconArea(animate);
+                hideOperatorName(animate);
             } else {
                 showSystemIconArea(animate);
+                showOperatorName(animate);
             }
         }
         if ((diff1 & DISABLE_NOTIFICATION_ICONS) != 0) {
@@ -207,6 +211,18 @@
         animateShow(mNotificationIconAreaInner, animate);
     }
 
+    public void hideOperatorName(boolean animate) {
+        if (mOperatorNameFrame != null) {
+            animateHide(mOperatorNameFrame, animate);
+        }
+    }
+
+    public void showOperatorName(boolean animate) {
+        if (mOperatorNameFrame != null) {
+            animateShow(mOperatorNameFrame, animate);
+        }
+    }
+
     /**
      * Hides a view.
      */
@@ -268,4 +284,11 @@
             parent.removeView(emergencyViewStub);
         }
     }
+
+    private void initOperatorName() {
+        if (getResources().getBoolean(R.bool.config_showOperatorNameInStatusBar)) {
+            ViewStub stub = mStatusBar.findViewById(R.id.operator_name);
+            mOperatorNameFrame = stub.inflate();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index dcb6a38..0d62703 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -142,7 +142,7 @@
                 && Math.abs(event.getY() - mDownY) < mTouchSlop;
     }
 
-    private boolean isWithinDoubleTapSlop(MotionEvent event) {
+    public boolean isWithinDoubleTapSlop(MotionEvent event) {
         if (!mActivated) {
             // If we're not activated there's no double tap slop to satisfy.
             return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 021b451..8afb849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -384,7 +384,7 @@
             if (mDozeParameters.getAlwaysOn()) {
                 // Setting power states can happen after we push out the frame. Make sure we
                 // stay fully opaque until the power state request reaches the lower levels.
-                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 30);
+                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 533771a..d226fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -264,8 +264,10 @@
         pw.println(" StatusBarTransitionsController:");
         mStatusBarIconController.getTransitionsController().dump(fd, pw, args);
         pw.println();
-        pw.println(" NavigationBarTransitionsController:");
-        mNavigationBarController.dump(fd, pw, args);
-        pw.println();
+        if (mNavigationBarController != null) {
+            pw.println(" NavigationBarTransitionsController:");
+            mNavigationBarController.dump(fd, pw, args);
+            pw.println();
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 87f5ca7..40ddf5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -124,8 +124,8 @@
         } else {
             if (selectedUser != null) {
                 // Show the selected user's static wallpaper.
-                return LoaderResult.success(
-                        mWallpaperManager.getBitmapAsUser(selectedUser.getIdentifier()));
+                return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
+                        selectedUser.getIdentifier(), true /* hardware */));
 
             } else {
                 // When there is no selected user, show the system wallpaper
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index cfe0a4a..6d3bc1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -437,7 +437,7 @@
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
-        mStatusBar.checkUserAutohide(v, event);
+        mStatusBar.checkUserAutohide(event);
         return false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index ee9a791..8e0a506 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -26,7 +26,6 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 
-import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
 import com.android.systemui.Dependency;
@@ -206,7 +205,7 @@
                     && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
                 Rect initialBounds = null;
                 int dragMode = calculateDragMode();
-                int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+                int createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
                 if (dragMode == DRAG_MODE_DIVIDER) {
                     initialBounds = new Rect();
                     mDivider.getView().calculateBoundsForPosition(mIsVertical
@@ -218,10 +217,10 @@
                             initialBounds);
                 } else if (dragMode == DRAG_MODE_RECENTS && mTouchDownX
                         < mContext.getResources().getDisplayMetrics().widthPixels / 2) {
-                    createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+                    createMode = ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
                 }
-                boolean docked = mRecentsComponent.dockTopTask(dragMode, createMode, initialBounds,
-                        MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
+                boolean docked = mRecentsComponent.splitPrimaryTask(dragMode, createMode,
+                        initialBounds, MetricsEvent.ACTION_WINDOW_DOCK_SWIPE);
                 if (docked) {
                     mDragMode = dragMode;
                     if (mDragMode == DRAG_MODE_DIVIDER) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index f3ca66f..c950036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -17,12 +17,19 @@
 package com.android.systemui.statusbar.phone;
 
 import android.content.Context;
+import android.os.Handler;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWallpaperVisibilityListener;
+import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.WindowManagerGlobal;
 
 import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
 
 public final class NavigationBarTransitions extends BarTransitions {
@@ -30,6 +37,7 @@
     private final NavigationBarView mView;
     private final IStatusBarService mBarService;
     private final LightBarTransitionsController mLightTransitionsController;
+    private boolean mWallpaperVisible;
 
     private boolean mLightsOut;
     private boolean mAutoDim;
@@ -41,6 +49,21 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
         mLightTransitionsController = new LightBarTransitionsController(view.getContext(),
                 this::applyDarkIntensity);
+
+        IWindowManager windowManagerService = Dependency.get(IWindowManager.class);
+        Handler handler = Handler.getMain();
+        try {
+            mWallpaperVisible = windowManagerService.registerWallpaperVisibilityListener(
+                new IWallpaperVisibilityListener.Stub() {
+                    @Override
+                    public void onWallpaperVisibilityChanged(boolean newVisibility,
+                            int displayId) throws RemoteException {
+                        mWallpaperVisible = newVisibility;
+                        handler.post(() -> applyLightsOut(true, false));
+                    }
+                }, Display.DEFAULT_DISPLAY);
+        } catch (RemoteException e) {
+        }
     }
 
     public void init() {
@@ -57,7 +80,7 @@
 
     @Override
     protected boolean isLightsOut(int mode) {
-        return super.isLightsOut(mode) || mAutoDim;
+        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
     }
 
     public LightBarTransitionsController getLightTransitionsController() {
@@ -85,7 +108,7 @@
         // ok, everyone, stop it right there
         navButtons.animate().cancel();
 
-        final float navButtonsAlpha = lightsOut ? 0.5f : 1f;
+        final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
 
         if (!animate) {
             navButtons.setAlpha(navButtonsAlpha);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9a7039a..094129c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -448,10 +448,18 @@
 
         // Always disable recents when alternate car mode UI is active.
         boolean disableRecent = mUseCarModeUi
-                        || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
-        final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
+                || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0);
+
+        boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)
                 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0);
 
+        if ((disableRecent || disableBack) && inScreenPinning()) {
+            // Don't hide back and recents buttons when in screen pinning mode, as they are used for
+            // exiting.
+            disableBack = false;
+            disableRecent = false;
+        }
+
         ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
         if (navButtons != null) {
             LayoutTransition lt = navButtons.getLayoutTransition();
@@ -461,20 +469,16 @@
                 }
             }
         }
-        if (inLockTask() && disableRecent && !disableHome) {
-            // Don't hide recents when in lock task, it is used for exiting.
-            // Unless home is hidden, then in DPM locked mode and no exit available.
-            disableRecent = false;
-        }
 
         getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
         getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
         getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
     }
 
-    private boolean inLockTask() {
+    private boolean inScreenPinning() {
         try {
-            return ActivityManager.getService().isInLockTaskMode();
+            return ActivityManager.getService().getLockTaskModeState()
+                    == ActivityManager.LOCK_TASK_MODE_PINNED;
         } catch (RemoteException e) {
             return false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 41a69b4..40fe50f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -4,10 +4,8 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Icon;
 import android.support.annotation.NonNull;
 import android.support.v4.util.ArrayMap;
-import android.support.v4.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -269,18 +267,26 @@
      */
     private void applyNotificationIconsTint() {
         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
-            boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
-            int color = StatusBarIconView.NO_COLOR;
-            boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
-            if (colorize) {
-                color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+            final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+            if (iv.getWidth() != 0) {
+                updateTintForIcon(iv);
+            } else {
+                iv.executeOnLayout(() -> updateTintForIcon(iv));
             }
-            v.setStaticDrawableColor(color);
-            v.setDecorColor(mIconTint);
         }
     }
 
+    private void updateTintForIcon(StatusBarIconView v) {
+        boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+        int color = StatusBarIconView.NO_COLOR;
+        boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
+        if (colorize) {
+            color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+        }
+        v.setStaticDrawableColor(color);
+        v.setDecorColor(mIconTint);
+    }
+
     public void setDark(boolean dark) {
         mNotificationIcons.setDark(dark, false, 0);
         mShelfIcons.setDark(dark, false, 0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c191618..af03440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -667,7 +667,7 @@
             return false;
         }
         initDownStates(event);
-        if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+        if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
             mIsExpansionFromHeadsUp = true;
             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
@@ -704,7 +704,7 @@
                     mInitialHeightOnTouch = mQsExpansionHeight;
                     mQsTracking = true;
                     mIntercepting = false;
-                    mNotificationStackScroller.removeLongPressCallback();
+                    mNotificationStackScroller.cancelLongPress();
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -740,7 +740,7 @@
                     mInitialTouchY = y;
                     mInitialTouchX = x;
                     mIntercepting = false;
-                    mNotificationStackScroller.removeLongPressCallback();
+                    mNotificationStackScroller.cancelLongPress();
                     return true;
                 }
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 4ae1393..b876286 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
@@ -62,7 +66,7 @@
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
 import com.android.systemui.statusbar.policy.BluetoothController;
@@ -523,12 +527,18 @@
         mCurrentNotifs.clear();
         mUiOffloadThread.submit(() -> {
             try {
-                int focusedId = ActivityManager.getService().getFocusedStackId();
-                if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
-                    checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+                final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
+                if (focusedStack != null) {
+                    final int windowingMode =
+                            focusedStack.configuration.windowConfiguration.getWindowingMode();
+                    if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+                        checkStack(focusedStack, notifs, noMan, pm);
+                    }
                 }
                 if (mDockedStackExists) {
-                    checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+                    checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED,
+                            notifs, noMan, pm);
                 }
             } catch (RemoteException e) {
                 e.rethrowFromSystemServer();
@@ -539,10 +549,19 @@
         });
     }
 
-    private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
+    private void checkStack(int windowingMode, int activityType,
+            ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) {
+        try {
+            final StackInfo info =
+                    ActivityManager.getService().getStackInfo(windowingMode, activityType);
+            checkStack(info, notifs, noMan, pm);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+    private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs,
             NotificationManager noMan, IPackageManager pm) {
         try {
-            StackInfo info = ActivityManager.getService().getStackInfo(stackId);
             if (info == null || info.topActivity == null) return;
             String pkg = info.topActivity.getPackageName();
             if (!hasNotif(notifs, pkg, info.userId)) {
@@ -620,12 +639,17 @@
     }
 
     private Intent getTaskIntent(int taskId, int userId) {
-        List<ActivityManager.RecentTaskInfo> tasks = mContext.getSystemService(ActivityManager.class)
-                .getRecentTasksForUser(NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId);
-        for (int i = 0; i < tasks.size(); i++) {
-            if (tasks.get(i).id == taskId) {
-                return tasks.get(i).baseIntent;
+        try {
+            final List<ActivityManager.RecentTaskInfo> tasks =
+                    ActivityManager.getService().getRecentTasks(
+                            NUM_TASKS_FOR_INSTANT_APP_INFO, 0, userId).getList();
+            for (int i = 0; i < tasks.size(); i++) {
+                if (tasks.get(i).id == taskId) {
+                    return tasks.get(i).baseIntent;
+                }
             }
+        } catch (RemoteException e) {
+            // Fall through
         }
         return null;
     }
@@ -744,7 +768,7 @@
         mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
     }
 
-    private final TaskStackListener mTaskListener = new TaskStackListener() {
+    private final TaskStackChangeListener mTaskListener = new TaskStackChangeListener() {
         @Override
         public void onTaskStackChanged() {
             // Listen for changes to stacks and then check which instant apps are foreground.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4739a2e..fa2b8e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -69,9 +69,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
@@ -105,10 +102,10 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -127,13 +124,11 @@
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
@@ -165,7 +160,6 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
-import com.android.systemui.SwipeHelper;
 import com.android.systemui.SystemUI;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.UiOffloadThread;
@@ -259,7 +253,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.Stack;
@@ -279,11 +272,9 @@
             = SystemProperties.getBoolean("debug.child_notifs", true);
     public static final boolean FORCE_REMOTE_INPUT_HISTORY =
             SystemProperties.getBoolean("debug.force_remoteinput_history", false);
-    private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
-    protected static final int MSG_SHOW_RECENT_APPS = 1019;
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
-    protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
     protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
     protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
     protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
@@ -365,10 +356,6 @@
     /** If true, the lockscreen will show a distinct wallpaper */
     private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
 
-    /* If true, the device supports freeform window management.
-     * This affects the status bar UI. */
-    private static final boolean FREEFORM_WINDOW_MANAGEMENT;
-
     /**
      * How long to wait before auto-dismissing a notification that was kept for remote input, and
      * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
@@ -387,19 +374,14 @@
 
     static {
         boolean onlyCoreApps;
-        boolean freeformWindowManagement;
         try {
             IPackageManager packageManager =
                     IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
             onlyCoreApps = packageManager.isOnlyCoreApps();
-            freeformWindowManagement = packageManager.hasSystemFeature(
-                    PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0);
         } catch (RemoteException e) {
             onlyCoreApps = false;
-            freeformWindowManagement = false;
         }
         ONLY_CORE_APPS = onlyCoreApps;
-        FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
     }
 
     /**
@@ -410,17 +392,17 @@
     protected boolean mShowLockscreenNotifications;
     protected boolean mAllowLockscreenRemoteInput;
 
-    PhoneStatusBarPolicy mIconPolicy;
+    private PhoneStatusBarPolicy mIconPolicy;
 
-    VolumeComponent mVolumeComponent;
-    BrightnessMirrorController mBrightnessMirrorController;
+    private VolumeComponent mVolumeComponent;
+    private BrightnessMirrorController mBrightnessMirrorController;
     protected FingerprintUnlockController mFingerprintUnlockController;
-    LightBarController mLightBarController;
+    private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
 
-    int mNaturalBarHeight = -1;
+    private int mNaturalBarHeight = -1;
 
-    Point mCurrentDisplaySize = new Point();
+    private final Point mCurrentDisplaySize = new Point();
 
     protected StatusBarWindowView mStatusBarWindow;
     protected PhoneStatusBarView mStatusBarView;
@@ -431,15 +413,13 @@
     private boolean mWakeUpComingFromTouch;
     private PointF mWakeUpTouchLocation;
 
-    int mPixelFormat;
-    Object mQueueLock = new Object();
+    private final Object mQueueLock = new Object();
 
     protected StatusBarIconController mIconController;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
-    View mExpandedContents;
-    TextView mNotificationPanelDebugText;
+    private TextView mNotificationPanelDebugText;
 
     /**
      * {@code true} if notifications not part of a group should by default be rendered in their
@@ -452,12 +432,10 @@
     private QSPanel mQSPanel;
 
     // top bar
-    protected KeyguardStatusBarView mKeyguardStatusBar;
-    boolean mLeaveOpenOnKeyguardHide;
+    private KeyguardStatusBarView mKeyguardStatusBar;
+    private boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationController mKeyguardIndicationController;
 
-    // Keyguard is going away soon.
-    private boolean mKeyguardGoingAway;
     // Keyguard is actually fading away now.
     protected boolean mKeyguardFadingAway;
     protected long mKeyguardFadingAwayDelay;
@@ -469,25 +447,19 @@
 
     private View mReportRejectedTouch;
 
-    int mMaxAllowedKeyguardNotifications;
+    private int mMaxAllowedKeyguardNotifications;
 
-    boolean mExpandedVisible;
+    private boolean mExpandedVisible;
 
-    // the tracker view
-    int mTrackingPosition; // the position of the top of the tracking view.
-
-    // Tracking finger for opening/closing.
-    boolean mTracking;
-
-    int[] mAbsPos = new int[2];
-    ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+    private final int[] mAbsPos = new int[2];
+    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
     // for disabling the status bar
-    int mDisabled1 = 0;
-    int mDisabled2 = 0;
+    private int mDisabled1 = 0;
+    private int mDisabled2 = 0;
 
     // tracking calls to View.setSystemUiVisibility()
-    int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+    private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
     private final Rect mLastFullscreenStackBounds = new Rect();
     private final Rect mLastDockedStackBounds = new Rect();
     private final Rect mTmpRect = new Rect();
@@ -495,7 +467,7 @@
     // last value sent to window manager
     private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
 
-    DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
 
     // XXX: gesture research
     private final GestureRecorder mGestureRec = DEBUG_GESTURES
@@ -507,14 +479,17 @@
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
-    private boolean mUserSetup = false;
-    private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
+    @VisibleForTesting
+    protected boolean mUserSetup = false;
+    private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
         @Override
         public void onUserSetupChanged() {
             final boolean userSetup = mDeviceProvisionedController.isUserSetup(
                     mDeviceProvisionedController.getCurrentUser());
-            if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
-                    "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
+            if (MULTIUSER_DEBUG) {
+                Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
+                        userSetup, mUserSetup));
+            }
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
@@ -528,7 +503,7 @@
         }
     };
 
-    protected H mHandler = createHandler();
+    protected final H mHandler = createHandler();
     final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange) {
@@ -537,8 +512,6 @@
                     && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
                     mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                     Settings.Global.HEADS_UP_OFF);
-            mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt(
-                    mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0);
             Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
             if (wasUsing != mUseHeadsUp) {
                 if (!mUseHeadsUp) {
@@ -566,26 +539,21 @@
         }
     };
 
-    private boolean mWaitingForKeyguardExit;
     protected boolean mDozing;
     private boolean mDozingRequested;
     protected boolean mScrimSrcModeEnabled;
 
-    public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN;
-    public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT;
-
     protected BackDropView mBackdrop;
     protected ImageView mBackdropFront, mBackdropBack;
-    protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
-    protected PorterDuffXfermode mSrcOverXferMode =
+    protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+    protected final PorterDuffXfermode mSrcOverXferMode =
             new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
     private MediaSessionManager mMediaSessionManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
-    private MediaController.Callback mMediaListener
-            = new MediaController.Callback() {
+    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             super.onPlaybackStateChanged(state);
@@ -607,17 +575,6 @@
         }
     };
 
-    private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
-            new OnChildLocationsChangedListener() {
-        @Override
-        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) {
-            userActivity();
-        }
-    };
-
-    private int mDisabledUnmodified1;
-    private int mDisabledUnmodified2;
-
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
             new ArraySet<>();
@@ -644,15 +601,6 @@
     private boolean mWereIconsJustHidden;
     private boolean mBouncerWasShowingWhenHidden;
 
-    public boolean isStartedGoingToSleep() {
-        return mStartedGoingToSleep;
-    }
-
-    /**
-     * If set, the device has started going to sleep but isn't fully non-interactive yet.
-     */
-    protected boolean mStartedGoingToSleep;
-
     private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
             new OnChildLocationsChangedListener() {
                 @Override
@@ -686,7 +634,6 @@
         @Override
         public void run() {
             mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
-            final String mediaKey = getCurrentMediaNotificationKey();
 
             // 1. Loop over mNotificationData entries:
             //   A. Keep list of visible notifications.
@@ -743,10 +690,10 @@
     private boolean mKeyguardRequested;
     private boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
+    private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private String mKeyToRemoveOnGutsClosed;
@@ -769,20 +716,21 @@
             goToLockedShade(null);
         }
     };
-    private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
-            = new HashMap<>();
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
-    private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onDreamingStateChanged(boolean dreaming) {
-            if (dreaming) {
-                maybeEscalateHeadsUp();
-            }
-        }
-    };
+    private final KeyguardUpdateMonitorCallback mUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onDreamingStateChanged(boolean dreaming) {
+                    if (dreaming) {
+                        maybeEscalateHeadsUp();
+                    }
+                }
+            };
 
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
@@ -861,10 +809,6 @@
 
         mRecents = getComponent(Recents.class);
 
-        final Configuration currentConfig = res.getConfiguration();
-        mLocale = currentConfig.locale;
-        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
@@ -994,9 +938,6 @@
         Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
-    protected void createIconController() {
-    }
-
     // ================================================================================
     // Constructing the view
     // ================================================================================
@@ -1012,16 +953,14 @@
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
-                R.id.notification_panel);
-        mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
-                R.id.notification_stack_scroller);
+        mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+        mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
         mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
                 R.id.notification_container_parent));
-        mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
+        mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header);
 
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, this);
@@ -1060,8 +999,7 @@
         putComponent(HeadsUpManager.class, mHeadsUpManager);
 
         if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
-                    R.id.header_debug_info);
+            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
         }
 
@@ -1075,9 +1013,6 @@
             // no window manager? good luck with that
         }
 
-        // figure out which pixel-format to use for the status bar.
-        mPixelFormat = PixelFormat.OPAQUE;
-
         mStackScroller.setLongPressListener(getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
@@ -1087,11 +1022,10 @@
 
         inflateEmptyShadeView();
         inflateDismissView();
-        mExpandedContents = mStackScroller;
 
-        mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
-        mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
-        mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
+        mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+        mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
+        mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
 
         if (ENABLE_LOCKSCREEN_WALLPAPER) {
             mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
@@ -1099,8 +1033,8 @@
 
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
-                (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
-                mNotificationPanel.getLockIcon());
+                        mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
+                        mNotificationPanel.getLockIcon());
         mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
 
 
@@ -1131,8 +1065,8 @@
             mNavigationBar.setLightBarController(mLightBarController);
         }
 
-        ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
-        ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
+        ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
+        ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
         View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
         mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController,
                 scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
@@ -1142,13 +1076,10 @@
                     }
                 });
         if (mScrimSrcModeEnabled) {
-            Runnable runnable = new Runnable() {
-                @Override
-                public void run() {
-                    boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
-                    mScrimController.setDrawBehindAsSrc(asSrc);
-                    mStackScroller.setDrawBackgroundAsSrc(asSrc);
-                }
+            Runnable runnable = () -> {
+                boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
+                mScrimController.setDrawBehindAsSrc(asSrc);
+                mStackScroller.setDrawBackgroundAsSrc(asSrc);
             };
             mBackdrop.setOnVisibilityChangedRunnable(runnable);
             runnable.run();
@@ -1170,11 +1101,11 @@
         if (container != null) {
             FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
             ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
-                    Dependency.get(ExtensionController.class).newExtension(QS.class)
+                    Dependency.get(ExtensionController.class)
+                            .newExtension(QS.class)
                             .withPlugin(QS.class)
-                            .withFeature(
-                                    PackageManager.FEATURE_AUTOMOTIVE, () -> new CarQSFragment())
-                            .withDefault(() -> new QSFragment())
+                            .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
+                            .withDefault(QSFragment::new)
                             .build());
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mIconController);
@@ -1276,7 +1207,7 @@
      */
     protected View.OnTouchListener getStatusBarWindowTouchListener() {
         return (v, event) -> {
-            checkUserAutohide(v, event);
+            checkUserAutohide(event);
             checkRemoteInputOutside(event);
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 if (mExpandedVisible) {
@@ -1380,8 +1311,7 @@
 
     public static SignalClusterView reinflateSignalCluster(View view) {
         Context context = view.getContext();
-        SignalClusterView signalCluster =
-                (SignalClusterView) view.findViewById(R.id.signal_cluster);
+        SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster);
         if (signalCluster != null) {
             ViewParent parent = signalCluster.getParent();
             if (parent instanceof ViewGroup) {
@@ -1421,20 +1351,17 @@
 
         mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
-        mDismissView.setOnButtonClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
-                clearAllNotifications();
-            }
+        mDismissView.setOnButtonClickListener(v -> {
+            mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+            clearAllNotifications();
         });
         mStackScroller.setDismissView(mDismissView);
     }
 
     protected void createUserSwitcher() {
         mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
-                mKeyguardStatusBar, mNotificationPanel);
+                mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mKeyguardStatusBar,
+                mNotificationPanel);
     }
 
     protected void inflateStatusBarWindow(Context context) {
@@ -1447,7 +1374,7 @@
         // animate-swipe all dismissable notifications, then animate the shade closed
         int numChildren = mStackScroller.getChildCount();
 
-        final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
+        final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
         final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
         for (int i = 0; i < numChildren; i++) {
             final View child = mStackScroller.getChildAt(i);
@@ -1487,20 +1414,18 @@
             return;
         }
 
-        addPostCollapseAction(new Runnable() {
-            @Override
-            public void run() {
-                mStackScroller.setDismissAllInProgress(false);
-                for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
-                    if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                        removeNotification(rowToRemove.getEntry().key, null);
-                    } else {
-                        rowToRemove.resetTranslation();
-                    }
+        addPostCollapseAction(() -> {
+            mStackScroller.setDismissAllInProgress(false);
+            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+                if (mStackScroller.canChildBeDismissed(rowToRemove)) {
+                    removeNotification(rowToRemove.getEntry().key, null);
+                } else {
+                    rowToRemove.resetTranslation();
                 }
-                try {
-                    mBarService.onClearAllNotifications(mCurrentUserId);
-                } catch (Exception ex) { }
+            }
+            try {
+                mBarService.onClearAllNotifications(mCurrentUserId);
+            } catch (Exception ex) {
             }
         });
 
@@ -1509,13 +1434,15 @@
     }
 
     private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
-        Runnable animationFinishAction = new Runnable() {
-            @Override
-            public void run() {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-            }
+        Runnable animationFinishAction = () -> {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
         };
 
+        if (hideAnimatedList.isEmpty()) {
+            animationFinishAction.run();
+            return;
+        }
+
         // let's disable our normal animations
         mStackScroller.setDismissAllInProgress(true);
 
@@ -1611,8 +1538,8 @@
         }
         int dockSide = WindowManagerProxy.getInstance().getDockSide();
         if (dockSide == WindowManager.DOCKED_INVALID) {
-            return mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
-                    ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
+            return mRecents.splitPrimaryTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
+                    ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
         } else {
             Divider divider = getComponent(Divider.class);
             if (divider != null && divider.isMinimized() && !divider.isHomeStackResizable()) {
@@ -1632,10 +1559,6 @@
         SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
     }
 
-    public UserHandle getCurrentUserHandle() {
-        return new UserHandle(mCurrentUserId);
-    }
-
     public void addNotification(StatusBarNotification notification, RankingMap ranking)
             throws InflationException {
         String key = notification.getKey();
@@ -1746,7 +1669,7 @@
         boolean deferRemoval = false;
         abortExistingInflation(key);
         if (mHeadsUpManager.isHeadsUp(key)) {
-            // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
+            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
             // sending look longer than it takes.
             // Also we should not defer the removal if reordering isn't allowed since otherwise
             // some notifications can't disappear before the panel is closed.
@@ -1772,9 +1695,7 @@
                 newHistory = new CharSequence[1];
             } else {
                 newHistory = new CharSequence[oldHistory.length + 1];
-                for (int i = 0; i < oldHistory.length; i++) {
-                    newHistory[i + 1] = oldHistory[i];
-                }
+                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
             }
             newHistory[0] = String.valueOf(entry.remoteInputText);
             b.setRemoteInputHistory(newHistory);
@@ -1833,7 +1754,7 @@
             mStackScroller.cleanUpViewState(entry.row);
         }
         // Let's remove the children if this was a summary
-        handleGroupSummaryRemoved(key, ranking);
+        handleGroupSummaryRemoved(key);
         StatusBarNotification old = removeNotificationViews(key, ranking);
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
@@ -1857,12 +1778,10 @@
      *
      * This also ensures that the animation looks nice and only consists of a single disappear
      * animation instead of multiple.
+     *  @param key the key of the notification was removed
      *
-     * @param key the key of the notification was removed
-     * @param ranking the current ranking
      */
-    private void handleGroupSummaryRemoved(String key,
-            RankingMap ranking) {
+    private void handleGroupSummaryRemoved(String key) {
         Entry entry = mNotificationData.get(key);
         if (entry != null && entry.row != null
                 && entry.row.isSummaryWithChildren()) {
@@ -1873,15 +1792,13 @@
             }
             List<ExpandableNotificationRow> notificationChildren =
                     entry.row.getNotificationChildren();
-            ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
             for (int i = 0; i < notificationChildren.size(); i++) {
                 ExpandableNotificationRow row = notificationChildren.get(i);
                 if ((row.getStatusBarNotification().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                    // the child is a forground service notification which we can't remove!
+                    // the child is a foreground service notification which we can't remove!
                     continue;
                 }
-                toRemove.add(row);
                 row.setKeepInParent(true);
                 // we need to set this state earlier as otherwise we might generate some weird
                 // animations
@@ -1901,7 +1818,9 @@
         final int id = n.getId();
         final int userId = n.getUserId();
         try {
-            mBarService.onNotificationClear(pkg, tag, id, userId);
+            // TODO: record actual dismissal surface
+            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
+                    NotificationStats.DISMISSAL_OTHER);
             if (FORCE_REMOTE_INPUT_HISTORY
                     && mKeysKeptForRemoteInput.contains(n.getKey())) {
                 mKeysKeptForRemoteInput.remove(n.getKey());
@@ -1924,19 +1843,14 @@
 
         // Do not modify the notifications during collapse.
         if (isCollapsing()) {
-            addPostCollapseAction(new Runnable() {
-                @Override
-                public void run() {
-                    updateNotificationShade();
-                }
-            });
+            addPostCollapseAction(this::updateNotificationShade);
             return;
         }
 
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
         final int N = activeNotifications.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             Entry ent = activeNotifications.get(i);
             if (ent.row.isDismissed() || ent.row.isRemoved()) {
                 // we don't want to update removed notifications because they could
@@ -1980,7 +1894,8 @@
 
         for (ExpandableNotificationRow remove : toRemove) {
             if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transfering this notification to its parent, don't generate an animation
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
                 mStackScroller.setChildTransferInProgress(true);
             }
             if (remove.isSummaryWithChildren()) {
@@ -1992,7 +1907,7 @@
 
         removeNotificationChildren();
 
-        for (int i=0; i<toShow.size(); i++) {
+        for (int i = 0; i < toShow.size(); i++) {
             View v = toShow.get(i);
             if (v.getParent() == null) {
                 mVisualStabilityManager.notifyViewAddition(v);
@@ -2066,6 +1981,7 @@
         mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
                 && (mUserSetup || mUserSwitcherController == null
                         || !mUserSwitcherController.isSimpleUserSwitcher())
+                && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0)
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
                 && !mDozing
                 && !ONLY_CORE_APPS);
@@ -2102,7 +2018,7 @@
                 }
             }
 
-            // Finally after removing and adding has been beformed we can apply the order.
+            // Finally after removing and adding has been performed we can apply the order.
             orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
         }
         if (orderChanged) {
@@ -2275,10 +2191,11 @@
             MediaController controller = null;
             for (int i = 0; i < N; i++) {
                 final Entry entry = activeNotifications.get(i);
+
                 if (isMediaNotification(entry)) {
                     final MediaSession.Token token =
-                            entry.notification.getNotification().extras
-                            .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+                            entry.notification.getNotification().extras.getParcelable(
+                                    Notification.EXTRA_MEDIA_SESSION);
                     if (token != null) {
                         MediaController aController = new MediaController(mContext, token);
                         if (PlaybackState.STATE_PLAYING ==
@@ -2316,7 +2233,7 @@
                                 if (entry.notification.getPackageName().equals(pkg)) {
                                     if (DEBUG_MEDIA) {
                                         Log.v(TAG, "DEBUG_MEDIA: found controller matching "
-                                            + entry.notification.getKey());
+                                                + entry.notification.getKey());
                                     }
                                     controller = aController;
                                     mediaNotification = entry;
@@ -2367,12 +2284,8 @@
     }
 
     private boolean isPlaybackActive(int state) {
-        if (state != PlaybackState.STATE_STOPPED
-                && state != PlaybackState.STATE_ERROR
-                && state != PlaybackState.STATE_NONE) {
-            return true;
-        }
-        return false;
+        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+                && state != PlaybackState.STATE_NONE;
     }
 
     private void clearCurrentMediaNotification() {
@@ -2397,7 +2310,7 @@
     /**
      * Hide the album artwork that is fading out and release its bitmap.
      */
-    protected Runnable mHideBackdropFront = new Runnable() {
+    protected final Runnable mHideBackdropFront = new Runnable() {
         @Override
         public void run() {
             if (DEBUG_MEDIA) {
@@ -2549,14 +2462,11 @@
                             .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
                             .setDuration(300)
                             .setStartDelay(0)
-                            .withEndAction(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mBackdrop.setVisibility(View.GONE);
-                                    mBackdropFront.animate().cancel();
-                                    mBackdropBack.setImageDrawable(null);
-                                    mHandler.post(mHideBackdropFront);
-                                }
+                            .withEndAction(() -> {
+                                mBackdrop.setVisibility(View.GONE);
+                                mBackdropFront.animate().cancel();
+                                mBackdropBack.setImageDrawable(null);
+                                mHandler.post(mHideBackdropFront);
                             });
                     if (mKeyguardFadingAway) {
                         mBackdrop.animate()
@@ -2587,8 +2497,6 @@
     @Override
     public void disable(int state1, int state2, boolean animate) {
         animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
-        mDisabledUnmodified1 = state1;
-        mDisabledUnmodified2 = state2;
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
         mDisabled1 = state1;
@@ -2624,8 +2532,13 @@
         flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_CLOCK))                 ? '!' : ' ');
         flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH))                ? 'S' : 's');
         flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_SEARCH))                ? '!' : ' ');
+        flagdbg.append("> disable2<");
         flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS))       ? 'Q' : 'q');
         flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_QUICK_SETTINGS))       ? '!' : ' ');
+        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS))         ? 'I' : 'i');
+        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_SYSTEM_ICONS))         ? '!' : ' ');
+        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))   ? 'N' : 'n');
+        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))   ? '!' : ' ');
         flagdbg.append('>');
         Log.d(TAG, flagdbg.toString());
 
@@ -2652,6 +2565,13 @@
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
             updateQsExpansionEnabled();
         }
+
+        if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+            updateQsExpansionEnabled();
+            if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+                animateCollapsePanels();
+            }
+        }
     }
 
     /**
@@ -2739,11 +2659,8 @@
                 // make sure that the window stays small for one frame until the touchableRegion is set.
                 mNotificationPanel.requestLayout();
                 mStatusBarWindowManager.setForceWindowCollapsed(true);
-                mNotificationPanel.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mStatusBarWindowManager.setForceWindowCollapsed(false);
-                    }
+                mNotificationPanel.post(() -> {
+                    mStatusBarWindowManager.setForceWindowCollapsed(false);
                 });
             }
         } else {
@@ -2755,15 +2672,12 @@
                 // we need to keep the panel open artificially, let's wait until the animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mStackScroller.runAfterAnimationFinished(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!mHeadsUpManager.hasPinnedHeadsUp()) {
-                            mStatusBarWindowManager.setHeadsUpShowing(false);
-                            mHeadsUpManager.setHeadsUpGoingAway(false);
-                        }
-                        removeRemoteInputEntriesKeptUntilCollapsed();
+                mStackScroller.runAfterAnimationFinished(() -> {
+                    if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+                        mStatusBarWindowManager.setHeadsUpShowing(false);
+                        mHeadsUpManager.setHeadsUpGoingAway(false);
                     }
+                    removeRemoteInputEntriesKeptUntilCollapsed();
                 });
             }
         }
@@ -3014,7 +2928,9 @@
     }
 
     boolean panelsEnabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS;
+        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0
+                && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
+                && !ONLY_CORE_APPS;
     }
 
     void makeExpandedVisible(boolean force) {
@@ -3030,7 +2946,6 @@
         mStatusBarWindowManager.setPanelVisible(true);
 
         visibilityChanged(true);
-        mWaitingForKeyguardExit = false;
         recomputeDisableFlags(!force /* animate */);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
@@ -3039,23 +2954,15 @@
         animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
-    private final Runnable mAnimateCollapsePanels = new Runnable() {
-        @Override
-        public void run() {
-            animateCollapsePanels();
-        }
-    };
+    private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
 
     public void postAnimateCollapsePanels() {
         mHandler.post(mAnimateCollapsePanels);
     }
 
     public void postAnimateForceCollapsePanels() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-            }
+        mHandler.post(() -> {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
         });
     }
 
@@ -3105,6 +3012,9 @@
             }
         }
 
+        // TODO(b/62444020): remove when this bug is fixed
+        Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
+                + mNotificationPanel.canPanelBeCollapsed());
         if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
             // release focus immediately to kick off focus change transition
             mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -3210,7 +3120,7 @@
 
         if (SPEW) {
             Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
-                + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking);
+                    + mDisabled1 + " mDisabled2=" + mDisabled2);
         } else if (CHATTY) {
             if (event.getAction() != MotionEvent.ACTION_MOVE) {
                 Log.d(TAG, String.format(
@@ -3295,10 +3205,8 @@
 
             sbModeChanged = sbMode != -1;
             if (sbModeChanged && sbMode != mStatusBarMode) {
-                if (sbMode != mStatusBarMode) {
-                    mStatusBarMode = sbMode;
-                    checkBarModes();
-                }
+                mStatusBarMode = sbMode;
+                checkBarModes();
                 touchAutoHide();
             }
 
@@ -3385,12 +3293,7 @@
         }
     }
 
-    private final Runnable mCheckBarModes = new Runnable() {
-        @Override
-        public void run() {
-            checkBarModes();
-        }
-    };
+    private final Runnable mCheckBarModes = this::checkBarModes;
 
     public void setInteracting(int barWindow, boolean interacting) {
         final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
@@ -3449,7 +3352,7 @@
         }
     }
 
-    void checkUserAutohide(View v, MotionEvent event) {
+    void checkUserAutohide(MotionEvent event) {
         if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0  // a transient bar is revealed
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
                 && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
@@ -3516,9 +3419,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible
-                    + ", mTrackingPosition=" + mTrackingPosition);
-            pw.println("  mTracking=" + mTracking);
+            pw.println("  mExpandedVisible=" + mExpandedVisible);
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + viewInfo(mStackScroller)
@@ -3606,16 +3507,12 @@
             if (false) {
                 pw.println("see the logcat for a dump of the views we have created.");
                 // must happen on ui thread
-                mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mStatusBarView.getLocationOnScreen(mAbsPos);
-                            Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
-                                    + ") " + mStatusBarView.getWidth() + "x"
-                                    + getStatusBarHeight());
-                            mStatusBarView.debug();
-                        }
-                    });
+                mHandler.post(() -> {
+                    mStatusBarView.getLocationOnScreen(mAbsPos);
+                    Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] +
+                            ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight());
+                    mStatusBarView.debug();
+                });
             }
         }
 
@@ -3695,49 +3592,43 @@
 
         final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                 mContext, intent, mCurrentUserId);
-        Runnable runnable = new Runnable() {
-            @Override
-            public void run() {
-                mAssistManager.hideAssist();
-                intent.setFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                int result = ActivityManager.START_CANCELED;
-                ActivityOptions options = new ActivityOptions(getActivityOptions());
-                options.setDisallowEnterPictureInPictureWhileLaunching(
-                        disallowEnterPictureInPictureWhileLaunching);
-                if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
-                    // Normally an activity will set it's requested rotation
-                    // animation on its window. However when launching an activity
-                    // causes the orientation to change this is too late. In these cases
-                    // the default animation is used. This doesn't look good for
-                    // the camera (as it rotates the camera contents out of sync
-                    // with physical reality). So, we ask the WindowManager to
-                    // force the crossfade animation if an orientation change
-                    // happens to occur during the launch.
-                    options.setRotationAnimationHint(
-                            WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
-                }
-                try {
-                    result = ActivityManager.getService().startActivityAsUser(
-                            null, mContext.getBasePackageName(),
-                            intent,
-                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                            null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
-                            options.toBundle(), UserHandle.CURRENT.getIdentifier());
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Unable to start activity", e);
-                }
-                if (callback != null) {
-                    callback.onActivityStarted(result);
-                }
+        Runnable runnable = () -> {
+            mAssistManager.hideAssist();
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            int result = ActivityManager.START_CANCELED;
+            ActivityOptions options = new ActivityOptions(getActivityOptions());
+            options.setDisallowEnterPictureInPictureWhileLaunching(
+                    disallowEnterPictureInPictureWhileLaunching);
+            if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
+                // Normally an activity will set it's requested rotation
+                // animation on its window. However when launching an activity
+                // causes the orientation to change this is too late. In these cases
+                // the default animation is used. This doesn't look good for
+                // the camera (as it rotates the camera contents out of sync
+                // with physical reality). So, we ask the WindowManager to
+                // force the crossfade animation if an orientation change
+                // happens to occur during the launch.
+                options.setRotationAnimationHint(
+                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+            }
+            try {
+                result = ActivityManager.getService().startActivityAsUser(
+                        null, mContext.getBasePackageName(),
+                        intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+                        options.toBundle(), UserHandle.CURRENT.getIdentifier());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Unable to start activity", e);
+            }
+            if (callback != null) {
+                callback.onActivityStarted(result);
             }
         };
-        Runnable cancelRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (callback != null) {
-                    callback.onActivityStarted(ActivityManager.START_CANCELED);
-                }
+        Runnable cancelRunnable = () -> {
+            if (callback != null) {
+                callback.onActivityStarted(ActivityManager.START_CANCELED);
             }
         };
         executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
@@ -3782,7 +3673,7 @@
         }, cancelAction, afterKeyguardGone);
     }
 
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3811,7 +3702,7 @@
         }
     };
 
-    private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3972,7 +3863,6 @@
      * The LEDs are turned off when the notification panel is shown, even just a little bit.
      * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
      */
-    // Old BaseStatusBar.handleVisibileToUserChanged
     private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
         try {
             if (visibleToUser) {
@@ -3997,8 +3887,8 @@
         // Report all notifications as invisible and turn down the
         // reporter.
         if (!mCurrentlyVisibleNotifications.isEmpty()) {
-            logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(),
-                    mCurrentlyVisibleNotifications);
+            logNotificationVisibilityChanges(
+                    Collections.emptyList(), mCurrentlyVisibleNotifications);
             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
         }
         mHandler.removeCallbacks(mVisibilityReporter);
@@ -4105,7 +3995,7 @@
         vib.vibrate(250, VIBRATION_ATTRIBUTES);
     }
 
-    Runnable mStartTracing = new Runnable() {
+    final Runnable mStartTracing = new Runnable() {
         @Override
         public void run() {
             vibrate();
@@ -4116,13 +4006,10 @@
         }
     };
 
-    Runnable mStopTracing = new Runnable() {
-        @Override
-        public void run() {
-            android.os.Debug.stopMethodTracing();
-            Log.d(TAG, "stopTracing");
-            vibrate();
-        }
+    final Runnable mStopTracing = () -> {
+        android.os.Debug.stopMethodTracing();
+        Log.d(TAG, "stopTracing");
+        vibrate();
     };
 
     @Override
@@ -4149,40 +4036,6 @@
         startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
     }
 
-    private static class FastColorDrawable extends Drawable {
-        private final int mColor;
-
-        public FastColorDrawable(int color) {
-            mColor = 0xff000000 | color;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.drawColor(mColor, PorterDuff.Mode.SRC);
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.OPAQUE;
-        }
-
-        @Override
-        public void setBounds(int left, int top, int right, int bottom) {
-        }
-
-        @Override
-        public void setBounds(Rect bounds) {
-        }
-    }
-
     public void destroy() {
         // Begin old BaseStatusBar.destroy().
         mContext.unregisterReceiver(mBaseBroadcastReceiver);
@@ -4275,6 +4128,9 @@
                 }
             }
         }
+        if (modeChange || command.equals(COMMAND_OPERATOR)) {
+            dispatchDemoCommandToView(command, args, R.id.operator_name);
+        }
     }
 
     private void dispatchDemoCommandToView(String command, Bundle args, int id) {
@@ -4400,31 +4256,23 @@
             Runnable endRunnable) {
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         mLaunchTransitionEndRunnable = endRunnable;
-        Runnable hideRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLaunchTransitionFadingAway = true;
-                if (beforeFading != null) {
-                    beforeFading.run();
-                }
-                mScrimController.forceHideScrims(true /* hide */, false /* animated */);
-                updateMediaMetaData(false, true);
-                mNotificationPanel.setAlpha(1);
-                mStackScroller.setParentNotFullyVisible(true);
-                mNotificationPanel.animate()
-                        .alpha(0)
-                        .setStartDelay(FADE_KEYGUARD_START_DELAY)
-                        .setDuration(FADE_KEYGUARD_DURATION)
-                        .withLayer()
-                        .withEndAction(new Runnable() {
-                            @Override
-                            public void run() {
-                                onLaunchTransitionFadingEnded();
-                            }
-                        });
-                mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
-                        LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
+        Runnable hideRunnable = () -> {
+            mLaunchTransitionFadingAway = true;
+            if (beforeFading != null) {
+                beforeFading.run();
             }
+            mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+            updateMediaMetaData(false, true);
+            mNotificationPanel.setAlpha(1);
+            mStackScroller.setParentNotFullyVisible(true);
+            mNotificationPanel.animate()
+                    .alpha(0)
+                    .setStartDelay(FADE_KEYGUARD_START_DELAY)
+                    .setDuration(FADE_KEYGUARD_DURATION)
+                    .withLayer()
+                    .withEndAction(this::onLaunchTransitionFadingEnded);
+            mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
+                    LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
         };
         if (mNotificationPanel.isLaunchTransitionRunning()) {
             mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
@@ -4553,7 +4401,6 @@
 
         // Treat Keyguard exit animation as an app transition to achieve nice transition for status
         // bar.
-        mKeyguardGoingAway = true;
         mKeyguardMonitor.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(true);
     }
@@ -4562,14 +4409,13 @@
      * Notifies the status bar the Keyguard is fading away with the specified timings.
      *
      * @param startTime the start time of the animations in uptime millis
-     * @param delay the precalculated animation delay in miliseconds
+     * @param delay the precalculated animation delay in milliseconds
      * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
     public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
         mKeyguardFadingAway = true;
         mKeyguardFadingAwayDelay = delay;
         mKeyguardFadingAwayDuration = fadeoutDuration;
-        mWaitingForKeyguardExit = false;
         mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
                         - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
                 LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
@@ -4589,14 +4435,9 @@
      */
     public void finishKeyguardFadingAway() {
         mKeyguardFadingAway = false;
-        mKeyguardGoingAway = false;
         mKeyguardMonitor.notifyKeyguardDoneFading();
     }
 
-    public void stopWaitingForKeyguardExit() {
-        mWaitingForKeyguardExit = false;
-    }
-
     private void updatePublicMode() {
         final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
         final boolean devicePublic = showingKeyguard
@@ -4677,12 +4518,14 @@
         final boolean useDarkTheme = systemColors != null
                 && (systemColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
         if (isUsingDarkTheme() != useDarkTheme) {
-            try {
-                mOverlayManager.setEnabled("com.android.systemui.theme.dark",
-                        useDarkTheme, mCurrentUserId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Can't change theme", e);
-            }
+            mUiOffloadThread.submit(() -> {
+                try {
+                    mOverlayManager.setEnabled("com.android.systemui.theme.dark",
+                            useDarkTheme, mCurrentUserId);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Can't change theme", e);
+                }
+            });
         }
 
         // Lock wallpaper defines the color of the majority of the views, hence we'll use it
@@ -4810,7 +4653,6 @@
     }
 
     protected void showBouncer() {
-        mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
         mStatusBarKeyguardViewManager.dismiss();
     }
 
@@ -4827,7 +4669,7 @@
 
     @Override
     public void onActivated(ActivatableNotificationView view) {
-        onActivated((View)view);
+        onActivated((View) view);
         mStackScroller.setActivatedChild(view);
     }
 
@@ -5000,7 +4842,7 @@
 
     @Override
     public void onTouchSlopExceeded() {
-        mStackScroller.removeLongPressCallback();
+        mStackScroller.cancelLongPress();
         mStackScroller.checkSnoozeLeavebehind();
     }
 
@@ -5022,6 +4864,10 @@
      * @param expandView The view to expand after going to the shade.
      */
     public void goToLockedShade(View expandView) {
+        if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+            return;
+        }
+
         int userId = mCurrentUserId;
         ExpandableNotificationRow row = null;
         if (expandView instanceof ExpandableNotificationRow) {
@@ -5129,51 +4975,41 @@
         updateNotifications();
         if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
             // Expand notification panel and the notification row, then click on remote input view
-            final Runnable clickPendingViewRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
-                    if (pendingWorkRemoteInputView == null) {
+            final Runnable clickPendingViewRunnable = () -> {
+                final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+                if (pendingWorkRemoteInputView == null) {
+                    return;
+                }
+
+                // Climb up the hierarchy until we get to the container for this row.
+                ViewParent p = pendingWorkRemoteInputView.getParent();
+                while (!(p instanceof ExpandableNotificationRow)) {
+                    if (p == null) {
                         return;
                     }
+                    p = p.getParent();
+                }
 
-                    // Climb up the hierarchy until we get to the container for this row.
-                    ViewParent p = pendingWorkRemoteInputView.getParent();
-                    while (!(p instanceof ExpandableNotificationRow)) {
-                        if (p == null) {
-                            return;
+                final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+                ViewParent viewParent = row.getParent();
+                if (viewParent instanceof NotificationStackScrollLayout) {
+                    final NotificationStackScrollLayout scrollLayout =
+                            (NotificationStackScrollLayout) viewParent;
+                    row.makeActionsVisibile();
+                    row.post(() -> {
+                        final Runnable finishScrollingCallback = () -> {
+                            mPendingWorkRemoteInputView.callOnClick();
+                            mPendingWorkRemoteInputView = null;
+                            scrollLayout.setFinishScrollingCallback(null);
+                        };
+                        if (scrollLayout.scrollTo(row)) {
+                            // It scrolls! So call it when it's finished.
+                            scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
+                        } else {
+                            // It does not scroll, so call it now!
+                            finishScrollingCallback.run();
                         }
-                        p = p.getParent();
-                    }
-
-                    final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
-                    ViewParent viewParent = row.getParent();
-                    if (viewParent instanceof NotificationStackScrollLayout) {
-                        final NotificationStackScrollLayout scrollLayout =
-                                (NotificationStackScrollLayout) viewParent;
-                        row.makeActionsVisibile();
-                        row.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                final Runnable finishScrollingCallback = new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        mPendingWorkRemoteInputView.callOnClick();
-                                        mPendingWorkRemoteInputView = null;
-                                        scrollLayout.setFinishScrollingCallback(null);
-                                    }
-                                };
-                                if (scrollLayout.scrollTo(row)) {
-                                    // It scrolls! So call it when it's finished.
-                                    scrollLayout.setFinishScrollingCallback(
-                                            finishScrollingCallback);
-                                } else {
-                                    // It does not scroll, so call it now!
-                                    finishScrollingCallback.run();
-                                }
-                            }
-                        });
-                    }
+                    });
                 }
             };
             mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
@@ -5236,7 +5072,7 @@
         }
     }
 
-    WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
             mNotificationPanel.onAffordanceLaunchEnded();
@@ -5259,12 +5095,7 @@
 
                 // This gets executed before we will show Keyguard, so post it in order that the state
                 // is correct.
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onCameraLaunchGestureDetected(mLastCameraLaunchSource);
-                    }
-                });
+                mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
             }
             updateIsKeyguard();
         }
@@ -5290,7 +5121,7 @@
         }
     };
 
-    ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+    final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn() {
             mFalsingManager.onScreenTurningOn();
@@ -5487,7 +5318,7 @@
     }
 
     private final class DozeServiceHost implements DozeHost {
-        private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+        private final ArrayList<Callback> mCallbacks = new ArrayList<>();
         private boolean mAnimateWakeup;
         private boolean mIgnoreTouchWhilePulsing;
 
@@ -5643,7 +5474,7 @@
 
         @Override
         public void onDoubleTap(float screenX, float screenY) {
-            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null 
+            if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
                 && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
                 mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
                 float viewX = screenX - mTmpInt2[0];
@@ -5700,7 +5531,7 @@
     protected NotificationData mNotificationData;
     protected NotificationStackScrollLayout mStackScroller;
 
-    protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+    protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
 
     protected RemoteInputController mRemoteInputController;
 
@@ -5710,34 +5541,30 @@
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
+    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
 
     protected int mCurrentUserId = 0;
-    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
 
-    protected int mLayoutDirection = -1; // invalid
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
-    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
-    protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
+    protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
+    protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
 
     /**
      * Notifications with keys in this set are not actually around anymore. We kept them around
      * when they were canceled in response to a remote input interaction. This allows us to show
      * what you replied and allows you to continue typing into it.
      */
-    protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+    protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
 
-    private Locale mLocale;
-
     protected boolean mUseHeadsUp = false;
-    protected boolean mHeadsUpTicker = false;
     protected boolean mDisableNotificationAlerts = false;
 
     protected DevicePolicyManager mDevicePolicyManager;
@@ -5772,13 +5599,11 @@
     private NotificationGuts mNotificationGutsExposed;
     private MenuItem mGutsMenuItem;
 
-    private KeyboardShortcuts mKeyboardShortcuts;
-
     protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
-    private NotificationClicker mNotificationClicker = new NotificationClicker();
+    private final NotificationClicker mNotificationClicker = new NotificationClicker();
 
     protected AssistManager mAssistManager;
 
@@ -5838,15 +5663,14 @@
         }
     };
 
-    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+    private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
 
         @Override
         public boolean onClickHandler(
                 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
             wakeUpIfDozing(SystemClock.uptimeMillis(), view);
 
-
-            if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+            if (handleRemoteInput(view, pendingIntent)) {
                 return true;
             }
 
@@ -5864,33 +5688,29 @@
             }
             final boolean isActivity = pendingIntent.isActivity();
             if (isActivity) {
-                final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
                 final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                         mContext, pendingIntent.getIntent(), mCurrentUserId);
-                dismissKeyguardThenExecute(new OnDismissAction() {
-                    @Override
-                    public boolean onDismiss() {
-                        try {
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-
-                        boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
-                        // close the shade if it was open
-                        if (handled && !mNotificationPanel.isFullyCollapsed()) {
-                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                    true /* force */);
-                            visibilityChanged(false);
-                            mAssistManager.hideAssist();
-
-                            // Wait for activity start.
-                            return true;
-                        } else {
-                            return false;
-                        }
-
+                dismissKeyguardThenExecute(() -> {
+                    try {
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
                     }
+
+                    boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
+
+                    // close the shade if it was open
+                    if (handled && !mNotificationPanel.isFullyCollapsed()) {
+                        animateCollapsePanels(
+                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+                        visibilityChanged(false);
+                        mAssistManager.hideAssist();
+
+                        // Wait for activity start.
+                        return true;
+                    } else {
+                        return false;
+                    }
+
                 }, afterKeyguardGone);
                 return true;
             } else {
@@ -5935,7 +5755,12 @@
                     WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         }
 
-        private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+        private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
+            if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+                // Skip remote input as doing so will expand the notification shade.
+                return true;
+            }
+
             Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
             RemoteInput[] inputs = null;
             if (tag instanceof RemoteInput[]) {
@@ -6050,7 +5875,7 @@
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 updateCurrentProfilesCache();
-                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
 
                 updateLockscreenNotificationSetting();
 
@@ -6061,8 +5886,7 @@
                 List<ActivityManager.RecentTaskInfo> recentTask = null;
                 try {
                     recentTask = ActivityManager.getService().getRecentTasks(1,
-                            ActivityManager.RECENT_WITH_EXCLUDED
-                            | ActivityManager.RECENT_INCLUDE_PROFILES,
+                            ActivityManager.RECENT_WITH_EXCLUDED,
                             mCurrentUserId).getList();
                 } catch (RemoteException e) {
                     // Abandon hope activity manager not running.
@@ -6073,8 +5897,7 @@
                         Toast toast = Toast.makeText(mContext,
                                 R.string.managed_profile_foreground_toast,
                                 Toast.LENGTH_SHORT);
-                        TextView text = (TextView) toast.getView().findViewById(
-                                android.R.id.message);
+                        TextView text = toast.getView().findViewById(android.R.id.message);
                         text.setCompoundDrawablesRelativeWithIntrinsicBounds(
                                 R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
                         int paddingPx = mContext.getResources().getDimensionPixelSize(
@@ -6150,15 +5973,12 @@
                 return;
             }
             final RankingMap currentRanking = getCurrentRanking();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    for (StatusBarNotification sbn : notifications) {
-                        try {
-                            addNotification(sbn, currentRanking);
-                        } catch (InflationException e) {
-                            handleInflationException(sbn, e);
-                        }
+            mHandler.post(() -> {
+                for (StatusBarNotification sbn : notifications) {
+                    try {
+                        addNotification(sbn, currentRanking);
+                    } catch (InflationException e) {
+                        handleInflationException(sbn, e);
                     }
                 }
             });
@@ -6169,40 +5989,37 @@
                 final RankingMap rankingMap) {
             if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
             if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        processForRemoteInput(sbn.getNotification());
-                        String key = sbn.getKey();
-                        mKeysKeptForRemoteInput.remove(key);
-                        boolean isUpdate = mNotificationData.get(key) != null;
-                        // In case we don't allow child notifications, we ignore children of
-                        // notifications that have a summary, since we're not going to show them
-                        // anyway. This is true also when the summary is canceled,
-                        // because children are automatically canceled by NoMan in that case.
-                        if (!ENABLE_CHILD_NOTIFICATIONS
+                mHandler.post(() -> {
+                    processForRemoteInput(sbn.getNotification());
+                    String key = sbn.getKey();
+                    mKeysKeptForRemoteInput.remove(key);
+                    boolean isUpdate = mNotificationData.get(key) != null;
+                    // In case we don't allow child notifications, we ignore children of
+                    // notifications that have a summary, since we're not going to show them
+                    // anyway. This is true also when the summary is canceled,
+                    // because children are automatically canceled by NoMan in that case.
+                    if (!ENABLE_CHILD_NOTIFICATIONS
                             && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
-                            }
+                        if (DEBUG) {
+                            Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                        }
 
-                            // Remove existing notification to avoid stale data.
-                            if (isUpdate) {
-                                removeNotification(key, rankingMap);
-                            } else {
-                                mNotificationData.updateRanking(rankingMap);
-                            }
-                            return;
+                        // Remove existing notification to avoid stale data.
+                        if (isUpdate) {
+                            removeNotification(key, rankingMap);
+                        } else {
+                            mNotificationData.updateRanking(rankingMap);
                         }
-                        try {
-                            if (isUpdate) {
-                                updateNotification(sbn, rankingMap);
-                            } else {
-                                addNotification(sbn, rankingMap);
-                            }
-                        } catch (InflationException e) {
-                            handleInflationException(sbn, e);
+                        return;
+                    }
+                    try {
+                        if (isUpdate) {
+                            updateNotification(sbn, rankingMap);
+                        } else {
+                            addNotification(sbn, rankingMap);
                         }
+                    } catch (InflationException e) {
+                        handleInflationException(sbn, e);
                     }
                 });
             }
@@ -6250,7 +6067,7 @@
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 return;
             }
-            Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+            Log.d(TAG, "disabling lockscreen notifications and alerting the user");
             // disable lockscreen notifications until user acts on the banner.
             Settings.Secure.putInt(mContext.getContentResolver(),
                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
@@ -6291,11 +6108,10 @@
 
     @Override  // NotificationData.Environment
     public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
-        final int thisUserId = mCurrentUserId;
         final int notificationUserId = n.getUserId();
         if (DEBUG && MULTIUSER_DEBUG) {
-            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
-                    n, thisUserId, notificationUserId));
+            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
+                    mCurrentUserId, notificationUserId));
         }
         return isCurrentProfile(notificationUserId);
     }
@@ -6343,21 +6159,15 @@
     }
 
     private void startNotificationGutsIntent(final Intent intent, final int appUid) {
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                AsyncTask.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        TaskStackBuilder.create(mContext)
-                                .addNextIntentWithParentStack(intent)
-                                .startActivities(getActivityOptions(),
-                                        new UserHandle(UserHandle.getUserId(appUid)));
-                    }
-                });
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-                return true;
-            }
+        dismissKeyguardThenExecute(() -> {
+            AsyncTask.execute(() -> {
+                TaskStackBuilder.create(mContext)
+                        .addNextIntentWithParentStack(intent)
+                        .startActivities(getActivityOptions(),
+                                new UserHandle(UserHandle.getUserId(appUid)));
+            });
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+            return true;
         }, false /* afterKeyguardGone */);
     }
 
@@ -6428,7 +6238,7 @@
                 startNotificationGutsIntent(intent, sbn.getUid());
             };
             final View.OnClickListener onDoneClick = (View v) -> {
-                saveAndCloseNotificationMenu(info, row, guts, v);
+                saveAndCloseNotificationMenu(row, guts, v);
             };
             final NotificationInfo.CheckSaveListener checkSaveListener =
                     (Runnable saveImportance) -> {
@@ -6445,7 +6255,7 @@
                 }
             };
 
-            ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
+            ArraySet<NotificationChannel> channels = new ArraySet<>();
             channels.add(row.getEntry().channel);
             if (row.isSummaryWithChildren()) {
                 // If this is a summary, then add in the children notification channels for the
@@ -6473,7 +6283,7 @@
         }
     }
 
-    private void saveAndCloseNotificationMenu(NotificationInfo info,
+    private void saveAndCloseNotificationMenu(
             ExpandableNotificationRow row, NotificationGuts guts, View done) {
         guts.resetFalsingCheck();
         int[] rowLocation = new int[2];
@@ -6489,14 +6299,15 @@
                 true /* removeControls */, x, y, true /* resetMenu */);
     }
 
-    protected SwipeHelper.LongPressListener getNotificationLongClicker() {
-        return new SwipeHelper.LongPressListener() {
+    protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+        return new ExpandableNotificationRow.LongPressListener() {
             @Override
             public boolean onLongPress(View v, final int x, final int y,
                     MenuItem item) {
                 if (!(v instanceof ExpandableNotificationRow)) {
                     return false;
                 }
+
                 if (v.getWindowToken() == null) {
                     Log.e(TAG, "Trying to show notification guts, but not attached to window");
                     return false;
@@ -6511,7 +6322,7 @@
                     closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
                             true /* removeControls */, -1 /* x */, -1 /* y */,
                             true /* resetMenu */);
-                    return false;
+                    return true;
                 }
                 bindGuts(row, item);
                 NotificationGuts guts = row.getGuts();
@@ -6642,13 +6453,6 @@
         updateHideIconsForBouncer(true /* animate */);
     }
 
-    protected void sendCloseSystemWindows(String reason) {
-        try {
-            ActivityManager.getService().closeSystemDialogs(reason);
-        } catch (RemoteException e) {
-        }
-    }
-
     protected void toggleKeyboardShortcuts(int deviceId) {
         KeyboardShortcuts.toggle(mContext, deviceId);
     }
@@ -6750,18 +6554,6 @@
         return isLockscreenPublicMode(userId);
     }
 
-    public void onNotificationClear(StatusBarNotification notification) {
-        try {
-            mBarService.onNotificationClear(
-                    notification.getPackageName(),
-                    notification.getTag(),
-                    notification.getId(),
-                    notification.getUserId());
-        } catch (android.os.RemoteException ex) {
-            // oh well
-        }
-    }
-
     /**
      * Called when the notification panel layouts
      */
@@ -6810,6 +6602,7 @@
         row.setRemoteViewClickHandler(mOnClickHandler);
         row.setInflationCallback(this);
         row.setSecureStateProvider(this::isKeyguardCurrentlySecure);
+        row.setLongPressListener(getNotificationLongClicker());
 
         // Get the app name.
         // Note that Notification.Builder#bindHeaderAppName has similar logic
@@ -6919,49 +6712,42 @@
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
-        final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
         final boolean afterKeyguardGone = intent.isActivity()
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                 mCurrentUserId);
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                new Thread() {
-                    @Override
-                    public void run() {
-                        try {
-                            // The intent we are sending is for the application, which
-                            // won't have permission to immediately start an activity after
-                            // the user switches to home.  We know it is safe to do at this
-                            // point, so make sure new activity switches are now allowed.
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-                        try {
-                            intent.send(null, 0, null, null, null, null, getActivityOptions());
-                        } catch (PendingIntent.CanceledException e) {
-                            // the stack trace isn't very helpful here.
-                            // Just log the exception message.
-                            Log.w(TAG, "Sending intent failed: " + e);
-
-                            // TODO: Dismiss Keyguard.
-                        }
-                        if (intent.isActivity()) {
-                            mAssistManager.hideAssist();
-                        }
-                    }
-                }.start();
-
-                if (!mNotificationPanel.isFullyCollapsed()) {
-                    // close the shade if it was open
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */, true /* delayed */);
-                    visibilityChanged(false);
-
-                    return true;
-                } else {
-                    return false;
+        dismissKeyguardThenExecute(() -> {
+            new Thread(() -> {
+                try {
+                    // The intent we are sending is for the application, which
+                    // won't have permission to immediately start an activity after
+                    // the user switches to home.  We know it is safe to do at this
+                    // point, so make sure new activity switches are now allowed.
+                    ActivityManager.getService().resumeAppSwitches();
+                } catch (RemoteException e) {
                 }
+                try {
+                    intent.send(null, 0, null, null, null, null, getActivityOptions());
+                } catch (PendingIntent.CanceledException e) {
+                    // the stack trace isn't very helpful here.
+                    // Just log the exception message.
+                    Log.w(TAG, "Sending intent failed: " + e);
+
+                    // TODO: Dismiss Keyguard.
+                }
+                if (intent.isActivity()) {
+                    mAssistManager.hideAssist();
+                }
+            }).start();
+
+            if (!mNotificationPanel.isFullyCollapsed()) {
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                        true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            } else {
+                return false;
             }
         }, afterKeyguardGone);
     }
@@ -6999,130 +6785,110 @@
 
             // Mark notification for one frame.
             row.setJustClicked(true);
-            DejankUtils.postAfterTraversal(new Runnable() {
-                @Override
-                public void run() {
-                    row.setJustClicked(false);
-                }
-            });
+            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
 
             final boolean afterKeyguardGone = intent.isActivity()
                     && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                             mCurrentUserId);
-            dismissKeyguardThenExecute(new OnDismissAction() {
-                @Override
-                public boolean onDismiss() {
-                    if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
-                        // Release the HUN notification to the shade.
+            dismissKeyguardThenExecute(() -> {
+                if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+                    // Release the HUN notification to the shade.
 
-                        if (isPanelFullyCollapsed()) {
-                            HeadsUpManager.setIsClickedNotification(row, true);
-                        }
-                        //
-                        // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
-                        // become canceled shortly by NoMan, but we can't assume that.
-                        mHeadsUpManager.releaseImmediately(notificationKey);
+                    if (isPanelFullyCollapsed()) {
+                        HeadsUpManager.setIsClickedNotification(row, true);
                     }
-                    StatusBarNotification parentToCancel = null;
-                    if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-                        StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
-                                        .getStatusBarNotification();
-                        if (shouldAutoCancel(summarySbn)) {
-                            parentToCancel = summarySbn;
-                        }
+                    //
+                    // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+                    // become canceled shortly by NoMan, but we can't assume that.
+                    mHeadsUpManager.releaseImmediately(notificationKey);
+                }
+                StatusBarNotification parentToCancel = null;
+                if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+                    StatusBarNotification summarySbn =
+                            mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+                    if (shouldAutoCancel(summarySbn)) {
+                        parentToCancel = summarySbn;
                     }
-                    final StatusBarNotification parentToCancelFinal = parentToCancel;
-                    final Runnable runnable = new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                // The intent we are sending is for the application, which
-                                // won't have permission to immediately start an activity after
-                                // the user switches to home.  We know it is safe to do at this
-                                // point, so make sure new activity switches are now allowed.
-                                ActivityManager.getService().resumeAppSwitches();
-                            } catch (RemoteException e) {
-                            }
-                            if (intent != null) {
-                                // If we are launching a work activity and require to launch
-                                // separate work challenge, we defer the activity action and cancel
-                                // notification until work challenge is unlocked.
-                                if (intent.isActivity()) {
-                                    final int userId = intent.getCreatorUserHandle()
-                                            .getIdentifier();
-                                    if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-                                            && mKeyguardManager.isDeviceLocked(userId)) {
-                                        // TODO(b/28935539): should allow certain activities to
-                                        // bypass work challenge
-                                        if (startWorkChallengeIfNecessary(userId,
-                                                intent.getIntentSender(), notificationKey)) {
-                                            // Show work challenge, do not run PendingIntent and
-                                            // remove notification
-                                            return;
-                                        }
-                                    }
-                                }
-                                try {
-                                    intent.send(null, 0, null, null, null, null,
-                                            getActivityOptions());
-                                } catch (PendingIntent.CanceledException e) {
-                                    // the stack trace isn't very helpful here.
-                                    // Just log the exception message.
-                                    Log.w(TAG, "Sending contentIntent failed: " + e);
-
-                                    // TODO: Dismiss Keyguard.
-                                }
-                                if (intent.isActivity()) {
-                                    mAssistManager.hideAssist();
+                }
+                final StatusBarNotification parentToCancelFinal = parentToCancel;
+                final Runnable runnable = () -> {
+                    try {
+                        // The intent we are sending is for the application, which
+                        // won't have permission to immediately start an activity after
+                        // the user switches to home.  We know it is safe to do at this
+                        // point, so make sure new activity switches are now allowed.
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
+                    }
+                    if (intent != null) {
+                        // If we are launching a work activity and require to launch
+                        // separate work challenge, we defer the activity action and cancel
+                        // notification until work challenge is unlocked.
+                        if (intent.isActivity()) {
+                            final int userId = intent.getCreatorUserHandle().getIdentifier();
+                            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+                                    && mKeyguardManager.isDeviceLocked(userId)) {
+                                // TODO(b/28935539): should allow certain activities to
+                                // bypass work challenge
+                                if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+                                        notificationKey)) {
+                                    // Show work challenge, do not run PendingIntent and
+                                    // remove notification
+                                    return;
                                 }
                             }
-
-                            try {
-                                mBarService.onNotificationClick(notificationKey);
-                            } catch (RemoteException ex) {
-                                // system process is dead if we're here.
-                            }
-                            if (parentToCancelFinal != null) {
-                                // We have to post it to the UI thread for synchronization
-                                mHandler.post(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        Runnable removeRunnable = new Runnable() {
-                                            @Override
-                                            public void run() {
-                                                performRemoveNotification(parentToCancelFinal);
-                                            }
-                                        };
-                                        if (isCollapsing()) {
-                                            // To avoid lags we're only performing the remove
-                                            // after the shade was collapsed
-                                            addPostCollapseAction(removeRunnable);
-                                        } else {
-                                            removeRunnable.run();
-                                        }
-                                    }
-                                });
-                            }
                         }
-                    };
+                        try {
+                            intent.send(null, 0, null, null, null, null, getActivityOptions());
+                        } catch (PendingIntent.CanceledException e) {
+                            // the stack trace isn't very helpful here.
+                            // Just log the exception message.
+                            Log.w(TAG, "Sending contentIntent failed: " + e);
 
-                    if (mStatusBarKeyguardViewManager.isShowing()
-                            && mStatusBarKeyguardViewManager.isOccluded()) {
-                        mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                    } else {
-                        new Thread(runnable).start();
+                            // TODO: Dismiss Keyguard.
+                        }
+                        if (intent.isActivity()) {
+                            mAssistManager.hideAssist();
+                        }
                     }
 
-                    if (!mNotificationPanel.isFullyCollapsed()) {
-                        // close the shade if it was open
-                        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed */);
-                        visibilityChanged(false);
-
-                        return true;
-                    } else {
-                        return false;
+                    try {
+                        mBarService.onNotificationClick(notificationKey);
+                    } catch (RemoteException ex) {
+                        // system process is dead if we're here.
                     }
+                    if (parentToCancelFinal != null) {
+                        // We have to post it to the UI thread for synchronization
+                        mHandler.post(() -> {
+                            Runnable removeRunnable =
+                                    () -> performRemoveNotification(parentToCancelFinal);
+                            if (isCollapsing()) {
+                                // To avoid lags we're only performing the remove
+                                // after the shade was collapsed
+                                addPostCollapseAction(removeRunnable);
+                            } else {
+                                removeRunnable.run();
+                            }
+                        });
+                    }
+                };
+
+                if (mStatusBarKeyguardViewManager.isShowing()
+                        && mStatusBarKeyguardViewManager.isOccluded()) {
+                    mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+                } else {
+                    new Thread(runnable).start();
+                }
+
+                if (!mNotificationPanel.isFullyCollapsed()) {
+                    // close the shade if it was open
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                            true /* delayed */);
+                    visibilityChanged(false);
+
+                    return true;
+                } else {
+                    return false;
                 }
             }, afterKeyguardGone);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c240765..bcda60e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,10 +14,10 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.annotation.ColorInt;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -29,11 +29,11 @@
 import android.widget.LinearLayout.LayoutParams;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 public interface StatusBarIconController {
 
@@ -149,6 +149,14 @@
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+
+            DisableStateTracker tracker =
+                    new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS);
+            mGroup.addOnAttachStateChangeListener(tracker);
+            if (mGroup.isAttachedToWindow()) {
+                // In case we miss the first onAttachedToWindow event
+                tracker.onViewAttachedToWindow(mGroup);
+            }
         }
 
         protected void onIconAdded(int index, String slot, boolean blocked,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 68f8e06..1c3ee75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -50,21 +49,17 @@
 public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
         ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
 
-    private final DarkIconDispatcher mDarkIconDispatcher;
+    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
+    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
+    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
 
     private Context mContext;
     private DemoStatusIcons mDemoStatusIcons;
 
-    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
-
-    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
-    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
     public StatusBarIconControllerImpl(Context context) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         Dependency.get(ConfigurationController.class).addCallback(this);
-        mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
         mContext = context;
 
         loadDimens();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bbce751..09828dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -225,7 +225,6 @@
         if (mShowing) {
             if (mOccluded && !mDozing) {
                 mStatusBar.hideKeyguard();
-                mStatusBar.stopWaitingForKeyguardExit();
                 if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
                     hideBouncer(false /* destroyView */);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index c0a6837..0d21c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.wifi.WifiManager.ActionListener;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -59,13 +58,19 @@
 
     private int mCurrentUser;
 
-    public AccessPointControllerImpl(Context context, Looper bgLooper) {
+    public AccessPointControllerImpl(Context context) {
         mContext = context;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
+        mWifiTracker = new WifiTracker(context, this, false, true);
         mCurrentUser = ActivityManager.getCurrentUser();
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        mWifiTracker.onDestroy();
+    }
+
     public boolean canConfigWifi() {
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
                 new UserHandle(mCurrentUser));
@@ -81,7 +86,7 @@
         if (DEBUG) Log.d(TAG, "addCallback " + callback);
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
-            mWifiTracker.startTracking();
+            mWifiTracker.onStart();
         }
     }
 
@@ -91,7 +96,7 @@
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         mCallbacks.remove(callback);
         if (mCallbacks.isEmpty()) {
-            mWifiTracker.stopTracking();
+            mWifiTracker.onStop();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index a456786..5159e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -71,7 +71,7 @@
                 break;
             case MSG_NO_SIM_VISIBLE_CHANGED:
                 for (SignalCallback signalCluster : mSignalCallbacks) {
-                    signalCluster.setNoSims(msg.arg1 != 0);
+                    signalCluster.setNoSims(msg.arg1 != 0, msg.arg2 != 0);
                 }
                 break;
             case MSG_ETHERNET_CHANGED:
@@ -144,8 +144,8 @@
     }
 
     @Override
-    public void setNoSims(boolean show) {
-        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+    public void setNoSims(boolean show, boolean simDetected) {
+        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 2771011..9eee906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -52,7 +52,7 @@
                 int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
                 String description, boolean isWide, int subId, boolean roaming) {}
         default void setSubs(List<SubscriptionInfo> subs) {}
-        default void setNoSims(boolean show) {}
+        default void setNoSims(boolean show, boolean simDetected) {}
 
         default void setEthernetIndicators(IconState icon) {}
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c217bda..40ee838 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -58,10 +58,8 @@
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 
@@ -116,7 +114,7 @@
 
     // States that don't belong to a subcontroller.
     private boolean mAirplaneMode = false;
-    private boolean mHasNoSims;
+    private boolean mHasNoSubs;
     private Locale mLocale = null;
     // This list holds our ordering.
     private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
@@ -140,6 +138,7 @@
     @VisibleForTesting
     ServiceState mLastServiceState;
     private boolean mUserSetup;
+    private boolean mSimDetected;
 
     /**
      * Construct this controller object and register for updates.
@@ -151,7 +150,7 @@
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
-                new AccessPointControllerImpl(context, bgLooper),
+                new AccessPointControllerImpl(context),
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController);
@@ -363,7 +362,7 @@
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        cb.setNoSims(mHasNoSims);
+        cb.setNoSims(mHasNoSubs, mSimDetected);
         mWifiSignalController.notifyListeners(cb);
         mEthernetSignalController.notifyListeners(cb);
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -383,13 +382,6 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... args) {
-                // Disable tethering if enabling Wifi
-                final int wifiApState = mWifiManager.getWifiApState();
-                if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
-                        (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
-                    mWifiManager.setWifiApEnabled(null, false);
-                }
-
                 mWifiManager.setWifiEnabled(enabled);
                 return null;
             }
@@ -498,13 +490,27 @@
 
     @VisibleForTesting
     protected void updateNoSims() {
-        boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
-        if (hasNoSims != mHasNoSims) {
-            mHasNoSims = hasNoSims;
-            mCallbackHandler.setNoSims(mHasNoSims);
+        boolean hasNoSubs = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
+        boolean simDetected = hasAnySim();
+        if (hasNoSubs != mHasNoSubs || simDetected != mSimDetected) {
+            mHasNoSubs = hasNoSubs;
+            mSimDetected = simDetected;
+            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
     }
 
+    private boolean hasAnySim() {
+        int simCount = mPhone.getSimCount();
+        for (int i = 0; i < simCount; i++) {
+            int state = mPhone.getSimState(i);
+            if (state != TelephonyManager.SIM_STATE_ABSENT
+                    && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @VisibleForTesting
     void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
         Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
@@ -631,7 +637,7 @@
     private void notifyListeners() {
         mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        mCallbackHandler.setNoSims(mHasNoSims);
+        mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
     }
 
     /**
@@ -804,6 +810,10 @@
                 } else {
                     mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
                 }
+                String ssid = args.getString("ssid");
+                if (ssid != null) {
+                    mDemoWifiState.ssid = ssid;
+                }
                 mDemoWifiState.enabled = show;
                 mWifiSignalController.notifyListeners();
             }
@@ -822,8 +832,8 @@
             }
             String nosim = args.getString("nosim");
             if (nosim != null) {
-                mHasNoSims = nosim.equals("show");
-                mCallbackHandler.setNoSims(mHasNoSims);
+                mHasNoSubs = nosim.equals("show");
+                mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
             }
             String mobile = args.getString("mobile");
             if (mobile != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 75532d9..1e14626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -240,7 +240,7 @@
      * motion.
      */
     private int mMaxScrollAfterExpand;
-    private SwipeHelper.LongPressListener mLongPressListener;
+    private ExpandableNotificationRow.LongPressListener mLongPressListener;
 
     private NotificationMenuRowPlugin mCurrMenuRow;
     private View mTranslatingParentView;
@@ -410,7 +410,6 @@
         mExpandHelper.setEventSource(this);
         mExpandHelper.setScrollAdapter(this);
         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
-        mSwipeHelper.setLongPressListener(mLongPressListener);
         mStackScrollAlgorithm = createStackScrollAlgorithm(context);
         initView(context);
         mFalsingManager = FalsingManager.getInstance(context);
@@ -884,8 +883,7 @@
         return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
     }
 
-    public void setLongPressListener(SwipeHelper.LongPressListener listener) {
-        mSwipeHelper.setLongPressListener(listener);
+    public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
         mLongPressListener = listener;
     }
 
@@ -1175,7 +1173,7 @@
         if (v instanceof ExpandableNotificationRow) {
             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
         }
-        removeLongPressCallback();
+        cancelLongPress();
         requestDisallowInterceptTouchEvent(true);
     }
 
@@ -2581,7 +2579,7 @@
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
         if (disallowIntercept) {
-            mSwipeHelper.removeLongPressCallback();
+            cancelLongPress();
         }
     }
 
@@ -3302,7 +3300,7 @@
         mIsBeingDragged = isDragged;
         if (isDragged) {
             requestDisallowInterceptTouchEvent(true);
-            removeLongPressCallback();
+            cancelLongPress();
         }
     }
 
@@ -3310,7 +3308,7 @@
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
         if (!hasWindowFocus) {
-            removeLongPressCallback();
+            cancelLongPress();
         }
     }
 
@@ -3324,7 +3322,7 @@
 
     @Override
     public void requestDisallowLongPress() {
-        removeLongPressCallback();
+        cancelLongPress();
     }
 
     @Override
@@ -3332,8 +3330,8 @@
         mDisallowDismissInThisMotion = true;
     }
 
-    public void removeLongPressCallback() {
-        mSwipeHelper.removeLongPressCallback();
+    public void cancelLongPress() {
+        mSwipeHelper.cancelLongPress();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index f4aebae..eca6127 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -14,6 +14,11 @@
 
 package com.android.systemui.util;
 
+import android.view.View;
+
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
+
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -28,4 +33,52 @@
             c.accept(list.get(i));
         }
     }
+
+    /**
+     * Sets the visibility of an UI element according to the DISABLE_* flags in
+     * {@link android.app.StatusBarManager}.
+     */
+    public static class DisableStateTracker implements CommandQueue.Callbacks,
+            View.OnAttachStateChangeListener {
+        private final int mMask1;
+        private final int mMask2;
+        private View mView;
+        private boolean mDisabled;
+
+        public DisableStateTracker(int disableMask, int disable2Mask) {
+            mMask1 = disableMask;
+            mMask2 = disable2Mask;
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            mView = v;
+            SysUiServiceProvider.getComponent(v.getContext(), CommandQueue.class)
+                    .addCallbacks(this);
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            SysUiServiceProvider.getComponent(mView.getContext(), CommandQueue.class)
+                    .removeCallbacks(this);
+            mView = null;
+        }
+
+        /**
+         * Sets visibility of this {@link View} given the states passed from
+         * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}.
+         */
+        @Override
+        public void disable(int state1, int state2, boolean animate) {
+            final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0);
+            if (disabled == mDisabled) return;
+            mDisabled = disabled;
+            mView.setVisibility(disabled ? View.GONE : View.VISIBLE);
+        }
+
+        /** @return {@code true} if and only if this {@link View} is currently disabled */
+        public boolean isDisabled() {
+            return mDisabled;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index b835909..5ec3dff 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -23,7 +23,7 @@
  */
 public class DelayedWakeLock implements WakeLock {
 
-    private static final long RELEASE_DELAY_MS = 100;
+    private static final long RELEASE_DELAY_MS = 120;
 
     private final Handler mHandler;
     private final WakeLock mInner;
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 27c16d5..b695919 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -38,6 +38,7 @@
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     SystemUIPluginLib \
+    SystemUISharedLib \
     android-support-v4 \
     android-support-v7-recyclerview \
     android-support-v7-preference \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 1f5255a..943020c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -287,6 +287,7 @@
         final Bundle extras = new Bundle();
         if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
         n.extras = extras;
+        n.when = System.currentTimeMillis() - 10000; // ten seconds ago
         final StatusBarNotification sbn = makeMockSBN(userid, "android",
                 SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
                 null, n);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
index 5fb0a3e..b86fc21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
@@ -44,7 +44,7 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.keyguard.WorkLockActivityController;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
+import com.android.systemui.recents.misc.TaskStackChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -68,7 +68,7 @@
     private @Mock IActivityManager mIActivityManager;
 
     private WorkLockActivityController mController;
-    private TaskStackListener mTaskStackListener;
+    private TaskStackChangeListener mTaskStackListener;
 
     @Before
     public void setUp() throws Exception {
@@ -78,8 +78,8 @@
         doReturn("com.example.test").when(mContext).getPackageName();
 
         // Construct controller. Save the TaskStackListener for injecting events.
-        final ArgumentCaptor<TaskStackListener> listenerCaptor =
-                ArgumentCaptor.forClass(TaskStackListener.class);
+        final ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
+                ArgumentCaptor.forClass(TaskStackChangeListener.class);
         mController =
                 new WorkLockActivityController(mContext, mSystemServicesProxy, mIActivityManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java
index 4f87b02..e023e87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/car/CarQsFragmentTest.java
@@ -14,6 +14,7 @@
 package com.android.systemui.qs.car;
 
 import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.support.test.filters.SmallTest;
@@ -27,6 +28,7 @@
 import com.android.keyguard.CarrierText;
 import com.android.systemui.Dependency;
 import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.policy.Clock;
 
 import org.junit.Before;
@@ -54,7 +56,7 @@
                         .replace(CarrierText.class, View.class)
                         .replace(Clock.class, View.class)
                         .build());
-
+        mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
         mDependency.injectTestDependency(Dependency.BG_LOOPER,
                 TestableLooper.get(this).getLooper());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
deleted file mode 100644
index 5a8c6dd..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/model/HighResThumbnailLoaderTest.java
+++ /dev/null
@@ -1,126 +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.recents.model;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.Looper;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.Task.TaskKey;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.recents.model.HighResThumbnailLoaderTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class HighResThumbnailLoaderTest extends SysuiTestCase {
-
-    private HighResThumbnailLoader mLoader;
-
-    @Mock
-    private SystemServicesProxy mMockSystemServicesProxy;
-    @Mock
-    private Task mTask;
-
-    private ThumbnailData mThumbnailData = new ThumbnailData();
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mLoader = new HighResThumbnailLoader(mMockSystemServicesProxy, Looper.getMainLooper(),
-                false);
-        mTask.key = new TaskKey(0, 0, null, 0, 0, 0);
-        when(mMockSystemServicesProxy.getTaskThumbnail(anyInt(), anyBoolean()))
-                .thenReturn(mThumbnailData);
-        mLoader.setVisible(true);
-        mLoader.setTaskLoadQueueIdle(true);
-    }
-
-    @Test
-    public void testLoading() throws Exception {
-        mLoader.setVisible(true);
-        assertTrue(mLoader.isLoading());
-        mLoader.setVisible(false);
-        assertFalse(mLoader.isLoading());
-        mLoader.setVisible(true);
-        mLoader.setFlingingFast(true);
-        assertFalse(mLoader.isLoading());
-        mLoader.setFlingingFast(false);
-        assertTrue(mLoader.isLoading());
-        mLoader.setFlingingFast(false);
-        mLoader.setTaskLoadQueueIdle(false);
-        assertFalse(mLoader.isLoading());
-        mLoader.setTaskLoadQueueIdle(true);
-        assertTrue(mLoader.isLoading());
-    }
-
-    @Test
-    public void testLoad() throws Exception {
-        mLoader.onTaskVisible(mTask);
-        mLoader.waitForLoaderIdle();
-        waitForIdleSync();
-        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
-    }
-
-    @Test
-    public void testFlinging_notLoaded() throws Exception {
-        mLoader.setFlingingFast(true);
-        mLoader.onTaskVisible(mTask);
-        mLoader.waitForLoaderIdle();
-        waitForIdleSync();
-        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
-    }
-
-    /**
-     * Tests whether task is loaded after stopping to fling
-     */
-    @Test
-    public void testAfterFlinging() throws Exception {
-        mLoader.setFlingingFast(true);
-        mLoader.onTaskVisible(mTask);
-        mLoader.setFlingingFast(false);
-        mLoader.waitForLoaderIdle();
-        waitForIdleSync();
-        verify(mTask).notifyTaskDataLoaded(mThumbnailData, null);
-    }
-
-    @Test
-    public void testAlreadyLoaded() throws Exception {
-        mTask.thumbnail = new ThumbnailData();
-        mTask.thumbnail.reducedResolution = false;
-        mLoader.onTaskVisible(mTask);
-        mLoader.waitForLoaderIdle();
-        waitForIdleSync();
-        verify(mTask, never()).notifyTaskDataLoaded(mThumbnailData, null);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
new file mode 100644
index 0000000..6b31c96
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationSnoozeTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.statusbar;
+
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.testing.UiThreadTest;
+import android.util.KeyValueListParser;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@UiThreadTest
+public class NotificationSnoozeTest extends SysuiTestCase {
+    private static final int RES_DEFAULT = 2;
+    private static final int[] RES_OPTIONS = {1, 2, 3};
+    private NotificationSnooze mNotificationSnooze;
+    private KeyValueListParser mMockParser;
+
+    @Before
+    public void setUp() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, null);
+        TestableResources resources = mContext.getOrCreateTestableResources();
+        resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT);
+        resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS);
+        mNotificationSnooze = new NotificationSnooze(mContext, null);
+        mMockParser = mock(KeyValueListParser.class);
+    }
+
+    @Test
+    public void testParseIntArrayNull() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn(null);
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(RES_OPTIONS, result);
+    }
+
+    @Test
+    public void testParseIntArrayLeadingSep() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn(":4:5:6");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(RES_OPTIONS, result);
+    }
+
+    @Test
+    public void testParseIntArrayEmptyItem() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn("4::6");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(RES_OPTIONS, result);
+    }
+
+    @Test
+    public void testParseIntArrayTrailingSep() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6:");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(3, result.length);
+        assertEquals(4, result[0]);  // respect order
+        assertEquals(5, result[1]);
+        assertEquals(6, result[2]);
+    }
+
+    @Test
+    public void testParseIntArrayGoodData() throws Exception {
+        when(mMockParser.getString(anyString(), isNull())).thenReturn("4:5:6");
+        mNotificationSnooze.setKeyValueListParser(mMockParser);
+
+        int[] result = mNotificationSnooze.parseIntArray("foo", RES_OPTIONS);
+        assertEquals(3, result.length);
+        assertEquals(4, result[0]);  // respect order
+        assertEquals(5, result[1]);
+        assertEquals(6, result[2]);
+    }
+
+    @Test
+    public void testGetOptionsWithNoConfig() throws Exception {
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertEquals(3, result.size());
+        assertEquals(1, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+        assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+    }
+
+    @Test
+    public void testGetOptionsWithInvalidConfig() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "this is garbage");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertEquals(3, result.size());
+        assertEquals(1, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(2, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(3, result.get(2).getMinutesToSnoozeFor());
+        assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+    }
+
+    @Test
+    public void testGetOptionsWithValidDefault() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "default=10,options_array=4:5:6:7");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertNotNull(mNotificationSnooze.getDefaultOption());  // pick one
+    }
+
+    @Test
+    public void testGetOptionsWithValidConfig() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "default=6,options_array=4:5:6:7");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertEquals(4, result.size());
+        assertEquals(4, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+        assertEquals(7, result.get(3).getMinutesToSnoozeFor());
+        assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor());
+    }
+
+    @Test
+    public void testGetOptionsWithLongConfig() throws Exception {
+        Settings.Global.putString(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+                "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17");
+        ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions();
+        assertTrue(result.size() > 3);
+        assertEquals(4, result.get(0).getMinutesToSnoozeFor());  // respect order
+        assertEquals(5, result.get(1).getMinutesToSnoozeFor());
+        assertEquals(6, result.get(2).getMinutesToSnoozeFor());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index c0de004..3b401a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -21,9 +21,11 @@
 
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import com.android.internal.app.NightDisplayController;
+import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
 import com.android.systemui.SysuiTestCase;
@@ -45,6 +47,8 @@
 
     @Before
     public void setUp() throws Exception {
+        mDependency.injectTestDependency(Dependency.BG_LOOPER,
+                TestableLooper.get(this).getLooper());
         Prefs.putBoolean(mContext, Key.QS_NIGHTDISPLAY_ADDED, false);
         mQsTileHost = Mockito.mock(QSTileHost.class);
         mAutoTileManager = new AutoTileManager(mContext, mQsTileHost);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index 0c1baaa..76f57f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -24,6 +24,7 @@
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.IWindowManager;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
@@ -41,6 +42,7 @@
 
     @Before
     public void setup() {
+        mDependency.injectMockDependency(IWindowManager.class);
         mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
         NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
         when(navBar.getCurrentView()).thenReturn(navBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index ac367d2..899e873 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -34,8 +34,8 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
+import android.app.StatusBarManager;
 import android.app.trust.TrustManager;
-import android.content.Context;
 import android.hardware.fingerprint.FingerprintManager;
 import android.metrics.LogMaker;
 import android.os.Binder;
@@ -50,21 +50,17 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.testing.AndroidTestingRunner;
-import android.testing.LayoutInflaterBuilder;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.MessageHandler;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.DisplayMetrics;
-import android.view.View;
 import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.logging.testing.FakeMetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.KeyguardHostView.OnDismissAction;
-import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
@@ -76,7 +72,6 @@
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -496,6 +491,28 @@
     }
 
     @Test
+    public void testDisableExpandStatusBar() {
+        mStatusBar.setBarStateForTest(StatusBarState.SHADE);
+        mStatusBar.setUserSetupForTest(true);
+        when(mStatusBar.isDeviceProvisioned()).thenReturn(true);
+
+        mStatusBar.disable(StatusBarManager.DISABLE_NONE,
+                StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
+        verify(mNotificationPanelView).setQsExpansionEnabled(false);
+        mStatusBar.animateExpandNotificationsPanel();
+        verify(mNotificationPanelView, never()).expand(anyBoolean());
+        mStatusBar.animateExpandSettingsPanel(null);
+        verify(mNotificationPanelView, never()).expand(anyBoolean());
+
+        mStatusBar.disable(StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE, false);
+        verify(mNotificationPanelView).setQsExpansionEnabled(true);
+        mStatusBar.animateExpandNotificationsPanel();
+        verify(mNotificationPanelView).expand(anyBoolean());
+        mStatusBar.animateExpandSettingsPanel(null);
+        verify(mNotificationPanelView).expand(anyBoolean());
+    }
+
+    @Test
     public void testDump_DoesNotCrash() {
         mStatusBar.dump(null, new PrintWriter(new ByteArrayOutputStream()), null);
     }
@@ -546,5 +563,9 @@
         public void setBarStateForTest(int state) {
             mState = state;
         }
+
+        public void setUserSetupForTest(boolean userSetup) {
+            mUserSetup = userSetup;
+        }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
index 51bd7bc..e3558d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CallbackHandlerTest.java
@@ -161,12 +161,11 @@
     @Test
     public void testSignalCallback_setNoSims() {
         boolean noSims = true;
-        mHandler.setNoSims(noSims);
+        boolean simDetected = false;
+        mHandler.setNoSims(noSims, simDetected);
         waitForCallbacks();
 
-        ArgumentCaptor<Boolean> noSimsArg = ArgumentCaptor.forClass(Boolean.class);
-        Mockito.verify(mSignalCallback).setNoSims(noSimsArg.capture());
-        assertEquals(noSims, (boolean) noSimsArg.getValue());
+        Mockito.verify(mSignalCallback).setNoSims(eq(noSims), eq(simDetected));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index b7e6a40..f685b1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -292,10 +292,8 @@
     }
 
     protected void verifyHasNoSims(boolean hasNoSimsVisible) {
-        ArgumentCaptor<Boolean> hasNoSimsArg = ArgumentCaptor.forClass(Boolean.class);
-
-        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(hasNoSimsArg.capture());
-        assertEquals("No sims", hasNoSimsVisible, (boolean) hasNoSimsArg.getValue());
+        Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
+                eq(hasNoSimsVisible), eq(false));
     }
 
     protected void verifyLastQsMobileDataIndicators(boolean visible, int icon, int typeIcon,
diff --git a/packages/VpnDialogs/res/values-cs/strings.xml b/packages/VpnDialogs/res/values-cs/strings.xml
index 7a3d515..47d950f 100644
--- a/packages/VpnDialogs/res/values-cs/strings.xml
+++ b/packages/VpnDialogs/res/values-cs/strings.xml
@@ -31,6 +31,6 @@
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"Změnit nastavení VPN"</string>
     <string name="configure" msgid="4905518375574791375">"Konfigurovat"</string>
     <string name="disconnect" msgid="971412338304200056">"Odpojit"</string>
-    <string name="open_app" msgid="3717639178595958667">"Spustit aplikaci"</string>
+    <string name="open_app" msgid="3717639178595958667">"Do aplikace"</string>
     <string name="dismiss" msgid="6192859333764711227">"Zavřít"</string>
 </resources>
diff --git a/proto/Android.bp b/proto/Android.bp
new file mode 100644
index 0000000..95f453c
--- /dev/null
+++ b/proto/Android.bp
@@ -0,0 +1,17 @@
+java_library_static {
+    name: "framework-protos",
+    host_supported: true,
+    proto: {
+        type: "nano",
+    },
+    srcs: ["src/**/*.proto"],
+    no_framework_libs: true,
+    target: {
+        android: {
+            jarjar_rules: "jarjar-rules.txt",
+        },
+        host: {
+            static_libs: ["libprotobuf-java-nano"],
+        },
+    },
+}
diff --git a/proto/Android.mk b/proto/Android.mk
deleted file mode 100644
index 1c03d16..0000000
--- a/proto/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := framework-protos
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-oj core-libart
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Host-side version of framework-protos
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := host-framework-protos
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_PROTOC_OPTIMIZE_TYPE := nano
-LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
-
-LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
-
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
-
-include $(BUILD_HOST_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 523e6b2..2e0f394 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3851,7 +3851,7 @@
     // Tag of a field for the length of the filter text
     FIELD_AUTOFILL_FILTERTEXT_LEN = 911;
 
-    // An autofill authentification succeeded
+    // An autofill authentication succeeded
     // Package: Package of app that was autofilled
     AUTOFILL_AUTHENTICATED = 912;
 
@@ -4462,25 +4462,25 @@
     // OS: O MR
     FIELD_AUTOFILL_PREVIOUS_LENGTH = 1125;
 
-    // An autofill dataset authentification succeeded
+    // An autofill dataset authentication succeeded
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     AUTOFILL_DATASET_AUTHENTICATED = 1126;
 
-    // An autofill service provided an invalid dataset authentification
+    // An autofill service provided an invalid dataset authentication
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127;
 
-    // An autofill service provided an invalid authentification extra
+    // An autofill service provided an invalid authentication extra
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     AUTOFILL_INVALID_AUTHENTICATION = 1128;
 
-    // An autofill service used a custom description (using RemoteViews) in the Save affordance
+    // An autofill service used a custom description (using RemoteViews) in the autofill save UI
     // Package: Package of app that is autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
@@ -4491,14 +4491,14 @@
     // OS: O MR
     FIELD_AUTOFILL_SAVE_TYPE = 1130;
 
-    // An autofill service used a custom subtitle (String) in the Save affordance
+    // An autofill service used a custom subtitle (String) in the autofill save UI
     // Package: Package of app that is autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
     AUTOFILL_SAVE_CUSTOM_SUBTITLE = 1131;
 
-    // User tapped a link in the custom description of the Save affordance provided by an autofill service
+    // User tapped a link in the custom description of the autofill save UI provided by an autofill service
     // Package: Package of app that is autofilled
     // OS: O MR
     // Type TYPE_UNKNOWN: The link was not properly set by the service
@@ -4518,102 +4518,241 @@
     // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
     AUTOFILL_SAVE_VALIDATION = 1133;
 
-    // Result of an operation in the autofill save affordance after the user tapped a link in the custom description
+    // Result of an operation in the autofill save UI after the user tapped a link in the custom description
     // provided by the autofill service
     // Package: Package of app that is autofilled
     // OS: O MR
-    // Type TYPE_OPEN: The save affordance was restored
-    // Type TYPE_DISMISS: The save affordcance was destroyed
+    // Type TYPE_OPEN: The autofill save UI was restored
+    // Type TYPE_DISMISS: The autofill save UI was destroyed
     // Type TYPE_FAILURE: An invalid opperation was reported by the app's AutofillManager
     AUTOFILL_PENDING_SAVE_UI_OPERATION = 1134;
 
+    // Autofill service called API that disables itself
+    // Package: Package of the autofill service
+    // OS: O MR
+    AUTOFILL_SERVICE_DISABLED_SELF = 1135;
+
+    // Counter showing how long it took (in ms) to show the autofill UI after a field was focused
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Package: Package of the autofill service
+    // OS: O MR
+    AUTOFILL_UI_LATENCY = 1136;
+
+    // Action: the snooze leave-behind was shown after the user clicked the snooze icon
+    // OS: O MR
+    NOTIFICATION_SNOOZE_CLICKED = 1137;
+
+    // Action: user selected a notification snooze duration from the drop down
+    // OS: O MR
+    NOTIFICATION_SELECT_SNOOZE = 1138;
+
+    // attached to NOTIFICATION_SNOOZED and NOTIFICATION_SELECT_SNOOZE events
+    // OS: O MR
+    FIELD_NOTIFICATION_SNOOZE_DURATION_MS = 1139;
+
+    // attached to NOTIFICATION_SELECT_SNOOZE events to indicate the option selected
+    // OS: O MR
+    FIELD_NOTIFICATION_SNOOZE_INDEX = 1140;
+
+    // Action: user tapped undo on the notification snooze leave-behind
+    // OS: O MR
+    NOTIFICATION_UNDO_SNOOZE = 1141;
+
+    // Action: user togged the visibility of the notification snooze options drop down
+    // OS: O MR
+    NOTIFICATION_SNOOZE_OPTIONS = 1142;
+
+    // OPEN: Settings > Display > Colors
+    // CATEGORY: SETTINGS
+    // OS: O MR
+    COLOR_MODE_SETTINGS = 1143;
+
     // ---- End O-MR1 Constants, all O-MR1 constants go above this line ----
 
     // OPEN: Settings > Network & Internet > Mobile network
     // CATEGORY: SETTINGS
-    SETTINGS_MOBILE_NETWORK_CATEGORY = 1139;
+    SETTINGS_MOBILE_NETWORK_CATEGORY = 1200;
 
     // ACTION: Settings > Network & Internet > Mobile network > Roaming
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_DATA_ROAMING_TOGGLE = 1140;
+    ACTION_MOBILE_NETWORK_DATA_ROAMING_TOGGLE = 1201;
 
     // ACTION: Settings > Network & Internet > Mobile network > Advanced
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_EXPAND_ADVANCED_FIELDS = 1141;
+    ACTION_MOBILE_NETWORK_EXPAND_ADVANCED_FIELDS = 1202;
 
     // ACTION: Settings > Network & Internet > Mobile network > Enhanced 4G LTE Mode
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_ENHANCED_4G_LTE_MODE_TOGGLE = 1142;
+    ACTION_MOBILE_ENHANCED_4G_LTE_MODE_TOGGLE = 1203;
 
     // ACTION: Settings > Network & Internet > Mobile network > Preferred network type
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_SELECT_PREFERRED_NETWORK = 1143;
+    ACTION_MOBILE_NETWORK_SELECT_PREFERRED_NETWORK = 1204;
 
     // ACTION: Settings > Network & Internet > Mobile network > Preferred network type (enabled networks)
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_SELECT_ENABLED_NETWORK = 1144;
+    ACTION_MOBILE_NETWORK_SELECT_ENABLED_NETWORK = 1205;
 
     // OPEN: Settings > Network & Internet > Mobile network > Carrier
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_EUICC_SETTING = 1145;
+    ACTION_MOBILE_NETWORK_EUICC_SETTING = 1206;
 
     // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_WIFI_CALLING = 1146;
+    ACTION_MOBILE_NETWORK_WIFI_CALLING = 1207;
 
     // ACTION: Settings > Network & Internet > Mobile network > Carrier video calling
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_VIDEO_CALLING_TOGGLE = 1147;
+    ACTION_MOBILE_NETWORK_VIDEO_CALLING_TOGGLE = 1208;
 
     // ACTION: Settings > Network & Internet > Mobile network > Automatically select network
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_AUTO_SELECT_NETWORK_TOGGLE = 1148;
+    ACTION_MOBILE_NETWORK_AUTO_SELECT_NETWORK_TOGGLE = 1209;
 
     // ACTION: Settings > Network & Internet > Mobile network > Network
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK = 1149;
+    ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK = 1210;
 
     // FIELD - Manually selected mobile network
-    FIELD_MOBILE_NETWORK = 1150;
+    FIELD_MOBILE_NETWORK = 1211;
 
     // OPEN: Settings > Network & Internet > Mobile network > Access Point Names
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_APN_SETTINGS = 1151;
+    ACTION_MOBILE_NETWORK_APN_SETTINGS = 1212;
 
     // OPEN: Settings > Network & Internet > Mobile network > Carrier settings
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_CARRIER_SETTINGS = 1152;
+    ACTION_MOBILE_NETWORK_CARRIER_SETTINGS = 1213;
 
     // OPEN: Settings > Network & Internet > Mobile network > System select
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_CDMA_SYSTEM_SELECT = 1153;
+    ACTION_MOBILE_NETWORK_CDMA_SYSTEM_SELECT = 1214;
 
     // OPEN: Settings > Network & Internet > Mobile network > CDMA subscription
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_CDMA_SUBSCRIPTION_SELECT = 1154;
+    ACTION_MOBILE_NETWORK_CDMA_SUBSCRIPTION_SELECT = 1215;
 
     // ACTION: Settings > Network & Internet > Mobile network > Set up data service
     // CATEGORY: SETTINGS
-    ACTION_MOBILE_NETWORK_SET_UP_DATA_SERVICE = 1155;
+    ACTION_MOBILE_NETWORK_SET_UP_DATA_SERVICE = 1216;
 
     // OPEN: Settings > Developer Options > Experiment dashboard
     // CATEGORY: SETTINGS
-    SETTINGS_FEATURE_FLAGS_DASHBOARD = 1156;
+    SETTINGS_FEATURE_FLAGS_DASHBOARD = 1217;
 
     // OPEN: Settings > Notifications > [App] > Topic Notifications
     // CATEGORY: SETTINGS
     // OS: P
-    NOTIFICATION_CHANNEL_GROUP = 1157;
+    NOTIFICATION_CHANNEL_GROUP = 1218;
 
     // OPEN: Settings > Developer options > Enable > Info dialog
     // CATEGORY: SETTINGS
     // OS: P
-    DIALOG_ENABLE_DEVELOPMENT_OPTIONS = 1158;
+    DIALOG_ENABLE_DEVELOPMENT_OPTIONS = 1219;
 
     // OPEN: Settings > Developer options > OEM unlocking > Info dialog
     // CATEGORY: SETTINGS
     // OS: P
-    DIALOG_ENABLE_OEM_UNLOCKING = 1159;
+    DIALOG_ENABLE_OEM_UNLOCKING = 1220;
+
+    // OPEN: Settings > Security > Nexus Imprint > [Fingerprint]
+    // CATEGORY: SETTINGS
+    // OS: P
+    FINGERPRINT_AUTHENTICATE_SIDECAR = 1221;
+
+    // OPEN: Settings > Developer options > USB debugging > Info dialog
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_ENABLE_ADB = 1222;
+
+    // OPEN: Settings > Developer options > Revoke USB debugging authorizations > Info dialog
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_CLEAR_ADB_KEYS = 1223;
+
+    // Open: Settings > Developer options > Quick setting tile config
+    // CATEGORY: SETTINGS
+    // OS: P
+    DEVELOPMENT_QS_TILE_CONFIG = 1224;
+
+    // OPEN: Settings > Developer options > Store logger data persistently on device > Info dialog
+    // CATEGORY: SETTINGS
+    // OS: P
+    DIALOG_LOG_PERSIST = 1225;
+
+    // ACTION: DND Settings > Priority only allows > Alarms toggle
+    // SUBTYPE: 0 is off, 1 is on
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_ZEN_ALLOW_ALARMS = 1226;
+
+    // ACTION: DND Settings > Priority only allows > Media toggle
+    // SUBTYPE: 0 is off, 1 is on
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_ZEN_ALLOW_MEDIA = 1227;
+
+    // An autofill service explicitly defined which view should commit the autofill context
+    // Package: Package of app that is autofilled
+    // OS: P
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION = 1228;
+
+    // The autofill context was commited when the user clicked a view explicitly marked by the
+    // service as committing it
+    // Package: Package of app that is autofilled
+    // OS: P
+    AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229;
+
+    // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling
+    // CATEGORY: SETTINGS
+    // OS: P
+    WIFI_CALLING_FOR_SUB = 1230;
+
+    // An autofill service asked to disable autofill for a given application.
+    // Package: Package of app that is being disabled for autofill
+    // Counter: duration (in ms) that autofill will be disabled
+    // OS: P
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_CLASS_NAME: Name of the Package of service that processed the request
+    AUTOFILL_SERVICE_DISABLED_APP = 1231;
+
+    // An autofill service asked to disable autofill for a given activity.
+    // Package: Package of app whose activity is being disabled for autofill
+    // Counter: duration (in ms) that autofill will be disabled
+    // OS: P
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill
+    AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232;
+
+    // ACTION: Stop an app and turn on background check
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACTION_APP_STOP_AND_BACKGROUND_CHECK = 1233;
+
+    // FIELD: The action type for each anomaly
+    // CATEGORY: SETTINGS
+    // OS: P
+    FIELD_ANOMALY_ACTION_TYPE = 1234;
+
+    // OPEN: Settings -> Battery -> Wakelock anomaly
+    // CATEGORY: SETTINGS
+    // OS: P
+    ANOMALY_TYPE_WAKELOCK = 1235;
+
+    // OPEN: Settings -> Battery -> Wakeup alarm anomaly
+    // CATEGORY: SETTINGS
+    // OS: P
+    ANOMALY_TYPE_WAKEUP_ALARM = 1236;
+
+    // OPEN: Settings -> Battery -> Unoptimized bt anomaly
+    // CATEGORY: SETTINGS
+    // OS: P
+    ANOMALY_TYPE_UNOPTIMIZED_BT = 1237;
+
+    // Open: Settings > Dev options > Oem unlock > lock it > warning dialog.
+    // OS: P
+    DIALOG_OEM_LOCK_INFO = 1238;
 
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 28bf856..9d25055 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -315,6 +315,55 @@
 
   // Pno scan metrics
   optional PnoScanMetrics pno_scan_metrics = 76;
+
+  // Histogram of "Connect to Network" notifications.
+  // The notification Action should be unset.
+  repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_count = 77;
+
+  // Histogram of "Connect to Network" notification user actions.
+  repeated ConnectToNetworkNotificationAndActionCount connect_to_network_notification_action_count = 78;
+
+  // The number of SSIDs blacklisted from recommendation by the open network
+  // notification recommender
+  optional int32 open_network_recommender_blacklist_size = 79;
+
+  // Is the available network notification feature turned on
+  optional bool is_wifi_networks_available_notification_on = 80;
+
+  // Count of recommendation updates made by the open network notification
+  // recommender
+  optional int32 num_open_network_recommendation_updates = 81;
+
+  // Count of connection attempts that were initiated unsuccessfully
+  optional int32 num_open_network_connect_message_failed_to_send = 82;
+
+  // Histogram counting instances of scans with N many HotSpot 2.0 R1 APs
+  repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_in_scan_histogram = 83;
+
+  // Histogram counting instances of scans with N many HotSpot 2.0 R2 APs
+  repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_in_scan_histogram = 84;
+
+  // Histogram counting instances of scans with N many unique HotSpot 2.0 R1 ESS.
+  // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
+  // (SSID, BSSID) tuple depending on AP configuration (in the above priority
+  // order).
+  repeated NumConnectableNetworksBucket observed_hotspot_r1_ess_in_scan_histogram = 85;
+
+  // Histogram counting instances of scans with N many unique HotSpot 2.0 R2 ESS.
+  // Where ESS is defined as the (HESSID, ANQP Domain ID), (SSID, ANQP Domain ID) or
+  // (SSID, BSSID) tuple depending on AP configuration (in the above priority
+  // order).
+  repeated NumConnectableNetworksBucket observed_hotspot_r2_ess_in_scan_histogram = 86;
+
+  // Histogram counting number of HotSpot 2.0 R1 APs per observed ESS in a scan
+  // (one value added per unique ESS - potentially multiple counts per single
+  // scan!)
+  repeated NumConnectableNetworksBucket observed_hotspot_r1_aps_per_ess_in_scan_histogram = 87;
+
+  // Histogram counting number of HotSpot 2.0 R2 APs per observed ESS in a scan
+  // (one value added per unique ESS - potentially multiple counts per single
+  // scan!)
+  repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_per_ess_in_scan_histogram = 88;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -624,6 +673,10 @@
     // Framework initiated disconnect. Sometimes generated to give an extra reason for a disconnect
     // Should typically be followed by a NETWORK_DISCONNECTION_EVENT with a local_gen = true
     TYPE_FRAMEWORK_DISCONNECT = 15;
+
+    // The NetworkAgent score for wifi has changed in a way that may impact
+    // connectivity
+    TYPE_SCORE_BREACH = 16;
   }
 
   enum FrameworkDisconnectReason {
@@ -735,6 +788,9 @@
 
   // Authentication failure reason, as reported by WifiManager (calculated from state & deauth code)
   optional AuthFailureReason auth_failure_reason = 13 [default = AUTH_FAILURE_UNKNOWN];
+
+  // NetworkAgent score of connected wifi
+  optional int32 last_score = 14 [default = -1];
 }
 
 // Wi-Fi Aware metrics
@@ -950,3 +1006,68 @@
   // Total number of pno scans that found any network
   optional int32 num_pno_found_network_events = 5;
 }
+
+// Number of occurrences for a particular "Connect to Network" Notification or
+// notification Action.
+message ConnectToNetworkNotificationAndActionCount {
+
+  // "Connect to Network" notifications
+  enum Notification {
+
+    // Default
+    NOTIFICATION_UNKNOWN = 0;
+
+    // Initial notification with a recommended network.
+    NOTIFICATION_RECOMMEND_NETWORK = 1;
+
+    // Notification when connecting to the recommended network.
+    NOTIFICATION_CONNECTING_TO_NETWORK = 2;
+
+    // Notification when successfully connected to the network.
+    NOTIFICATION_CONNECTED_TO_NETWORK = 3;
+
+    // Notification when failed to connect to network.
+    NOTIFICATION_FAILED_TO_CONNECT = 4;
+  }
+
+  // "Connect to Network" notification actions
+  enum Action {
+
+    // Default
+    ACTION_UNKNOWN = 0;
+
+    // User dismissed the "Connect to Network" notification.
+    ACTION_USER_DISMISSED_NOTIFICATION = 1;
+
+    // User tapped action button to connect to recommended network.
+    ACTION_CONNECT_TO_NETWORK = 2;
+
+    // User tapped action button to open Wi-Fi Settings.
+    ACTION_PICK_WIFI_NETWORK = 3;
+
+    // User tapped "Failed to connect" notification to open Wi-Fi Settings.
+    ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE = 4;
+  }
+
+  // Recommenders of the "Connect to Network" notification
+  enum Recommender {
+
+    // Default.
+    RECOMMENDER_UNKNOWN = 0;
+
+    // Open Network Available recommender.
+    RECOMMENDER_OPEN = 1;
+  }
+
+  // Notification Type.
+  optional Notification notification = 1;
+
+  // Action Type.
+  optional Action action = 2;
+
+  // Recommender Type.
+  optional Recommender recommender = 3;
+
+  // Occurrences of this action.
+  optional int32 count = 4;
+}
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index 21438e0..0854b95 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -12,7 +12,7 @@
     libRS \
     libcutils \
     liblog \
-    libskia \
+    libhwui \
     libutils \
     libui \
     libgui \
@@ -23,9 +23,7 @@
 
 LOCAL_C_INCLUDES += \
     $(JNI_H_INCLUDE) \
-    frameworks/rs \
-    frameworks/base/core/jni \
-    frameworks/base/libs/hwui
+    frameworks/rs
 
 LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index f9f6bf7..b32be73 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -1328,7 +1328,7 @@
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con,
                                                   (RsType)type, (RsAllocationMipmapControl)mip,
-                                                  ptr, bitmap.getSize(), usage);
+                                                  ptr, bitmap.computeByteSize(), usage);
     return id;
 }
 
@@ -1356,7 +1356,7 @@
     const void* ptr = bitmap.getPixels();
     jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con,
                                                       (RsType)type, (RsAllocationMipmapControl)mip,
-                                                      ptr, bitmap.getSize(), usage);
+                                                      ptr, bitmap.computeByteSize(), usage);
     return id;
 }
 
@@ -1371,7 +1371,7 @@
     const void* ptr = bitmap.getPixels();
     rsAllocation2DData((RsContext)con, (RsAllocation)alloc, 0, 0,
                        0, RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X,
-                       w, h, ptr, bitmap.getSize(), 0);
+                       w, h, ptr, bitmap.computeByteSize(), 0);
 }
 
 static void
@@ -1381,7 +1381,7 @@
     GraphicsJNI::getSkBitmap(_env, jbitmap, &bitmap);
 
     void* ptr = bitmap.getPixels();
-    rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, bitmap.getSize());
+    rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, bitmap.computeByteSize());
     bitmap.notifyPixelsChanged();
 }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index c60647f..f6fcaae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -19,6 +19,9 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.PowerManager;
+import android.util.DebugUtils;
+import android.util.ExceptionUtils;
+import android.util.Log;
 import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -31,6 +34,7 @@
 import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.util.BitUtils;
 import com.android.server.LocalServices;
 
 /**
@@ -188,6 +192,7 @@
         }
 
         if (mEventHandler == null) {
+            if (DEBUG) Slog.d(TAG, "mEventHandler == null for event " + event);
             super.onInputEvent(event, policyFlags);
             return;
         }
@@ -339,6 +344,8 @@
             MotionEvent transformedEvent = MotionEvent.obtain(event);
             mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
             transformedEvent.recycle();
+        } else {
+            if (DEBUG) Slog.d(TAG, "mEventHandler == null for " + event);
         }
     }
 
@@ -366,11 +373,20 @@
     }
 
     @Override
+    public EventStreamTransformation getNext() {
+        return null;
+    }
+
+    @Override
     public void clearEvents(int inputSource) {
         /* do nothing */
     }
 
     void setUserAndEnabledFeatures(int userId, int enabledFeatures) {
+        if (DEBUG) {
+            Slog.i(TAG, "setUserAndEnabledFeatures(userId = " + userId + ", enabledFeatures = 0x"
+                    + Integer.toHexString(enabledFeatures) + ")");
+        }
         if (mEnabledFeatures == enabledFeatures && mUserId == userId) {
             return;
         }
@@ -397,6 +413,8 @@
     }
 
     private void enableFeatures() {
+        if (DEBUG) Slog.i(TAG, "enableFeatures()");
+
         resetStreamState();
 
         if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
@@ -443,7 +461,7 @@
      */
     private void addFirstEventHandler(EventStreamTransformation handler) {
         if (mEventHandler != null) {
-           handler.setNext(mEventHandler);
+            handler.setNext(mEventHandler);
         } else {
             handler.setNext(this);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index 892e9da..f5b0eb1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -23,15 +23,12 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.provider.Settings;
-import android.util.Slog;
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
 /**
@@ -55,11 +52,10 @@
  *
  * Each instance is associated to a single user (and it does not handle user switch itself).
  */
-public class AutoclickController implements EventStreamTransformation {
+public class AutoclickController extends BaseEventStreamTransformation {
 
     private static final String LOG_TAG = AutoclickController.class.getSimpleName();
 
-    private EventStreamTransformation mNext;
     private final Context mContext;
     private final int mUserId;
 
@@ -88,9 +84,7 @@
             mClickScheduler.cancel();
         }
 
-        if (mNext != null) {
-            mNext.onMotionEvent(event, rawEvent, policyFlags);
-        }
+        super.onMotionEvent(event, rawEvent, policyFlags);
     }
 
     @Override
@@ -103,21 +97,7 @@
             }
         }
 
-        if (mNext != null) {
-          mNext.onKeyEvent(event, policyFlags);
-        }
-    }
-
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
+        super.onKeyEvent(event, policyFlags);
     }
 
     @Override
@@ -126,9 +106,7 @@
             mClickScheduler.cancel();
         }
 
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
+        super.clearEvents(inputSource);
     }
 
     @Override
@@ -418,7 +396,7 @@
          * Creates and forwards click event sequence.
          */
         private void sendClick() {
-            if (mLastMotionEvent == null || mNext == null) {
+            if (mLastMotionEvent == null || getNext() == null) {
                 return;
             }
 
@@ -448,10 +426,10 @@
             MotionEvent upEvent = MotionEvent.obtain(downEvent);
             upEvent.setAction(MotionEvent.ACTION_UP);
 
-            mNext.onMotionEvent(downEvent, downEvent, mEventPolicyFlags);
+            AutoclickController.super.onMotionEvent(downEvent, downEvent, mEventPolicyFlags);
             downEvent.recycle();
 
-            mNext.onMotionEvent(upEvent, upEvent, mEventPolicyFlags);
+            AutoclickController.super.onMotionEvent(upEvent, upEvent, mEventPolicyFlags);
             upEvent.recycle();
         }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/BaseEventStreamTransformation.java b/services/accessibility/java/com/android/server/accessibility/BaseEventStreamTransformation.java
new file mode 100644
index 0000000..ce54586
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/BaseEventStreamTransformation.java
@@ -0,0 +1,31 @@
+/*
+ ** Copyright 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.server.accessibility;
+
+abstract class BaseEventStreamTransformation implements EventStreamTransformation {
+    private EventStreamTransformation mNext;
+
+    @Override
+    public void setNext(EventStreamTransformation next) {
+        mNext = next;
+    }
+
+    @Override
+    public EventStreamTransformation getNext() {
+        return mNext;
+    }
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java b/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java
index fdc4098..7982996 100644
--- a/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java
+++ b/services/accessibility/java/com/android/server/accessibility/EventStreamTransformation.java
@@ -65,7 +65,12 @@
      * @param rawEvent The raw motion event.
      * @param policyFlags Policy flags for the event.
      */
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+    default void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.onMotionEvent(event, rawEvent, policyFlags);
+        }
+    }
 
     /**
      * Receives a key event.
@@ -73,14 +78,24 @@
      * @param event The key event.
      * @param policyFlags Policy flags for the event.
      */
-    public void onKeyEvent(KeyEvent event, int policyFlags);
+    default void onKeyEvent(KeyEvent event, int policyFlags) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.onKeyEvent(event, policyFlags);
+        }
+    }
 
     /**
      * Receives an accessibility event.
      *
      * @param event The accessibility event.
      */
-    public void onAccessibilityEvent(AccessibilityEvent event);
+    default void onAccessibilityEvent(AccessibilityEvent event) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.onAccessibilityEvent(event);
+        }
+    };
 
     /**
      * Sets the next transformation.
@@ -90,14 +105,26 @@
     public void setNext(EventStreamTransformation next);
 
     /**
+     * Gets the next transformation.
+     *
+     * @return The next transformation.
+     */
+    public EventStreamTransformation getNext();
+
+    /**
      * Clears internal state associated with events from specific input source.
      *
      * @param inputSource The input source class for which transformation state should be cleared.
      */
-    public void clearEvents(int inputSource);
+    default void clearEvents(int inputSource) {
+        EventStreamTransformation next = getNext();
+        if (next != null) {
+            next.clearEvents(inputSource);
+        }
+    }
 
     /**
      * Destroys this transformation.
      */
-    public void onDestroy();
+    default void onDestroy() {}
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
index f00a954..7724945 100644
--- a/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java
@@ -22,14 +22,12 @@
 import android.util.Pools;
 import android.util.Slog;
 import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
 
 /**
  * Intercepts key events and forwards them to accessibility manager service.
  */
-public class KeyboardInterceptor implements EventStreamTransformation, Handler.Callback {
+public class KeyboardInterceptor extends BaseEventStreamTransformation implements Handler.Callback {
     private static final int MESSAGE_PROCESS_QUEUED_EVENTS = 1;
     private static final String LOG_TAG = "KeyboardInterceptor";
 
@@ -37,7 +35,6 @@
     private final WindowManagerPolicy mPolicy;
     private final Handler mHandler;
 
-    private EventStreamTransformation mNext;
     private KeyEventHolder mEventQueueStart;
     private KeyEventHolder mEventQueueEnd;
 
@@ -65,13 +62,6 @@
     }
 
     @Override
-    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (mNext != null) {
-            mNext.onMotionEvent(event, rawEvent, policyFlags);
-        }
-    }
-
-    @Override
     public void onKeyEvent(KeyEvent event, int policyFlags) {
         /*
          * Certain keys have system-level behavior that affects accessibility services.
@@ -90,29 +80,6 @@
     }
 
     @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
-    }
-
-    @Override
-    public void clearEvents(int inputSource) {
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-    }
-
-    @Override
     public boolean handleMessage(Message msg) {
         if (msg.what != MESSAGE_PROCESS_QUEUED_EVENTS) {
             Slog.e(LOG_TAG, "Unexpected message type");
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
index 98b8e6b..a10b7a2 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java
@@ -56,6 +56,7 @@
  * constraints.
  */
 class MagnificationController implements Handler.Callback {
+    private static final boolean DEBUG = false;
     private static final String LOG_TAG = "MagnificationController";
 
     public static final float MIN_SCALE = 1.0f;
@@ -509,6 +510,12 @@
 
     private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
             boolean animate, int id) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG,
+                    "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX
+                            + ", centerY = " + centerY + ", animate = " + animate + ", id = " + id
+                            + ")");
+        }
         final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
         sendSpecToAnimation(mCurrentMagnificationSpec, animate);
         if (isMagnifying() && (id != INVALID_ID)) {
@@ -535,7 +542,9 @@
 
             final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX;
             final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY;
-            updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY);
+            if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) {
+                onMagnificationChangedLocked();
+            }
             if (id != INVALID_ID) {
                 mIdOfLastServiceToMagnify = id;
             }
@@ -633,6 +642,11 @@
     }
 
     private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG,
+                    "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX
+                            + ", nonNormOffsetY = " + nonNormOffsetY + ")");
+        }
         boolean changed = false;
         final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
         if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) {
@@ -750,6 +764,9 @@
     }
 
     private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) {
+        if (DEBUG) {
+            Slog.i(LOG_TAG, "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")");
+        }
         if (Thread.currentThread().getId() == mMainThreadId) {
             mSpecAnimationBridge.updateSentSpecMainThread(spec, animate);
         } else {
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index d6452f8..9b2b4eb 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -42,14 +42,12 @@
 import android.util.TypedValue;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
 import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -102,31 +100,23 @@
  * 7. The magnification scale will be persisted in settings and in the cloud.
  */
 @SuppressWarnings("WeakerAccess")
-class MagnificationGestureHandler implements EventStreamTransformation {
-    private static final String LOG_TAG = "MagnificationEventHandler";
+class MagnificationGestureHandler extends BaseEventStreamTransformation {
+    private static final String LOG_TAG = "MagnificationGestureHandler";
 
     private static final boolean DEBUG_ALL = false;
     private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL;
     private static final boolean DEBUG_DETECTING = false || DEBUG_ALL;
-    private static final boolean DEBUG_PANNING = false || DEBUG_ALL;
-
-    /** @see #handleMotionEventStateDelegating */
-    @VisibleForTesting static final int STATE_DELEGATING = 1;
-    /** @see DetectingStateHandler */
-    @VisibleForTesting static final int STATE_DETECTING = 2;
-    /** @see ViewportDraggingStateHandler */
-    @VisibleForTesting static final int STATE_VIEWPORT_DRAGGING = 3;
-    /** @see PanningScalingStateHandler */
-    @VisibleForTesting static final int STATE_PANNING_SCALING = 4;
+    private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL;
 
     private static final float MIN_SCALE = 2.0f;
     private static final float MAX_SCALE = 5.0f;
 
     @VisibleForTesting final MagnificationController mMagnificationController;
 
-    @VisibleForTesting final DetectingStateHandler mDetectingStateHandler;
-    @VisibleForTesting final PanningScalingStateHandler mPanningScalingStateHandler;
-    @VisibleForTesting final ViewportDraggingStateHandler mViewportDraggingStateHandler;
+    @VisibleForTesting final DelegatingState mDelegatingState;
+    @VisibleForTesting final DetectingState mDetectingState;
+    @VisibleForTesting final PanningScalingState mPanningScalingState;
+    @VisibleForTesting final ViewportDraggingState mViewportDraggingState;
 
     private final ScreenStateReceiver mScreenStateReceiver;
 
@@ -138,21 +128,12 @@
     final boolean mDetectTripleTap;
 
     /**
-     * Whether {@link #mShortcutTriggered shortcut} is enabled
+     * Whether {@link DetectingState#mShortcutTriggered shortcut} is enabled
      */
     final boolean mDetectShortcutTrigger;
 
-    EventStreamTransformation mNext;
-
-    @VisibleForTesting int mCurrentState;
-    @VisibleForTesting int mPreviousState;
-
-    @VisibleForTesting boolean mShortcutTriggered;
-
-    /**
-     * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link #STATE_DELEGATING}
-     */
-    long mDelegatingStateDownTime;
+    @VisibleForTesting State mCurrentState;
+    @VisibleForTesting State mPreviousState;
 
     private PointerCoords[] mTempPointerCoords;
     private PointerProperties[] mTempPointerProperties;
@@ -174,10 +155,10 @@
             boolean detectShortcutTrigger) {
         mMagnificationController = magnificationController;
 
-        mDetectingStateHandler = new DetectingStateHandler(context);
-        mViewportDraggingStateHandler = new ViewportDraggingStateHandler();
-        mPanningScalingStateHandler =
-                new PanningScalingStateHandler(context);
+        mDelegatingState = new DelegatingState();
+        mDetectingState = new DetectingState(context);
+        mViewportDraggingState = new ViewportDraggingState();
+        mPanningScalingState = new PanningScalingState(context);
 
         mDetectTripleTap = detectTripleTap;
         mDetectShortcutTrigger = detectShortcutTrigger;
@@ -189,62 +170,29 @@
             mScreenStateReceiver = null;
         }
 
-        transitionTo(STATE_DETECTING);
+        transitionTo(mDetectingState);
     }
 
     @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
+
         if ((!mDetectTripleTap && !mDetectShortcutTrigger)
                 || !event.isFromSource(SOURCE_TOUCHSCREEN)) {
             dispatchTransformedEvent(event, rawEvent, policyFlags);
             return;
         }
-        // Local copy to avoid dispatching the same event to more than one state handler
-        // in case mPanningScalingStateHandler changes mCurrentState
-        int currentState = mCurrentState;
-        mPanningScalingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-        switch (currentState) {
-            case STATE_DELEGATING: {
-                handleMotionEventStateDelegating(event, rawEvent, policyFlags);
-            }
-            break;
-            case STATE_DETECTING: {
-                mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-            }
-            break;
-            case STATE_VIEWPORT_DRAGGING: {
-                mViewportDraggingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
-            }
-            break;
-            case STATE_PANNING_SCALING: {
-                // mPanningScalingStateHandler handles events only
-                // if this is the current state since it uses ScaleGestureDetector
-                // and a GestureDetector which need well formed event stream.
-            }
-            break;
-            default: {
-                throw new IllegalStateException("Unknown state: " + currentState);
-            }
-        }
+
+        handleEventWith(mCurrentState, event, rawEvent, policyFlags);
     }
 
-    @Override
-    public void onKeyEvent(KeyEvent event, int policyFlags) {
-        if (mNext != null) {
-            mNext.onKeyEvent(event, policyFlags);
-        }
-    }
+    private void handleEventWith(State stateHandler,
+            MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        // To keep InputEventConsistencyVerifiers within GestureDetectors happy
+        mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
+        mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
 
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
+        stateHandler.onMotionEvent(event, rawEvent, policyFlags);
     }
 
     @Override
@@ -253,13 +201,16 @@
             clearAndTransitionToStateDetecting();
         }
 
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
+        super.clearEvents(inputSource);
     }
 
     @Override
     public void onDestroy() {
+        if (DEBUG_STATE_TRANSITIONS) {
+            Slog.i(LOG_TAG, "onDestroy(); delayed = "
+                    + MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
+        }
+
         if (mScreenStateReceiver != null) {
             mScreenStateReceiver.unregister();
         }
@@ -272,59 +223,21 @@
             if (wasMagnifying) {
                 clearAndTransitionToStateDetecting();
             } else {
-                toggleShortcutTriggered();
+                mDetectingState.toggleShortcutTriggered();
             }
         }
     }
 
-    private void toggleShortcutTriggered() {
-        setShortcutTriggered(!mShortcutTriggered);
-    }
-
-    private void setShortcutTriggered(boolean state) {
-        if (mShortcutTriggered == state) {
-            return;
-        }
-
-        mShortcutTriggered = state;
-        mMagnificationController.setForceShowMagnifiableBounds(state);
-    }
-
     void clearAndTransitionToStateDetecting() {
-        setShortcutTriggered(false);
-        mCurrentState = STATE_DETECTING;
-        mDetectingStateHandler.clear();
-        mViewportDraggingStateHandler.clear();
-        mPanningScalingStateHandler.clear();
-    }
-
-    private void handleMotionEventStateDelegating(MotionEvent event,
-            MotionEvent rawEvent, int policyFlags) {
-        if (event.getActionMasked() == ACTION_UP) {
-            transitionTo(STATE_DETECTING);
-        }
-        delegateEvent(event, rawEvent, policyFlags);
-    }
-
-    void delegateEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mDelegatingStateDownTime = event.getDownTime();
-        }
-        if (mNext != null) {
-            // We cache some events to see if the user wants to trigger magnification.
-            // If no magnification is triggered we inject these events with adjusted
-            // time and down time to prevent subsequent transformations being confused
-            // by stale events. After the cached events, which always have a down, are
-            // injected we need to also update the down time of all subsequent non cached
-            // events. All delegated events cached and non-cached are delivered here.
-            event.setDownTime(mDelegatingStateDownTime);
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
-        }
+        mCurrentState = mDelegatingState;
+        mDetectingState.clear();
+        mViewportDraggingState.clear();
+        mPanningScalingState.clear();
     }
 
     private void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent,
             int policyFlags) {
-        if (mNext == null) return; // Nowhere to dispatch to
+        if (DEBUG_ALL) Slog.i(LOG_TAG, "dispatchTransformedEvent(event = " + event + ")");
 
         // If the touchscreen event is within the magnified portion of the screen we have
         // to change its location to be where the user thinks he is poking the
@@ -351,7 +264,7 @@
                     coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
                     event.getFlags());
         }
-        mNext.onMotionEvent(event, rawEvent, policyFlags);
+        super.onMotionEvent(event, rawEvent, policyFlags);
     }
 
     private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
@@ -386,9 +299,10 @@
         return mTempPointerProperties;
     }
 
-    private void transitionTo(int state) {
+    private void transitionTo(State state) {
         if (DEBUG_STATE_TRANSITIONS) {
-            Slog.i(LOG_TAG, (stateToString(mCurrentState) + " -> " + stateToString(state)
+            Slog.i(LOG_TAG,
+                    (State.nameOf(mCurrentState) + " -> " + State.nameOf(state)
                     + " at " + asList(copyOfRange(new RuntimeException().getStackTrace(), 1, 5)))
                     .replace(getClass().getName(), ""));
         }
@@ -396,40 +310,40 @@
         mCurrentState = state;
     }
 
-    private static String stateToString(int state) {
-        switch (state) {
-            case STATE_DELEGATING: return "STATE_DELEGATING";
-            case STATE_DETECTING: return "STATE_DETECTING";
-            case STATE_VIEWPORT_DRAGGING: return "STATE_VIEWPORT_DRAGGING";
-            case STATE_PANNING_SCALING: return "STATE_PANNING_SCALING";
-            case 0: return "0";
-            default: throw new IllegalArgumentException("Unknown state: " + state);
-        }
-    }
-
-    private interface MotionEventHandler {
-
+    interface State {
         void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
 
-        void clear();
+        default void clear() {}
+
+        default String name() {
+            return getClass().getSimpleName();
+        }
+
+        static String nameOf(@Nullable State s) {
+            return s != null ? s.name() : "null";
+        }
     }
 
     /**
      * This class determines if the user is performing a scale or pan gesture.
      *
-     * @see #STATE_PANNING_SCALING
+     * Unlike when {@link ViewportDraggingState dragging the viewport}, in panning mode the viewport
+     * moves in the same direction as the fingers, and allows to easily and precisely scale the
+     * magnification level.
+     * This makes it the preferred mode for one-off adjustments, due to its precision and ease of
+     * triggering.
      */
-    final class PanningScalingStateHandler extends SimpleOnGestureListener
-            implements OnScaleGestureListener, MotionEventHandler {
+    final class PanningScalingState extends SimpleOnGestureListener
+            implements OnScaleGestureListener, State {
 
         private final ScaleGestureDetector mScaleGestureDetector;
-        private final GestureDetector mGestureDetector;
+        private final GestureDetector mScrollGestureDetector;
         final float mScalingThreshold;
 
         float mInitialScaleFactor = -1;
         boolean mScaling;
 
-        public PanningScalingStateHandler(Context context) {
+        public PanningScalingState(Context context) {
             final TypedValue scaleValue = new TypedValue();
             context.getResources().getValue(
                     com.android.internal.R.dimen.config_screen_magnification_scaling_threshold,
@@ -437,35 +351,27 @@
             mScalingThreshold = scaleValue.getFloat();
             mScaleGestureDetector = new ScaleGestureDetector(context, this);
             mScaleGestureDetector.setQuickScaleEnabled(false);
-            mGestureDetector = new GestureDetector(context, this);
+            mScrollGestureDetector = new GestureDetector(context, this);
         }
 
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            // Dispatches #onScaleBegin, #onScale, #onScaleEnd
-            mScaleGestureDetector.onTouchEvent(event);
-            // Dispatches #onScroll
-            mGestureDetector.onTouchEvent(event);
-
-            if (mCurrentState != STATE_PANNING_SCALING) {
-                return;
-            }
-
             int action = event.getActionMasked();
+
             if (action == ACTION_POINTER_UP
                     && event.getPointerCount() == 2 // includes the pointer currently being released
-                    && mPreviousState == STATE_VIEWPORT_DRAGGING) {
+                    && mPreviousState == mViewportDraggingState) {
 
-                persistScaleAndTransitionTo(STATE_VIEWPORT_DRAGGING);
+                persistScaleAndTransitionTo(mViewportDraggingState);
 
             } else if (action == ACTION_UP) {
 
-                persistScaleAndTransitionTo(STATE_DETECTING);
+                persistScaleAndTransitionTo(mDetectingState);
 
             }
         }
 
-        public void persistScaleAndTransitionTo(int state) {
+        public void persistScaleAndTransitionTo(State state) {
             mMagnificationController.persistScale();
             clear();
             transitionTo(state);
@@ -474,16 +380,16 @@
         @Override
         public boolean onScroll(MotionEvent first, MotionEvent second,
                 float distanceX, float distanceY) {
-            if (mCurrentState != STATE_PANNING_SCALING) {
+            if (mCurrentState != mPanningScalingState) {
                 return true;
             }
-            if (DEBUG_PANNING) {
+            if (DEBUG_PANNING_SCALING) {
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + distanceX
                         + " scrollY: " + distanceY);
             }
             mMagnificationController.offsetMagnifiedRegion(distanceX, distanceY,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            return true;
+            return /* event consumed: */ true;
         }
 
         @Override
@@ -494,12 +400,8 @@
                     return false;
                 }
                 final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
-                if (abs(deltaScale) > mScalingThreshold) {
-                    mScaling = true;
-                    return true;
-                } else {
-                    return false;
-                }
+                mScaling = abs(deltaScale) > mScalingThreshold;
+                return mScaling;
             }
 
             final float initialScale = mMagnificationController.getScale();
@@ -523,14 +425,15 @@
 
             final float pivotX = detector.getFocusX();
             final float pivotY = detector.getFocusY();
+            if (DEBUG_PANNING_SCALING) Slog.i(LOG_TAG, "Scaled content to: " + scale + "x");
             mMagnificationController.setScale(scale, pivotX, pivotY, false,
                     AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-            return true;
+            return /* handled: */ true;
         }
 
         @Override
         public boolean onScaleBegin(ScaleGestureDetector detector) {
-            return (mCurrentState == STATE_PANNING_SCALING);
+            return /* continue recognizing: */ (mCurrentState == mPanningScalingState);
         }
 
         @Override
@@ -546,7 +449,7 @@
 
         @Override
         public String toString() {
-            return "MagnifiedContentInteractionStateHandler{" +
+            return "PanningScalingState{" +
                     "mInitialScaleFactor=" + mInitialScaleFactor +
                     ", mScaling=" + mScaling +
                     '}';
@@ -558,9 +461,11 @@
      * determined that the user is performing a single-finger drag of the
      * magnification viewport.
      *
-     * @see #STATE_VIEWPORT_DRAGGING
+     * Unlike when {@link PanningScalingState panning}, the viewport moves in the opposite direction
+     * of the finger, and any part of the screen is reachable without lifting the finger.
+     * This makes it the preferable mode for tasks like reading text spanning full screen width.
      */
-    final class ViewportDraggingStateHandler implements MotionEventHandler {
+    final class ViewportDraggingState implements State {
 
         /** Whether to disable zoom after dragging ends */
         boolean mZoomedInBeforeDrag;
@@ -572,7 +477,7 @@
             switch (action) {
                 case ACTION_POINTER_DOWN: {
                     clear();
-                    transitionTo(STATE_PANNING_SCALING);
+                    transitionTo(mPanningScalingState);
                 }
                 break;
                 case ACTION_MOVE: {
@@ -594,7 +499,7 @@
                 case ACTION_UP: {
                     if (!mZoomedInBeforeDrag) zoomOff();
                     clear();
-                    transitionTo(STATE_DETECTING);
+                    transitionTo(mDetectingState);
                 }
                 break;
 
@@ -613,25 +518,51 @@
 
         @Override
         public String toString() {
-            return "ViewportDraggingStateHandler{" +
+            return "ViewportDraggingState{" +
                     "mZoomedInBeforeDrag=" + mZoomedInBeforeDrag +
                     ", mLastMoveOutsideMagnifiedRegion=" + mLastMoveOutsideMagnifiedRegion +
                     '}';
         }
     }
 
+    final class DelegatingState implements State {
+        /**
+         * Time of last {@link MotionEvent#ACTION_DOWN} while in {@link DelegatingState}
+         */
+        public long mLastDelegatedDownEventTime;
+
+        @Override
+        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            if (event.getActionMasked() == ACTION_UP) {
+                transitionTo(mDetectingState);
+            }
+
+            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                mLastDelegatedDownEventTime = event.getDownTime();
+            }
+            if (getNext() != null) {
+                // We cache some events to see if the user wants to trigger magnification.
+                // If no magnification is triggered we inject these events with adjusted
+                // time and down time to prevent subsequent transformations being confused
+                // by stale events. After the cached events, which always have a down, are
+                // injected we need to also update the down time of all subsequent non cached
+                // events. All delegated events cached and non-cached are delivered here.
+                event.setDownTime(mLastDelegatedDownEventTime);
+                dispatchTransformedEvent(event, rawEvent, policyFlags);
+            }
+        }
+    }
+
     /**
      * This class handles motion events when the event dispatch has not yet
      * determined what the user is doing. It watches for various tap events.
-     *
-     * @see #STATE_DETECTING
      */
-    final class DetectingStateHandler implements MotionEventHandler, Handler.Callback {
+    final class DetectingState implements State, Handler.Callback {
 
         private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
         private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
 
-        final int mLongTapMinDelay = ViewConfiguration.getJumpTapTimeout();
+        final int mLongTapMinDelay;
         final int mSwipeMinDistance;
         final int mMultiTapMaxDelay;
         final int mMultiTapMaxDistance;
@@ -642,9 +573,12 @@
         private MotionEvent mLastUp;
         private MotionEvent mPreLastUp;
 
+        @VisibleForTesting boolean mShortcutTriggered;
+
         Handler mHandler = new Handler(this);
 
-        public DetectingStateHandler(Context context) {
+        public DetectingState(Context context) {
+            mLongTapMinDelay = ViewConfiguration.getLongPressTimeout();
             mMultiTapMaxDelay = ViewConfiguration.getDoubleTapTimeout()
                     + context.getResources().getInteger(
                     com.android.internal.R.integer.config_screen_magnification_multi_tap_adjustment);
@@ -661,7 +595,7 @@
                 }
                 break;
                 case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
-                    transitionToDelegatingState(/* andClear */ true);
+                    transitionToDelegatingStateAndClear();
                 }
                 break;
                 default: {
@@ -682,12 +616,12 @@
                     if (!mMagnificationController.magnificationRegionContains(
                             event.getX(), event.getY())) {
 
-                        transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+                        transitionToDelegatingStateAndClear();
 
                     } else if (isMultiTapTriggered(2 /* taps */)) {
 
                         // 3tap and hold
-                        delayedTransitionToDraggingState(event);
+                        afterLongTapTimeoutTransitionToDraggingState(event);
 
                     } else if (mDetectTripleTap
                             // If magnified, delay an ACTION_DOWN for mMultiTapMaxDelay
@@ -695,21 +629,21 @@
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
                             || mMagnificationController.isMagnifying()) {
 
-                        delayedTransitionToDelegatingState();
+                        afterMultiTapTimeoutTransitionToDelegatingState();
 
                     } else {
 
                         // Delegate pending events without delay
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
                     if (mMagnificationController.isMagnifying()) {
-                        transitionTo(STATE_PANNING_SCALING);
+                        transitionTo(mPanningScalingState);
                         clear();
                     } else {
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
@@ -722,7 +656,7 @@
                             && !isMultiTapTriggered(2 /* taps */)) {
 
                         // Swipe detected - delegate skipping timeout
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
                     }
                 }
                 break;
@@ -733,7 +667,7 @@
                     if (!mMagnificationController.magnificationRegionContains(
                             event.getX(), event.getY())) {
 
-                        transitionToDelegatingState(/* andClear */ !mShortcutTriggered);
+                        transitionToDelegatingStateAndClear();
 
                     } else if (isMultiTapTriggered(3 /* taps */)) {
 
@@ -742,12 +676,11 @@
                     } else if (
                             // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
                             isFingerDown()
-                                //TODO long tap should never happen here
-                            && (timeBetween(mLastDown, /* mLastUp */ event) >= mLongTapMinDelay)
-                                    || distance(mLastDown, /* mLastUp */ event)
-                                            >= mSwipeMinDistance) {
+                            //TODO long tap should never happen here
+                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
+                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))) {
 
-                        transitionToDelegatingState(/* andClear */ true);
+                        transitionToDelegatingStateAndClear();
 
                     }
                 }
@@ -795,15 +728,15 @@
             return MotionEventInfo.countOf(mDelayedEventQueue, ACTION_UP);
         }
 
-        /** -> {@link #STATE_DELEGATING} */
-        public void delayedTransitionToDelegatingState() {
+        /** -> {@link DelegatingState} */
+        public void afterMultiTapTimeoutTransitionToDelegatingState() {
             mHandler.sendEmptyMessageDelayed(
                     MESSAGE_TRANSITION_TO_DELEGATING_STATE,
                     mMultiTapMaxDelay);
         }
 
-        /** -> {@link #STATE_VIEWPORT_DRAGGING} */
-        public void delayedTransitionToDraggingState(MotionEvent event) {
+        /** -> {@link ViewportDraggingState} */
+        public void afterLongTapTimeoutTransitionToDraggingState(MotionEvent event) {
             mHandler.sendMessageDelayed(
                     mHandler.obtainMessage(MESSAGE_ON_TRIPLE_TAP_AND_HOLD, event),
                     ViewConfiguration.getLongPressTimeout());
@@ -846,11 +779,7 @@
                 MotionEventInfo info = mDelayedEventQueue;
                 mDelayedEventQueue = info.mNext;
 
-                // Because MagnifiedInteractionStateHandler requires well-formed event stream
-                mPanningScalingStateHandler.onMotionEvent(
-                        info.event, info.rawEvent, info.policyFlags);
-
-                delegateEvent(info.event, info.rawEvent, info.policyFlags);
+                handleEventWith(mDelegatingState, info.event, info.rawEvent, info.policyFlags);
 
                 info.recycle();
             }
@@ -868,10 +797,10 @@
             mLastUp = null;
         }
 
-        void transitionToDelegatingState(boolean andClear) {
-            transitionTo(STATE_DELEGATING);
+        void transitionToDelegatingStateAndClear() {
+            transitionTo(mDelegatingState);
             sendDelayedMotionEvents();
-            if (andClear) clear();
+            clear();
         }
 
         private void onTripleTap(MotionEvent up) {
@@ -895,24 +824,40 @@
             if (DEBUG_DETECTING) Slog.i(LOG_TAG, "onTripleTapAndHold()");
             clear();
 
-            mViewportDraggingStateHandler.mZoomedInBeforeDrag =
+            mViewportDraggingState.mZoomedInBeforeDrag =
                     mMagnificationController.isMagnifying();
 
             zoomOn(down.getX(), down.getY());
 
-            transitionTo(STATE_VIEWPORT_DRAGGING);
+            transitionTo(mViewportDraggingState);
         }
 
         @Override
         public String toString() {
-            return "DetectingStateHandler{" +
+            return "DetectingState{" +
                     "tapCount()=" + tapCount() +
+                    ", mShortcutTriggered=" + mShortcutTriggered +
                     ", mDelayedEventQueue=" + MotionEventInfo.toString(mDelayedEventQueue) +
                     '}';
         }
+
+        void toggleShortcutTriggered() {
+            setShortcutTriggered(!mShortcutTriggered);
+        }
+
+        void setShortcutTriggered(boolean state) {
+            if (mShortcutTriggered == state) {
+                return;
+            }
+
+            mShortcutTriggered = state;
+            mMagnificationController.setForceShowMagnifiableBounds(state);
+        }
     }
 
     private void zoomOn(float centerX, float centerY) {
+        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOn(" + centerX + ", " + centerY + ")");
+
         final float scale = MathUtils.constrain(
                 mMagnificationController.getPersistedScale(),
                 MIN_SCALE, MAX_SCALE);
@@ -923,6 +868,8 @@
     }
 
     private void zoomOff() {
+        if (DEBUG_DETECTING) Slog.i(LOG_TAG, "zoomOff()");
+
         mMagnificationController.reset(/* animate */ true);
     }
 
@@ -935,16 +882,15 @@
 
     @Override
     public String toString() {
-        return "MagnificationGestureHandler{" +
-                "mDetectingStateHandler=" + mDetectingStateHandler +
-                ", mMagnifiedInteractionStateHandler=" + mPanningScalingStateHandler +
-                ", mViewportDraggingStateHandler=" + mViewportDraggingStateHandler +
+        return "MagnificationGesture{" +
+                "mDetectingState=" + mDetectingState +
+                ", mDelegatingState=" + mDelegatingState +
+                ", mMagnifiedInteractionState=" + mPanningScalingState +
+                ", mViewportDraggingState=" + mViewportDraggingState +
                 ", mDetectTripleTap=" + mDetectTripleTap +
                 ", mDetectShortcutTrigger=" + mDetectShortcutTrigger +
-                ", mCurrentState=" + stateToString(mCurrentState) +
-                ", mPreviousState=" + stateToString(mPreviousState) +
-                ", mShortcutTriggered=" + mShortcutTriggered +
-                ", mDelegatingStateDownTime=" + mDelegatingStateDownTime +
+                ", mCurrentState=" + State.nameOf(mCurrentState) +
+                ", mPreviousState=" + State.nameOf(mPreviousState) +
                 ", mMagnificationController=" + mMagnificationController +
                 '}';
     }
@@ -1051,7 +997,7 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-            mGestureHandler.setShortcutTriggered(false);
+            mGestureHandler.mDetectingState.setShortcutTriggered(false);
         }
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
index 48041ad..b6b7812 100644
--- a/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
+++ b/services/accessibility/java/com/android/server/accessibility/MotionEventInjector.java
@@ -30,13 +30,13 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.InputDevice;
-import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicy;
-import android.view.accessibility.AccessibilityEvent;
+
 import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -45,7 +45,7 @@
  * <p>
  * All methods except {@code injectEvents} must be called only from the main thread.
  */
-public class MotionEventInjector implements EventStreamTransformation, Handler.Callback {
+public class MotionEventInjector extends BaseEventStreamTransformation implements Handler.Callback {
     private static final String LOG_TAG = "MotionEventInjector";
     private static final int MESSAGE_SEND_MOTION_EVENT = 1;
     private static final int MESSAGE_INJECT_EVENTS = 2;
@@ -68,7 +68,6 @@
     private final Handler mHandler;
     private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
 
-    private EventStreamTransformation mNext;
     private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
     private IntArray mSequencesInProgress = new IntArray(5);
     private boolean mIsDestroyed = false;
@@ -117,25 +116,6 @@
     }
 
     @Override
-    public void onKeyEvent(KeyEvent event, int policyFlags) {
-        if (mNext != null) {
-            mNext.onKeyEvent(event, policyFlags);
-        }
-    }
-
-    @Override
-    public void onAccessibilityEvent(AccessibilityEvent event) {
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
-    }
-
-    @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
-    }
-
-    @Override
     public void clearEvents(int inputSource) {
         /*
          * Reset state for motion events passing through so we won't send a cancel event for
@@ -187,7 +167,7 @@
             return;
         }
 
-        if (mNext == null) {
+        if (getNext() == null) {
             notifyService(serviceInterface, sequence, false);
             return;
         }
@@ -262,17 +242,24 @@
                 int continuedPointerId = mStrokeIdToPointerId
                         .get(touchPoint.mContinuedStrokeId, -1);
                 if (continuedPointerId == -1) {
+                    Slog.w(LOG_TAG, "Can't continue gesture due to unknown continued stroke id in "
+                            + touchPoint);
                     return false;
                 }
                 mStrokeIdToPointerId.put(touchPoint.mStrokeId, continuedPointerId);
                 int lastPointIndex = findPointByStrokeId(
                         mLastTouchPoints, mNumLastTouchPoints, touchPoint.mContinuedStrokeId);
                 if (lastPointIndex < 0) {
+                    Slog.w(LOG_TAG, "Can't continue gesture due continued gesture id of "
+                            + touchPoint + " not matching any previous strokes in "
+                            + Arrays.asList(mLastTouchPoints));
                     return false;
                 }
                 if (mLastTouchPoints[lastPointIndex].mIsEndOfPath
                         || (mLastTouchPoints[lastPointIndex].mX != touchPoint.mX)
                         || (mLastTouchPoints[lastPointIndex].mY != touchPoint.mY)) {
+                    Slog.w(LOG_TAG, "Can't continue gesture due to points mismatch between "
+                            + mLastTouchPoints[lastPointIndex] + " and " + touchPoint);
                     return false;
                 }
                 // Update the last touch point to match the continuation, so the gestures will
@@ -292,8 +279,8 @@
 
     private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
             int policyFlags) {
-        if (mNext != null) {
-            mNext.onMotionEvent(event, rawEvent, policyFlags);
+        if (getNext() != null) {
+            super.onMotionEvent(event, rawEvent, policyFlags);
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 mOpenGesturesInProgress.put(event.getSource(), true);
             }
@@ -305,7 +292,7 @@
     }
 
     private void cancelAnyGestureInProgress(int source) {
-        if ((mNext != null) && mOpenGesturesInProgress.get(source, false)) {
+        if ((getNext() != null) && mOpenGesturesInProgress.get(source, false)) {
             long now = SystemClock.uptimeMillis();
             MotionEvent cancelEvent =
                     obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index e380f2c..a32686d 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -55,7 +55,8 @@
  *
  * @hide
  */
-class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDetector.Listener {
+class TouchExplorer extends BaseEventStreamTransformation
+        implements AccessibilityGestureDetector.Listener {
 
     private static final boolean DEBUG = false;
 
@@ -131,9 +132,6 @@
     // the two dragging pointers as opposed to use the location of the primary one.
     private final int mScaledMinPointerDistanceToUseMiddleLocation;
 
-    // The handler to which to delegate events.
-    private EventStreamTransformation mNext;
-
     // Helper class to track received pointers.
     private final ReceivedPointerTracker mReceivedPointerTracker;
 
@@ -198,9 +196,7 @@
         if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) {
             clear();
         }
-        if (mNext != null) {
-            mNext.clearEvents(inputSource);
-        }
+        super.clearEvents(inputSource);
     }
 
     @Override
@@ -258,16 +254,9 @@
     }
 
     @Override
-    public void setNext(EventStreamTransformation next) {
-        mNext = next;
-    }
-
-    @Override
     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
-            if (mNext != null) {
-                mNext.onMotionEvent(event, rawEvent, policyFlags);
-            }
+            super.onMotionEvent(event, rawEvent, policyFlags);
             return;
         }
 
@@ -311,13 +300,6 @@
     }
 
     @Override
-    public void onKeyEvent(KeyEvent event, int policyFlags) {
-        if (mNext != null) {
-            mNext.onKeyEvent(event, policyFlags);
-        }
-    }
-
-    @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
         final int eventType = event.getEventType();
 
@@ -353,9 +335,7 @@
                 mLastTouchedWindowId = event.getWindowId();
             } break;
         }
-        if (mNext != null) {
-            mNext.onAccessibilityEvent(event);
-        }
+        super.onAccessibilityEvent(event);
     }
 
     @Override
@@ -969,12 +949,10 @@
 
         // Make sure that the user will see the event.
         policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-        if (mNext != null) {
-            // TODO: For now pass null for the raw event since the touch
-            //       explorer is the last event transformation and it does
-            //       not care about the raw event.
-            mNext.onMotionEvent(event, null, policyFlags);
-        }
+        // TODO: For now pass null for the raw event since the touch
+        //       explorer is the last event transformation and it does
+        //       not care about the raw event.
+        super.onMotionEvent(event, null, policyFlags);
 
         mInjectedPointerTracker.onMotionEvent(event);
 
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 80b5477..6c15438 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -21,9 +21,12 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -71,7 +74,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.service.appwidget.AppWidgetServiceDumpProto;
 import android.service.appwidget.WidgetProto;
 import android.text.TextUtils;
@@ -100,14 +102,11 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
 import com.android.internal.widget.IRemoteViewsFactory;
 import com.android.server.LocalServices;
 import com.android.server.WidgetBackupProvider;
 import com.android.server.policy.IconUtilities;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -159,7 +158,9 @@
     // Bump if the stored widgets need to be upgraded.
     private static final int CURRENT_VERSION = 1;
 
-    private static final AtomicLong REQUEST_COUNTER = new AtomicLong();
+    // Every widget update request is associated which an increasing sequence number. This is
+    // used to verify which request has successfully been received by the host.
+    private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -171,29 +172,31 @@
                 Slog.i(TAG, "Received broadcast: " + action + " on user " + userId);
             }
 
-            if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
-                onConfigurationChanged();
-            } else if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)
-                    || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
-                synchronized (mLock) {
-                    reloadWidgetsMaskedState(userId);
-                }
-            } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                updateWidgetPackageSuspensionMaskedState(packages, true, getSendingUserId());
-            } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) {
-                String[] packages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                updateWidgetPackageSuspensionMaskedState(packages, false, getSendingUserId());
-            } else {
-                onPackageBroadcastReceived(intent, userId);
+            switch (action) {
+                case Intent.ACTION_CONFIGURATION_CHANGED:
+                    onConfigurationChanged();
+                    break;
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                    synchronized (mLock) {
+                        reloadWidgetsMaskedState(userId);
+                    }
+                    break;
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    updateWidgetPackageSuspensionMaskedState(intent, true, getSendingUserId());
+                    break;
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    updateWidgetPackageSuspensionMaskedState(intent, false, getSendingUserId());
+                    break;
+                default:
+                    onPackageBroadcastReceived(intent, getSendingUserId());
+                    break;
             }
         }
     };
 
-    // Manages active connections to RemoteViewsServices.
-    private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
-            mBoundRemoteViewsServices = new HashMap<>();
-
     // Manages persistent references to RemoteViewsServices from different App Widgets.
     private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
             mRemoteViewsServicesAppWidgets = new HashMap<>();
@@ -379,25 +382,32 @@
         boolean changed = false;
         boolean componentsModified = false;
 
-        String pkgList[] = null;
-        if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
-            pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            added = true;
-        } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
-            pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
-            added = false;
-        } else {
-            Uri uri = intent.getData();
-            if (uri == null) {
-                return;
+        final String pkgList[];
+        switch (action) {
+            case Intent.ACTION_PACKAGES_SUSPENDED:
+            case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                changed = true;
+                break;
+            case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+                added = true;
+                // Follow through
+            case Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+                pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                break;
+            default: {
+                Uri uri = intent.getData();
+                if (uri == null) {
+                    return;
+                }
+                String pkgName = uri.getSchemeSpecificPart();
+                if (pkgName == null) {
+                    return;
+                }
+                pkgList = new String[] { pkgName };
+                added = Intent.ACTION_PACKAGE_ADDED.equals(action);
+                changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
             }
-            String pkgName = uri.getSchemeSpecificPart();
-            if (pkgName == null) {
-                return;
-            }
-            pkgList = new String[] { pkgName };
-            added = Intent.ACTION_PACKAGE_ADDED.equals(action);
-            changed = Intent.ACTION_PACKAGE_CHANGED.equals(action);
         }
         if (pkgList == null || pkgList.length == 0) {
             return;
@@ -517,12 +527,13 @@
     /**
      * Incrementally update the masked state due to package suspension state.
      */
-    private void updateWidgetPackageSuspensionMaskedState(String[] packagesArray, boolean suspended,
+    private void updateWidgetPackageSuspensionMaskedState(Intent intent, boolean suspended,
             int profileId) {
+        String[] packagesArray = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
         if (packagesArray == null) {
             return;
         }
-        Set<String> packages = new ArraySet<String>(Arrays.asList(packagesArray));
+        Set<String> packages = new ArraySet<>(Arrays.asList(packagesArray));
         synchronized (mLock) {
             final int N = mProviders.size();
             for (int i = 0; i < N; i++) {
@@ -814,9 +825,9 @@
             Host host = lookupOrAddHostLocked(id);
             host.callbacks = callbacks;
 
+            long updateSequenceNo = UPDATE_COUNTER.incrementAndGet();
             int N = appWidgetIds.length;
             ArrayList<PendingHostUpdate> outUpdates = new ArrayList<>(N);
-
             LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>();
             for (int i = 0; i < N; i++) {
                 if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) {
@@ -828,6 +839,8 @@
                     }
                 }
             }
+            // Reset the update counter once all the updates have been calculated
+            host.lastWidgetUpdateSequenceNo = updateSequenceNo;
             return new ParceledListSlice<>(outUpdates);
         }
     }
@@ -1206,17 +1219,14 @@
     }
 
     @Override
-    public void bindRemoteViewsService(String callingPackage, int appWidgetId,
-            Intent intent, IBinder callbacks) {
+    public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent,
+            IApplicationThread caller, IBinder activtiyToken, IServiceConnection connection,
+            int flags) {
         final int userId = UserHandle.getCallingUserId();
-
         if (DEBUG) {
             Slog.i(TAG, "bindRemoteViewsService() " + userId);
         }
 
-        // Make sure the package runs under the caller uid.
-        mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
         synchronized (mLock) {
             ensureGroupStateLoadedLocked(userId);
 
@@ -1251,76 +1261,35 @@
             mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
                     componentName, widget.provider.getUserId());
 
-            // Good to go - the service pakcage is correct, it exists for the correct
+            // Good to go - the service package is correct, it exists for the correct
             // user, and requires the bind permission.
 
-            // If there is already a connection made for this service intent, then
-            // disconnect from that first. (This does not allow multiple connections
-            // to the same service under the same key).
-            ServiceConnectionProxy connection = null;
-            FilterComparison fc = new FilterComparison(intent);
-            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                // Ask ActivityManager to bind it. Notice that we are binding the service with the
+                // caller app instead of DevicePolicyManagerService.
+                if(ActivityManager.getService().bindService(
+                        caller, activtiyToken, intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        connection, flags, mContext.getOpPackageName(),
+                        widget.provider.getUserId()) != 0) {
 
-            if (mBoundRemoteViewsServices.containsKey(key)) {
-                connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
-                connection.disconnect();
-                unbindService(connection);
-                mBoundRemoteViewsServices.remove(key);
-            }
-
-            // Bind to the RemoteViewsService (which will trigger a callback to the
-            // RemoteViewsAdapter.onServiceConnected())
-            connection = new ServiceConnectionProxy(callbacks);
-            bindService(intent, connection, widget.provider.info.getProfile());
-            mBoundRemoteViewsServices.put(key, connection);
-
-            // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
-            // can determine when we can call back to the RemoteViewsService later to
-            // destroy associated factories.
-            Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
-            incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
-        }
-    }
-
-    @Override
-    public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
-        final int userId = UserHandle.getCallingUserId();
-
-        if (DEBUG) {
-            Slog.i(TAG, "unbindRemoteViewsService() " + userId);
-        }
-
-        // Make sure the package runs under the caller uid.
-        mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
-        synchronized (mLock) {
-            ensureGroupStateLoadedLocked(userId);
-
-            // Unbind from the RemoteViewsService (which will trigger a callback to the bound
-            // RemoteViewsAdapter)
-            Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
-                    new FilterComparison(intent));
-            if (mBoundRemoteViewsServices.containsKey(key)) {
-                // We don't need to use the appWidgetId until after we are sure there is something
-                // to unbind. Note that this may mask certain issues with apps calling unbind()
-                // more than necessary.
-
-                // NOTE: The lookup is enforcing security across users by making
-                // sure the caller can only access widgets it hosts or provides.
-                Widget widget = lookupWidgetLocked(appWidgetId,
-                        Binder.getCallingUid(), callingPackage);
-
-                if (widget == null) {
-                    throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+                    // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+                    // can determine when we can call back to the RemoteViewsService later to
+                    // destroy associated factories.
+                    incrementAppWidgetServiceRefCount(appWidgetId,
+                            Pair.create(widget.provider.id.uid, new FilterComparison(intent)));
+                    return true;
                 }
-
-                ServiceConnectionProxy connection = (ServiceConnectionProxy)
-                        mBoundRemoteViewsServices.get(key);
-                connection.disconnect();
-                mContext.unbindService(connection);
-                mBoundRemoteViewsServices.remove(key);
+            } catch (RemoteException ex) {
+                // Same process, should not happen.
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
+
+        // Failed to bind.
+        return false;
     }
 
     @Override
@@ -1751,7 +1720,9 @@
 
     private void deleteAppWidgetLocked(Widget widget) {
         // We first unbind all services that are bound to this id
-        unbindAppWidgetRemoteViewsServicesLocked(widget);
+        // Check if we need to destroy any services (if no other app widgets are
+        // referencing the same service)
+        decrementAppWidgetServiceRefCount(widget);
 
         Host host = widget.host;
         host.widgets.remove(widget);
@@ -1793,28 +1764,6 @@
         }
     }
 
-    // Unbinds from a RemoteViewsService when we delete an app widget
-    private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
-        int appWidgetId = widget.appWidgetId;
-        // Unbind all connections to Services bound to this AppWidgetId
-        Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
-                .iterator();
-        while (it.hasNext()) {
-            final Pair<Integer, Intent.FilterComparison> key = it.next();
-            if (key.first == appWidgetId) {
-                final ServiceConnectionProxy conn = (ServiceConnectionProxy)
-                        mBoundRemoteViewsServices.get(key);
-                conn.disconnect();
-                mContext.unbindService(conn);
-                it.remove();
-            }
-        }
-
-        // Check if we need to destroy any services (if no other app widgets are
-        // referencing the same service)
-        decrementAppWidgetServiceRefCount(widget);
-    }
-
     // Destroys the cached factory on the RemoteViewsService's side related to the specified intent
     private void destroyRemoteViewsService(final Intent intent, Widget widget) {
         final ServiceConnection conn = new ServiceConnection() {
@@ -1850,7 +1799,7 @@
     // Adds to the ref-count for a given RemoteViewsService intent
     private void incrementAppWidgetServiceRefCount(int appWidgetId,
             Pair<Integer, FilterComparison> serviceId) {
-        HashSet<Integer> appWidgetIds = null;
+        final HashSet<Integer> appWidgetIds;
         if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
             appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
         } else {
@@ -1914,9 +1863,9 @@
             // method with a wrong id. In that case, ignore the call.
             return;
         }
-        long requestId = REQUEST_COUNTER.incrementAndGet();
+        long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
-            widget.updateRequestIds.put(viewId, requestId);
+            widget.updateSequenceNos.put(viewId, requestId);
         }
         if (widget == null || widget.host == null || widget.host.zombie
                 || widget.host.callbacks == null || widget.provider == null
@@ -1941,7 +1890,7 @@
             int appWidgetId, int viewId, long requestId) {
         try {
             callbacks.viewDataChanged(appWidgetId, viewId);
-            host.lastWidgetUpdateRequestId = requestId;
+            host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
             // It failed; remove the callback. No need to prune because
             // we know that this host is still referenced by this instance.
@@ -1988,9 +1937,9 @@
     }
 
     private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
-        long requestId = REQUEST_COUNTER.incrementAndGet();
+        long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
-            widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId);
+            widget.updateSequenceNos.put(ID_VIEWS_UPDATE, requestId);
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
                 || widget.host.callbacks == null || widget.host.zombie) {
@@ -2013,7 +1962,7 @@
             int appWidgetId, RemoteViews views, long requestId) {
         try {
             callbacks.updateAppWidget(appWidgetId, views);
-            host.lastWidgetUpdateRequestId = requestId;
+            host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
             synchronized (mLock) {
                 Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -2023,11 +1972,11 @@
     }
 
     private void scheduleNotifyProviderChangedLocked(Widget widget) {
-        long requestId = REQUEST_COUNTER.incrementAndGet();
+        long requestId = UPDATE_COUNTER.incrementAndGet();
         if (widget != null) {
             // When the provider changes, reset everything else.
-            widget.updateRequestIds.clear();
-            widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId);
+            widget.updateSequenceNos.clear();
+            widget.updateSequenceNos.append(ID_PROVIDER_CHANGED, requestId);
         }
         if (widget == null || widget.provider == null || widget.provider.zombie
                 || widget.host.callbacks == null || widget.host.zombie) {
@@ -2050,7 +1999,7 @@
             int appWidgetId, AppWidgetProviderInfo info, long requestId) {
         try {
             callbacks.providerChanged(appWidgetId, info);
-            host.lastWidgetUpdateRequestId = requestId;
+            host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
             synchronized (mLock){
                 Slog.e(TAG, "Widget host dead: " + host.id, re);
@@ -2427,14 +2376,14 @@
             out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
         }
         if (widget.options != null) {
-            out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
-            out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
-            out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
-            out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+            int minWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+            int minHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+            int maxWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
+            int maxHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+            out.attribute(null, "min_width", Integer.toHexString((minWidth > 0) ? minWidth : 0));
+            out.attribute(null, "min_height", Integer.toHexString((minHeight > 0) ? minHeight : 0));
+            out.attribute(null, "max_width", Integer.toHexString((maxWidth > 0) ? maxWidth : 0));
+            out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
             out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
                     AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
         }
@@ -2693,11 +2642,9 @@
 
             // No file written for this user - nothing to do.
             AtomicFile file = getSavedStateFile(profileId);
-            try {
-                FileInputStream stream = file.openRead();
+            try (FileInputStream stream = file.openRead()) {
                 version = readProfileStateFromFileLocked(stream, profileId, loadedWidgets);
-                IoUtils.closeQuietly(stream);
-            } catch (FileNotFoundException e) {
+            } catch (IOException e) {
                 Slog.w(TAG, "Failed to read state: " + e);
             }
         }
@@ -3887,7 +3834,11 @@
         boolean zombie; // if we're in safe mode, don't prune this just because nobody references it
 
         int tag = TAG_UNDEFINED; // for use while saving state (the index)
-        long lastWidgetUpdateRequestId; // request id for the last update successfully sent
+        // Sequence no for the last update successfully sent. This is updated whenever a
+        // widget update is successfully sent to the host callbacks. As all new/undelivered updates
+        // will have sequenceNo greater than this, all those updates will be sent when the host
+        // callbacks are attached again.
+        long lastWidgetUpdateSequenceNo;
 
         public int getUserId() {
             return UserHandle.getUserId(id.uid);
@@ -3914,18 +3865,18 @@
          */
         public boolean getPendingUpdatesForId(int appWidgetId,
                 LongSparseArray<PendingHostUpdate> outUpdates) {
-            long updateRequestId = lastWidgetUpdateRequestId;
+            long updateSequenceNo = lastWidgetUpdateSequenceNo;
             int N = widgets.size();
             for (int i = 0; i < N; i++) {
                 Widget widget = widgets.get(i);
                 if (widget.appWidgetId == appWidgetId) {
                     outUpdates.clear();
-                    for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) {
-                        long requestId = widget.updateRequestIds.valueAt(j);
-                        if (requestId <= updateRequestId) {
+                    for (int j = widget.updateSequenceNos.size() - 1; j >= 0; j--) {
+                        long requestId = widget.updateSequenceNos.valueAt(j);
+                        if (requestId <= updateSequenceNo) {
                             continue;
                         }
-                        int id = widget.updateRequestIds.keyAt(j);
+                        int id = widget.updateSequenceNos.keyAt(j);
                         final PendingHostUpdate update;
                         switch (id) {
                             case ID_PROVIDER_CHANGED:
@@ -4021,8 +3972,8 @@
         RemoteViews maskedViews;
         Bundle options;
         Host host;
-        // Request ids for various operations
-        SparseLongArray updateRequestIds = new SparseLongArray(2);
+        // Map of request type to updateSequenceNo.
+        SparseLongArray updateSequenceNos = new SparseLongArray(2);
 
         @Override
         public String toString() {
@@ -4048,40 +3999,6 @@
         }
     }
 
-    /**
-     * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
-     * needs to be a static inner class since a reference to the ServiceConnection is held globally
-     * and may lead us to leak AppWidgetService instances (if there were more than one).
-     */
-    private static final class ServiceConnectionProxy implements ServiceConnection {
-        private final IRemoteViewsAdapterConnection mConnectionCb;
-
-        ServiceConnectionProxy(IBinder connectionCb) {
-            mConnectionCb = IRemoteViewsAdapterConnection.Stub
-                    .asInterface(connectionCb);
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                mConnectionCb.onServiceConnected(service);
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Error passing service interface", re);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-            disconnect();
-        }
-
-        public void disconnect() {
-            try {
-                mConnectionCb.onServiceDisconnected();
-            } catch (RemoteException re) {
-                Slog.e(TAG, "Error clearing service interface", re);
-            }
-        }
-    }
-
     private class LoadedWidgetState {
         final Widget widget;
         final int hostTag;
@@ -4635,7 +4552,9 @@
                         // reconstructed due to the restore
                         host.widgets.remove(widget);
                         provider.widgets.remove(widget);
-                        unbindAppWidgetRemoteViewsServicesLocked(widget);
+                        // Check if we need to destroy any services (if no other app widgets are
+                        // referencing the same service)
+                        decrementAppWidgetServiceRefCount(widget);
                         removeWidgetLocked(widget);
                     }
                 }
diff --git a/services/art-profile b/services/art-profile
index ae5c909..ac5ecaf 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -2134,23 +2134,15 @@
 HPLcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;->requestShowFillUi(Landroid/view/autofill/AutofillId;IILandroid/view/autofill/IAutofillWindowPresenter;)V
 HPLcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;->save()V
 HPLcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;->startIntentSender(Landroid/content/IntentSender;)V
-HPLcom/android/server/backup/BackupManagerService$1;->run()V
-HPLcom/android/server/backup/BackupManagerService$3;->onReceive(Landroid/content/Context;Landroid/content/Intent;)V
-HPLcom/android/server/backup/BackupManagerService$7;-><init>(Lcom/android/server/backup/BackupManagerService;Ljava/lang/String;Ljava/util/HashSet;)V
-HPLcom/android/server/backup/BackupManagerService$7;->run()V
-HPLcom/android/server/backup/BackupManagerService$BackupRequest;-><init>(Lcom/android/server/backup/BackupManagerService;Ljava/lang/String;)V
-HPLcom/android/server/backup/BackupManagerService;->-wrap18(Lcom/android/server/backup/BackupManagerService;Ljava/lang/String;Ljava/util/HashSet;)V
-HPLcom/android/server/backup/BackupManagerService;->addPackageParticipantsLockedInner(Ljava/lang/String;Ljava/util/List;)V
-HPLcom/android/server/backup/BackupManagerService;->allAgentPackages()Ljava/util/List;
-HPLcom/android/server/backup/BackupManagerService;->appIsDisabled(Landroid/content/pm/ApplicationInfo;Landroid/content/pm/PackageManager;)Z
-HPLcom/android/server/backup/BackupManagerService;->appIsEligibleForBackup(Landroid/content/pm/ApplicationInfo;Landroid/content/pm/PackageManager;)Z
-HPLcom/android/server/backup/BackupManagerService;->dataChanged(Ljava/lang/String;)V
-HPLcom/android/server/backup/BackupManagerService;->dataChangedImpl(Ljava/lang/String;)V
-HPLcom/android/server/backup/BackupManagerService;->dataChangedImpl(Ljava/lang/String;Ljava/util/HashSet;)V
-HPLcom/android/server/backup/BackupManagerService;->dataChangedTargets(Ljava/lang/String;)Ljava/util/HashSet;
-HPLcom/android/server/backup/BackupManagerService;->dequeueFullBackupLocked(Ljava/lang/String;)V
-HPLcom/android/server/backup/BackupManagerService;->readFullBackupSchedule()Ljava/util/ArrayList;
-HPLcom/android/server/backup/BackupManagerService;->writeToJournalLocked(Ljava/lang/String;)V
+HPLcom/android/server/backup/RefactoredBackupManagerService;->addPackageParticipantsLockedInner(Ljava/lang/String;Ljava/util/List;)V
+HPLcom/android/server/backup/RefactoredBackupManagerService;->allAgentPackages()Ljava/util/List;
+HPLcom/android/server/backup/RefactoredBackupManagerService;->dataChanged(Ljava/lang/String;)V
+HPLcom/android/server/backup/RefactoredBackupManagerService;->dataChangedImpl(Ljava/lang/String;)V
+HPLcom/android/server/backup/RefactoredBackupManagerService;->dataChangedImpl(Ljava/lang/String;Ljava/util/HashSet;)V
+HPLcom/android/server/backup/RefactoredBackupManagerService;->dataChangedTargets(Ljava/lang/String;)Ljava/util/HashSet;
+HPLcom/android/server/backup/RefactoredBackupManagerService;->dequeueFullBackupLocked(Ljava/lang/String;)V
+HPLcom/android/server/backup/RefactoredBackupManagerService;->readFullBackupSchedule()Ljava/util/ArrayList;
+HPLcom/android/server/backup/RefactoredBackupManagerService;->writeToJournalLocked(Ljava/lang/String;)V
 HPLcom/android/server/backup/BackupManagerServiceInterface;->acknowledgeAdbBackupOrRestore(IZLjava/lang/String;Ljava/lang/String;Landroid/app/backup/IFullBackupRestoreObserver;)V
 HPLcom/android/server/backup/BackupManagerServiceInterface;->adbBackup(Landroid/os/ParcelFileDescriptor;ZZZZZZZZ[Ljava/lang/String;)V
 HPLcom/android/server/backup/BackupManagerServiceInterface;->adbRestore(Landroid/os/ParcelFileDescriptor;)V
@@ -2200,6 +2192,8 @@
 HPLcom/android/server/backup/Trampoline;->dataChanged(Ljava/lang/String;)V
 HPLcom/android/server/backup/TransportManager$TransportBoundListener;->onTransportBound(Lcom/android/internal/backup/IBackupTransport;)Z
 HPLcom/android/server/backup/TransportManager;->onPackageRemoved(Ljava/lang/String;)V
+HPLcom/android/server/backup/utils/AppBackupUtils;->appIsDisabled(Landroid/content/pm/ApplicationInfo;Landroid/content/pm/PackageManager;)Z
+HPLcom/android/server/backup/utils/AppBackupUtils;->appIsEligibleForBackup(Landroid/content/pm/ApplicationInfo;Landroid/content/pm/PackageManager;)Z
 HPLcom/android/server/clipboard/ClipboardService$ClipboardImpl;->hasPrimaryClip(Ljava/lang/String;)Z
 HPLcom/android/server/clipboard/ClipboardService;->clipboardAccessAllowed(ILjava/lang/String;I)Z
 HPLcom/android/server/companion/CompanionDeviceManagerService$CompanionDeviceManagerImpl;->checkCallerIsSystemOr(Ljava/lang/String;I)V
@@ -9364,61 +9358,30 @@
 PLcom/android/server/autofill/ui/AutoFillUI;->lambda$-com_android_server_autofill_ui_AutoFillUI_4826(Lcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;Ljava/lang/String;)V
 PLcom/android/server/autofill/ui/AutoFillUI;->setCallback(Lcom/android/server/autofill/ui/AutoFillUI$AutoFillUiCallback;)V
 PLcom/android/server/autofill/ui/OverlayControl;-><init>(Landroid/content/Context;)V
-PLcom/android/server/backup/-$Lambda$UGPbw6RN8_4TeqlxQ94PEo_ieak;->$m$0()V
-PLcom/android/server/backup/-$Lambda$UGPbw6RN8_4TeqlxQ94PEo_ieak;-><init>(BLjava/lang/Object;)V
-PLcom/android/server/backup/-$Lambda$UGPbw6RN8_4TeqlxQ94PEo_ieak;->run()V
-PLcom/android/server/backup/BackupManagerService$1;-><init>(Lcom/android/server/backup/BackupManagerService;)V
-PLcom/android/server/backup/BackupManagerService$2;-><init>(Lcom/android/server/backup/BackupManagerService;)V
-PLcom/android/server/backup/BackupManagerService$2;->onTransportBound(Lcom/android/internal/backup/IBackupTransport;)Z
-PLcom/android/server/backup/BackupManagerService$3;-><init>(Lcom/android/server/backup/BackupManagerService;)V
-PLcom/android/server/backup/BackupManagerService$4;-><init>(Lcom/android/server/backup/BackupManagerService;J)V
-PLcom/android/server/backup/BackupManagerService$4;->run()V
-PLcom/android/server/backup/BackupManagerService$8;-><init>(Lcom/android/server/backup/BackupManagerService;Landroid/content/ComponentName;Landroid/app/backup/ISelectBackupTransportCallback;)V
-PLcom/android/server/backup/BackupManagerService$8;->onSuccess(Ljava/lang/String;)V
-PLcom/android/server/backup/BackupManagerService$BackupHandler;-><init>(Lcom/android/server/backup/BackupManagerService;Landroid/os/Looper;)V
-PLcom/android/server/backup/BackupManagerService$BackupHandler;->handleMessage(Landroid/os/Message;)V
-PLcom/android/server/backup/BackupManagerService$FullBackupEntry;-><init>(Lcom/android/server/backup/BackupManagerService;Ljava/lang/String;J)V
-PLcom/android/server/backup/BackupManagerService$FullBackupEntry;->compareTo(Lcom/android/server/backup/BackupManagerService$FullBackupEntry;)I
-PLcom/android/server/backup/BackupManagerService$FullBackupEntry;->compareTo(Ljava/lang/Object;)I
-PLcom/android/server/backup/BackupManagerService$Lifecycle;-><init>(Landroid/content/Context;)V
-PLcom/android/server/backup/BackupManagerService$Lifecycle;->onStart()V
-PLcom/android/server/backup/BackupManagerService$Lifecycle;->onUnlockUser(I)V
-PLcom/android/server/backup/BackupManagerService$ProvisionedObserver;-><init>(Lcom/android/server/backup/BackupManagerService;Landroid/os/Handler;)V
-PLcom/android/server/backup/BackupManagerService$RunBackupReceiver;-><init>(Lcom/android/server/backup/BackupManagerService;)V
-PLcom/android/server/backup/BackupManagerService$RunBackupReceiver;-><init>(Lcom/android/server/backup/BackupManagerService;Lcom/android/server/backup/BackupManagerService$RunBackupReceiver;)V
-PLcom/android/server/backup/BackupManagerService$RunInitializeReceiver;-><init>(Lcom/android/server/backup/BackupManagerService;)V
-PLcom/android/server/backup/BackupManagerService$RunInitializeReceiver;-><init>(Lcom/android/server/backup/BackupManagerService;Lcom/android/server/backup/BackupManagerService$RunInitializeReceiver;)V
-PLcom/android/server/backup/BackupManagerService;->-get3(Lcom/android/server/backup/BackupManagerService;)Landroid/content/pm/PackageManager;
-PLcom/android/server/backup/BackupManagerService;->-get6(Lcom/android/server/backup/BackupManagerService;)Lcom/android/server/backup/TransportManager;
-PLcom/android/server/backup/BackupManagerService;->-wrap11(I)Z
-PLcom/android/server/backup/BackupManagerService;->-wrap17(Lcom/android/server/backup/BackupManagerService;Ljava/lang/String;)V
-PLcom/android/server/backup/BackupManagerService;->-wrap23(Lcom/android/server/backup/BackupManagerService;Ljava/lang/String;)V
-PLcom/android/server/backup/BackupManagerService;->-wrap25(Lcom/android/server/backup/BackupManagerService;)V
-PLcom/android/server/backup/BackupManagerService;->-wrap5(Landroid/content/pm/PackageInfo;)Z
-PLcom/android/server/backup/BackupManagerService;->-wrap9(I)Z
-PLcom/android/server/backup/BackupManagerService;-><init>(Landroid/content/Context;Lcom/android/server/backup/Trampoline;)V
-PLcom/android/server/backup/BackupManagerService;->addPackageParticipantsLocked([Ljava/lang/String;)V
-PLcom/android/server/backup/BackupManagerService;->appGetsFullBackup(Landroid/content/pm/PackageInfo;)Z
-PLcom/android/server/backup/BackupManagerService;->backupSettingMigrated(I)Z
-PLcom/android/server/backup/BackupManagerService;->enqueueFullBackup(Ljava/lang/String;J)V
-PLcom/android/server/backup/BackupManagerService;->getCurrentTransport()Ljava/lang/String;
-PLcom/android/server/backup/BackupManagerService;->initPackageTracking()V
-PLcom/android/server/backup/BackupManagerService;->isBackupEnabled()Z
-PLcom/android/server/backup/BackupManagerService;->lambda$-com_android_server_backup_BackupManagerService_56585()V
-PLcom/android/server/backup/BackupManagerService;->parseLeftoverJournals()V
-PLcom/android/server/backup/BackupManagerService;->readBackupEnableState(I)Z
-PLcom/android/server/backup/BackupManagerService;->removePackageFromSetLocked(Ljava/util/HashSet;Ljava/lang/String;)V
-PLcom/android/server/backup/BackupManagerService;->removePackageParticipantsLocked([Ljava/lang/String;I)V
-PLcom/android/server/backup/BackupManagerService;->scheduleNextFullBackupJob(J)V
-PLcom/android/server/backup/BackupManagerService;->selectBackupTransportAsync(Landroid/content/ComponentName;Landroid/app/backup/ISelectBackupTransportCallback;)V
-PLcom/android/server/backup/BackupManagerService;->setBackupEnabled(Z)V
-PLcom/android/server/backup/BackupManagerService;->updateStateForTransport(Ljava/lang/String;)V
-PLcom/android/server/backup/BackupManagerService;->writeBackupEnableState(ZI)V
-PLcom/android/server/backup/BackupManagerService;->writeFullBackupScheduleAsync()V
+PLcom/android/server/backup/RefactoredBackupManagerService$Lifecycle;-><init>(Landroid/content/Context;)V
+PLcom/android/server/backup/RefactoredBackupManagerService$Lifecycle;->onStart()V
+PLcom/android/server/backup/RefactoredBackupManagerService$Lifecycle;->onUnlockUser(I)V
+PLcom/android/server/backup/RefactoredBackupManagerService;-><init>(Landroid/content/Context;Lcom/android/server/backup/Trampoline;)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->addPackageParticipantsLocked([Ljava/lang/String;)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->backupSettingMigrated(I)Z
+PLcom/android/server/backup/RefactoredBackupManagerService;->enqueueFullBackup(Ljava/lang/String;J)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->getCurrentTransport()Ljava/lang/String;
+PLcom/android/server/backup/RefactoredBackupManagerService;->initPackageTracking()V
+PLcom/android/server/backup/RefactoredBackupManagerService;->isBackupEnabled()Z
+PLcom/android/server/backup/RefactoredBackupManagerService;->parseLeftoverJournals()V
+PLcom/android/server/backup/RefactoredBackupManagerService;->readBackupEnableState(I)Z
+PLcom/android/server/backup/RefactoredBackupManagerService;->removePackageFromSetLocked(Ljava/util/HashSet;Ljava/lang/String;)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->removePackageParticipantsLocked([Ljava/lang/String;I)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->scheduleNextFullBackupJob(J)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->selectBackupTransportAsync(Landroid/content/ComponentName;Landroid/app/backup/ISelectBackupTransportCallback;)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->setBackupEnabled(Z)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->updateStateForTransport(Ljava/lang/String;)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->writeBackupEnableState(ZI)V
+PLcom/android/server/backup/RefactoredBackupManagerService;->writeFullBackupScheduleAsync()V
 PLcom/android/server/backup/FullBackupJob;->schedule(Landroid/content/Context;J)V
 PLcom/android/server/backup/KeyValueBackupJob;->cancel(Landroid/content/Context;)V
 PLcom/android/server/backup/Trampoline;-><init>(Landroid/content/Context;)V
-PLcom/android/server/backup/Trampoline;->createBackupManagerService()Lcom/android/server/backup/BackupManagerServiceInterface;
+PLcom/android/server/backup/Trampoline;->createRefactoredBackupManagerService()Lcom/android/server/backup/BackupManagerServiceInterface;
 PLcom/android/server/backup/Trampoline;->createService()Lcom/android/server/backup/BackupManagerServiceInterface;
 PLcom/android/server/backup/Trampoline;->getCurrentTransport()Ljava/lang/String;
 PLcom/android/server/backup/Trampoline;->getSuppressFile()Ljava/io/File;
@@ -9459,6 +9422,14 @@
 PLcom/android/server/backup/TransportManager;->registerAllTransports()V
 PLcom/android/server/backup/TransportManager;->selectTransport(Ljava/lang/String;)Ljava/lang/String;
 PLcom/android/server/backup/TransportManager;->tryBindTransport(Landroid/content/ComponentName;)V
+PLcom/android/server/backup/internal/BackupHandler;-><init>(Lcom/android/server/backup/RefactoredBackupManagerService;Landroid/os/Looper;)V
+PLcom/android/server/backup/fullbackup/FullBackupEntry;->compareTo(Lcom/android/server/backup/fullbackup/FullBackupEntry;)I
+PLcom/android/server/backup/fullbackup/FullBackupEntry;->compareTo(Ljava/lang/Object;)I
+PLcom/android/server/backup/internal/BackupHandler;->handleMessage(Landroid/os/Message;)V
+PLcom/android/server/backup/internal/ProvisionedObserver;-><init>(Lcom/android/server/backup/RefactoredBackupManagerService;Landroid/os/Handler;)V
+PLcom/android/server/backup/internal/RunBackupReceiver;-><init>(Lcom/android/server/backup/RefactoredBackupManagerService;)V
+PLcom/android/server/backup/internal/RunInitializeReceiver;-><init>(Lcom/android/server/backup/RefactoredBackupManagerService;)V
+PLcom/android/server/backup/utils/AppBackupUtils;->appGetsFullBackup(Landroid/content/pm/PackageInfo;)Z
 PLcom/android/server/camera/CameraServiceProxy$1;-><init>(Lcom/android/server/camera/CameraServiceProxy;)V
 PLcom/android/server/camera/CameraServiceProxy$2;-><init>(Lcom/android/server/camera/CameraServiceProxy;)V
 PLcom/android/server/camera/CameraServiceProxy$2;->notifyCameraState(Ljava/lang/String;IILjava/lang/String;)V
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index b9bea16..caff0ba 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -115,6 +115,7 @@
     private final SparseBooleanArray mDisabledUsers = new SparseBooleanArray();
 
     private final LocalLog mRequestsHistory = new LocalLog(20);
+    private final LocalLog mUiLatencyHistory = new LocalLog(20);
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -306,7 +307,7 @@
         AutofillManagerServiceImpl service = mServicesCache.get(resolvedUserId);
         if (service == null) {
             service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
-                    resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
+                    mUiLatencyHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
             mServicesCache.put(userId, service);
         }
         return service;
@@ -446,7 +447,6 @@
         android.view.autofill.Helper.sDebug = debug;
     }
 
-
     private void setVerboseLocked(boolean verbose) {
         com.android.server.autofill.Helper.sVerbose = verbose;
         android.view.autofill.Helper.sVerbose = verbose;
@@ -532,12 +532,13 @@
         @Override
         public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
                 Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
-                String packageName) {
+                ComponentName componentName) {
 
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
             appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
             autofillId = Preconditions.checkNotNull(autofillId, "autoFillId");
-            packageName = Preconditions.checkNotNull(packageName, "packageName");
+            componentName = Preconditions.checkNotNull(componentName, "componentName");
+            final String packageName = Preconditions.checkNotNull(componentName.getPackageName());
 
             Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId");
 
@@ -550,7 +551,7 @@
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
                 return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
-                        autofillId, bounds, value, hasCallback, flags, packageName);
+                        autofillId, bounds, value, hasCallback, flags, componentName);
             }
         }
 
@@ -602,7 +603,8 @@
         @Override
         public int updateOrRestartSession(IBinder activityToken, IBinder appCallback,
                 AutofillId autoFillId, Rect bounds, AutofillValue value, int userId,
-                boolean hasCallback, int flags, String packageName, int sessionId, int action) {
+                boolean hasCallback, int flags, ComponentName componentName, int sessionId,
+                int action) {
             boolean restart = false;
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
@@ -613,7 +615,7 @@
             }
             if (restart) {
                 return startSession(activityToken, appCallback, autoFillId, bounds, value, userId,
-                        hasCallback, flags, packageName);
+                        hasCallback, flags, componentName);
             }
 
             // Nothing changed...
@@ -736,6 +738,8 @@
                 if (showHistory) {
                     pw.println("Requests history:");
                     mRequestsHistory.reverseDump(fd, pw, args);
+                    pw.println("UI latency history:");
+                    mUiLatencyHistory.reverseDump(fd, pw, args);
                 }
             } finally {
                 setDebugLocked(oldDebug);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 59022e3..d8eaccc 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -42,6 +42,7 @@
 import android.os.Looper;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.service.autofill.AutofillService;
@@ -51,11 +52,13 @@
 import android.service.autofill.FillResponse;
 import android.service.autofill.IAutoFillService;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
@@ -63,6 +66,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
 import com.android.server.autofill.ui.AutoFillUI;
 
@@ -89,6 +94,7 @@
     private final Context mContext;
     private final Object mLock;
     private final AutoFillUI mUi;
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private RemoteCallbackList<IAutoFillManagerClient> mClients;
     private AutofillServiceInfo mInfo;
@@ -96,6 +102,19 @@
     private static final Random sRandom = new Random();
 
     private final LocalLog mRequestsHistory;
+    private final LocalLog mUiLatencyHistory;
+
+    /**
+     * Apps disabled by the service; key is package name, value is when they will be enabled again.
+     */
+    private ArrayMap<String, Long> mDisabledApps;
+
+    /**
+     * Activities disabled by the service; key is component name, value is when they will be enabled
+     * again.
+     */
+    private ArrayMap<ComponentName, Long> mDisabledActivities;
+
     /**
      * Whether service was disabled for user due to {@link UserManager} restrictions.
      */
@@ -137,10 +156,11 @@
     private long mLastPrune = 0;
 
     AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
-            int userId, AutoFillUI ui, boolean disabled) {
+            LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
         mContext = context;
         mLock = lock;
         mRequestsHistory = requestsHistory;
+        mUiLatencyHistory = uiLatencyHistory;
         mUserId = userId;
         mUi = ui;
         updateLocked(disabled);
@@ -209,31 +229,39 @@
                 serviceComponent = ComponentName.unflattenFromString(componentName);
                 serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
                         0, mUserId);
+                if (serviceInfo == null) {
+                    Slog.e(TAG, "Bad AutofillService name: " + componentName);
+                }
             } catch (RuntimeException | RemoteException e) {
-                Slog.e(TAG, "Bad autofill service name " + componentName + ": " + e);
-                return;
+                Slog.e(TAG, "Error getting service info for '" + componentName + "': " + e);
+                serviceInfo = null;
             }
         }
         try {
             if (serviceInfo != null) {
                 mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
                         serviceComponent, mUserId);
+                if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
             } else {
                 mInfo = null;
-            }
-            final boolean isEnabled = isEnabled();
-            if (wasEnabled != isEnabled) {
-                if (!isEnabled) {
-                    final int sessionCount = mSessions.size();
-                    for (int i = sessionCount - 1; i >= 0; i--) {
-                        final Session session = mSessions.valueAt(i);
-                        session.removeSelfLocked();
-                    }
+                if (sDebug) {
+                    Slog.d(TAG, "Reset component for user " + mUserId + " (" + componentName + ")");
                 }
-                sendStateToClients(false);
             }
         } catch (Exception e) {
-            Slog.e(TAG, "Bad AutofillService '" + componentName + "': " + e);
+            Slog.e(TAG, "Bad AutofillServiceInfo for '" + componentName + "': " + e);
+            mInfo = null;
+        }
+        final boolean isEnabled = isEnabled();
+        if (wasEnabled != isEnabled) {
+            if (!isEnabled) {
+                final int sessionCount = mSessions.size();
+                for (int i = sessionCount - 1; i >= 0; i--) {
+                    final Session session = mSessions.valueAt(i);
+                    session.removeSelfLocked();
+                }
+            }
+            sendStateToClients(false);
         }
     }
 
@@ -270,25 +298,46 @@
     int startSessionLocked(@NonNull IBinder activityToken, int uid,
             @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId,
             @NonNull Rect virtualBounds, @Nullable AutofillValue value, boolean hasCallback,
-            int flags, @NonNull String packageName) {
+            int flags, @NonNull ComponentName componentName) {
         if (!isEnabled()) {
             return 0;
         }
+
+        final String shortComponentName = componentName.toShortString();
+
+        if (isAutofillDisabledLocked(componentName)) {
+            if (sDebug) {
+                Slog.d(TAG, "startSession(" + shortComponentName
+                        + "): ignored because disabled by service");
+            }
+
+            final IAutoFillManagerClient client = IAutoFillManagerClient.Stub
+                    .asInterface(appCallbackToken);
+            try {
+                client.setSessionFinished(AutofillManager.STATE_DISABLED_BY_SERVICE);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not notify " + shortComponentName + " that it's disabled: " + e);
+            }
+
+            return NO_SESSION;
+        }
+
         if (sVerbose) Slog.v(TAG, "startSession(): token=" + activityToken + ", flags=" + flags);
 
         // Occasionally clean up abandoned sessions
         pruneAbandonedSessionsLocked();
 
         final Session newSession = createSessionByTokenLocked(activityToken, uid, appCallbackToken,
-                hasCallback, packageName);
+                hasCallback, componentName);
         if (newSession == null) {
             return NO_SESSION;
         }
 
         final String historyItem =
-                "id=" + newSession.id + " uid=" + uid + " s=" + mInfo.getServiceInfo().packageName
-                        + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds + " hc=" +
-                        hasCallback + " f=" + flags;
+                "id=" + newSession.id + " uid=" + uid + " a=" + shortComponentName
+                + " s=" + mInfo.getServiceInfo().packageName
+                + " u=" + mUserId + " i=" + autofillId + " b=" + virtualBounds
+                + " hc=" + hasCallback + " f=" + flags;
         mRequestsHistory.log(historyItem);
 
         newSession.updateLocked(autofillId, virtualBounds, value, ACTION_START_SESSION, flags);
@@ -323,6 +372,8 @@
             return;
         }
 
+        session.logContextCommittedLocked();
+
         final boolean finished = session.showSaveLocked();
         if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
 
@@ -345,17 +396,31 @@
     }
 
     void disableOwnedAutofillServicesLocked(int uid) {
-        if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid != uid) {
+        Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
+        if (mInfo == null) return;
+
+        final ServiceInfo serviceInfo = mInfo.getServiceInfo();
+        if (serviceInfo.applicationInfo.uid != uid) {
+            Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
+                    + " instead of " + serviceInfo.applicationInfo.uid
+                    + " for service " + mInfo);
             return;
         }
+
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final String autoFillService = getComponentNameFromSettings();
-            if (mInfo.getServiceInfo().getComponentName().equals(
-                    ComponentName.unflattenFromString(autoFillService))) {
+            final ComponentName componentName = serviceInfo.getComponentName();
+            if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
+                mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
+                        componentName.getPackageName());
                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
                         Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
                 destroySessionsLocked();
+            } else {
+                Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
+                        + serviceInfo + ") does not match Settings (" + autoFillService + ")");
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -363,7 +428,8 @@
     }
 
     private Session createSessionByTokenLocked(@NonNull IBinder activityToken, int uid,
-            @NonNull IBinder appCallbackToken, boolean hasCallback, @NonNull String packageName) {
+            @NonNull IBinder appCallbackToken, boolean hasCallback,
+            @NonNull ComponentName componentName) {
         // use random ids so that one app cannot know that another app creates sessions
         int sessionId;
         int tries = 0;
@@ -379,7 +445,7 @@
 
         final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
                 sessionId, uid, activityToken, appCallbackToken, hasCallback,
-                mInfo.getServiceInfo().getComponentName(), packageName);
+                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), componentName);
         mSessions.put(newSession.id, newSession);
 
         return newSession;
@@ -534,8 +600,9 @@
     void setAuthenticationSelected(int sessionId, @Nullable Bundle clientState) {
         synchronized (mLock) {
             if (isValidEventLocked("setAuthenticationSelected()", sessionId)) {
-                mEventHistory
-                        .addEvent(new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState));
+                mEventHistory.addEvent(
+                        new Event(Event.TYPE_AUTHENTICATION_SELECTED, null, clientState, null, null,
+                                null, null, null, null));
             }
         }
     }
@@ -549,7 +616,7 @@
             if (isValidEventLocked("logDatasetAuthenticationSelected()", sessionId)) {
                 mEventHistory.addEvent(
                         new Event(Event.TYPE_DATASET_AUTHENTICATION_SELECTED, selectedDataset,
-                                clientState));
+                                clientState, null, null, null, null, null, null));
             }
         }
     }
@@ -560,7 +627,8 @@
     void logSaveShown(int sessionId, @Nullable Bundle clientState) {
         synchronized (mLock) {
             if (isValidEventLocked("logSaveShown()", sessionId)) {
-                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState));
+                mEventHistory.addEvent(new Event(Event.TYPE_SAVE_SHOWN, null, clientState, null,
+                        null, null, null, null, null));
             }
         }
     }
@@ -573,7 +641,28 @@
         synchronized (mLock) {
             if (isValidEventLocked("logDatasetSelected()", sessionId)) {
                 mEventHistory.addEvent(
-                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState));
+                        new Event(Event.TYPE_DATASET_SELECTED, selectedDataset, clientState, null,
+                                null, null, null, null, null));
+            }
+        }
+    }
+
+    /**
+     * Updates the last fill response when an autofill context is committed.
+     */
+    void logContextCommitted(int sessionId, @Nullable Bundle clientState,
+            @Nullable ArrayList<String> selectedDatasets,
+            @Nullable ArraySet<String> ignoredDatasets,
+            @Nullable ArrayList<AutofillId> changedFieldIds,
+            @Nullable ArrayList<String> changedDatasetIds,
+            @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
+            @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds) {
+        synchronized (mLock) {
+            if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
+                mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null,
+                        clientState, selectedDatasets, ignoredDatasets,
+                        changedFieldIds, changedDatasetIds,
+                        manuallyFilledFieldIds, manuallyFilledDatasetIds));
             }
         }
     }
@@ -609,6 +698,46 @@
         pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
         pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
 
+        pw.print(prefix); pw.print("Disabled apps: ");
+
+        if (mDisabledApps == null) {
+            pw.println("N/A");
+        } else {
+            final int size = mDisabledApps.size();
+            pw.println(size);
+            final StringBuilder builder = new StringBuilder();
+            final long now = SystemClock.elapsedRealtime();
+            for (int i = 0; i < size; i++) {
+                final String packageName = mDisabledApps.keyAt(i);
+                final long expiration = mDisabledApps.valueAt(i);
+                 builder.append(prefix).append(prefix)
+                     .append(i).append(". ").append(packageName).append(": ");
+                 TimeUtils.formatDuration((expiration - now), builder);
+                 builder.append('\n');
+             }
+             pw.println(builder);
+        }
+
+        pw.print(prefix); pw.print("Disabled activities: ");
+
+        if (mDisabledActivities == null) {
+            pw.println("N/A");
+        } else {
+            final int size = mDisabledActivities.size();
+            pw.println(size);
+            final StringBuilder builder = new StringBuilder();
+            final long now = SystemClock.elapsedRealtime();
+            for (int i = 0; i < size; i++) {
+                final ComponentName component = mDisabledActivities.keyAt(i);
+                final long expiration = mDisabledActivities.valueAt(i);
+                 builder.append(prefix).append(prefix)
+                     .append(i).append(". ").append(component).append(": ");
+                 TimeUtils.formatDuration((expiration - now), builder);
+                 builder.append('\n');
+             }
+             pw.println(builder);
+        }
+
         final int size = mSessions.size();
         if (size == 0) {
             pw.print(prefix); pw.println("No sessions");
@@ -684,7 +813,23 @@
                     synchronized (mLock) {
                         resetSession = resetClient || isClientSessionDestroyedLocked(client);
                     }
-                    client.setState(isEnabled(), resetSession, resetClient);
+                    int flags = 0;
+                    if (isEnabled()) {
+                        flags |= AutofillManager.SET_STATE_FLAG_ENABLED;
+                    }
+                    if (resetSession) {
+                        flags |= AutofillManager.SET_STATE_FLAG_RESET_SESSION;
+                    }
+                    if (resetClient) {
+                        flags |= AutofillManager.SET_STATE_FLAG_RESET_CLIENT;
+                    }
+                    if (sDebug) {
+                        flags |= AutofillManager.SET_STATE_FLAG_DEBUG;
+                    }
+                    if (sVerbose) {
+                        flags |= AutofillManager.SET_STATE_FLAG_VERBOSE;
+                    }
+                    client.setState(flags);
                 } catch (RemoteException re) {
                     /* ignore */
                 }
@@ -709,6 +854,87 @@
         return mSetupComplete && mInfo != null && !mDisabled;
     }
 
+    /**
+     * Called by {@link Session} when service asked to disable autofill for an app.
+     */
+    void disableAutofillForApp(@NonNull String packageName, long duration) {
+        synchronized (mLock) {
+            if (mDisabledApps == null) {
+                mDisabledApps = new ArrayMap<>(1);
+            }
+            long expiration = SystemClock.elapsedRealtime() + duration;
+            // Protect it against overflow
+            if (expiration < 0) {
+                expiration = Long.MAX_VALUE;
+            }
+            mDisabledApps.put(packageName, expiration);
+            int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
+            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
+                    packageName, getServicePackageName())
+                    .setCounterValue(intDuration));
+        }
+    }
+
+    /**
+     * Called by {@link Session} when service asked to disable autofill an app.
+     */
+    void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) {
+        synchronized (mLock) {
+            if (mDisabledActivities == null) {
+                mDisabledActivities = new ArrayMap<>(1);
+            }
+            long expiration = SystemClock.elapsedRealtime() + duration;
+            // Protect it against overflow
+            if (expiration < 0) {
+                expiration = Long.MAX_VALUE;
+            }
+            mDisabledActivities.put(componentName, expiration);
+            int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
+            mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY,
+                    componentName.getPackageName(), getServicePackageName())
+                    .addTaggedData(MetricsEvent.FIELD_CLASS_NAME, componentName.getClassName())
+                    .setCounterValue(intDuration));
+        }
+    }
+
+    /**
+     * Checks if autofill is disabled by service to the given activity.
+     */
+    private boolean isAutofillDisabledLocked(@NonNull ComponentName componentName) {
+        // Check activities first.
+        long elapsedTime = 0;
+        if (mDisabledActivities != null) {
+            elapsedTime = SystemClock.elapsedRealtime();
+            final Long expiration = mDisabledActivities.get(componentName);
+            if (expiration != null) {
+                if (expiration >= elapsedTime) return true;
+                // Restriction expired - clean it up.
+                if (sVerbose) {
+                    Slog.v(TAG, "Removing " + componentName.toShortString() + " from disabled list");
+                }
+                mDisabledActivities.remove(componentName);
+            }
+        }
+
+        // Then check apps.
+        final String packageName = componentName.getPackageName();
+        if (mDisabledApps == null) return false;
+
+        final Long expiration = mDisabledApps.get(packageName);
+        if (expiration == null) return false;
+
+        if (elapsedTime == 0) {
+            elapsedTime = SystemClock.elapsedRealtime();
+        }
+
+        if (expiration >= elapsedTime) return true;
+
+        // Restriction expired - clean it up.
+        if (sVerbose)  Slog.v(TAG, "Removing " + packageName + " from disabled list");
+        mDisabledApps.remove(packageName);
+        return false;
+    }
+
     @Override
     public String toString() {
         return "AutofillManagerServiceImpl: [userId=" + mUserId
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f81ecf2..3564432 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -16,10 +16,10 @@
 
 package com.android.server.autofill;
 
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
-import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS;
-import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE;
 import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
 import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
 import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -43,6 +43,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.metrics.LogMaker;
 import android.os.Binder;
@@ -50,19 +51,23 @@
 import android.os.IBinder;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
+import android.service.autofill.InternalSanitizer;
 import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.SaveRequest;
 import android.service.autofill.ValueFinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
@@ -70,11 +75,11 @@
 import android.view.autofill.IAutofillWindowPresenter;
 
 import com.android.internal.R;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.autofill.ui.PendingUi;
@@ -123,8 +128,8 @@
     @GuardedBy("mLock")
     @NonNull private IBinder mActivityToken;
 
-    /** Package name of the app that is auto-filled */
-    @NonNull private final String mPackageName;
+    /** Component that's being auto-filled */
+    @NonNull private final ComponentName mComponentName;
 
     @GuardedBy("mLock")
     private final ArrayMap<AutofillId, ViewState> mViewStates = new ArrayMap<>();
@@ -183,18 +188,32 @@
     private ArrayList<String> mSelectedDatasetIds;
 
     /**
+     * When the session started (using elapsed time since boot).
+     */
+    private final long mStartTime;
+
+    /**
+     * When the UI was shown for the first time (using elapsed time since boot).
+     */
+    @GuardedBy("mLock")
+    private long mUiShownTime;
+
+    @GuardedBy("mLock")
+    private final LocalLog mUiLatencyHistory;
+
+    /**
      * Receiver of assist data from the app's {@link Activity}.
      */
-    private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
+    private final IAssistDataReceiver mAssistReceiver = new IAssistDataReceiver.Stub() {
         @Override
-        public void send(int resultCode, Bundle resultData) throws RemoteException {
-            final AssistStructure structure = resultData.getParcelable(KEY_STRUCTURE);
+        public void onHandleAssistData(Bundle resultData) throws RemoteException {
+            final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE);
             if (structure == null) {
                 Slog.e(TAG, "No assist structure - app might have crashed providing it");
                 return;
             }
 
-            final Bundle receiverExtras = resultData.getBundle(KEY_RECEIVER_EXTRAS);
+            final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS);
             if (receiverExtras == null) {
                 Slog.e(TAG, "No receiver extras - app might have crashed providing it");
                 return;
@@ -243,6 +262,11 @@
 
             mRemoteFillService.onFillRequest(request);
         }
+
+        @Override
+        public void onHandleAssistScreenshot(Bitmap screenshot) {
+            // Do nothing
+        }
     };
 
     /**
@@ -403,18 +427,20 @@
     Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
             @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
             @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
-            @NonNull IBinder client, boolean hasCallback,
-            @NonNull ComponentName componentName, @NonNull String packageName) {
+            @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
+            @NonNull ComponentName serviceComponentName, @NonNull ComponentName componentName) {
         id = sessionId;
         this.uid = uid;
+        mStartTime = SystemClock.elapsedRealtime();
         mService = service;
         mLock = lock;
         mUi = ui;
         mHandlerCaller = handlerCaller;
-        mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
+        mRemoteFillService = new RemoteFillService(context, serviceComponentName, userId, this);
         mActivityToken = activityToken;
         mHasCallback = hasCallback;
-        mPackageName = packageName;
+        mUiLatencyHistory = uiLatencyHistory;
+        mComponentName = componentName;
         mClient = IAutoFillManagerClient.Stub.asInterface(client);
 
         writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
@@ -460,21 +486,42 @@
                         + id + " destroyed");
                 return;
             }
-        }
-        if (response == null) {
-            processNullResponseLocked(requestFlags);
-            return;
+            if (response == null) {
+                processNullResponseLocked(requestFlags);
+                return;
+            }
         }
 
         mService.setLastResponse(serviceUid, id, response);
 
-        if ((response.getDatasets() == null || response.getDatasets().isEmpty())
-                        && response.getAuthentication() == null) {
+        int sessionFinishedState = 0;
+        final long disableDuration = response.getDisableDuration();
+        if (disableDuration > 0) {
+            final int flags = response.getFlags();
+            if (sDebug) {
+                final StringBuilder message = new StringBuilder("Service disabled autofill for ")
+                        .append(mComponentName)
+                        .append(": flags=").append(flags)
+                        .append(", duration=");
+                TimeUtils.formatDuration(disableDuration, message);
+                Slog.d(TAG, message.toString());
+            }
+            if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
+                mService.disableAutofillForActivity(mComponentName, disableDuration);
+            } else {
+                mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration);
+            }
+            sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
+        }
+
+        if (((response.getDatasets() == null || response.getDatasets().isEmpty())
+                        && response.getAuthentication() == null)
+                || disableDuration > 0) {
             // Response is "empty" from an UI point of view, need to notify client.
-            notifyUnavailableToClient(false);
+            notifyUnavailableToClient(sessionFinishedState);
         }
         synchronized (mLock) {
-            processResponseLocked(response, requestFlags);
+            processResponseLocked(response, null, requestFlags);
         }
 
         final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
@@ -741,13 +788,21 @@
         }
 
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
-        if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
+        final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
+        if (sDebug) {
+            Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result
+                    + ", clientState=" + newClientState);
+        }
         if (result instanceof FillResponse) {
             writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
-            replaceResponseLocked(authenticatedResponse, (FillResponse) result);
+            replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
         } else if (result instanceof Dataset) {
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
                 writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
+                if (newClientState != null) {
+                    if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
+                    mClientState = newClientState;
+                }
                 final Dataset dataset = (Dataset) result;
                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 autoFill(requestId, datasetIdx, dataset, false);
@@ -811,6 +866,191 @@
     }
 
     /**
+     * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_CONTEXT_COMMITTED}
+     * when necessary.
+     */
+    public void logContextCommittedLocked() {
+        final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
+        if (lastResponse == null) return;
+
+        final int flags = lastResponse.getFlags();
+        if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
+            if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
+            return;
+        }
+
+        ArraySet<String> ignoredDatasets = null;
+        ArrayList<AutofillId> changedFieldIds = null;
+        ArrayList<String> changedDatasetIds = null;
+        ArrayMap<AutofillId, ArraySet<String>> manuallyFilledIds = null;
+
+        boolean hasAtLeastOneDataset = false;
+        final int responseCount = mResponses.size();
+        for (int i = 0; i < responseCount; i++) {
+            final FillResponse response = mResponses.valueAt(i);
+            final List<Dataset> datasets = response.getDatasets();
+            if (datasets == null || datasets.isEmpty()) {
+                if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + i);
+            } else {
+                for (int j = 0; j < datasets.size(); j++) {
+                    final Dataset dataset = datasets.get(j);
+                    final String datasetId = dataset.getId();
+                    if (datasetId == null) {
+                        if (sVerbose) {
+                            Slog.v(TAG, "logContextCommitted() skipping idless dataset " + dataset);
+                        }
+                    } else {
+                        hasAtLeastOneDataset = true;
+                        if (mSelectedDatasetIds == null
+                                || !mSelectedDatasetIds.contains(datasetId)) {
+                            if (sVerbose) Slog.v(TAG, "adding ignored dataset " + datasetId);
+                            if (ignoredDatasets == null) {
+                                ignoredDatasets = new ArraySet<>();
+                            }
+                            ignoredDatasets.add(datasetId);
+                        }
+                    }
+                }
+            }
+        }
+        if (!hasAtLeastOneDataset) {
+            if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no datasets)");
+            return;
+        }
+
+        for (int i = 0; i < mViewStates.size(); i++) {
+            final ViewState viewState = mViewStates.valueAt(i);
+            final int state = viewState.getState();
+
+            // When value changed, we need to log if it was:
+            // - autofilled -> changedDatasetIds
+            // - not autofilled but matches a dataset value -> manuallyFilledIds
+            if ((state & ViewState.STATE_CHANGED) != 0) {
+
+                // Check if autofilled value was changed
+                if ((state & ViewState.STATE_AUTOFILLED) != 0) {
+                    final String datasetId = viewState.getDatasetId();
+                    if (datasetId == null) {
+                        // Sanity check - should never happen.
+                        Slog.w(TAG, "logContextCommitted(): no dataset id on " + viewState);
+                        continue;
+                    }
+
+                    // Must first check if final changed value is not the same as value sent by
+                    // service.
+                    final AutofillValue autofilledValue = viewState.getAutofilledValue();
+                    final AutofillValue currentValue = viewState.getCurrentValue();
+                    if (autofilledValue != null && autofilledValue.equals(currentValue)) {
+                        if (sDebug) {
+                            Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState
+                                    + " because it has same value that was autofilled");
+                        }
+                        continue;
+                    }
+
+                    if (sDebug) {
+                        Slog.d(TAG, "logContextCommitted() found changed state: " + viewState);
+                    }
+                    if (changedFieldIds == null) {
+                        changedFieldIds = new ArrayList<>();
+                        changedDatasetIds = new ArrayList<>();
+                    }
+                    changedFieldIds.add(viewState.id);
+                    changedDatasetIds.add(datasetId);
+                } else {
+                    // Check if value match a dataset.
+                    final AutofillValue currentValue = viewState.getCurrentValue();
+                    if (currentValue == null) {
+                        if (sDebug) {
+                            Slog.d(TAG, "logContextCommitted(): skipping view witout current value "
+                                    + "( " + viewState + ")");
+                        }
+                        continue;
+                    }
+                    for (int j = 0; j < responseCount; j++) {
+                        final FillResponse response = mResponses.valueAt(j);
+                        final List<Dataset> datasets = response.getDatasets();
+                        if (datasets == null || datasets.isEmpty()) {
+                            if (sVerbose) Slog.v(TAG,  "logContextCommitted() no datasets at " + j);
+                        } else {
+                            for (int k = 0; k < datasets.size(); k++) {
+                                final Dataset dataset = datasets.get(k);
+                                final String datasetId = dataset.getId();
+                                if (datasetId == null) {
+                                    if (sVerbose) {
+                                        Slog.v(TAG, "logContextCommitted() skipping idless dataset "
+                                                + dataset);
+                                    }
+                                } else {
+                                    final ArrayList<AutofillValue> values = dataset.getFieldValues();
+                                    for (int l = 0; l < values.size(); l++) {
+                                        final AutofillValue candidate = values.get(l);
+                                        if (currentValue.equals(candidate)) {
+                                            if (sDebug) {
+                                                Slog.d(TAG, "field " + viewState.id
+                                                        + " was manually filled with value set by "
+                                                        + "dataset " + datasetId);
+                                            }
+                                            if (manuallyFilledIds == null) {
+                                                manuallyFilledIds = new ArrayMap<>();
+                                            }
+                                            ArraySet<String> datasetIds =
+                                                    manuallyFilledIds.get(viewState.id);
+                                            if (datasetIds == null) {
+                                                datasetIds = new ArraySet<>(1);
+                                                manuallyFilledIds.put(viewState.id, datasetIds);
+                                            }
+                                            datasetIds.add(datasetId);
+                                        }
+                                    }
+                                    if (mSelectedDatasetIds == null
+                                            || !mSelectedDatasetIds.contains(datasetId)) {
+                                        if (sVerbose) {
+                                            Slog.v(TAG, "adding ignored dataset " + datasetId);
+                                        }
+                                        if (ignoredDatasets == null) {
+                                            ignoredDatasets = new ArraySet<>();
+                                        }
+                                        ignoredDatasets.add(datasetId);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (sVerbose) {
+            Slog.v(TAG, "logContextCommitted(): id=" + id
+                    + ", selectedDatasetids=" + mSelectedDatasetIds
+                    + ", ignoredDatasetIds=" + ignoredDatasets
+                    + ", changedAutofillIds=" + changedFieldIds
+                    + ", changedDatasetIds=" + changedDatasetIds
+                    + ", manuallyFilledIds=" + manuallyFilledIds);
+        }
+
+        ArrayList<AutofillId> manuallyFilledFieldIds = null;
+        ArrayList<ArrayList<String>> manuallyFilledDatasetIds = null;
+
+        // Must "flatten" the map to the parcellable collection primitives
+        if (manuallyFilledIds != null) {
+            final int size = manuallyFilledIds.size();
+            manuallyFilledFieldIds = new ArrayList<>(size);
+            manuallyFilledDatasetIds = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                final AutofillId fieldId = manuallyFilledIds.keyAt(i);
+                final ArraySet<String> datasetIds = manuallyFilledIds.valueAt(i);
+                manuallyFilledFieldIds.add(fieldId);
+                manuallyFilledDatasetIds.add(new ArrayList<>(datasetIds));
+            }
+        }
+        mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets,
+                changedFieldIds, changedDatasetIds,
+                manuallyFilledFieldIds, manuallyFilledDatasetIds);
+    }
+
+    /**
      * Shows the save UI, when session can be saved.
      *
      * @return {@code true} if session is done, or {@code false} if it's pending user action.
@@ -837,6 +1077,8 @@
             return true;
         }
 
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
+
         // Cache used to make sure changed fields do not belong to a dataset.
         final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
         final ArraySet<AutofillId> allIds = new ArraySet<>();
@@ -876,6 +1118,7 @@
                         break;
                     }
                 }
+                value = getSanitizedValue(sanitizers, id, value);
                 currentValues.put(id, value);
                 final AutofillValue filledValue = viewState.getAutofilledValue();
 
@@ -938,6 +1181,7 @@
                     boolean isValid;
                     try {
                         isValid = validator.isValid(valueFinder);
+                        if (sDebug) Slog.d(TAG, validator + " returned " + isValid);
                         log.setType(isValid
                                 ? MetricsEvent.TYPE_SUCCESS
                                 : MetricsEvent.TYPE_DISMISS);
@@ -996,7 +1240,8 @@
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
-                        mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+                        mService.getServicePackageName(), saveInfo, valueFinder,
+                        mComponentName.getPackageName(), this,
                         mPendingSaveUi);
                 if (client != null) {
                     try {
@@ -1018,6 +1263,48 @@
         return true;
     }
 
+    @Nullable
+    private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
+        if (saveInfo == null) return null;
+
+        final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
+        if (sanitizerKeys == null) return null;
+
+        final int size = sanitizerKeys.length ;
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
+        if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
+        final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
+        for (int i = 0; i < size; i++) {
+            final InternalSanitizer sanitizer = sanitizerKeys[i];
+            final AutofillId[] ids = sanitizerValues[i];
+            if (sDebug) {
+                Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
+                        + Arrays.toString(ids));
+            }
+            for (AutofillId id : ids) {
+                sanitizers.put(id, sanitizer);
+            }
+        }
+        return sanitizers;
+    }
+
+    @NonNull
+    private AutofillValue getSanitizedValue(
+            @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
+            @NonNull AutofillId id,
+            @NonNull AutofillValue value) {
+        if (sanitizers == null) return value;
+
+        final InternalSanitizer sanitizer = sanitizers.get(id);
+        if (sanitizer == null) {
+            return value;
+        }
+
+        final AutofillValue sanitized = sanitizer.sanitize(value);
+        if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
+        return sanitized;
+    }
+
     /**
      * Returns whether the session is currently showing the save UI
      */
@@ -1081,6 +1368,9 @@
             return;
         }
 
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
+                createSanitizers(getSaveInfoLocked());
+
         final int numContexts = mContexts.size();
 
         for (int contextNum = 0; contextNum < numContexts; contextNum++) {
@@ -1107,7 +1397,9 @@
                 }
                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
 
-                node.updateAutofillValue(value);
+                final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+
+                node.updateAutofillValue(sanitizedValue);
             }
 
             // Sanitize structure before it's sent to service.
@@ -1329,11 +1621,10 @@
      * Checks whether a view should be ignored.
      */
     private boolean isIgnoredLocked(AutofillId id) {
-        if (mResponses == null || mResponses.size() == 0) {
-            return false;
-        }
         // Always check the latest response only
-        final FillResponse response = mResponses.valueAt(mResponses.size() - 1);
+        final FillResponse response = getLastResponseLocked(null);
+        if (response == null) return false;
+
         return ArrayUtils.contains(response.getIgnoredIds(), id);
     }
 
@@ -1354,7 +1645,32 @@
         }
 
         getUiForShowing().showFillUi(filledId, response, filterText,
-                mService.getServicePackageName(), mPackageName, this);
+                mService.getServicePackageName(), mComponentName.getPackageName(), this);
+
+        synchronized (mLock) {
+            if (mUiShownTime == 0) {
+                // Log first time UI is shown.
+                mUiShownTime = SystemClock.elapsedRealtime();
+                final long duration = mUiShownTime - mStartTime;
+                if (sDebug) {
+                    final StringBuilder msg = new StringBuilder("1st UI for ")
+                            .append(mActivityToken)
+                            .append(" shown in ");
+                    TimeUtils.formatDuration(duration, msg);
+                    Slog.d(TAG, msg.toString());
+                }
+                final StringBuilder historyLog = new StringBuilder("id=").append(id)
+                        .append(" app=").append(mActivityToken)
+                        .append(" svc=").append(mService.getServicePackageName())
+                        .append(" latency=");
+                TimeUtils.formatDuration(duration, historyLog);
+                mUiLatencyHistory.log(historyLog.toString());
+
+                final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
+                        .setCounterValue((int) duration);
+                mMetricsLogger.write(metricsLog);
+            }
+        }
     }
 
     boolean isDestroyed() {
@@ -1369,14 +1685,14 @@
         }
     }
 
-    private void notifyUnavailableToClient(boolean sessionFinished) {
+    private void notifyUnavailableToClient(int sessionFinishedState) {
         synchronized (mLock) {
             if (mCurrentViewId == null) return;
             try {
                 if (mHasCallback) {
-                    mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished);
-                } else if (sessionFinished) {
-                    mClient.setSessionFinished(AutofillManager.STATE_FINISHED);
+                    mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
+                } else if (sessionFinishedState != 0) {
+                    mClient.setSessionFinished(sessionFinishedState);
                 }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
@@ -1385,18 +1701,21 @@
     }
 
     private void updateTrackedIdsLocked() {
-        if (mResponses == null || mResponses.size() == 0) {
-            return;
-        }
-
         // Only track the views of the last response as only those are reported back to the
         // service, see #showSaveLocked
-        final FillResponse response = mResponses.valueAt(getLastResponseIndexLocked());
+        final FillResponse response = getLastResponseLocked(null);
+        if (response == null) return;
 
         ArraySet<AutofillId> trackedViews = null;
         boolean saveOnAllViewsInvisible = false;
+        boolean saveOnFinish = true;
         final SaveInfo saveInfo = response.getSaveInfo();
+        final AutofillId saveTriggerId;
         if (saveInfo != null) {
+            saveTriggerId = saveInfo.getTriggerId();
+            if (saveTriggerId != null) {
+                writeLog(MetricsEvent.AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION);
+            }
             saveOnAllViewsInvisible =
                     (saveInfo.getFlags() & SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) != 0;
 
@@ -1413,6 +1732,12 @@
                     Collections.addAll(trackedViews, saveInfo.getOptionalIds());
                 }
             }
+            if ((saveInfo.getFlags() & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) {
+                saveOnFinish = false;
+            }
+
+        } else {
+            saveTriggerId = null;
         }
 
         // Must also track that are part of datasets, otherwise the FillUI won't be hidden when
@@ -1437,17 +1762,18 @@
 
         try {
             if (sVerbose) {
-                Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds);
+                Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
+                        + " (triggering on " + saveTriggerId + ")");
             }
             mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
-                    toArray(fillableIds));
+                    saveOnFinish, toArray(fillableIds), saveTriggerId);
         } catch (RemoteException e) {
             Slog.w(TAG, "Cannot set tracked ids", e);
         }
     }
 
     private void replaceResponseLocked(@NonNull FillResponse oldResponse,
-            @NonNull FillResponse newResponse) {
+            @NonNull FillResponse newResponse, @Nullable Bundle newClientState) {
         // Disassociate view states with the old response
         setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, true);
         // Move over the id
@@ -1455,7 +1781,7 @@
         // Replace the old response
         mResponses.put(newResponse.getRequestId(), newResponse);
         // Now process the new response
-        processResponseLocked(newResponse, 0);
+        processResponseLocked(newResponse, newClientState, 0);
     }
 
     private void processNullResponseLocked(int flags) {
@@ -1465,11 +1791,12 @@
         }
         mService.resetLastResponse();
         // Nothing to be done, but need to notify client.
-        notifyUnavailableToClient(true);
+        notifyUnavailableToClient(AutofillManager.STATE_FINISHED);
         removeSelf();
     }
 
-    private void processResponseLocked(@NonNull FillResponse newResponse, int flags) {
+    private void processResponseLocked(@NonNull FillResponse newResponse,
+            @Nullable Bundle newClientState, int flags) {
         // Make sure we are hiding the UI which will be shown
         // only if handling the current response requires it.
         mUi.hideAll(this);
@@ -1477,14 +1804,18 @@
         final int requestId = newResponse.getRequestId();
         if (sVerbose) {
             Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId
-                    + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse);
+                    + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse
+                    + ",newClientState=" + newClientState);
         }
 
         if (mResponses == null) {
-            mResponses = new SparseArray<>(4);
+            // Set initial capacity as 2 to handle cases where service always requires auth.
+            // TODO: add a metric for number of responses set by server, so we can use its average
+            // as the initial array capacitiy.
+            mResponses = new SparseArray<>(2);
         }
         mResponses.put(requestId, newResponse);
-        mClientState = newResponse.getClientState();
+        mClientState = newClientState != null ? newClientState : newResponse.getClientState();
 
         setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
         updateTrackedIdsLocked();
@@ -1557,6 +1888,10 @@
             final AutofillId id = ids.get(j);
             final AutofillValue value = values.get(j);
             final ViewState viewState = createOrUpdateViewStateLocked(id, state, value);
+            final String datasetId = dataset.getId();
+            if (datasetId != null) {
+                viewState.setDatasetId(datasetId);
+            }
             if (response != null) {
                 viewState.setResponse(response);
             } else if (clearResponse) {
@@ -1654,15 +1989,23 @@
 
     @Override
     public String toString() {
-        return "Session: [id=" + id + ", pkg=" + mPackageName + "]";
+        return "Session: [id=" + id + ", component=" + mComponentName + "]";
     }
 
     void dumpLocked(String prefix, PrintWriter pw) {
         final String prefix2 = prefix + "  ";
         pw.print(prefix); pw.print("id: "); pw.println(id);
         pw.print(prefix); pw.print("uid: "); pw.println(uid);
-        pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
+        pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName);
         pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
+        pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
+        pw.print(prefix); pw.print("Time to show UI: ");
+        if (mUiShownTime == 0) {
+            pw.println("N/A");
+        } else {
+            TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
+            pw.println();
+        }
         pw.print(prefix); pw.print("mResponses: ");
         if (mResponses == null) {
             pw.println("null");
@@ -1883,7 +2226,7 @@
     }
 
     private LogMaker newLogMaker(int category, String servicePackageName) {
-        return Helper.newLogMaker(category, mPackageName, servicePackageName);
+        return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName);
     }
 
     private void writeLog(int category) {
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index 51659bb..1d8110f 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -78,6 +78,7 @@
     private AutofillValue mAutofilledValue;
     private Rect mVirtualBounds;
     private int mState;
+    private String mDatasetId;
 
     ViewState(Session session, AutofillId id, Listener listener, int state) {
         mSession = session;
@@ -148,6 +149,15 @@
         mState &= ~state;
     }
 
+    @Nullable
+    String getDatasetId() {
+        return mDatasetId;
+    }
+
+    void setDatasetId(String datasetId) {
+        mDatasetId = datasetId;
+    }
+
     // TODO: refactor / rename / document this method (and maybeCallOnFillReady) to make it clear
     // that it can change the value and update the UI; similarly, should replace code that
     // directly sets mAutofillValue to use encapsulation.
@@ -182,13 +192,15 @@
 
     @Override
     public String toString() {
-        return "ViewState: [id=" + id + ", currentValue=" + mCurrentValue
+        return "ViewState: [id=" + id + ", datasetId=" + mDatasetId
+                + ", currentValue=" + mCurrentValue
                 + ", autofilledValue=" + mAutofilledValue
                 + ", bounds=" + mVirtualBounds + ", state=" + getStateAsString() + "]";
     }
 
     void dump(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("id:" ); pw.println(this.id);
+        pw.print(prefix); pw.print("datasetId:" ); pw.println(this.mDatasetId);
         pw.print(prefix); pw.print("state:" ); pw.println(getStateAsString());
         pw.print(prefix); pw.print("response:");
         if (mResponse == null) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 36b95fc..dc36518 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -325,14 +325,14 @@
     }
 
     /**
-     * Hides all UI affordances.
+     * Hides all autofill UIs.
      */
     public void hideAll(@Nullable AutoFillUiCallback callback) {
         mHandler.post(() -> hideAllUiThread(callback));
     }
 
     /**
-     * Destroy all UI affordances.
+     * Destroy all autofill UIs.
      */
     public void destroyAll(@Nullable PendingUi pendingSaveUi,
             @Nullable AutoFillUiCallback callback, boolean notifyClient) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 371e74d..6d3d792 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -55,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
 
 final class FillUi {
     private static final String TAG = "FillUi";
@@ -156,6 +157,11 @@
                 final int index = dataset.getFieldIds().indexOf(focusedViewId);
                 if (index >= 0) {
                     final RemoteViews presentation = dataset.getFieldPresentation(index);
+                    if (presentation == null) {
+                        Slog.w(TAG, "not displaying UI on field " + focusedViewId + " because "
+                                + "service didn't provide a presentation for it on " + dataset);
+                        continue;
+                    }
                     final View view;
                     try {
                         if (sVerbose) Slog.v(TAG, "setting remote view for " + focusedViewId);
@@ -164,15 +170,18 @@
                         Slog.e(TAG, "Error inflating remote views", e);
                         continue;
                     }
-                    final AutofillValue value = dataset.getFieldValues().get(index);
+                    final Pattern filter = dataset.getFilter(index);
                     String valueText = null;
-                    // If the dataset needs auth - don't add its text to allow guessing
-                    // its content based on how filtering behaves.
-                    if (value != null && value.isText() && dataset.getAuthentication() == null) {
-                        valueText = value.getTextValue().toString().toLowerCase();
+                    if (filter == null) {
+                        final AutofillValue value = dataset.getFieldValues().get(index);
+                        // If the dataset needs auth - don't add its text to allow guessing
+                        // its content based on how filtering behaves.
+                        if (value != null && value.isText() && dataset.getAuthentication() == null) {
+                            valueText = value.getTextValue().toString().toLowerCase();
+                        }
                     }
 
-                    items.add(new ViewItem(dataset, valueText, view));
+                    items.add(new ViewItem(dataset, filter, valueText, view));
                 }
             }
 
@@ -331,11 +340,17 @@
         private final String mValue;
         private final Dataset mDataset;
         private final View mView;
+        private final Pattern mFilter;
 
-        ViewItem(Dataset dataset, String value, View view) {
+        ViewItem(Dataset dataset, Pattern filter, String value, View view) {
             mDataset = dataset;
             mValue = value;
             mView = view;
+            mFilter = filter;
+        }
+
+        public Pattern getFilter() {
+            return mFilter;
         }
 
         public View getView() {
@@ -349,12 +364,6 @@
         public String getValue() {
             return mValue;
         }
-
-        @Override
-        public String toString() {
-            // Used for filtering in the adapter
-            return mValue;
-        }
     }
 
     private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
@@ -516,10 +525,16 @@
                     for (int i = 0; i < itemCount; i++) {
                         final ViewItem item = mAllItems.get(i);
                         final String value = item.getValue();
-                        // No value, i.e. null, matches any filter
-                        if ((value == null && item.mDataset.getAuthentication() == null)
-                                || (value != null
-                                        && value.toLowerCase().startsWith(constraintLowerCase))) {
+                        final Pattern filter = item.getFilter();
+                        final boolean matches;
+                        if (filter != null) {
+                            matches = filter.matcher(constraintLowerCase).matches();
+                        } else {
+                            matches = (value == null)
+                                    ? (item.mDataset.getAuthentication() == null)
+                                    : value.toLowerCase().startsWith(constraintLowerCase);
+                        }
+                        if (matches) {
                             filteredItems.add(item);
                         }
                     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/PendingUi.java b/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
index 0851d3b..d1dfb5c 100644
--- a/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/PendingUi.java
@@ -21,7 +21,7 @@
 import android.view.autofill.IAutoFillManagerClient;
 
 /**
- * Helper class used to handle a pending Autofill affordance such as the Save UI.
+ * Helper class used to handle a pending Autofill UI such as the save UI.
  *
  * <p>This class is not thread safe.
  */
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index cd9bdb7..307f74d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -32,11 +32,15 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.service.autofill.BatchUpdates;
 import android.service.autofill.CustomDescription;
+import android.service.autofill.InternalTransformation;
+import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.ValueFinder;
 import android.text.Html;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -57,6 +61,7 @@
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 
 /**
  * Autofill Save Prompt
@@ -185,68 +190,17 @@
 
         setServiceIcon(context, view, serviceIcon);
 
-        ScrollView subtitleContainer = null;
-        final CustomDescription customDescription = info.getCustomDescription();
-        if (customDescription != null) {
-            writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
-
+        final boolean hasCustomDescription =
+                applyCustomDescription(context, view, valueFinder, info);
+        if (hasCustomDescription) {
             mSubTitle = null;
-            if (sDebug) Slog.d(TAG, "Using custom description");
-
-            final RemoteViews presentation = customDescription.getPresentation(valueFinder);
-            if (presentation != null) {
-                final RemoteViews.OnClickHandler handler = new RemoteViews.OnClickHandler() {
-                    @Override
-                    public boolean onClickHandler(View view, PendingIntent pendingIntent,
-                            Intent intent) {
-                        final LogMaker log =
-                                newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
-                        // We need to hide the Save UI before launching the pending intent, and
-                        // restore back it once the activity is finished, and that's achieved by
-                        // adding a custom extra in the activity intent.
-                        final boolean isValid = isValidLink(pendingIntent, intent);
-                        if (!isValid) {
-                            log.setType(MetricsEvent.TYPE_UNKNOWN);
-                            mMetricsLogger.write(log);
-                            return false;
-                        }
-                        if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
-                        final IBinder token = mPendingUi.getToken();
-                        intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
-                        try {
-                            pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
-                                    intent);
-                            mPendingUi.setState(PendingUi.STATE_PENDING);
-                            if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
-                            hide();
-                            log.setType(MetricsEvent.TYPE_OPEN);
-                            mMetricsLogger.write(log);
-                            return true;
-                        } catch (RemoteException e) {
-                            Slog.w(TAG, "error triggering pending intent: " + intent);
-                            log.setType(MetricsEvent.TYPE_FAILURE);
-                            mMetricsLogger.write(log);
-                            return false;
-                        }
-                    }
-                };
-
-                try {
-                    final View customSubtitleView = presentation.apply(context, null, handler);
-                    subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
-                    subtitleContainer.addView(customSubtitleView);
-                    subtitleContainer.setVisibility(View.VISIBLE);
-                } catch (Exception e) {
-                    Slog.e(TAG, "Could not inflate custom description. ", e);
-                }
-            } else {
-                Slog.w(TAG, "could not create remote presentation for custom title");
-            }
+            if (sDebug) Slog.d(TAG, "on constructor: applied custom description");
         } else {
             mSubTitle = info.getDescription();
             if (mSubTitle != null) {
                 writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
-                subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
+                final ScrollView subtitleContainer =
+                        view.findViewById(R.id.autofill_save_custom_subtitle);
                 final TextView subtitleView = new TextView(context);
                 subtitleView.setText(mSubTitle);
                 subtitleContainer.addView(subtitleView,
@@ -271,6 +225,10 @@
         mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel);
         mDialog.setContentView(view);
 
+        // Dialog can be dismissed when touched outside, but the negative listener should not be
+        // notified (hence the null argument).
+        mDialog.setOnDismissListener((d) -> mListener.onCancel(null));
+
         final Window window = mDialog.getWindow();
         window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
         window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -289,6 +247,122 @@
         show();
     }
 
+    private boolean applyCustomDescription(@NonNull Context context, @NonNull View saveUiView,
+            @NonNull ValueFinder valueFinder, @NonNull SaveInfo info) {
+        final CustomDescription customDescription = info.getCustomDescription();
+        if (customDescription == null) {
+            return false;
+        }
+        final int type = info.getType();
+        writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
+
+        final RemoteViews template = customDescription.getPresentation();
+        if (template == null) {
+            Slog.w(TAG, "No remote view on custom description");
+            return false;
+        }
+
+        // First apply the unconditional transformations (if any) to the templates.
+        final ArrayList<Pair<Integer, InternalTransformation>> transformations =
+                customDescription.getTransformations();
+        if (transformations != null) {
+            if (!InternalTransformation.batchApply(valueFinder, template, transformations)) {
+                Slog.w(TAG, "could not apply main transformations on custom description");
+                return false;
+            }
+        }
+
+        final RemoteViews.OnClickHandler handler = new RemoteViews.OnClickHandler() {
+            @Override
+            public boolean onClickHandler(View view, PendingIntent pendingIntent,
+                    Intent intent) {
+                final LogMaker log =
+                        newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
+                // We need to hide the Save UI before launching the pending intent, and
+                // restore back it once the activity is finished, and that's achieved by
+                // adding a custom extra in the activity intent.
+                final boolean isValid = isValidLink(pendingIntent, intent);
+                if (!isValid) {
+                    log.setType(MetricsEvent.TYPE_UNKNOWN);
+                    mMetricsLogger.write(log);
+                    return false;
+                }
+                if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
+                final IBinder token = mPendingUi.getToken();
+                intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
+                try {
+                    mPendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
+                            intent);
+                    mPendingUi.setState(PendingUi.STATE_PENDING);
+                    if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
+                    hide();
+                    log.setType(MetricsEvent.TYPE_OPEN);
+                    mMetricsLogger.write(log);
+                    return true;
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "error triggering pending intent: " + intent);
+                    log.setType(MetricsEvent.TYPE_FAILURE);
+                    mMetricsLogger.write(log);
+                    return false;
+                }
+            }
+        };
+
+        try {
+            // Create the remote view peer.
+            final View customSubtitleView = template.apply(context, null, handler);
+
+            // And apply batch updates (if any).
+            final ArrayList<Pair<InternalValidator, BatchUpdates>> updates =
+                    customDescription.getUpdates();
+            if (updates != null) {
+                final int size = updates.size();
+                if (sDebug) Slog.d(TAG, "custom description has " + size + " batch updates");
+                for (int i = 0; i < size; i++) {
+                    final Pair<InternalValidator, BatchUpdates> pair = updates.get(i);
+                    final InternalValidator condition = pair.first;
+                    if (condition == null || !condition.isValid(valueFinder)) {
+                        if (sDebug) Slog.d(TAG, "Skipping batch update #" + i );
+                        continue;
+                    }
+                    final BatchUpdates batchUpdates = pair.second;
+                    // First apply the updates...
+                    final RemoteViews templateUpdates = batchUpdates.getUpdates();
+                    if (templateUpdates != null) {
+                        if (sDebug) Slog.d(TAG, "Applying template updates for batch update #" + i);
+                        templateUpdates.reapply(context, customSubtitleView);
+                    }
+                    // Then the transformations...
+                    final ArrayList<Pair<Integer, InternalTransformation>> batchTransformations =
+                            batchUpdates.getTransformations();
+                    if (batchTransformations != null) {
+                        if (sDebug) {
+                            Slog.d(TAG, "Applying child transformation for batch update #" + i
+                                    + ": " + batchTransformations);
+                        }
+                        if (!InternalTransformation.batchApply(valueFinder, template,
+                                batchTransformations)) {
+                            Slog.w(TAG, "Could not apply child transformation for batch update "
+                                    + "#" + i + ": " + batchTransformations);
+                            return false;
+                        }
+                        template.reapply(context, customSubtitleView);
+                    }
+                }
+            }
+
+            // Finally, add the custom description to the save UI.
+            final ScrollView subtitleContainer =
+                    saveUiView.findViewById(R.id.autofill_save_custom_subtitle);
+            subtitleContainer.addView(customSubtitleView);
+            subtitleContainer.setVisibility(View.VISIBLE);
+            return true;
+        } catch (Exception e) {
+            Slog.e(TAG, "Error applying custom description. ", e);
+        }
+        return false;
+    }
+
     private void setServiceIcon(Context context, View view, Drawable serviceIcon) {
         final ImageView iconView = view.findViewById(R.id.autofill_save_icon);
         final Resources res = context.getResources();
diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
index cd60182..245241c 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
@@ -123,7 +123,7 @@
     // group the calls of these methods in a block syncrhonized on
     // a reference of this object.
     public synchronized long getKeyValueBackupIntervalMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupIntervalMilliseconds(...) returns "
                     + mKeyValueBackupIntervalMilliseconds);
         }
@@ -131,7 +131,7 @@
     }
 
     public synchronized long getKeyValueBackupFuzzMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupFuzzMilliseconds(...) returns "
                     + mKeyValueBackupFuzzMilliseconds);
         }
@@ -139,7 +139,7 @@
     }
 
     public synchronized boolean getKeyValueBackupRequireCharging() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupRequireCharging(...) returns "
                     + mKeyValueBackupRequireCharging);
         }
@@ -147,7 +147,7 @@
     }
 
     public synchronized int getKeyValueBackupRequiredNetworkType() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupRequiredNetworkType(...) returns "
                     + mKeyValueBackupRequiredNetworkType);
         }
@@ -155,7 +155,7 @@
     }
 
     public synchronized long getFullBackupIntervalMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupIntervalMilliseconds(...) returns "
                     + mFullBackupIntervalMilliseconds);
         }
@@ -163,7 +163,7 @@
     }
 
     public synchronized boolean getFullBackupRequireCharging() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging);
         }
         return mFullBackupRequireCharging;
@@ -171,7 +171,7 @@
     }
 
     public synchronized int getFullBackupRequiredNetworkType() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupRequiredNetworkType(...) returns "
                     + mFullBackupRequiredNetworkType);
         }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index f525797..622b842 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -192,6 +192,10 @@
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
+/**
+ * @Deprecated Use RefactoredBackupManagerService instead. This class is only
+ * kept for fallback and archeology reasons and will be removed soon.
+ */
 public class BackupManagerService implements BackupManagerServiceInterface {
 
     private static final String TAG = "BackupManagerService";
@@ -315,7 +319,6 @@
     boolean mProvisioned;
     boolean mAutoRestore;
     PowerManager.WakeLock mWakelock;
-    HandlerThread mHandlerThread;
     BackupHandler mBackupHandler;
     PendingIntent mRunBackupIntent, mRunInitIntent;
     BroadcastReceiver mRunBackupReceiver, mRunInitReceiver;
@@ -397,45 +400,47 @@
         @Override
         public void onUnlockUser(int userId) {
             if (userId == UserHandle.USER_SYSTEM) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
-                sInstance.initialize(userId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                // Migrate legacy setting
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
-                if (!backupSettingMigrated(userId)) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Backup enable apparently not migrated");
-                    }
-                    final ContentResolver r = sInstance.mContext.getContentResolver();
-                    final int enableState = Settings.Secure.getIntForUser(r,
-                            Settings.Secure.BACKUP_ENABLED, -1, userId);
-                    if (enableState >= 0) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                        }
-                        writeBackupEnableState(enableState != 0, userId);
-                        Settings.Secure.putStringForUser(r,
-                                Settings.Secure.BACKUP_ENABLED, null, userId);
-                    } else {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                        }
-                    }
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
-                try {
-                    sInstance.setBackupEnabled(readBackupEnableState(userId));
-                } catch (RemoteException e) {
-                    // can't happen; it's a local object
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                sInstance.unlockSystemUser();
             }
         }
     }
 
+    // Called through the trampoline from onUnlockUser(), then we buck the work
+    // off to the background thread to keep the unlock time down.
+    public void unlockSystemUser() {
+        // Migrate legacy setting
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+        if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+            if (DEBUG) {
+                Slog.i(TAG, "Backup enable apparently not migrated");
+            }
+            final ContentResolver r = sInstance.mContext.getContentResolver();
+            final int enableState = Settings.Secure.getIntForUser(r,
+                    Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+            if (enableState >= 0) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Migrating enable state " + (enableState != 0));
+                }
+                writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+                Settings.Secure.putStringForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "Backup not yet configured; retaining null enable state");
+                }
+            }
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+        try {
+            sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+        } catch (RemoteException e) {
+            // can't happen; it's a local object
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
     class ProvisionedObserver extends ContentObserver {
         public ProvisionedObserver(Handler handler) {
             super(handler);
@@ -1208,7 +1213,7 @@
 
     // ----- Main service implementation -----
 
-    public BackupManagerService(Context context, Trampoline parent) {
+    public BackupManagerService(Context context, Trampoline parent, HandlerThread backupThread) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -1221,9 +1226,7 @@
         mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
 
         // spin up the backup/restore handler thread
-        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
-        mHandlerThread.start();
-        mBackupHandler = new BackupHandler(mHandlerThread.getLooper());
+        mBackupHandler = new BackupHandler(backupThread.getLooper());
 
         // Set up our bookkeeping
         final ContentResolver resolver = context.getContentResolver();
@@ -1348,7 +1351,7 @@
         if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
 
         mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
-                mTransportBoundListener, mHandlerThread.getLooper());
+                mTransportBoundListener, backupThread.getLooper());
         mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
@@ -1983,7 +1986,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[] { pkgName };
                 }
@@ -1991,7 +1994,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -2001,7 +2004,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -2033,7 +2037,7 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (appGetsFullBackup(app)
@@ -2050,7 +2054,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -2074,8 +2079,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
@@ -2871,7 +2877,7 @@
                     // The backend reports that our dataset has been wiped.  Note this in
                     // the event log; the no-success code below will reset the backup
                     // state as well.
-                    EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+                    EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
                 }
             } catch (Exception e) {
                 Slog.e(TAG, "Error in backup thread", e);
@@ -9775,7 +9781,8 @@
                     }
 
                     Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
-                    EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+                    String transportDirName = transport.transportDirName();
+                    EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
                     long startRealtime = SystemClock.elapsedRealtime();
                     int status = transport.initializeDevice();
 
@@ -9788,7 +9795,7 @@
                         Slog.i(TAG, "Device init successful");
                         int millis = (int) (SystemClock.elapsedRealtime() - startRealtime);
                         EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
-                        resetBackupState(new File(mBaseStateDir, transport.transportDirName()));
+                        resetBackupState(new File(mBaseStateDir, transportDirName));
                         EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
                         synchronized (mQueueLock) {
                             recordInitPendingLocked(false, transportName);
diff --git a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
index 5dfa630..041f9ed 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
@@ -39,6 +39,8 @@
  */
 public interface BackupManagerServiceInterface {
 
+  void unlockSystemUser();
+
   // Utility: build a new random integer token
   int generateRandomIntegerToken();
 
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 82638b4..b81a54d 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -61,7 +61,7 @@
     @Override
     public boolean onStartJob(JobParameters params) {
         mParams = params;
-        Trampoline service = BackupManagerService.getInstance();
+        Trampoline service = RefactoredBackupManagerService.getInstance();
         return service.beginFullBackup(this);
     }
 
@@ -69,7 +69,7 @@
     public boolean onStopJob(JobParameters params) {
         if (mParams != null) {
             mParams = null;
-            Trampoline service = BackupManagerService.getInstance();
+            Trampoline service = RefactoredBackupManagerService.getInstance();
             service.endFullBackup();
         }
         return false;
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 279c828..b38b25a 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -4,8 +4,8 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
-import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
-import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_BACKUP_INTERVAL;
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
@@ -19,6 +19,8 @@
 import android.os.SELinux;
 import android.util.Slog;
 
+import com.android.server.backup.utils.FullBackupUtils;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -78,7 +80,7 @@
         mNewStateName = new File(mStateDir,
                 pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
 
-        mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+        mManifestFile = new File(mDataDir, RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME);
     }
 
     public void backupOnePackage() throws IOException {
@@ -188,7 +190,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                 }
-                BackupManagerService.writeAppManifest(
+                FullBackupUtils.writeAppManifest(
                         mPackage, mPackageManager, mManifestFile, false, false);
                 FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                         mDataDir.getAbsolutePath(),
@@ -251,7 +253,7 @@
             t.start();
 
             // Now pull data from the app and stuff it into the output
-            BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+            FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
 
             if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                 Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
index b62bb5c..a2de8e7 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -13,6 +13,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.backup.restore.PerformAdbRestoreTask;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -41,7 +43,7 @@
     private final File mDataDir;
 
     FileMetadata mInfo;
-    BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+    PerformAdbRestoreTask mRestoreTask;
     ParcelFileDescriptor mInFD;
     IBackupAgent mAgent;
     int mToken;
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index d8411e2..5dfb0bc 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -71,7 +71,7 @@
             if (delay <= 0) {
                 delay = interval + new Random().nextInt((int) fuzz);
             }
-            if (BackupManagerService.DEBUG_SCHEDULING) {
+            if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
             }
             JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
@@ -110,7 +110,7 @@
         }
 
         // Time to run a key/value backup!
-        Trampoline service = BackupManagerService.getInstance();
+        Trampoline service = RefactoredBackupManagerService.getInstance();
         try {
             service.backupNow();
         } catch (RemoteException e) {}
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 8d91e0d..f658f22 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -30,6 +30,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
+import com.android.server.backup.utils.AppBackupUtils;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -140,7 +142,7 @@
         int N = pkgs.size();
         for (int a = N-1; a >= 0; a--) {
             PackageInfo pkg = pkgs.get(a);
-            if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
                 pkgs.remove(a);
             }
         }
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 8b4cc7f..20f2369 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -237,7 +237,6 @@
     private boolean mProvisioned;
     private boolean mAutoRestore;
     private PowerManager.WakeLock mWakelock;
-    private HandlerThread mHandlerThread;
     private BackupHandler mBackupHandler;
     private PendingIntent mRunBackupIntent;
     private PendingIntent mRunInitIntent;
@@ -548,45 +547,47 @@
         @Override
         public void onUnlockUser(int userId) {
             if (userId == UserHandle.USER_SYSTEM) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
-                sInstance.initialize(userId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                // Migrate legacy setting
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
-                if (!backupSettingMigrated(userId)) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Backup enable apparently not migrated");
-                    }
-                    final ContentResolver r = sInstance.mContext.getContentResolver();
-                    final int enableState = Settings.Secure.getIntForUser(r,
-                            Settings.Secure.BACKUP_ENABLED, -1, userId);
-                    if (enableState >= 0) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                        }
-                        writeBackupEnableState(enableState != 0, userId);
-                        Settings.Secure.putStringForUser(r,
-                                Settings.Secure.BACKUP_ENABLED, null, userId);
-                    } else {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                        }
-                    }
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
-                try {
-                    sInstance.setBackupEnabled(readBackupEnableState(userId));
-                } catch (RemoteException e) {
-                    // can't happen; it's a local object
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                sInstance.unlockSystemUser();
             }
         }
     }
 
+    // Called through the trampoline from onUnlockUser(), then we buck the work
+    // off to the background thread to keep the unlock time down.
+    public void unlockSystemUser() {
+        // Migrate legacy setting
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+        if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+            if (DEBUG) {
+                Slog.i(TAG, "Backup enable apparently not migrated");
+            }
+            final ContentResolver r = sInstance.mContext.getContentResolver();
+            final int enableState = Settings.Secure.getIntForUser(r,
+                    Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+            if (enableState >= 0) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Migrating enable state " + (enableState != 0));
+                }
+                writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+                Settings.Secure.putStringForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+            } else {
+                if (DEBUG) {
+                    Slog.i(TAG, "Backup not yet configured; retaining null enable state");
+                }
+            }
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+        try {
+            sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+        } catch (RemoteException e) {
+            // can't happen; it's a local object
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
     // Bookkeeping of in-flight operations for timeout etc. purposes.  The operation
     // token is the index of the entry in the pending-operations list.
     public static final int OP_PENDING = 0;
@@ -653,7 +654,7 @@
 
     // Persistently track the need to do a full init
     private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
-    private ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
+    private final ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
 
     // Round-robin queue for scheduling full backup passes
     private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
@@ -721,7 +722,8 @@
 
     // ----- Main service implementation -----
 
-    public RefactoredBackupManagerService(Context context, Trampoline parent) {
+    public RefactoredBackupManagerService(Context context, Trampoline parent,
+            HandlerThread backupThread) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -734,9 +736,7 @@
         mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
 
         // spin up the backup/restore handler thread
-        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
-        mHandlerThread.start();
-        mBackupHandler = new BackupHandler(this, mHandlerThread.getLooper());
+        mBackupHandler = new BackupHandler(this, backupThread.getLooper());
 
         // Set up our bookkeeping
         final ContentResolver resolver = context.getContentResolver();
@@ -816,7 +816,7 @@
         if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
 
         mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
-                mTransportBoundListener, mHandlerThread.getLooper());
+                mTransportBoundListener, backupThread.getLooper());
         mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
@@ -1188,7 +1188,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[]{pkgName};
                 }
@@ -1196,7 +1196,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -1206,7 +1206,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -1238,7 +1239,7 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (AppBackupUtils.appGetsFullBackup(app)
@@ -1256,7 +1257,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -1280,8 +1282,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index fcd929a..9847edf 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -28,11 +28,15 @@
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
@@ -75,6 +79,8 @@
     final boolean mGlobalDisable;
     volatile BackupManagerServiceInterface mService;
 
+    private HandlerThread mHandlerThread;
+
     public Trampoline(Context context) {
         mContext = context;
         mGlobalDisable = isBackupDisabled();
@@ -98,7 +104,7 @@
 
     protected boolean isRefactoredServiceEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 1) == 0;
+                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 0) == 0;
     }
 
     protected int binderGetCallingUid() {
@@ -111,11 +117,11 @@
     }
 
     protected BackupManagerServiceInterface createRefactoredBackupManagerService() {
-        return new RefactoredBackupManagerService(mContext, this);
+        return new RefactoredBackupManagerService(mContext, this, mHandlerThread);
     }
 
     protected BackupManagerServiceInterface createBackupManagerService() {
-        return new BackupManagerService(mContext, this);
+        return new BackupManagerService(mContext, this, mHandlerThread);
     }
 
     // internal control API
@@ -139,6 +145,24 @@
         }
     }
 
+    void unlockSystemUser() {
+        mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
+        mHandlerThread.start();
+
+        Handler h = new Handler(mHandlerThread.getLooper());
+        h.post(() -> {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+            initialize(UserHandle.USER_SYSTEM);
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+            BackupManagerServiceInterface svc = mService;
+            Slog.i(TAG, "Unlocking system user; mService=" + mService);
+            if (svc != null) {
+                svc.unlockSystemUser();
+            }
+        });
+    }
+
     public void setBackupServiceActive(final int userHandle, boolean makeActive) {
         // Only the DPM should be changing the active state of backup
         final int caller = binderGetCallingUid();
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 9aae384..7a0173f 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -341,9 +341,9 @@
     private class TransportConnection implements ServiceConnection {
 
         // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
-        private IBackupTransport mBinder;
+        private volatile IBackupTransport mBinder;
         private final List<TransportReadyCallback> mListeners = new ArrayList<>();
-        private String mTransportName;
+        private volatile String mTransportName;
 
         private final ComponentName mTransportComponent;
 
@@ -426,25 +426,24 @@
                     + rebindTimeout + "ms");
         }
 
+        // Intentionally not synchronized -- the variable is volatile and changes to its value
+        // are inside synchronized blocks, providing a memory sync barrier; and this method
+        // does not touch any other state protected by that lock.
         private IBackupTransport getBinder() {
-            synchronized (mTransportLock) {
-                return mBinder;
-            }
+            return mBinder;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private String getName() {
-            synchronized (mTransportLock) {
-                return mTransportName;
-            }
+            return mTransportName;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private void bindIfUnbound() {
-            synchronized (mTransportLock) {
-                if (mBinder == null) {
-                    Slog.d(TAG,
-                            "Rebinding to transport " + mTransportComponent.flattenToShortString());
-                    bindToTransport(mTransportComponent, this);
-                }
+            if (mBinder == null) {
+                Slog.d(TAG,
+                        "Rebinding to transport " + mTransportComponent.flattenToShortString());
+                bindToTransport(mTransportComponent, this);
             }
         }
 
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 7a8a920e..c0caa557 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -339,7 +339,7 @@
                 // The backend reports that our dataset has been wiped.  Note this in
                 // the event log; the no-success code below will reset the backup
                 // state as well.
-                EventLog.writeEvent(EventLogTags.BACKUP_RESET, mTransport.transportDirName());
+                EventLog.writeEvent(EventLogTags.BACKUP_RESET, transportName);
             }
         } catch (Exception e) {
             Slog.e(TAG, "Error in backup thread", e);
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
index 939b1ae..690922f 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -79,7 +79,8 @@
                 }
 
                 Slog.i(TAG, "Initializing (wiping) backup transport storage: " + transportName);
-                EventLog.writeEvent(EventLogTags.BACKUP_START, transport.transportDirName());
+                String transportDirName = transport.transportDirName();
+                EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
                 long startRealtime = SystemClock.elapsedRealtime();
                 int status = transport.initializeDevice();
 
@@ -94,7 +95,7 @@
                     EventLog.writeEvent(EventLogTags.BACKUP_INITIALIZE);
                     backupManagerService
                             .resetBackupState(new File(backupManagerService.getBaseStateDir(),
-                                    transport.transportDirName()));
+                                    transportDirName));
                     EventLog.writeEvent(EventLogTags.BACKUP_SUCCESS, 0, millis);
                     synchronized (backupManagerService.getQueueLock()) {
                         backupManagerService.recordInitPendingLocked(false, transportName);
diff --git a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
index a6897d0..1df0bf0 100644
--- a/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/services/backup/java/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -23,6 +23,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.server.backup.RefactoredBackupManagerService;
@@ -38,19 +39,22 @@
     public void onReceive(Context context, Intent intent) {
         if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
             synchronized (backupManagerService.getQueueLock()) {
+                final ArraySet<String> pendingInits = backupManagerService.getPendingInits();
                 if (DEBUG) {
-                    Slog.v(TAG, "Running a device init");
+                    Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
                 }
 
-                String[] pendingInits = (String[]) backupManagerService.getPendingInits().toArray();
-                backupManagerService.clearPendingInits();
-                PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
-                        pendingInits, null);
+                if (pendingInits.size() > 0) {
+                    final String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
+                    PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
+                            transports, null);
 
-                // Acquire the wakelock and pass it to the init thread.  it will
-                // be released once init concludes.
-                backupManagerService.getWakelock().acquire();
-                backupManagerService.getBackupHandler().post(initTask);
+                    // Acquire the wakelock and pass it to the init thread.  it will
+                    // be released once init concludes.
+                    backupManagerService.clearPendingInits();
+                    backupManagerService.getWakelock().acquire();
+                    backupManagerService.getBackupHandler().post(initTask);
+                }
             }
         }
     }
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 599485f..633bb3e 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -6,6 +6,7 @@
 
 LOCAL_AIDL_INCLUDES := \
     frameworks/native/aidl/binder \
+    system/core/storaged/binder \
     system/netd/server/binder \
     system/vold/binder
 
@@ -13,6 +14,7 @@
     $(call all-java-files-under,java) \
     java/com/android/server/EventLogTags.logtags \
     java/com/android/server/am/EventLogTags.logtags \
+    ../../../../system/core/storaged/binder/android/os/IStoraged.aidl \
     ../../../../system/netd/server/binder/android/net/INetd.aidl \
     ../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \
     ../../../../system/vold/binder/android/os/IVold.aidl \
@@ -34,6 +36,8 @@
     time_zone_distro \
     time_zone_distro_installer \
     android.hidl.base-V1.0-java \
+    android.hardware.health-V1.0-java \
+    android.hardware.health-V2.0-java \
     android.hardware.weaver-V1.0-java \
     android.hardware.biometrics.fingerprint-V2.1-java \
     android.hardware.oemlock-V1.0-java \
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 4c08f62..3904fc9 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -63,6 +63,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
@@ -190,7 +191,8 @@
 
     /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
-     * used to determine the earliest we can dispatch the next such alarm.
+     * used to determine the earliest we can dispatch the next such alarm. Times are in the
+     * 'elapsed' timebase.
      */
     final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
 
@@ -355,6 +357,22 @@
             TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw);
             pw.println();
         }
+
+        void dumpProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ConstantsProto.MIN_FUTURITY_DURATION_MS, MIN_FUTURITY);
+            proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL);
+            proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS,
+                    ALLOW_WHILE_IDLE_SHORT_TIME);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS,
+                    ALLOW_WHILE_IDLE_LONG_TIME);
+            proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS,
+                    ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+
+            proto.end(token);
+        }
     }
 
     final Constants mConstants;
@@ -632,6 +650,20 @@
             b.append('}');
             return b.toString();
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+                long nowRTC) {
+            final long token = proto.start(fieldId);
+
+            proto.write(BatchProto.START_REALTIME, start);
+            proto.write(BatchProto.END_REALTIME, end);
+            proto.write(BatchProto.FLAGS, flags);
+            for (Alarm a : alarms) {
+                a.writeToProto(proto, BatchProto.ALARMS, nowElapsed, nowRTC);
+            }
+
+            proto.end(token);
+        }
     }
 
     static class BatchTimeOrder implements Comparator<Batch> {
@@ -1007,6 +1039,29 @@
                     + ", alarmType=" + mAlarmType
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(InFlightProto.UID, mUid);
+            proto.write(InFlightProto.TAG, mTag);
+            proto.write(InFlightProto.WHEN_ELAPSED_MS, mWhenElapsed);
+            proto.write(InFlightProto.ALARM_TYPE, mAlarmType);
+            if (mPendingIntent != null) {
+                mPendingIntent.writeToProto(proto, InFlightProto.PENDING_INTENT);
+            }
+            if (mBroadcastStats != null) {
+                mBroadcastStats.writeToProto(proto, InFlightProto.BROADCAST_STATS);
+            }
+            if (mFilterStats != null) {
+                mFilterStats.writeToProto(proto, InFlightProto.FILTER_STATS);
+            }
+            if (mWorkSource != null) {
+                mWorkSource.writeToProto(proto, InFlightProto.WORK_SOURCE);
+            }
+
+            proto.end(token);
+        }
     }
 
     static final class FilterStats {
@@ -1037,6 +1092,20 @@
                     + ", nesting=" + nesting
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(FilterStatsProto.TAG, mTag);
+            proto.write(FilterStatsProto.LAST_FLIGHT_TIME_REALTIME, lastTime);
+            proto.write(FilterStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+            proto.write(FilterStatsProto.COUNT, count);
+            proto.write(FilterStatsProto.WAKEUP_COUNT, numWakeup);
+            proto.write(FilterStatsProto.START_TIME_REALTIME, startTime);
+            proto.write(FilterStatsProto.NESTING, nesting);
+
+            proto.end(token);
+        }
     }
 
     static final class BroadcastStats {
@@ -1067,6 +1136,20 @@
                     + ", nesting=" + nesting
                     + "}";
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+
+            proto.write(BroadcastStatsProto.UID, mUid);
+            proto.write(BroadcastStatsProto.PACKAGE_NAME, mPackageName);
+            proto.write(BroadcastStatsProto.TOTAL_FLIGHT_DURATION_MS, aggregateTime);
+            proto.write(BroadcastStatsProto.COUNT, count);
+            proto.write(BroadcastStatsProto.WAKEUP_COUNT, numWakeup);
+            proto.write(BroadcastStatsProto.START_TIME_REALTIME, startTime);
+            proto.write(BroadcastStatsProto.NESTING, nesting);
+
+            proto.end(token);
+        }
     }
 
     final SparseArray<ArrayMap<String, BroadcastStats>> mBroadcastStats
@@ -1128,14 +1211,14 @@
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         mDateChangeSender = PendingIntent.getBroadcastAsUser(getContext(), 0, intent,
                 Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
-        
+
         // now that we have initied the driver schedule the alarm
         mClockReceiver = new ClockReceiver();
         mClockReceiver.scheduleTimeTickEvent();
         mClockReceiver.scheduleDateChangedEvent();
         mInteractiveStateReceiver = new InteractiveStateReceiver();
         mUninstallReceiver = new UninstallReceiver();
-        
+
         if (mNativeData != 0) {
             AlarmThread waitThread = new AlarmThread();
             waitThread.start();
@@ -1568,7 +1651,12 @@
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
-            dumpImpl(pw);
+
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpImpl(pw);
+            }
         }
     };
 
@@ -1681,7 +1769,7 @@
                 pw.print("      Idling until: ");
                 if (mPendingIdleUntil != null) {
                     pw.println(mPendingIdleUntil);
-                    mPendingIdleUntil.dump(pw, "        ", nowRTC, nowELAPSED, sdf);
+                    mPendingIdleUntil.dump(pw, "        ", nowELAPSED, nowRTC, sdf);
                 } else {
                     pw.println("null");
                 }
@@ -1691,7 +1779,7 @@
             if (mNextWakeFromIdle != null) {
                 pw.println();
                 pw.print("  Next wake from idle: "); pw.println(mNextWakeFromIdle);
-                mNextWakeFromIdle.dump(pw, "    ", nowRTC, nowELAPSED, sdf);
+                mNextWakeFromIdle.dump(pw, "    ", nowELAPSED, nowRTC, sdf);
             }
 
             pw.println();
@@ -1760,6 +1848,7 @@
                 }
             };
             int len = 0;
+            // Get the top 10 FilterStats, ordered by aggregateTime.
             for (int iu=0; iu<mBroadcastStats.size(); iu++) {
                 ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
                 for (int ip=0; ip<uidStats.size(); ip++) {
@@ -1886,6 +1975,244 @@
         }
     }
 
+    void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        synchronized (mLock) {
+            final long nowRTC = System.currentTimeMillis();
+            final long nowElapsed = SystemClock.elapsedRealtime();
+            proto.write(AlarmManagerServiceProto.CURRENT_TIME, nowRTC);
+            proto.write(AlarmManagerServiceProto.ELAPSED_REALTIME, nowElapsed);
+            proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_CLOCK_TIME,
+                    mLastTimeChangeClockTime);
+            proto.write(AlarmManagerServiceProto.LAST_TIME_CHANGE_REALTIME,
+                    mLastTimeChangeRealtime);
+
+            mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
+
+            final int foregroundUidsSize = mForegroundUids.size();
+            for (int i = 0; i < foregroundUidsSize; i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
+                }
+            }
+            for (String pkg : mForcedAppStandbyPackages) {
+                proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
+            }
+
+            proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
+            if (!mInteractive) {
+                // Durations
+                proto.write(AlarmManagerServiceProto.TIME_SINCE_NON_INTERACTIVE_MS,
+                        nowElapsed - mNonInteractiveStartTime);
+                proto.write(AlarmManagerServiceProto.MAX_WAKEUP_DELAY_MS,
+                        currentNonWakeupFuzzLocked(nowElapsed));
+                proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_DISPATCH_MS,
+                        nowElapsed - mLastAlarmDeliveryTime);
+                proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_DELIVERY_MS,
+                        nowElapsed - mNextNonWakeupDeliveryTime);
+            }
+
+            proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_NON_WAKEUP_ALARM_MS,
+                    mNextNonWakeup - nowElapsed);
+            proto.write(AlarmManagerServiceProto.TIME_UNTIL_NEXT_WAKEUP_MS,
+                    mNextWakeup - nowElapsed);
+            proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_MS,
+                    nowElapsed - mLastWakeup);
+            proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
+                    nowElapsed - mLastWakeupSet);
+            proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
+            for (int i : mDeviceIdleUserWhitelist) {
+                proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
+            }
+
+            final TreeSet<Integer> users = new TreeSet<>();
+            final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
+            for (int i = 0; i < nextAlarmClockForUserSize; i++) {
+                users.add(mNextAlarmClockForUser.keyAt(i));
+            }
+            final int pendingSendNextAlarmClockChangedForUserSize =
+                    mPendingSendNextAlarmClockChangedForUser.size();
+            for (int i = 0; i < pendingSendNextAlarmClockChangedForUserSize; i++) {
+                users.add(mPendingSendNextAlarmClockChangedForUser.keyAt(i));
+            }
+            for (int user : users) {
+                final AlarmManager.AlarmClockInfo next = mNextAlarmClockForUser.get(user);
+                final long time = next != null ? next.getTriggerTime() : 0;
+                final boolean pendingSend = mPendingSendNextAlarmClockChangedForUser.get(user);
+                final long aToken = proto.start(AlarmManagerServiceProto.NEXT_ALARM_CLOCK_METADATA);
+                proto.write(AlarmClockMetadataProto.USER, user);
+                proto.write(AlarmClockMetadataProto.IS_PENDING_SEND, pendingSend);
+                proto.write(AlarmClockMetadataProto.TRIGGER_TIME_MS, time);
+                proto.end(aToken);
+            }
+            for (Batch b : mAlarmBatches) {
+                b.writeToProto(proto, AlarmManagerServiceProto.PENDING_ALARM_BATCHES,
+                        nowElapsed, nowRTC);
+            }
+            for (int i = 0; i < mPendingBackgroundAlarms.size(); i++) {
+                final ArrayList<Alarm> blockedAlarms = mPendingBackgroundAlarms.valueAt(i);
+                if (blockedAlarms != null) {
+                    for (Alarm a : blockedAlarms) {
+                        a.writeToProto(proto,
+                                AlarmManagerServiceProto.PENDING_USER_BLOCKED_BACKGROUND_ALARMS,
+                                nowElapsed, nowRTC);
+                    }
+                }
+            }
+            if (mPendingIdleUntil != null) {
+                mPendingIdleUntil.writeToProto(
+                        proto, AlarmManagerServiceProto.PENDING_IDLE_UNTIL, nowElapsed, nowRTC);
+            }
+            for (Alarm a : mPendingWhileIdleAlarms) {
+                a.writeToProto(proto, AlarmManagerServiceProto.PENDING_WHILE_IDLE_ALARMS,
+                        nowElapsed, nowRTC);
+            }
+            if (mNextWakeFromIdle != null) {
+                mNextWakeFromIdle.writeToProto(proto, AlarmManagerServiceProto.NEXT_WAKE_FROM_IDLE,
+                        nowElapsed, nowRTC);
+            }
+
+            for (Alarm a : mPendingNonWakeupAlarms) {
+                a.writeToProto(proto, AlarmManagerServiceProto.PAST_DUE_NON_WAKEUP_ALARMS,
+                        nowElapsed, nowRTC);
+            }
+
+            proto.write(AlarmManagerServiceProto.DELAYED_ALARM_COUNT, mNumDelayedAlarms);
+            proto.write(AlarmManagerServiceProto.TOTAL_DELAY_TIME_MS, mTotalDelayTime);
+            proto.write(AlarmManagerServiceProto.MAX_DELAY_DURATION_MS, mMaxDelayTime);
+            proto.write(AlarmManagerServiceProto.MAX_NON_INTERACTIVE_DURATION_MS,
+                    mNonInteractiveTime);
+
+            proto.write(AlarmManagerServiceProto.BROADCAST_REF_COUNT, mBroadcastRefCount);
+            proto.write(AlarmManagerServiceProto.PENDING_INTENT_SEND_COUNT, mSendCount);
+            proto.write(AlarmManagerServiceProto.PENDING_INTENT_FINISH_COUNT, mSendFinishCount);
+            proto.write(AlarmManagerServiceProto.LISTENER_SEND_COUNT, mListenerCount);
+            proto.write(AlarmManagerServiceProto.LISTENER_FINISH_COUNT, mListenerFinishCount);
+
+            for (InFlight f : mInFlight) {
+                f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES);
+            }
+
+            proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS,
+                    mAllowWhileIdleMinTime);
+            for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
+                final long token = proto.start(
+                        AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
+                proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.UID,
+                        mLastAllowWhileIdleDispatch.keyAt(i));
+                proto.write(AlarmManagerServiceProto.LastAllowWhileIdleDispatch.TIME_MS,
+                        mLastAllowWhileIdleDispatch.valueAt(i));
+                proto.end(token);
+            }
+
+            mLog.writeToProto(proto, AlarmManagerServiceProto.RECENT_PROBLEMS);
+
+            final FilterStats[] topFilters = new FilterStats[10];
+            final Comparator<FilterStats> comparator = new Comparator<FilterStats>() {
+                @Override
+                public int compare(FilterStats lhs, FilterStats rhs) {
+                    if (lhs.aggregateTime < rhs.aggregateTime) {
+                        return 1;
+                    } else if (lhs.aggregateTime > rhs.aggregateTime) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+            int len = 0;
+            // Get the top 10 FilterStats, ordered by aggregateTime.
+            for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+                ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+                for (int ip = 0; ip < uidStats.size(); ++ip) {
+                    BroadcastStats bs = uidStats.valueAt(ip);
+                    for (int is = 0; is < bs.filterStats.size(); ++is) {
+                        FilterStats fs = bs.filterStats.valueAt(is);
+                        int pos = len > 0
+                                ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
+                        if (pos < 0) {
+                            pos = -pos - 1;
+                        }
+                        if (pos < topFilters.length) {
+                            int copylen = topFilters.length - pos - 1;
+                            if (copylen > 0) {
+                                System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+                            }
+                            topFilters[pos] = fs;
+                            if (len < topFilters.length) {
+                                len++;
+                            }
+                        }
+                    }
+                }
+            }
+            for (int i = 0; i < len; ++i) {
+                final long token = proto.start(AlarmManagerServiceProto.TOP_ALARMS);
+                FilterStats fs = topFilters[i];
+
+                proto.write(AlarmManagerServiceProto.TopAlarm.UID, fs.mBroadcastStats.mUid);
+                proto.write(AlarmManagerServiceProto.TopAlarm.PACKAGE_NAME,
+                        fs.mBroadcastStats.mPackageName);
+                fs.writeToProto(proto, AlarmManagerServiceProto.TopAlarm.FILTER);
+
+                proto.end(token);
+            }
+
+            final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>();
+            for (int iu = 0; iu < mBroadcastStats.size(); ++iu) {
+                ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.valueAt(iu);
+                for (int ip = 0; ip < uidStats.size(); ++ip) {
+                    final long token = proto.start(AlarmManagerServiceProto.ALARM_STATS);
+
+                    BroadcastStats bs = uidStats.valueAt(ip);
+                    bs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.BROADCAST);
+
+                    // uidStats is an ArrayMap, which we can't sort.
+                    tmpFilters.clear();
+                    for (int is = 0; is < bs.filterStats.size(); ++is) {
+                        tmpFilters.add(bs.filterStats.valueAt(is));
+                    }
+                    Collections.sort(tmpFilters, comparator);
+                    for (FilterStats fs : tmpFilters) {
+                        fs.writeToProto(proto, AlarmManagerServiceProto.AlarmStat.FILTERS);
+                    }
+
+                    proto.end(token);
+                }
+            }
+
+            if (RECORD_DEVICE_IDLE_ALARMS) {
+                for (int i = 0; i < mAllowWhileIdleDispatches.size(); i++) {
+                    IdleDispatchEntry ent = mAllowWhileIdleDispatches.get(i);
+                    final long token = proto.start(
+                            AlarmManagerServiceProto.ALLOW_WHILE_IDLE_DISPATCHES);
+
+                    proto.write(IdleDispatchEntryProto.UID, ent.uid);
+                    proto.write(IdleDispatchEntryProto.PKG, ent.pkg);
+                    proto.write(IdleDispatchEntryProto.TAG, ent.tag);
+                    proto.write(IdleDispatchEntryProto.OP, ent.op);
+                    proto.write(IdleDispatchEntryProto.ENTRY_CREATION_REALTIME,
+                            ent.elapsedRealtime);
+                    proto.write(IdleDispatchEntryProto.ARG_REALTIME, ent.argRealtime);
+
+                    proto.end(token);
+                }
+            }
+
+            if (WAKEUP_STATS) {
+                for (WakeupEvent event : mRecentWakeups) {
+                    final long token = proto.start(AlarmManagerServiceProto.RECENT_WAKEUP_HISTORY);
+                    proto.write(WakeupEventProto.UID, event.uid);
+                    proto.write(WakeupEventProto.ACTION, event.action);
+                    proto.write(WakeupEventProto.WHEN, event.when);
+                    proto.end(token);
+                }
+            }
+        }
+
+        proto.flush();
+    }
+
     private void logBatchesLocked(SimpleDateFormat sdf) {
         ByteArrayOutputStream bs = new ByteArrayOutputStream(2048);
         PrintWriter pw = new PrintWriter(bs);
@@ -2328,24 +2655,24 @@
                 alarmSeconds = when / 1000;
                 alarmNanoseconds = (when % 1000) * 1000 * 1000;
             }
-            
+
             set(mNativeData, type, alarmSeconds, alarmNanoseconds);
         } else {
             Message msg = Message.obtain();
             msg.what = ALARM_EVENT;
-            
+
             mHandler.removeMessages(ALARM_EVENT);
             mHandler.sendMessageAtTime(msg, when);
         }
     }
 
     private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
-            String prefix, String label, long nowRTC, long nowELAPSED, SimpleDateFormat sdf) {
+            String prefix, String label, long nowELAPSED, long nowRTC, SimpleDateFormat sdf) {
         for (int i=list.size()-1; i>=0; i--) {
             Alarm a = list.get(i);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ", nowRTC, nowELAPSED, sdf);
+            a.dump(pw, prefix + "  ", nowELAPSED, nowRTC, sdf);
         }
     }
 
@@ -2355,8 +2682,6 @@
         case RTC_WAKEUP : return "RTC_WAKEUP";
         case ELAPSED_REALTIME : return "ELAPSED";
         case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP";
-        default:
-            break;
         }
         return "--unknown--";
     }
@@ -2368,7 +2693,7 @@
             final String label = labelForType(a.type);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ", nowRTC, nowELAPSED, sdf);
+            a.dump(pw, prefix + "  ", nowELAPSED, nowRTC, sdf);
         }
     }
 
@@ -2533,7 +2858,7 @@
             return 0;
         }
     }
-    
+
     private static class Alarm {
         public final int type;
         public final long origWhen;
@@ -2627,7 +2952,7 @@
             return sb.toString();
         }
 
-        public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
+        public void dump(PrintWriter pw, String prefix, long nowELAPSED, long nowRTC,
                 SimpleDateFormat sdf) {
             final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
             pw.print(prefix); pw.print("tag="); pw.println(statsTag);
@@ -2656,6 +2981,30 @@
                 pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
             }
         }
+
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long nowElapsed,
+                long nowRTC) {
+            final long token = proto.start(fieldId);
+
+            proto.write(AlarmProto.TAG, statsTag);
+            proto.write(AlarmProto.TYPE, type);
+            proto.write(AlarmProto.WHEN_ELAPSED_MS, whenElapsed - nowElapsed);
+            proto.write(AlarmProto.WINDOW_LENGTH_MS, windowLength);
+            proto.write(AlarmProto.REPEAT_INTERVAL_MS, repeatInterval);
+            proto.write(AlarmProto.COUNT, count);
+            proto.write(AlarmProto.FLAGS, flags);
+            if (alarmClock != null) {
+                alarmClock.writeToProto(proto, AlarmProto.ALARM_CLOCK);
+            }
+            if (operation != null) {
+                operation.writeToProto(proto, AlarmProto.OPERATION);
+            }
+            if (listener != null) {
+                proto.write(AlarmProto.LISTENER, listener.asBinder().toString());
+            }
+
+            proto.end(token);
+        }
     }
 
     void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) {
@@ -2752,7 +3101,7 @@
         {
             super("AlarmManager");
         }
-        
+
         public void run()
         {
             ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
@@ -2918,10 +3267,10 @@
         public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2;
         public static final int LISTENER_TIMEOUT = 3;
         public static final int REPORT_ALARMS_ACTIVE = 4;
-        
+
         public AlarmHandler() {
         }
-        
+
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -2969,7 +3318,7 @@
             }
         }
     }
-    
+
     class ClockReceiver extends BroadcastReceiver {
         public ClockReceiver() {
             IntentFilter filter = new IntentFilter();
@@ -2977,7 +3326,7 @@
             filter.addAction(Intent.ACTION_DATE_CHANGED);
             getContext().registerReceiver(this, filter);
         }
-        
+
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
@@ -2996,7 +3345,7 @@
                 scheduleDateChangedEvent();
             }
         }
-        
+
         public void scheduleTimeTickEvent() {
             final long currentTime = System.currentTimeMillis();
             final long nextTime = 60000 * ((currentTime / 60000) + 1);
@@ -3026,7 +3375,7 @@
                     Process.myUid(), "android");
         }
     }
-    
+
     class InteractiveStateReceiver extends BroadcastReceiver {
         public InteractiveStateReceiver() {
             IntentFilter filter = new IntentFilter();
@@ -3059,7 +3408,7 @@
             sdFilter.addAction(Intent.ACTION_UID_REMOVED);
             getContext().registerReceiver(this, sdFilter);
         }
-        
+
         @Override
         public void onReceive(Context context, Intent intent) {
             synchronized (mLock) {
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index 50b8df2..4ffa5f1 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -491,7 +491,8 @@
             return Collections.emptyList();
         }
         synchronized (this) {
-            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false);
+            Ops pkgOps = getOpsRawLocked(uid, resolvedPackageName, false /* edit */,
+                    false /* uidMismatchExpected */);
             if (pkgOps == null) {
                 return null;
             }
@@ -530,7 +531,8 @@
 
     private void pruneOp(Op op, int uid, String packageName) {
         if (op.time == 0 && op.rejectTime == 0) {
-            Ops ops = getOpsRawLocked(uid, packageName, false);
+            Ops ops = getOpsRawLocked(uid, packageName, false /* edit */,
+                    false /* uidMismatchExpected */);
             if (ops != null) {
                 ops.remove(op.op);
                 if (ops.size() <= 0) {
@@ -1046,7 +1048,9 @@
     public int checkPackage(int uid, String packageName) {
         Preconditions.checkNotNull(packageName);
         synchronized (this) {
-            if (getOpsRawLocked(uid, packageName, true) != null) {
+            Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+                    true /* uidMismatchExpected */);
+            if (ops != null) {
                 return AppOpsManager.MODE_ALLOWED;
             } else {
                 return AppOpsManager.MODE_ERRORED;
@@ -1090,7 +1094,8 @@
     private int noteOperationUnchecked(int code, int uid, String packageName,
             int proxyUid, String proxyPackageName) {
         synchronized (this) {
-            Ops ops = getOpsRawLocked(uid, packageName, true);
+            Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+                    false /* uidMismatchExpected */);
             if (ops == null) {
                 if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName);
@@ -1148,7 +1153,8 @@
         }
         ClientState client = (ClientState)token;
         synchronized (this) {
-            Ops ops = getOpsRawLocked(uid, resolvedPackageName, true);
+            Ops ops = getOpsRawLocked(uid, resolvedPackageName, true /* edit */,
+                    false /* uidMismatchExpected */);
             if (ops == null) {
                 if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
                         + " package " + resolvedPackageName);
@@ -1274,7 +1280,8 @@
         return uidState;
     }
 
-    private Ops getOpsRawLocked(int uid, String packageName, boolean edit) {
+    private Ops getOpsRawLocked(int uid, String packageName, boolean edit,
+            boolean uidMismatchExpected) {
         UidState uidState = getUidStateLocked(uid, edit);
         if (uidState == null) {
             return null;
@@ -1326,10 +1333,12 @@
                     if (pkgUid != uid) {
                         // Oops!  The package name is not valid for the uid they are calling
                         // under.  Abort.
-                        RuntimeException ex = new RuntimeException("here");
-                        ex.fillInStackTrace();
-                        Slog.w(TAG, "Bad call: specified package " + packageName
-                                + " under uid " + uid + " but it is really " + pkgUid, ex);
+                        if (!uidMismatchExpected) {
+                            RuntimeException ex = new RuntimeException("here");
+                            ex.fillInStackTrace();
+                            Slog.w(TAG, "Bad call: specified package " + packageName
+                                    + " under uid " + uid + " but it is really " + pkgUid, ex);
+                        }
                         return null;
                     }
                 } finally {
@@ -1359,7 +1368,8 @@
     }
 
     private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
-        Ops ops = getOpsRawLocked(uid, packageName, edit);
+        Ops ops = getOpsRawLocked(uid, packageName, edit,
+                false /* uidMismatchExpected */);
         if (ops == null) {
             return null;
         }
@@ -1393,7 +1403,8 @@
                 if (AppOpsManager.opAllowSystemBypassRestriction(code)) {
                     // If we are the system, bypass user restrictions for certain codes
                     synchronized (this) {
-                        Ops ops = getOpsRawLocked(uid, packageName, true);
+                        Ops ops = getOpsRawLocked(uid, packageName, true /* edit */,
+                                false /* uidMismatchExpected */);
                         if ((ops != null) && ops.isPrivileged) {
                             return false;
                         }
@@ -1713,7 +1724,8 @@
                         out.startTag(null, "uid");
                         out.attribute(null, "n", Integer.toString(pkg.getUid()));
                         synchronized (this) {
-                            Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(), false);
+                            Ops ops = getOpsRawLocked(pkg.getUid(), pkg.getPackageName(),
+                                    false /* edit */, false /* uidMismatchExpected */);
                             // Should always be present as the list of PackageOps is generated
                             // from Ops.
                             if (ops != null) {
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 83bd9eb..ad30897 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -20,9 +20,11 @@
 import android.database.ContentObserver;
 import android.os.BatteryStats;
 
+import android.os.PowerManager;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.DumpUtils;
 import com.android.server.am.BatteryStatsService;
@@ -34,9 +36,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V2_0.IHealthInfoCallback;
+import android.hardware.health.V2_0.IHealth;
+import android.hardware.health.V2_0.Result;
 import android.os.BatteryManager;
+import android.os.BatteryManagerProto;
 import android.os.BatteryManagerInternal;
-import android.os.BatteryProperties;
+import android.os.BatteryProperty;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -52,6 +61,7 @@
 import android.provider.Settings;
 import android.service.battery.BatteryServiceDumpProto;
 import android.util.EventLog;
+import android.util.MutableInt;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -61,6 +71,10 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * <p>BatteryService monitors the charging status, and charge level of the device
@@ -99,6 +113,8 @@
 
     private static final int BATTERY_SCALE = 100;    // battery capacity is a percentage
 
+    private static final long HEALTH_HAL_WAIT_MS = 1000;
+
     // Used locally for determining when to make a last ditch effort to log
     // discharge stats before the device dies.
     private int mCriticalBatteryLevel;
@@ -117,8 +133,8 @@
 
     private final Object mLock = new Object();
 
-    private BatteryProperties mBatteryProps;
-    private final BatteryProperties mLastBatteryProps = new BatteryProperties();
+    private HealthInfo mHealthInfo;
+    private final HealthInfo mLastHealthInfo = new HealthInfo();
     private boolean mBatteryLevelCritical;
     private int mLastBatteryStatus;
     private int mLastBatteryHealth;
@@ -156,6 +172,10 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
+    private HealthServiceWrapper mHealthServiceWrapper;
+    private HealthHalCallback mHealthHalCallback;
+    private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+
     public BatteryService(Context context) {
         super(context);
 
@@ -194,17 +214,12 @@
 
     @Override
     public void onStart() {
-        IBinder b = ServiceManager.getService("batteryproperties");
-        final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
-                IBatteryPropertiesRegistrar.Stub.asInterface(b);
-        try {
-            batteryPropertiesRegistrar.registerListener(new BatteryListener());
-        } catch (RemoteException e) {
-            // Should never happen.
-        }
+        registerHealthCallback();
 
         mBinderService = new BinderService();
         publishBinderService("battery", mBinderService);
+        mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
+        publishBinderService("batteryproperties", mBatteryPropertiesRegistrar);
         publishLocalService(BatteryManagerInternal.class, new LocalService());
     }
 
@@ -230,6 +245,43 @@
         }
     }
 
+    private void registerHealthCallback() {
+        mHealthServiceWrapper = new HealthServiceWrapper();
+        mHealthHalCallback = new HealthHalCallback();
+        // IHealth is lazily retrieved.
+        try {
+            mHealthServiceWrapper.init(mHealthHalCallback,
+                    new HealthServiceWrapper.IServiceManagerSupplier() {},
+                    new HealthServiceWrapper.IHealthSupplier() {});
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "health: cannot register callback. (RemoteException)");
+            throw ex.rethrowFromSystemServer();
+        } catch (NoSuchElementException ex) {
+            Slog.e(TAG, "health: cannot register callback. (no supported health HAL service)");
+            throw ex;
+        }
+
+        // init register for new service notifications, and IServiceManager should return the
+        // existing service in a near future. Wait for this.update() to instantiate
+        // the initial mHealthInfo.
+        long beforeWait = SystemClock.uptimeMillis();
+        synchronized (mLock) {
+            while (mHealthInfo == null) {
+                Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait) +
+                        "ms for callbacks. Waiting another " + HEALTH_HAL_WAIT_MS + " ms...");
+                try {
+                    mLock.wait(HEALTH_HAL_WAIT_MS);
+                } catch (InterruptedException ex) {
+                    Slog.i(TAG, "health: InterruptedException when waiting for update. "
+                        + " Continuing...");
+                }
+            }
+        }
+
+        Slog.i(TAG, "health: Waited " + (SystemClock.uptimeMillis() - beforeWait)
+                + "ms and received the update.");
+    }
+
     private void updateBatteryWarningLevelLocked() {
         final ContentResolver resolver = mContext.getContentResolver();
         int defWarnLevel = mContext.getResources().getInteger(
@@ -250,16 +302,16 @@
     private boolean isPoweredLocked(int plugTypeSet) {
         // assume we are powered if battery state is unknown so
         // the "stay on while plugged in" option will work.
-        if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
             return true;
         }
-        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) {
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
             return true;
         }
         return false;
@@ -276,21 +328,23 @@
          *   (becomes <= mLowBatteryWarningLevel).
          */
         return !plugged
-                && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-                && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
+                && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
                 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
     }
 
     private void shutdownIfNoPowerLocked() {
         // shut down gracefully if our battery is critically low and we are not powered.
         // wait until the system has booted before attempting to display the shutdown dialog.
-        if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+        if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     if (mActivityManagerInternal.isSystemReady()) {
                         Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                         intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.putExtra(Intent.EXTRA_REASON,
+                                PowerManager.SHUTDOWN_LOW_BATTERY);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     }
@@ -303,13 +357,15 @@
         // shut down gracefully if temperature is too high (> 68.0C by default)
         // wait until the system has booted before attempting to display the
         // shutdown dialog.
-        if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) {
+        if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     if (mActivityManagerInternal.isSystemReady()) {
                         Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                         intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.putExtra(Intent.EXTRA_REASON,
+                                PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     }
@@ -318,28 +374,51 @@
         }
     }
 
-    private void update(BatteryProperties props) {
+    private void update(HealthInfo info) {
         synchronized (mLock) {
             if (!mUpdatesStopped) {
-                mBatteryProps = props;
+                mHealthInfo = info;
                 // Process the new values.
                 processValuesLocked(false);
+                mLock.notifyAll(); // for any waiters on new info
             } else {
-                mLastBatteryProps.set(props);
+                copy(mLastHealthInfo, info);
             }
         }
     }
 
+    private static void copy(HealthInfo dst, HealthInfo src) {
+        dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
+        dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
+        dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
+        dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
+        dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
+        dst.legacy.batteryStatus = src.legacy.batteryStatus;
+        dst.legacy.batteryHealth = src.legacy.batteryHealth;
+        dst.legacy.batteryPresent = src.legacy.batteryPresent;
+        dst.legacy.batteryLevel = src.legacy.batteryLevel;
+        dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
+        dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
+        dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
+        dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
+        dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
+        dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
+        dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
+        dst.batteryCurrentAverage = src.batteryCurrentAverage;
+        dst.batteryCapacity = src.batteryCapacity;
+        dst.energyCounter = src.energyCounter;
+    }
+
     private void processValuesLocked(boolean force) {
         boolean logOutlier = false;
         long dischargeDuration = 0;
 
-        mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel);
-        if (mBatteryProps.chargerAcOnline) {
+        mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
+        if (mHealthInfo.legacy.chargerAcOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
-        } else if (mBatteryProps.chargerUsbOnline) {
+        } else if (mHealthInfo.legacy.chargerUsbOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
-        } else if (mBatteryProps.chargerWirelessOnline) {
+        } else if (mHealthInfo.legacy.chargerWirelessOnline) {
             mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
         } else {
             mPlugType = BATTERY_PLUGGED_NONE;
@@ -347,30 +426,17 @@
 
         if (DEBUG) {
             Slog.d(TAG, "Processing new values: "
-                    + "chargerAcOnline=" + mBatteryProps.chargerAcOnline
-                    + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
-                    + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
-                    + ", maxChargingCurrent" + mBatteryProps.maxChargingCurrent
-                    + ", maxChargingVoltage" + mBatteryProps.maxChargingVoltage
-                    + ", batteryStatus=" + mBatteryProps.batteryStatus
-                    + ", batteryHealth=" + mBatteryProps.batteryHealth
-                    + ", batteryPresent=" + mBatteryProps.batteryPresent
-                    + ", batteryLevel=" + mBatteryProps.batteryLevel
-                    + ", batteryTechnology=" + mBatteryProps.batteryTechnology
-                    + ", batteryVoltage=" + mBatteryProps.batteryVoltage
-                    + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter
-                    + ", batteryFullCharge=" + mBatteryProps.batteryFullCharge
-                    + ", batteryTemperature=" + mBatteryProps.batteryTemperature
+                    + "info=" + mHealthInfo
                     + ", mBatteryLevelCritical=" + mBatteryLevelCritical
                     + ", mPlugType=" + mPlugType);
         }
 
         // Let the battery stats keep track of the current level.
         try {
-            mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
-                    mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
-                    mBatteryProps.batteryVoltage, mBatteryProps.batteryChargeCounter,
-                    mBatteryProps.batteryFullCharge);
+            mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
+                    mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
+                    mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
+                    mHealthInfo.legacy.batteryFullCharge);
         } catch (RemoteException e) {
             // Should never happen.
         }
@@ -378,16 +444,16 @@
         shutdownIfNoPowerLocked();
         shutdownIfOverTempLocked();
 
-        if (force || (mBatteryProps.batteryStatus != mLastBatteryStatus ||
-                mBatteryProps.batteryHealth != mLastBatteryHealth ||
-                mBatteryProps.batteryPresent != mLastBatteryPresent ||
-                mBatteryProps.batteryLevel != mLastBatteryLevel ||
+        if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
+                mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
+                mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+                mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
                 mPlugType != mLastPlugType ||
-                mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
-                mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
-                mBatteryProps.maxChargingCurrent != mLastMaxChargingCurrent ||
-                mBatteryProps.maxChargingVoltage != mLastMaxChargingVoltage ||
-                mBatteryProps.batteryChargeCounter != mLastChargeCounter ||
+                mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
+                mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
+                mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
+                mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
+                mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
                 mInvalidCharger != mLastInvalidCharger)) {
 
             if (mPlugType != mLastPlugType) {
@@ -396,33 +462,33 @@
 
                     // There's no value in this data unless we've discharged at least once and the
                     // battery level has changed; so don't log until it does.
-                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) {
+                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
                         dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                         logOutlier = true;
                         EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
-                                mDischargeStartLevel, mBatteryProps.batteryLevel);
+                                mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
                         // make sure we see a discharge event before logging again
                         mDischargeStartTime = 0;
                     }
                 } else if (mPlugType == BATTERY_PLUGGED_NONE) {
                     // charging -> discharging or we just powered up
                     mDischargeStartTime = SystemClock.elapsedRealtime();
-                    mDischargeStartLevel = mBatteryProps.batteryLevel;
+                    mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
                 }
             }
-            if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
-                    mBatteryProps.batteryHealth != mLastBatteryHealth ||
-                    mBatteryProps.batteryPresent != mLastBatteryPresent ||
+            if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
+                    mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
+                    mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
                     mPlugType != mLastPlugType) {
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
-                        mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0,
-                        mPlugType, mBatteryProps.batteryTechnology);
+                        mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
+                        mPlugType, mHealthInfo.legacy.batteryTechnology);
             }
-            if (mBatteryProps.batteryLevel != mLastBatteryLevel) {
+            if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
                 // too noisy.
                 EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
-                        mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature);
+                        mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
             }
             if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
                     mPlugType == BATTERY_PLUGGED_NONE) {
@@ -435,16 +501,16 @@
             if (!mBatteryLevelLow) {
                 // Should we now switch in to low battery mode?
                 if (mPlugType == BATTERY_PLUGGED_NONE
-                        && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel) {
+                        && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
                     mBatteryLevelLow = true;
                 }
             } else {
                 // Should we now switch out of low battery mode?
                 if (mPlugType != BATTERY_PLUGGED_NONE) {
                     mBatteryLevelLow = false;
-                } else if (mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel)  {
+                } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel)  {
                     mBatteryLevelLow = false;
-                } else if (force && mBatteryProps.batteryLevel >= mLowBatteryWarningLevel) {
+                } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
                     // If being forced, the previous state doesn't matter, we will just
                     // absolutely check to see if we are now above the warning level.
                     mBatteryLevelLow = false;
@@ -491,7 +557,7 @@
                     }
                 });
             } else if (mSentLowBatteryBroadcast &&
-                    mBatteryProps.batteryLevel >= mLowBatteryCloseWarningLevel) {
+                    mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
                 mSentLowBatteryBroadcast = false;
                 final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -517,16 +583,16 @@
                 logOutlierLocked(dischargeDuration);
             }
 
-            mLastBatteryStatus = mBatteryProps.batteryStatus;
-            mLastBatteryHealth = mBatteryProps.batteryHealth;
-            mLastBatteryPresent = mBatteryProps.batteryPresent;
-            mLastBatteryLevel = mBatteryProps.batteryLevel;
+            mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
+            mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
+            mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
+            mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
             mLastPlugType = mPlugType;
-            mLastBatteryVoltage = mBatteryProps.batteryVoltage;
-            mLastBatteryTemperature = mBatteryProps.batteryTemperature;
-            mLastMaxChargingCurrent = mBatteryProps.maxChargingCurrent;
-            mLastMaxChargingVoltage = mBatteryProps.maxChargingVoltage;
-            mLastChargeCounter = mBatteryProps.batteryChargeCounter;
+            mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
+            mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
+            mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
+            mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
+            mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
             mLastBatteryLevelCritical = mBatteryLevelCritical;
             mLastInvalidCharger = mInvalidCharger;
         }
@@ -538,38 +604,26 @@
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_REPLACE_PENDING);
 
-        int icon = getIconLocked(mBatteryProps.batteryLevel);
+        int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
 
         intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
-        intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus);
-        intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth);
-        intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent);
-        intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel);
+        intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
+        intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
         intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
         intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
-        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage);
-        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature);
-        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
+        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
+        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
+        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
         intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
-        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
-        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mBatteryProps.batteryChargeCounter);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
+        intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
+        intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
         if (DEBUG) {
-            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED.  level:" + mBatteryProps.batteryLevel +
-                    ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
-                    ", health:" + mBatteryProps.batteryHealth +
-                    ", present:" + mBatteryProps.batteryPresent +
-                    ", voltage: " + mBatteryProps.batteryVoltage +
-                    ", temperature: " + mBatteryProps.batteryTemperature +
-                    ", technology: " + mBatteryProps.batteryTechnology +
-                    ", AC powered:" + mBatteryProps.chargerAcOnline +
-                    ", USB powered:" + mBatteryProps.chargerUsbOnline +
-                    ", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
-                    ", icon:" + icon  + ", invalid charger:" + mInvalidCharger +
-                    ", maxChargingCurrent:" + mBatteryProps.maxChargingCurrent +
-                    ", maxChargingVoltage:" + mBatteryProps.maxChargingVoltage +
-                    ", chargeCounter:" + mBatteryProps.batteryChargeCounter);
+            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+                    + ", info:" + mHealthInfo.toString());
         }
 
         mHandler.post(new Runnable() {
@@ -630,14 +684,14 @@
                 long durationThreshold = Long.parseLong(durationThresholdString);
                 int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
                 if (duration <= durationThreshold &&
-                        mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) {
+                        mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
                     // If the discharge cycle is bad enough we want to know about it.
                     logBatteryStatsLocked();
                 }
                 if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
                         " discharge threshold: " + dischargeThreshold);
                 if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
-                        (mDischargeStartLevel - mBatteryProps.batteryLevel));
+                        (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
             } catch (NumberFormatException e) {
                 Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
                         durationThresholdString + " or " + dischargeThresholdString);
@@ -646,14 +700,14 @@
     }
 
     private int getIconLocked(int level) {
-        if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+        if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery_charge;
-        } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
             return com.android.internal.R.drawable.stat_sys_battery;
-        } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
-                || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+        } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+                || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
             if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
-                    && mBatteryProps.batteryLevel >= 100) {
+                    && mHealthInfo.legacy.batteryLevel >= 100) {
                 return com.android.internal.R.drawable.stat_sys_battery_charge;
             } else {
                 return com.android.internal.R.drawable.stat_sys_battery;
@@ -715,11 +769,11 @@
                 getContext().enforceCallingOrSelfPermission(
                         android.Manifest.permission.DEVICE_POWER, null);
                 if (!mUpdatesStopped) {
-                    mLastBatteryProps.set(mBatteryProps);
+                    copy(mLastHealthInfo, mHealthInfo);
                 }
-                mBatteryProps.chargerAcOnline = false;
-                mBatteryProps.chargerUsbOnline = false;
-                mBatteryProps.chargerWirelessOnline = false;
+                mHealthInfo.legacy.chargerAcOnline = false;
+                mHealthInfo.legacy.chargerUsbOnline = false;
+                mHealthInfo.legacy.chargerWirelessOnline = false;
                 long ident = Binder.clearCallingIdentity();
                 try {
                     mUpdatesStopped = true;
@@ -746,30 +800,30 @@
                 }
                 try {
                     if (!mUpdatesStopped) {
-                        mLastBatteryProps.set(mBatteryProps);
+                        copy(mLastHealthInfo, mHealthInfo);
                     }
                     boolean update = true;
                     switch (key) {
                         case "present":
-                            mBatteryProps.batteryPresent = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
                             break;
                         case "ac":
-                            mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
                             break;
                         case "usb":
-                            mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
                             break;
                         case "wireless":
-                            mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+                            mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
                             break;
                         case "status":
-                            mBatteryProps.batteryStatus = Integer.parseInt(value);
+                            mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
                             break;
                         case "level":
-                            mBatteryProps.batteryLevel = Integer.parseInt(value);
+                            mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
                             break;
                         case "temp":
-                            mBatteryProps.batteryTemperature = Integer.parseInt(value);
+                            mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
                             break;
                         case "invalid":
                             mInvalidCharger = Integer.parseInt(value);
@@ -801,7 +855,7 @@
                 try {
                     if (mUpdatesStopped) {
                         mUpdatesStopped = false;
-                        mBatteryProps.set(mLastBatteryProps);
+                        copy(mHealthInfo, mLastHealthInfo);
                         processValuesFromShellLocked(pw, opts);
                     }
                 } finally {
@@ -828,20 +882,20 @@
                 if (mUpdatesStopped) {
                     pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
                 }
-                pw.println("  AC powered: " + mBatteryProps.chargerAcOnline);
-                pw.println("  USB powered: " + mBatteryProps.chargerUsbOnline);
-                pw.println("  Wireless powered: " + mBatteryProps.chargerWirelessOnline);
-                pw.println("  Max charging current: " + mBatteryProps.maxChargingCurrent);
-                pw.println("  Max charging voltage: " + mBatteryProps.maxChargingVoltage);
-                pw.println("  Charge counter: " + mBatteryProps.batteryChargeCounter);
-                pw.println("  status: " + mBatteryProps.batteryStatus);
-                pw.println("  health: " + mBatteryProps.batteryHealth);
-                pw.println("  present: " + mBatteryProps.batteryPresent);
-                pw.println("  level: " + mBatteryProps.batteryLevel);
+                pw.println("  AC powered: " + mHealthInfo.legacy.chargerAcOnline);
+                pw.println("  USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
+                pw.println("  Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
+                pw.println("  Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
+                pw.println("  Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
+                pw.println("  Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
+                pw.println("  status: " + mHealthInfo.legacy.batteryStatus);
+                pw.println("  health: " + mHealthInfo.legacy.batteryHealth);
+                pw.println("  present: " + mHealthInfo.legacy.batteryPresent);
+                pw.println("  level: " + mHealthInfo.legacy.batteryLevel);
                 pw.println("  scale: " + BATTERY_SCALE);
-                pw.println("  voltage: " + mBatteryProps.batteryVoltage);
-                pw.println("  temperature: " + mBatteryProps.batteryTemperature);
-                pw.println("  technology: " + mBatteryProps.batteryTechnology);
+                pw.println("  voltage: " + mHealthInfo.legacy.batteryVoltage);
+                pw.println("  temperature: " + mHealthInfo.legacy.batteryTemperature);
+                pw.println("  technology: " + mHealthInfo.legacy.batteryTechnology);
             } else {
                 Shell shell = new Shell();
                 shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -854,26 +908,26 @@
 
         synchronized (mLock) {
             proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
-            int batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_NONE;
-            if (mBatteryProps.chargerAcOnline) {
-                batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_AC;
-            } else if (mBatteryProps.chargerUsbOnline) {
-                batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_USB;
-            } else if (mBatteryProps.chargerWirelessOnline) {
-                batteryPluggedValue = BatteryServiceDumpProto.BATTERY_PLUGGED_WIRELESS;
+            int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
+            if (mHealthInfo.legacy.chargerAcOnline) {
+                batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
+            } else if (mHealthInfo.legacy.chargerUsbOnline) {
+                batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
+            } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+                batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
             }
             proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mBatteryProps.maxChargingCurrent);
-            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mBatteryProps.maxChargingVoltage);
-            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mBatteryProps.batteryChargeCounter);
-            proto.write(BatteryServiceDumpProto.STATUS, mBatteryProps.batteryStatus);
-            proto.write(BatteryServiceDumpProto.HEALTH, mBatteryProps.batteryHealth);
-            proto.write(BatteryServiceDumpProto.IS_PRESENT, mBatteryProps.batteryPresent);
-            proto.write(BatteryServiceDumpProto.LEVEL, mBatteryProps.batteryLevel);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
+            proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
+            proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+            proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
+            proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
+            proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
+            proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
             proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
-            proto.write(BatteryServiceDumpProto.VOLTAGE, mBatteryProps.batteryVoltage);
-            proto.write(BatteryServiceDumpProto.TEMPERATURE, mBatteryProps.batteryTemperature);
-            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mBatteryProps.batteryTechnology);
+            proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
+            proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
+            proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
         }
         proto.flush();
     }
@@ -906,8 +960,8 @@
          * Synchronize on BatteryService.
          */
         public void updateLightsLocked() {
-            final int level = mBatteryProps.batteryLevel;
-            final int status = mBatteryProps.batteryStatus;
+            final int level = mHealthInfo.legacy.batteryLevel;
+            final int status = mHealthInfo.legacy.batteryStatus;
             if (level < mLowBatteryWarningLevel) {
                 if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                     // Solid red when battery is charging
@@ -933,15 +987,43 @@
         }
     }
 
-    private final class BatteryListener extends IBatteryPropertiesListener.Stub {
-        @Override public void batteryPropertiesChanged(BatteryProperties props) {
-            final long identity = Binder.clearCallingIdentity();
+    private final class HealthHalCallback extends IHealthInfoCallback.Stub
+            implements HealthServiceWrapper.Callback {
+        @Override public void healthInfoChanged(HealthInfo props) {
+            BatteryService.this.update(props);
+        }
+        // on new service registered
+        @Override public void onRegistration(IHealth oldService, IHealth newService,
+                String instance) {
+            if (newService == null) return;
+
             try {
-                BatteryService.this.update(props);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+                if (oldService != null) {
+                    int r = oldService.unregisterCallback(this);
+                    if (r != Result.SUCCESS) {
+                        Slog.w(TAG, "health: cannot unregister previous callback: " +
+                                Result.toString(r));
+                    }
+                }
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "health: cannot unregister previous callback (transaction error): "
+                            + ex.getMessage());
             }
-       }
+
+            try {
+                int r = newService.registerCallback(this);
+                if (r != Result.SUCCESS) {
+                    Slog.w(TAG, "health: cannot register callback: " + Result.toString(r));
+                    return;
+                }
+                // registerCallback does NOT guarantee that update is called
+                // immediately, so request a manual update here.
+                newService.update();
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "health: cannot register callback (transaction error): "
+                        + ex.getMessage());
+            }
+        }
     }
 
     private final class BinderService extends Binder {
@@ -962,6 +1044,66 @@
         }
     }
 
+    // Reduced IBatteryPropertiesRegistrar that only implements getProperty for usage
+    // in BatteryManager.
+    private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub {
+        public void registerListener(IBatteryPropertiesListener listener) {
+            Slog.e(TAG, "health: must not call registerListener on battery properties");
+        }
+        public void unregisterListener(IBatteryPropertiesListener listener) {
+            Slog.e(TAG, "health: must not call unregisterListener on battery properties");
+        }
+        public int getProperty(int id, final BatteryProperty prop) throws RemoteException {
+            IHealth service = mHealthServiceWrapper.getLastService();
+            if (service == null) throw new RemoteException("no health service");
+            final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED);
+            switch(id) {
+                case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER:
+                    service.getChargeCounter((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW:
+                    service.getCurrentNow((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE:
+                    service.getCurrentAverage((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_CAPACITY:
+                    service.getCapacity((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_STATUS:
+                    service.getChargeStatus((int result, int value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+                case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER:
+                    service.getEnergyCounter((int result, long value) -> {
+                        outResult.value = result;
+                        if (result == Result.SUCCESS) prop.setLong(value);
+                    });
+                    break;
+            }
+            return outResult.value;
+        }
+        public void scheduleUpdate() throws RemoteException {
+            IHealth service = mHealthServiceWrapper.getLastService();
+            if (service == null) throw new RemoteException("no health service");
+            service.update();
+        }
+    }
+
     private final class LocalService extends BatteryManagerInternal {
         @Override
         public boolean isPowered(int plugTypeSet) {
@@ -980,7 +1122,7 @@
         @Override
         public int getBatteryLevel() {
             synchronized (mLock) {
-                return mBatteryProps.batteryLevel;
+                return mHealthInfo.legacy.batteryLevel;
             }
         }
 
@@ -998,4 +1140,142 @@
             }
         }
     }
+
+    /**
+     * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when
+     * necessary.
+     *
+     * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and
+     * the internal service is refreshed.
+     * On death of an existing IHealth service, the internal service is NOT cleared to avoid
+     * race condition between death notification and new service notification. Hence,
+     * a caller must check for transaction errors when calling into the service.
+     *
+     * @hide Should only be used internally.
+     */
+    @VisibleForTesting
+    static final class HealthServiceWrapper {
+        private static final String TAG = "HealthServiceWrapper";
+        public static final String INSTANCE_HEALTHD = "backup";
+        public static final String INSTANCE_VENDOR = "default";
+        // All interesting instances, sorted by priority high -> low.
+        private static final List<String> sAllInstances =
+                Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
+
+        private final IServiceNotification mNotification = new Notification();
+        // These variables are fixed after init.
+        private Callback mCallback;
+        private IHealthSupplier mHealthSupplier;
+        private String mInstanceName;
+
+        private final Object mLastServiceSetLock = new Object();
+        // Last IHealth service received.
+        // set must be also be guarded with mLastServiceSetLock to ensure ordering.
+        private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
+
+        /**
+         * init should be called after constructor. For testing purposes, init is not called by
+         * constructor.
+         */
+        HealthServiceWrapper() {
+        }
+
+        IHealth getLastService() {
+            return mLastService.get();
+        }
+
+        /**
+         * Start monitoring registration of new IHealth services. Only instances that are in
+         * {@code sAllInstances} and in device / framework manifest are used. This function should
+         * only be called once.
+         * @throws RemoteException transaction error when talking to IServiceManager
+         * @throws NoSuchElementException if one of the following cases:
+         *         - No service manager;
+         *         - none of {@code sAllInstances} are in manifests (i.e. not
+         *           available on this device), or none of these instances are available to current
+         *           process.
+         * @throws NullPointerException when callback is null or supplier is null
+         */
+        void init(Callback callback,
+                  IServiceManagerSupplier managerSupplier,
+                  IHealthSupplier healthSupplier)
+                throws RemoteException, NoSuchElementException, NullPointerException {
+            if (callback == null || managerSupplier == null || healthSupplier == null)
+                throw new NullPointerException();
+
+            mCallback = callback;
+            mHealthSupplier = healthSupplier;
+
+            IServiceManager manager = managerSupplier.get();
+            for (String name : sAllInstances) {
+                if (manager.getTransport(IHealth.kInterfaceName, name) !=
+                        IServiceManager.Transport.EMPTY) {
+                    mInstanceName = name;
+                    break;
+                }
+            }
+
+            if (mInstanceName == null) {
+                throw new NoSuchElementException(String.format(
+                        "No IHealth service instance among %s is available. Perhaps no permission?",
+                        sAllInstances.toString()));
+            }
+
+            manager.registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+            Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
+        }
+
+        interface Callback {
+            /**
+             * This function is invoked asynchronously when a new and related IServiceNotification
+             * is received.
+             * @param service the recently retrieved service from IServiceManager.
+             * Can be a dead service before service notification of a new service is delivered.
+             * Implementation must handle cases for {@link RemoteException}s when calling
+             * into service.
+             * @param instance instance name.
+             */
+            void onRegistration(IHealth oldService, IHealth newService, String instance);
+        }
+
+        /**
+         * Supplier of services.
+         * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+         */
+        interface IServiceManagerSupplier {
+            default IServiceManager get() throws NoSuchElementException, RemoteException {
+                return IServiceManager.getService();
+            }
+        }
+        /**
+         * Supplier of services.
+         * Must not return null; throw {@link NoSuchElementException} if a service is not available.
+         */
+        interface IHealthSupplier {
+            default IHealth get(String name) throws NoSuchElementException, RemoteException {
+                return IHealth.getService(name);
+            }
+        }
+
+        private class Notification extends IServiceNotification.Stub {
+            @Override
+            public final void onRegistration(String interfaceName, String instanceName,
+                    boolean preexisting) {
+                if (!IHealth.kInterfaceName.equals(interfaceName)) return;
+                if (!mInstanceName.equals(instanceName)) return;
+                try {
+                    // ensures the order of multiple onRegistration on different threads.
+                    synchronized (mLastServiceSetLock) {
+                        IHealth newService = mHealthSupplier.get(instanceName);
+                        IHealth oldService = mLastService.getAndSet(newService);
+                        Slog.i(TAG, "health: new instance registered " + instanceName);
+                        mCallback.onRegistration(oldService, newService, instanceName);
+                    }
+                } catch (NoSuchElementException | RemoteException ex) {
+                    Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
+                           ex.getMessage() + ". Perhaps no permission?");
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3ac6f2e..7e65d36 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -69,7 +70,6 @@
 import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.Uri;
-import android.net.metrics.DefaultNetworkEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.util.MultinetworkPolicyTracker;
@@ -90,6 +90,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -125,6 +126,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -141,6 +143,7 @@
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
+import com.android.server.utils.PriorityDump;
 
 import com.google.android.collect.Lists;
 
@@ -680,6 +683,28 @@
     }
     private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker();
 
+    /**
+     * Helper class which parses out priority arguments and dumps sections according to their
+     * priority. If priority arguments are omitted, function calls the legacy dump command.
+     */
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        @Override
+        public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, new String[] {DIAG_ARG}, asProto);
+            doDump(fd, pw, new String[] {SHORT_ARG}, asProto);
+        }
+
+        @Override
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, args, asProto);
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+           doDump(fd, pw, args, asProto);
+        }
+    };
+
     public ConnectivityService(Context context, INetworkManagementService netManager,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         this(context, netManager, statsService, policyManager, new IpConnectivityLog());
@@ -780,6 +805,13 @@
             mNetworksDefined++;  // used only in the log() statement below.
         }
 
+        // Do the same for Ethernet, since it's often not specified in the configs, although many
+        // devices can use it via USB host adapters.
+        if (mNetConfigs[TYPE_ETHERNET] == null && hasService(Context.ETHERNET_SERVICE)) {
+            mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
+            mNetworksDefined++;
+        }
+
         if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
 
         mProtectedNetworks = new ArrayList<Integer>();
@@ -1853,8 +1885,13 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        PriorityDump.dump(mPriorityDumper, fd, writer, args);
+    }
+
+    private void doDump(FileDescriptor fd, PrintWriter writer, String[] args, boolean asProto) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        if (asProto) return;
 
         if (argsContain(args, DIAG_ARG)) {
             dumpNetworkDiagnostics(pw);
@@ -2256,7 +2293,7 @@
                 // Let rematchAllNetworksAndRequests() below record a new default network event
                 // if there is a fallback. Taken together, the two form a X -> 0, 0 -> Y sequence
                 // whose timestamps tell how long it takes to recover a default network.
-                logDefaultNetworkEvent(null, nai);
+                metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(null, nai);
             }
             notifyIfacesChangedForNetworkStats();
             // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
@@ -4986,7 +5023,8 @@
             // Notify system services that this network is up.
             makeDefault(newNetwork);
             // Log 0 -> X and Y -> X default network transitions, where X is the new default.
-            logDefaultNetworkEvent(newNetwork, oldDefaultNetwork);
+            metricsLogger().defaultNetworkMetrics().logDefaultNetworkEvent(
+                    newNetwork, oldDefaultNetwork);
             // Have a new default network, release the transition wakelock in
             scheduleReleaseNetworkTransitionWakelock();
         }
@@ -5540,25 +5578,15 @@
         return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
     }
 
-    private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
-        int newNetid = NETID_UNSET;
-        int prevNetid = NETID_UNSET;
-        int[] transports = new int[0];
-        boolean hadIPv4 = false;
-        boolean hadIPv6 = false;
+    @VisibleForTesting
+    public boolean hasService(String name) {
+        return ServiceManager.checkService(name) != null;
+    }
 
-        if (newNai != null) {
-            newNetid = newNai.network.netId;
-            transports = newNai.networkCapabilities.getTransportTypes();
-        }
-        if (prevNai != null) {
-            prevNetid = prevNai.network.netId;
-            final LinkProperties lp = prevNai.linkProperties;
-            hadIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
-            hadIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
-        }
-
-        mMetricsLog.log(new DefaultNetworkEvent(newNetid, transports, prevNetid, hadIPv4, hadIPv6));
+    @VisibleForTesting
+    protected IpConnectivityMetrics.Logger metricsLogger() {
+        return checkNotNull(LocalServices.getService(IpConnectivityMetrics.Logger.class),
+                "no IpConnectivityMetrics service");
     }
 
     private void logNetworkEvent(NetworkAgentInfo nai, int evtype) {
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index abbc89e..0921a00 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -307,6 +307,12 @@
      */
     private int[] mTempWhitelistAppIdArray = new int[0];
 
+    /**
+     * Apps in the system whitelist that have been taken out (probably because the user wanted to).
+     * They can be restored back by calling restoreAppToSystemWhitelist(String).
+     */
+    private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
+
     private static final int EVENT_NULL = 0;
     private static final int EVENT_NORMAL = 1;
     private static final int EVENT_LIGHT_IDLE = 2;
@@ -760,17 +766,15 @@
         public long NOTIFICATION_WHITELIST_DURATION;
 
         private final ContentResolver mResolver;
-        private final boolean mHasWatch;
+        private final boolean mSmallBatteryDevice;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         public Constants(Handler handler, ContentResolver resolver) {
             super(handler);
             mResolver = resolver;
-            mHasWatch = getContext().getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WATCH);
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                              : Settings.Global.DEVICE_IDLE_CONSTANTS),
+            mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice();
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS),
                     false, this);
             updateConstants();
         }
@@ -784,8 +788,7 @@
             synchronized (DeviceIdleController.this) {
                 try {
                     mParser.setString(Settings.Global.getString(mResolver,
-                            mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                                      : Settings.Global.DEVICE_IDLE_CONSTANTS));
+                            Settings.Global.DEVICE_IDLE_CONSTANTS));
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
@@ -815,7 +818,7 @@
                 MIN_DEEP_MAINTENANCE_TIME = mParser.getLong(
                         KEY_MIN_DEEP_MAINTENANCE_TIME,
                         !COMPRESS_TIME ? 30 * 1000L : 5 * 1000L);
-                long inactiveTimeoutDefault = (mHasWatch ? 15 : 30) * 60 * 1000L;
+                long inactiveTimeoutDefault = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -825,7 +828,7 @@
                 LOCATION_ACCURACY = mParser.getFloat(KEY_LOCATION_ACCURACY, 20);
                 MOTION_INACTIVE_TIMEOUT = mParser.getLong(KEY_MOTION_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
-                long idleAfterInactiveTimeout = (mHasWatch ? 15 : 30) * 60 * 1000L;
+                long idleAfterInactiveTimeout = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
                 IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? idleAfterInactiveTimeout
                                        : (idleAfterInactiveTimeout / 10));
@@ -1162,6 +1165,38 @@
             }
         }
 
+        @Override public void removeSystemPowerWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.d(TAG, "removeAppFromSystemWhitelist(name = " + name + ")");
+            }
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                removeSystemPowerWhitelistAppInternal(name);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override public void restoreSystemPowerWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.d(TAG, "restoreAppToSystemWhitelist(name = " + name + ")");
+            }
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                restoreSystemPowerWhitelistAppInternal(name);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        public String[] getRemovedSystemPowerWhitelistApps() {
+            return getRemovedSystemPowerWhitelistAppsInternal();
+        }
+
         @Override public String[] getSystemPowerWhitelistExceptIdle() {
             return getSystemPowerWhitelistExceptIdleInternal();
         }
@@ -1298,6 +1333,10 @@
         public int[] getPowerSaveWhitelistUserAppIds() {
             return DeviceIdleController.this.getPowerSaveWhitelistUserAppIds();
         }
+
+        public int[] getPowerSaveTempWhitelistAppIds() {
+            return DeviceIdleController.this.getAppIdTempWhitelistInternal();
+        }
     }
 
     public DeviceIdleController(Context context) {
@@ -1504,6 +1543,42 @@
         }
     }
 
+    void resetSystemPowerWhitelistInternal() {
+        synchronized (this) {
+            mPowerSaveWhitelistApps.putAll(mRemovedFromSystemWhitelistApps);
+            mRemovedFromSystemWhitelistApps.clear();
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+        }
+    }
+
+    public boolean restoreSystemPowerWhitelistAppInternal(String name) {
+        synchronized (this) {
+            if (!mRemovedFromSystemWhitelistApps.containsKey(name)) {
+                return false;
+            }
+            mPowerSaveWhitelistApps.put(name, mRemovedFromSystemWhitelistApps.remove(name));
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+            return true;
+        }
+    }
+
+    public boolean removeSystemPowerWhitelistAppInternal(String name) {
+        synchronized (this) {
+            if (!mPowerSaveWhitelistApps.containsKey(name)) {
+                return false;
+            }
+            mRemovedFromSystemWhitelistApps.put(name, mPowerSaveWhitelistApps.remove(name));
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+            return true;
+        }
+    }
+
     public boolean addPowerSaveWhitelistExceptIdleInternal(String name) {
         synchronized (this) {
             try {
@@ -1565,6 +1640,17 @@
         }
     }
 
+    public String[] getRemovedSystemPowerWhitelistAppsInternal() {
+        synchronized (this) {
+            int size = mRemovedFromSystemWhitelistApps.size();
+            final String[] apps = new String[size];
+            for (int i = 0; i < size; i++) {
+                apps[i] = mRemovedFromSystemWhitelistApps.keyAt(i);
+            }
+            return apps;
+        }
+    }
+
     public String[] getUserPowerWhitelistInternal() {
         synchronized (this) {
             int size = mPowerSaveWhitelistUserApps.size();
@@ -2481,21 +2567,31 @@
                 }
 
                 String tagName = parser.getName();
-                if (tagName.equals("wl")) {
-                    String name = parser.getAttributeValue(null, "n");
-                    if (name != null) {
-                        try {
-                            ApplicationInfo ai = pm.getApplicationInfo(name,
-                                    PackageManager.MATCH_ANY_USER);
-                            mPowerSaveWhitelistUserApps.put(ai.packageName,
-                                    UserHandle.getAppId(ai.uid));
-                        } catch (PackageManager.NameNotFoundException e) {
+                switch (tagName) {
+                    case "wl":
+                        String name = parser.getAttributeValue(null, "n");
+                        if (name != null) {
+                            try {
+                                ApplicationInfo ai = pm.getApplicationInfo(name,
+                                        PackageManager.MATCH_ANY_USER);
+                                mPowerSaveWhitelistUserApps.put(ai.packageName,
+                                        UserHandle.getAppId(ai.uid));
+                            } catch (PackageManager.NameNotFoundException e) {
+                            }
                         }
-                    }
-                } else {
-                    Slog.w(TAG, "Unknown element under <config>: "
-                            + parser.getName());
-                    XmlUtils.skipCurrentTag(parser);
+                        break;
+                    case "un-wl":
+                        final String packageName = parser.getAttributeValue(null, "n");
+                        if (mPowerSaveWhitelistApps.containsKey(packageName)) {
+                            mRemovedFromSystemWhitelistApps.put(packageName,
+                                    mPowerSaveWhitelistApps.remove(packageName));
+                        }
+                        break;
+                    default:
+                        Slog.w(TAG, "Unknown element under <config>: "
+                                + parser.getName());
+                        XmlUtils.skipCurrentTag(parser);
+                        break;
                 }
             }
 
@@ -2556,6 +2652,11 @@
             out.attribute(null, "n", name);
             out.endTag(null, "wl");
         }
+        for (int i = 0; i < mRemovedFromSystemWhitelistApps.size(); i++) {
+            out.startTag(null, "un-wl");
+            out.attribute(null, "n", mRemovedFromSystemWhitelistApps.keyAt(i));
+            out.endTag(null, "un-wl");
+        }
         out.endTag(null, "config");
         out.endDocument();
     }
@@ -2584,6 +2685,13 @@
         pw.println("    Print currently whitelisted apps.");
         pw.println("  whitelist [package ...]");
         pw.println("    Add (prefix with +) or remove (prefix with -) packages.");
+        pw.println("  sys-whitelist [package ...|reset]");
+        pw.println("    Prefix the package with '-' to remove it from the system whitelist or '+'"
+                + " to put it back in the system whitelist.");
+        pw.println("    Note that only packages that were"
+                + " earlier removed from the system whitelist can be added back.");
+        pw.println("    reset will reset the whitelist to the original state");
+        pw.println("    Prints the system whitelist if no arguments are specified");
         pw.println("  except-idle-whitelist [package ...|reset]");
         pw.println("    Prefix the package with '+' to add it to whitelist or "
                 + "'=' to check if it is already whitelisted");
@@ -2944,6 +3052,50 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+        } else if ("sys-whitelist".equals(cmd)) {
+            String arg = shell.getNextArg();
+            if (arg != null) {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    if ("reset".equals(arg)) {
+                        resetSystemPowerWhitelistInternal();
+                    } else {
+                        do {
+                            if (arg.length() < 1
+                                    || (arg.charAt(0) != '-' && arg.charAt(0) != '+')) {
+                                pw.println("Package must be prefixed with + or - " + arg);
+                                return -1;
+                            }
+                            final char op = arg.charAt(0);
+                            final String pkg = arg.substring(1);
+                            switch (op) {
+                                case '+':
+                                    if (restoreSystemPowerWhitelistAppInternal(pkg)) {
+                                        pw.println("Restored " + pkg);
+                                    }
+                                    break;
+                                case '-':
+                                    if (removeSystemPowerWhitelistAppInternal(pkg)) {
+                                        pw.println("Removed " + pkg);
+                                    }
+                                    break;
+                            }
+                        } while ((arg = shell.getNextArg()) != null);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } else {
+                synchronized (this) {
+                    for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+                        pw.print(mPowerSaveWhitelistApps.keyAt(j));
+                        pw.print(",");
+                        pw.println(mPowerSaveWhitelistApps.valueAt(j));
+                    }
+                }
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -3027,6 +3179,14 @@
                     pw.println(mPowerSaveWhitelistApps.keyAt(i));
                 }
             }
+            size = mRemovedFromSystemWhitelistApps.size();
+            if (size > 0) {
+                pw.println("  Removed from whitelist system apps:");
+                for (int i = 0; i < size; i++) {
+                    pw.print("    ");
+                    pw.println(mRemovedFromSystemWhitelistApps.keyAt(i));
+                }
+            }
             size = mPowerSaveWhitelistUserApps.size();
             if (size > 0) {
                 pw.println("  Whitelist user apps:");
diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java
index 800081e..2d2c6b0 100644
--- a/services/core/java/com/android/server/DiskStatsService.java
+++ b/services/core/java/com/android/server/DiskStatsService.java
@@ -202,6 +202,8 @@
             JSONObject json = new JSONObject(jsonString);
             pw.print("App Size: ");
             pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+            pw.print("App Data Size: ");
+            pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
             pw.print("App Cache Size: ");
             pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
             pw.print("Photos Size: ");
@@ -220,6 +222,8 @@
             pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
             pw.print("App Sizes: ");
             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
+            pw.print("App Data Sizes: ");
+            pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY));
             pw.print("Cache Sizes: ");
             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
         } catch (IOException | JSONException e) {
@@ -235,6 +239,8 @@
 
             proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
                     json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+            proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE,
+                    json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
             proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
                     json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
             proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
@@ -252,22 +258,26 @@
 
             JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
             JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+            JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
             JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
             final int len = packageNamesArray.length();
-            if (len == appSizesArray.length() && len == cacheSizesArray.length()) {
+            if (len == appSizesArray.length()
+                    && len == appDataSizesArray.length()
+                    && len == cacheSizesArray.length()) {
                 for (int i = 0; i < len; i++) {
                     long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
 
                     proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
                             packageNamesArray.getString(i));
                     proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
+                    proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE, appDataSizesArray.getLong(i));
                     proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
 
                     proto.end(packageToken);
                 }
             } else {
-                Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray "
-                        + "are not the same");
+                Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray "
+                        + " and cacheSizesArray are not the same");
             }
 
             proto.end(cachedValuesToken);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index c23757f..f007bcc 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -52,6 +52,7 @@
 import android.annotation.BinderThread;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -1469,7 +1470,9 @@
                 broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
                 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
 
-                buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
+                final String defaultImiId = mSettings.getSelectedInputMethod();
+                final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+                buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
                 resetDefaultImeLocked(mContext);
                 updateFromSettingsLocked(true);
                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
@@ -3190,6 +3193,7 @@
         }
     }
 
+    @MainThread
     @Override
     public boolean handleMessage(Message msg) {
         SomeArgs args;
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 40499c9..119c9df 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -38,6 +38,8 @@
 
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.util.proto.ProtoOutputStream;
+
 import com.android.internal.util.FastPrintWriter;
 
 /**
@@ -279,6 +281,31 @@
         return printedSomething;
     }
 
+    void writeProtoMap(ProtoOutputStream proto, long fieldId, ArrayMap<String, F[]> map) {
+        int N = map.size();
+        for (int mapi = 0; mapi < N; mapi++) {
+            long token = proto.start(fieldId);
+            proto.write(IntentResolverProto.ArrayMapEntry.KEY, map.keyAt(mapi));
+            for (F f : map.valueAt(mapi)) {
+                if (f != null) {
+                    proto.write(IntentResolverProto.ArrayMapEntry.VALUES, f.toString());
+                }
+            }
+            proto.end(token);
+        }
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        writeProtoMap(proto, IntentResolverProto.FULL_MIME_TYPES, mTypeToFilter);
+        writeProtoMap(proto, IntentResolverProto.BASE_MIME_TYPES, mBaseTypeToFilter);
+        writeProtoMap(proto, IntentResolverProto.WILD_MIME_TYPES, mWildTypeToFilter);
+        writeProtoMap(proto, IntentResolverProto.SCHEMES, mSchemeToFilter);
+        writeProtoMap(proto, IntentResolverProto.NON_DATA_ACTIONS, mActionToFilter);
+        writeProtoMap(proto, IntentResolverProto.MIME_TYPED_ACTIONS, mTypedActionToFilter);
+        proto.end(token);
+    }
+
     public boolean dump(PrintWriter out, String title, String prefix, String packageName,
             boolean printFilter, boolean collapseDuplicates) {
         String innerPrefix = prefix + "  ";
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 3056831..1154fbe 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -33,6 +33,7 @@
 import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
 import android.net.IpSecUdpEncapResponse;
+import android.net.NetworkUtils;
 import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
@@ -42,11 +43,14 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -54,6 +58,7 @@
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import libcore.io.IoUtils;
 
 /** @hide */
@@ -252,7 +257,11 @@
             return (mReferenceCount.get() > 0);
         }
 
-        public void checkOwnerOrSystemAndThrow() {
+        /**
+         * Ensures that the caller is either the owner of this resource or has the system UID and
+         * throws a SecurityException otherwise.
+         */
+        public void checkOwnerOrSystem() {
             if (uid != Binder.getCallingUid()
                     && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
                 throw new SecurityException("Only the owner may access managed resources!");
@@ -335,12 +344,12 @@
     private class ManagedResourceArray<T extends ManagedResource> {
         SparseArray<T> mArray = new SparseArray<>();
 
-        T get(int key) {
+        T getAndCheckOwner(int key) {
             T val = mArray.get(key);
             // The value should never be null unless the resource doesn't exist
             // (since we do not allow null resources to be added).
             if (val != null) {
-                val.checkOwnerOrSystemAndThrow();
+                val.checkOwnerOrSystem();
             }
             return val;
         }
@@ -405,12 +414,8 @@
                             .ipSecDeleteSecurityAssociation(
                                     mResourceId,
                                     direction,
-                                    (mConfig.getLocalAddress() != null)
-                                            ? mConfig.getLocalAddress().getHostAddress()
-                                            : "",
-                                    (mConfig.getRemoteAddress() != null)
-                                            ? mConfig.getRemoteAddress().getHostAddress()
-                                            : "",
+                                    mConfig.getLocalAddress(),
+                                    mConfig.getRemoteAddress(),
                                     spi);
                 } catch (ServiceSpecificException e) {
                     // FIXME: get the error code and throw is at an IOException from Errno Exception
@@ -638,11 +643,45 @@
         }
     }
 
+    /**
+     * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
+     * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
+     */
+    private static void checkInetAddress(String inetAddress) {
+        if (TextUtils.isEmpty(inetAddress)) {
+            throw new IllegalArgumentException("Unspecified address");
+        }
+
+        InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
+
+        if (checkAddr.isAnyLocalAddress()) {
+            throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
+        }
+    }
+
+    /**
+     * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
+     * DIRECTION_IN or DIRECTION_OUT
+     */
+    private static void checkDirection(int direction) {
+        switch (direction) {
+            case IpSecTransform.DIRECTION_OUT:
+            case IpSecTransform.DIRECTION_IN:
+                return;
+        }
+        throw new IllegalArgumentException("Invalid Direction: " + direction);
+    }
+
     @Override
     /** Get a new SPI and maintain the reservation in the system server */
     public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
             int direction, String remoteAddress, int requestedSpi, IBinder binder)
             throws RemoteException {
+        checkDirection(direction);
+        checkInetAddress(remoteAddress);
+        /* requestedSpi can be anything in the int range, so no check is needed. */
+        checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
+
         int resourceId = mNextResourceId.getAndIncrement();
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
@@ -651,9 +690,7 @@
         try {
             if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) {
                 return new IpSecSpiResponse(
-                        IpSecManager.Status.RESOURCE_UNAVAILABLE,
-                        INVALID_RESOURCE_ID,
-                        spi);
+                        IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
             }
             spi =
                     mSrvConfig
@@ -686,7 +723,7 @@
             throws RemoteException {
         // We want to non-destructively get so that we can check credentials before removing
         // this from the records.
-        T record = resArray.get(resourceId);
+        T record = resArray.getAndCheckOwner(resourceId);
 
         if (record == null) {
             throw new IllegalArgumentException(
@@ -717,7 +754,7 @@
      * and re-binding, during which the system could *technically* hand that port out to someone
      * else.
      */
-    private void bindToRandomPort(FileDescriptor sockFd) throws IOException {
+    private int bindToRandomPort(FileDescriptor sockFd) throws IOException {
         for (int i = MAX_PORT_BIND_ATTEMPTS; i > 0; i--) {
             try {
                 FileDescriptor probeSocket = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
@@ -726,7 +763,7 @@
                 Os.close(probeSocket);
                 Log.v(TAG, "Binding to port " + port);
                 Os.bind(sockFd, INADDR_ANY, port);
-                return;
+                return port;
             } catch (ErrnoException e) {
                 // Someone miraculously claimed the port just after we closed probeSocket.
                 if (e.errno == OsConstants.EADDRINUSE) {
@@ -751,6 +788,8 @@
             throw new IllegalArgumentException(
                     "Specified port number must be a valid non-reserved UDP port");
         }
+        checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
+
         int resourceId = mNextResourceId.getAndIncrement();
         FileDescriptor sockFd = null;
         try {
@@ -764,7 +803,7 @@
                 Log.v(TAG, "Binding to port " + port);
                 Os.bind(sockFd, INADDR_ANY, port);
             } else {
-                bindToRandomPort(sockFd);
+                port = bindToRandomPort(sockFd);
             }
             // This code is common to both the unspecified and specified port cases
             Os.setsockoptInt(
@@ -792,6 +831,74 @@
     }
 
     /**
+     * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
+     * IllegalArgumentException if they are not.
+     */
+    private void checkIpSecConfig(IpSecConfig config) {
+        if (config.getLocalAddress() == null) {
+            throw new IllegalArgumentException("Invalid null Local InetAddress");
+        }
+
+        if (config.getRemoteAddress() == null) {
+            throw new IllegalArgumentException("Invalid null Remote InetAddress");
+        }
+
+        switch (config.getMode()) {
+            case IpSecTransform.MODE_TRANSPORT:
+                if (!config.getLocalAddress().isEmpty()) {
+                    throw new IllegalArgumentException("Non-empty Local Address");
+                }
+                // Must be valid, and not a wildcard
+                checkInetAddress(config.getRemoteAddress());
+                break;
+            case IpSecTransform.MODE_TUNNEL:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid IpSecTransform.mode: " + config.getMode());
+        }
+
+        switch (config.getEncapType()) {
+            case IpSecTransform.ENCAP_NONE:
+                break;
+            case IpSecTransform.ENCAP_ESPINUDP:
+            case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
+                if (mUdpSocketRecords.getAndCheckOwner(
+                            config.getEncapSocketResourceId()) == null) {
+                    throw new IllegalStateException(
+                            "No Encapsulation socket for Resource Id: "
+                                    + config.getEncapSocketResourceId());
+                }
+
+                int port = config.getEncapRemotePort();
+                if (port <= 0 || port > 0xFFFF) {
+                    throw new IllegalArgumentException("Invalid remote UDP port: " + port);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
+        }
+
+        for (int direction : DIRECTIONS) {
+            IpSecAlgorithm crypt = config.getEncryption(direction);
+            IpSecAlgorithm auth = config.getAuthentication(direction);
+            IpSecAlgorithm authenticatedEncryption = config.getAuthenticatedEncryption(direction);
+            if (authenticatedEncryption == null && crypt == null && auth == null) {
+                throw new IllegalArgumentException(
+                        "No Encryption or Authentication algorithms specified");
+            } else if (authenticatedEncryption != null && (auth != null || crypt != null)) {
+                throw new IllegalArgumentException(
+                        "Authenticated Encryption is mutually"
+                                + " exclusive with other Authentication or Encryption algorithms");
+            }
+
+            if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
+                throw new IllegalStateException("No SPI for specified Resource Id");
+            }
+        }
+    }
+
+    /**
      * Create a transport mode transform, which represent two security associations (one in each
      * direction) in the kernel. The transform will be cached by the system server and must be freed
      * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
@@ -801,17 +908,19 @@
     @Override
     public synchronized IpSecTransformResponse createTransportModeTransform(
             IpSecConfig c, IBinder binder) throws RemoteException {
+        checkIpSecConfig(c);
+        checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
         int resourceId = mNextResourceId.getAndIncrement();
         if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
         SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
-        // TODO: Basic input validation here since it's coming over the Binder
+
         int encapType, encapLocalPort = 0, encapRemotePort = 0;
         UdpSocketRecord socketRecord = null;
         encapType = c.getEncapType();
         if (encapType != IpSecTransform.ENCAP_NONE) {
-            socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
+            socketRecord = mUdpSocketRecords.getAndCheckOwner(c.getEncapSocketResourceId());
             encapLocalPort = socketRecord.getPort();
             encapRemotePort = c.getEncapRemotePort();
         }
@@ -819,31 +928,30 @@
         for (int direction : DIRECTIONS) {
             IpSecAlgorithm auth = c.getAuthentication(direction);
             IpSecAlgorithm crypt = c.getEncryption(direction);
+            IpSecAlgorithm authCrypt = c.getAuthenticatedEncryption(direction);
 
-            spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
+            spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
             int spi = spis[direction].getSpi();
             try {
-                mSrvConfig.getNetdInstance()
+                mSrvConfig
+                        .getNetdInstance()
                         .ipSecAddSecurityAssociation(
                                 resourceId,
                                 c.getMode(),
                                 direction,
-                                (c.getLocalAddress() != null)
-                                        ? c.getLocalAddress().getHostAddress()
-                                        : "",
-                                (c.getRemoteAddress() != null)
-                                        ? c.getRemoteAddress().getHostAddress()
-                                        : "",
-                                (c.getNetwork() != null)
-                                        ? c.getNetwork().getNetworkHandle()
-                                        : 0,
+                                c.getLocalAddress(),
+                                c.getRemoteAddress(),
+                                (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
                                 spi,
                                 (auth != null) ? auth.getName() : "",
-                                (auth != null) ? auth.getKey() : null,
+                                (auth != null) ? auth.getKey() : new byte[] {},
                                 (auth != null) ? auth.getTruncationLengthBits() : 0,
                                 (crypt != null) ? crypt.getName() : "",
-                                (crypt != null) ? crypt.getKey() : null,
+                                (crypt != null) ? crypt.getKey() : new byte[] {},
                                 (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                                (authCrypt != null) ? authCrypt.getName() : "",
+                                (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+                                (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
                                 encapType,
                                 encapLocalPort,
                                 encapRemotePort);
@@ -879,7 +987,7 @@
         // Synchronize liberally here because we are using ManagedResources in this block
         TransformRecord info;
         // FIXME: this code should be factored out into a security check + getter
-        info = mTransformRecords.get(resourceId);
+        info = mTransformRecords.getAndCheckOwner(resourceId);
 
         if (info == null) {
             throw new IllegalArgumentException("Transform " + resourceId + " is not active");
@@ -899,12 +1007,8 @@
                                 socket.getFileDescriptor(),
                                 resourceId,
                                 direction,
-                                (c.getLocalAddress() != null)
-                                        ? c.getLocalAddress().getHostAddress()
-                                        : "",
-                                (c.getRemoteAddress() != null)
-                                        ? c.getRemoteAddress().getHostAddress()
-                                        : "",
+                                c.getLocalAddress(),
+                                c.getRemoteAddress(),
                                 info.getSpiRecord(direction).getSpi());
             }
         } catch (ServiceSpecificException e) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 340d672..0fd59ea 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.annotation.NonNull;
-import android.content.pm.PackageManagerInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import com.android.internal.content.PackageMonitor;
@@ -56,6 +55,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 2f95aa2..c60d7b0 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -1140,17 +1140,6 @@
     }
 
     @Override
-    public void setInterfaceIpv6NdOffload(String iface, boolean enable) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute(
-                    "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable"));
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
     public void addRoute(int netId, RouteInfo route) {
         modifyRoute("add", "" + netId, route);
     }
@@ -1991,8 +1980,13 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
+        final boolean useTls = Settings.Global.getInt(resolver,
+                Settings.Global.DNS_TLS_DISABLED, 0) == 0;
+        final String tlsHostname = "";
+        final String[] tlsFingerprints = new String[0];
         try {
-            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params);
+            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params,
+                    useTls, tlsHostname, tlsFingerprints);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c7e22be..bfbce40 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IStoraged;
 import android.os.IVold;
 import android.os.IVoldListener;
 import android.os.IVoldTaskListener;
@@ -386,17 +387,6 @@
         }
     }
 
-    private static String escapeNull(String arg) {
-        if (TextUtils.isEmpty(arg)) {
-            return "!";
-        } else {
-            if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
-                throw new IllegalArgumentException(arg);
-            }
-            return arg;
-        }
-    }
-
     /** List of crypto types.
       * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
       * corresponding commands in CommandListener.cpp */
@@ -406,6 +396,7 @@
     private final Context mContext;
 
     private volatile IVold mVold;
+    private volatile IStoraged mStoraged;
 
     private volatile boolean mSystemReady = false;
     private volatile boolean mBootCompleted = false;
@@ -416,10 +407,6 @@
     private final Callbacks mCallbacks;
     private final LockPatternUtils mLockPatternUtils;
 
-    private final Object mUnmountLock = new Object();
-    @GuardedBy("mUnmountLock")
-    private CountDownLatch mUnmountSignal;
-
     /**
      * The size of the crypto algorithm key in bits for OBB files. Currently
      * Twofish is used which takes 128-bit keys.
@@ -654,8 +641,8 @@
                     break;
                 }
                 case H_PARTITION_FORGET: {
-                    final String partGuid = (String) msg.obj;
-                    forgetPartition(partGuid);
+                    final VolumeRecord rec = (VolumeRecord) msg.obj;
+                    forgetPartition(rec.partGuid, rec.fsUuid);
                     break;
                 }
                 case H_RESET: {
@@ -699,18 +686,6 @@
         }
     };
 
-    @Override
-    public void waitForAsecScan() {
-        throw new UnsupportedOperationException();
-    }
-
-    private void waitForLatch(CountDownLatch latch, String condition) {
-        try {
-            waitForLatch(latch, condition, -1);
-        } catch (TimeoutException ignored) {
-        }
-    }
-
     private void waitForLatch(CountDownLatch latch, String condition, long timeoutMillis)
             throws TimeoutException {
         final long startMillis = SystemClock.elapsedRealtime();
@@ -836,6 +811,7 @@
                 }
                 for (int userId : systemUnlockedUsers) {
                     mVold.onUserStarted(userId);
+                    mStoraged.onUserStarted(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -851,6 +827,7 @@
         // bind mount against.
         try {
             mVold.onUserStarted(userId);
+            mStoraged.onUserStarted(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -877,6 +854,7 @@
 
         try {
             mVold.onUserStopped(userId);
+            mStoraged.onUserStopped(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1362,13 +1340,36 @@
     }
 
     private void connect() {
-        IBinder binder = ServiceManager.getService("vold");
+        IBinder binder = ServiceManager.getService("storaged");
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "storaged died; reconnecting");
+                        mStoraged = null;
+                        connect();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                binder = null;
+            }
+        }
+
+        if (binder != null) {
+            mStoraged = IStoraged.Stub.asInterface(binder);
+        } else {
+            Slog.w(TAG, "storaged not found; trying again");
+        }
+
+        binder = ServiceManager.getService("vold");
         if (binder != null) {
             try {
                 binder.linkToDeath(new DeathRecipient() {
                     @Override
                     public void binderDied() {
                         Slog.w(TAG, "vold died; reconnecting");
+                        mVold = null;
                         connect();
                     }
                 }, 0);
@@ -1381,18 +1382,21 @@
             mVold = IVold.Stub.asInterface(binder);
             try {
                 mVold.setListener(mListener);
-                onDaemonConnected();
-                return;
             } catch (RemoteException e) {
+                mVold = null;
                 Slog.w(TAG, "vold listener rejected; trying again", e);
             }
         } else {
             Slog.w(TAG, "vold not found; trying again");
         }
 
-        BackgroundThread.getHandler().postDelayed(() -> {
-            connect();
-        }, DateUtils.SECOND_IN_MILLIS);
+        if (mStoraged == null || mVold == null) {
+            BackgroundThread.getHandler().postDelayed(() -> {
+                connect();
+            }, DateUtils.SECOND_IN_MILLIS);
+        } else {
+            onDaemonConnected();
+        }
     }
 
     private void systemReady() {
@@ -1533,48 +1537,6 @@
     }
 
     @Override
-    public boolean isUsbMassStorageConnected() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setUsbMassStorageEnabled(boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isUsbMassStorageEnabled() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getVolumeState(String mountPoint) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isExternalStorageEmulated() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int mountVolume(String path) {
-        mount(findVolumeIdForPathOrThrow(path));
-        return 0;
-    }
-
-    @Override
-    public void unmountVolume(String path, boolean force, boolean removeEncryption) {
-        unmount(findVolumeIdForPathOrThrow(path));
-    }
-
-    @Override
-    public int formatVolume(String path) {
-        format(findVolumeIdForPathOrThrow(path));
-        return 0;
-    }
-
-    @Override
     public void mount(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
 
@@ -1594,22 +1556,6 @@
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
-
-        // TODO: expand PMS to know about multiple volumes
-        if (vol.isPrimaryPhysical()) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                synchronized (mUnmountLock) {
-                    mUnmountSignal = new CountDownLatch(1);
-                    mPms.updateExternalMediaStatus(false, true);
-                    waitForLatch(mUnmountSignal, "mUnmountSignal");
-                    mUnmountSignal = null;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
         try {
             mVold.unmount(vol.id);
         } catch (Exception e) {
@@ -1748,7 +1694,7 @@
         synchronized (mLock) {
             final VolumeRecord rec = mRecords.remove(fsUuid);
             if (rec != null && !TextUtils.isEmpty(rec.partGuid)) {
-                mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+                mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
             }
             mCallbacks.notifyVolumeForgotten(fsUuid);
 
@@ -1772,7 +1718,7 @@
                 final String fsUuid = mRecords.keyAt(i);
                 final VolumeRecord rec = mRecords.valueAt(i);
                 if (!TextUtils.isEmpty(rec.partGuid)) {
-                    mHandler.obtainMessage(H_PARTITION_FORGET, rec.partGuid).sendToTarget();
+                    mHandler.obtainMessage(H_PARTITION_FORGET, rec).sendToTarget();
                 }
                 mCallbacks.notifyVolumeForgotten(fsUuid);
             }
@@ -1787,9 +1733,9 @@
         }
     }
 
-    private void forgetPartition(String partGuid) {
+    private void forgetPartition(String partGuid, String fsUuid) {
         try {
-            mVold.forgetPartition(partGuid);
+            mVold.forgetPartition(partGuid, fsUuid);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -2004,11 +1950,6 @@
         }
     }
 
-    @Override
-    public int[] getStorageUsers(String path) {
-        throw new UnsupportedOperationException();
-    }
-
     private void warnOnNotMounted() {
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
@@ -2023,79 +1964,6 @@
         Slog.w(TAG, "No primary storage mounted!");
     }
 
-    @Override
-    public String[] getSecureContainerList() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
-            int ownerUid, boolean external) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int resizeSecureContainer(String id, int sizeMb, String key) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int finalizeSecureContainer(String id) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int destroySecureContainer(String id, boolean force) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int unmountSecureContainer(String id, boolean force) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isSecureContainerMounted(String id) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int renameSecureContainer(String oldId, String newId) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getSecureContainerPath(String id) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getSecureContainerFilesystemPath(String id) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void finishMediaUpdate() {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("no permission to call finishMediaUpdate()");
-        }
-        if (mUnmountSignal != null) {
-            mUnmountSignal.countDown();
-        } else {
-            Slog.w(TAG, "Odd, nobody asked to unmount?");
-        }
-    }
-
     private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
         if (callerUid == android.os.Process.SYSTEM_UID) {
             return true;
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 94397d0..58731d2 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -64,6 +64,11 @@
     public static final int PHASE_SYSTEM_SERVICES_READY = 500;
 
     /**
+     * After receiving this boot phase, services can safely call into device specific services.
+     */
+    public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520;
+
+    /**
      * After receiving this boot phase, services can broadcast Intents.
      */
     public static final int PHASE_ACTIVITY_MANAGER_READY = 550;
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 046eb76..8b79b9d 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -373,12 +373,24 @@
             if (mCurrentVibration.hasLongerTimeout(newOneShot.getTiming())
                     && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {
                 if (DEBUG) {
-                    Slog.e(TAG, "Ignoring incoming vibration in favor of current vibration");
+                    Slog.d(TAG, "Ignoring incoming vibration in favor of current vibration");
                 }
                 return;
             }
         }
 
+        // If the current vibration is repeating and the incoming one is non-repeating, then ignore
+        // the non-repeating vibration. This is so that we don't cancel vibrations that are meant
+        // to grab the attention of the user, like ringtones and alarms, in favor of one-shot
+        // vibrations that are likely quite short.
+        if (!isRepeatingVibration(effect)
+                && mCurrentVibration != null && isRepeatingVibration(mCurrentVibration.mEffect)) {
+            if (DEBUG) {
+                Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+            }
+            return;
+        }
+
         Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);
 
         // Only link against waveforms since they potentially don't have a finish if
@@ -404,6 +416,16 @@
         }
     }
 
+    private static boolean isRepeatingVibration(VibrationEffect effect) {
+        if (effect instanceof VibrationEffect.Waveform) {
+            final VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
+            if (waveform.getRepeatIndex() >= 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void addToPreviousVibrationsLocked(Vibration vib) {
         if (mPreviousVibrations.size() > mPreviousVibrationsLimit) {
             mPreviousVibrations.removeFirst();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 98e08e0..0d4f5cb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2248,12 +2248,10 @@
                         Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
                                 + response);
                     }
-                    Bundle result2 = new Bundle();
-                    result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
                     try {
-                        response.onResult(result2);
+                        response.onResult(result);
                     } catch (RemoteException e) {
-                        // ignore
+                        Slog.e(TAG, "Error calling onResult()", e);
                     }
                 }
             }
@@ -2969,9 +2967,13 @@
                              * have users launching arbitrary activities by tricking users to
                              * interact with malicious notifications.
                              */
-                            checkKeyIntent(
+                            if (!checkKeyIntent(
                                     Binder.getCallingUid(),
-                                    intent);
+                                    intent)) {
+                                onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                        "invalid intent in bundle returned");
+                                return;
+                            }
                             doNotification(
                                     mAccounts,
                                     account,
@@ -3366,9 +3368,13 @@
             Intent intent = null;
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
-                checkKeyIntent(
+                if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent);
+                        intent)) {
+                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "invalid intent in bundle returned");
+                    return;
+                }
             }
             IAccountManagerResponse response;
             if (mExpectActivityLaunch && result != null
@@ -4716,9 +4722,7 @@
          * into launching arbitrary intents on the device via by tricking to click authenticator
          * supplied entries in the system Settings app.
          */
-        protected void checkKeyIntent(
-                int authUid,
-                Intent intent) throws SecurityException {
+         protected boolean checkKeyIntent(int authUid, Intent intent) {
             intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -4727,6 +4731,9 @@
             try {
                 PackageManager pm = mContext.getPackageManager();
                 ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                if (resolveInfo == null) {
+                    return false;
+                }
                 ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
                 int targetUid = targetActivityInfo.applicationInfo.uid;
                 if (!isExportedSystemActivity(targetActivityInfo)
@@ -4736,9 +4743,10 @@
                     String activityName = targetActivityInfo.name;
                     String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
                             + "does not share a signature with the supplying authenticator (%s).";
-                    throw new SecurityException(
-                            String.format(tmpl, activityName, pkgName, mAccountType));
+                    Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
+                    return false;
                 }
+                return true;
             } finally {
                 Binder.restoreCallingIdentity(bid);
             }
@@ -4888,9 +4896,13 @@
             }
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
-                checkKeyIntent(
+                if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent);
+                        intent)) {
+                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "invalid intent in bundle returned");
+                    return;
+                }
             }
             if (result != null
                     && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
@@ -5089,8 +5101,16 @@
                 logStatement.bindLong(4, callingUid);
                 logStatement.bindString(5, tableName);
                 logStatement.bindLong(6, userDebugDbInsertionPoint);
-                logStatement.execute();
-                logStatement.clearBindings();
+                try {
+                    logStatement.execute();
+                } catch (IllegalStateException e) {
+                    // Guard against crash, DB can already be closed
+                    // since this statement is executed on a handler thread
+                    Slog.w(TAG, "Failed to insert a log record. accountId=" + accountId
+                            + " action=" + action + " tableName=" + tableName + " Error: " + e);
+                } finally {
+                    logStatement.clearBindings();
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 90ad8a5..2131731 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3150,7 +3150,7 @@
                         sr.userId, sr.crashCount, sr.shortName, app.pid);
                 bringDownServiceLocked(sr);
             } else if (!allowRestart
-                    || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
+                    || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
                 bringDownServiceLocked(sr);
             } else {
                 boolean canceled = scheduleServiceRestartLocked(sr, true);
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
new file mode 100644
index 0000000..c04ddf8
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -0,0 +1,615 @@
+/*
+ * 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.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.proto.ActivityDisplayProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
+import static com.android.server.am.proto.ActivityDisplayProto.ID;
+
+import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.ConfigurationContainer;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Exactly one of these classes per Display in the system. Capable of holding zero or more
+ * attached {@link ActivityStack}s.
+ */
+class ActivityDisplay extends ConfigurationContainer<ActivityStack> {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
+    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+
+    static final int POSITION_TOP = Integer.MAX_VALUE;
+    static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
+    private ActivityStackSupervisor mSupervisor;
+    /** Actual Display this object tracks. */
+    int mDisplayId;
+    Display mDisplay;
+
+    /** All of the stacks on this display. Order matters, topmost stack is in front of all other
+     * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
+    private final ArrayList<ActivityStack> mStacks = new ArrayList<>();
+
+    /** Array of all UIDs that are present on the display. */
+    private IntArray mDisplayAccessUIDs = new IntArray();
+
+    /** All tokens used to put activities on this stack to sleep (including mOffToken) */
+    final ArrayList<ActivityManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
+    /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
+    ActivityManagerInternal.SleepToken mOffToken;
+
+    private boolean mSleeping;
+
+    // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+    // through the list to find them.
+    private ActivityStack mHomeStack = null;
+    private ActivityStack mRecentsStack = null;
+    private ActivityStack mPinnedStack = null;
+    private ActivityStack mSplitScreenPrimaryStack = null;
+
+    ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
+        mSupervisor = supervisor;
+        mDisplayId = displayId;
+        final Display display = supervisor.mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            throw new IllegalStateException("Display does not exist displayId=" + displayId);
+        }
+        mDisplay = display;
+    }
+
+    void addChild(ActivityStack stack, int position) {
+        if (position == POSITION_BOTTOM) {
+            position = 0;
+        } else if (position == POSITION_TOP) {
+            position = mStacks.size();
+        }
+        if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
+                + " to displayId=" + mDisplayId + " position=" + position);
+        addStackReferenceIfNeeded(stack);
+        positionChildAt(stack, position);
+        mSupervisor.mService.updateSleepIfNeededLocked();
+    }
+
+    void removeChild(ActivityStack stack) {
+        if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
+                + " from displayId=" + mDisplayId);
+        mStacks.remove(stack);
+        removeStackReferenceIfNeeded(stack);
+        mSupervisor.mService.updateSleepIfNeededLocked();
+    }
+
+    void positionChildAtTop(ActivityStack stack) {
+        positionChildAt(stack, mStacks.size());
+    }
+
+    void positionChildAtBottom(ActivityStack stack) {
+        positionChildAt(stack, 0);
+    }
+
+    private void positionChildAt(ActivityStack stack, int position) {
+        mStacks.remove(stack);
+        mStacks.add(getTopInsertPosition(stack, position), stack);
+    }
+
+    private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
+        int position = mStacks.size();
+        if (position > 0) {
+            final ActivityStack topStack = mStacks.get(position - 1);
+            if (topStack.getWindowConfiguration().isAlwaysOnTop() && topStack != stack) {
+                // If the top stack is always on top, we move this stack just below it.
+                position--;
+            }
+        }
+        return Math.min(position, candidatePosition);
+    }
+
+    <T extends ActivityStack> T getStack(int stackId) {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.mStackId == stackId) {
+                return (T) stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the topmost stack on the display that is compatible with the input windowing mode and
+     * activity type. {@code null} means no compatible stack on the display.
+     * @see ConfigurationContainer#isCompatible(int, int)
+     */
+    <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        if (activityType == ACTIVITY_TYPE_HOME) {
+            return (T) mHomeStack;
+        } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+            return (T) mRecentsStack;
+        }
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            return (T) mPinnedStack;
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            return (T) mSplitScreenPrimaryStack;
+        }
+
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.isCompatible(windowingMode, activityType)) {
+                return (T) stack;
+            }
+        }
+        return null;
+    }
+
+    private boolean alwaysCreateStack(int windowingMode, int activityType) {
+        // Always create a stack for fullscreen, freeform, and split-screen-secondary windowing
+        // modes so that we can manage visual ordering and return types correctly.
+        return activityType == ACTIVITY_TYPE_STANDARD
+                && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_FREEFORM
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+    }
+
+    /**
+     * Returns an existing stack compatible with the windowing mode and activity type or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getStack(int, int)
+     * @see #createStack(int, int, boolean)
+     */
+    <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
+            boolean onTop) {
+        if (!alwaysCreateStack(windowingMode, activityType)) {
+            T stack = getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return createStack(windowingMode, activityType, onTop);
+    }
+
+    /**
+     * Returns an existing stack compatible with the input params or creates one
+     * if a compatible stack doesn't exist.
+     * @see #getOrCreateStack(int, int, boolean)
+     */
+    <T extends ActivityStack> T getOrCreateStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, int activityType,
+            boolean onTop) {
+        final int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+        return getOrCreateStack(windowingMode, activityType, onTop);
+    }
+
+    /**
+     * Creates a stack matching the input windowing mode and activity type on this display.
+     * @param windowingMode The windowing mode the stack should be created in. If
+     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+     *                      be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+     * @param activityType The activityType the stack should be created in. If
+     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+     * @return The newly created stack.
+     */
+    <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) {
+
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+            // anything else should be passing it in anyways...
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+
+        if (activityType != ACTIVITY_TYPE_STANDARD) {
+            // For now there can be only one stack of a particular non-standard activity type on a
+            // display. So, get that ignoring whatever windowing mode it is currently in.
+            T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+            if (stack != null) {
+                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+                        + activityType + " already on display=" + this + ". Can't have multiple.");
+            }
+        }
+
+        final ActivityManagerService service = mSupervisor.mService;
+        if (!isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+                service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
+                service.mSupportsPictureInPicture, activityType)) {
+            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+                    + windowingMode);
+        }
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            // TODO: Should be okay to have stacks with with undefined windowing mode long term, but
+            // have to set them to something for now due to logic that depending on them.
+            windowingMode = getWindowingMode(); // Put in current display's windowing mode
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Else fullscreen for now...
+                windowingMode = WINDOWING_MODE_FULLSCREEN;
+            }
+        }
+
+        final int stackId = mSupervisor.getNextStackId();
+        return createStackUnchecked(windowingMode, activityType, stackId, onTop);
+    }
+
+    @VisibleForTesting
+    <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+            int stackId, boolean onTop) {
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
+        }
+        final T stack = (T) new ActivityStack(
+                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
+
+        if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            // Make sure recents stack exist when creating a dock stack as it normally needs to be
+            // on the other side of the docked stack and we make visibility decisions based on that.
+            // TODO: Not sure if this is needed after we change to calculate visibility based on
+            // stack z-order vs. id.
+            getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
+        }
+        return stack;
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        if (windowingModes == null || windowingModes.length == 0) {
+            return;
+        }
+
+        for (int j = windowingModes.length - 1 ; j >= 0; --j) {
+            final int windowingMode = windowingModes[j];
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = mStacks.get(i);
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    continue;
+                }
+                if (stack.getWindowingMode() != windowingMode) {
+                    continue;
+                }
+                mSupervisor.removeStack(stack);
+            }
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        if (activityTypes == null || activityTypes.length == 0) {
+            return;
+        }
+
+        for (int j = activityTypes.length - 1 ; j >= 0; --j) {
+            final int activityType = activityTypes[j];
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = mStacks.get(i);
+                if (stack.getActivityType() == activityType) {
+                    mSupervisor.removeStack(stack);
+                }
+            }
+        }
+    }
+
+    void onStackWindowingModeChanged(ActivityStack stack) {
+        removeStackReferenceIfNeeded(stack);
+        addStackReferenceIfNeeded(stack);
+    }
+
+    private void addStackReferenceIfNeeded(ActivityStack stack) {
+        final int activityType = stack.getActivityType();
+        final int windowingMode = stack.getWindowingMode();
+
+        if (activityType == ACTIVITY_TYPE_HOME) {
+            if (mHomeStack != null && mHomeStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+                        + mHomeStack + " already exist on display=" + this + " stack=" + stack);
+            }
+            mHomeStack = stack;
+        } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+            if (mRecentsStack != null && mRecentsStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded: recents stack="
+                        + mRecentsStack + " already exist on display=" + this + " stack=" + stack);
+            }
+            mRecentsStack = stack;
+        }
+        if (windowingMode == WINDOWING_MODE_PINNED) {
+            if (mPinnedStack != null && mPinnedStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
+                        + mPinnedStack + " already exist on display=" + this
+                        + " stack=" + stack);
+            }
+            mPinnedStack = stack;
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            if (mSplitScreenPrimaryStack != null && mSplitScreenPrimaryStack != stack) {
+                throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+                        + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+                        + " already exist on display=" + this + " stack=" + stack);
+            }
+            mSplitScreenPrimaryStack = stack;
+        }
+    }
+
+    private void removeStackReferenceIfNeeded(ActivityStack stack) {
+        if (stack == mHomeStack) {
+            mHomeStack = null;
+        } else if (stack == mRecentsStack) {
+            mRecentsStack = null;
+        } else if (stack == mPinnedStack) {
+            mPinnedStack = null;
+        } else if (stack == mSplitScreenPrimaryStack) {
+            mSplitScreenPrimaryStack = null;
+        }
+    }
+
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    private boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+                    windowingMode, activityType);
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+                // Use the display's windowing mode.
+                windowingMode = getWindowingMode();
+            }
+        }
+
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        final ActivityManagerService service = mSupervisor.mService;
+        boolean supportsMultiWindow = service.mSupportsMultiWindow;
+        boolean supportsSplitScreen = service.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = service.mSupportsFreeformWindowManagement;
+        boolean supportsPip = service.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        final boolean inSplitScreenMode = hasSplitScreenPrimaryStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+                && supportsSplitScreen) {
+            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+
+        if (windowingMode != WINDOWING_MODE_UNDEFINED
+                && isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                supportsFreeform, supportsPip, activityType)) {
+            return windowingMode;
+        }
+        // Return the display's windowing mode
+        return getWindowingMode();
+    }
+
+    /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
+    int getTopVisibleStackActivityType(int excludeWindowingMode) {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.getWindowingMode() == excludeWindowingMode) {
+                continue;
+            }
+            if (stack.shouldBeVisible(null /* starting */)) {
+                return stack.getActivityType();
+            }
+        }
+        return ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Get the topmost stack on the display. It may be different from focused stack, because
+     * focus may be on another display.
+     */
+    ActivityStack getTopStack() {
+        return mStacks.isEmpty() ? null : mStacks.get(mStacks.size() - 1);
+    }
+
+    boolean isTopStack(ActivityStack stack) {
+        return stack == getTopStack();
+    }
+
+    int getIndexOf(ActivityStack stack) {
+        return mStacks.indexOf(stack);
+    }
+
+    void onLockTaskPackagesUpdated() {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            mStacks.get(i).onLockTaskPackagesUpdated();
+        }
+    }
+
+    /** We are in the process of exiting split-screen mode. */
+    void onExitingSplitScreenMode() {
+        // Remove reference to the primary-split-screen stack so it no longer has any effect on the
+        // display. For example, we want to be able to create fullscreen stack for standard activity
+        // types when exiting split-screen mode.
+        mSplitScreenPrimaryStack = null;
+    }
+
+    ActivityStack getSplitScreenPrimaryStack() {
+        return mSplitScreenPrimaryStack;
+    }
+
+    boolean hasSplitScreenPrimaryStack() {
+        return mSplitScreenPrimaryStack != null;
+    }
+
+    PinnedActivityStack getPinnedStack() {
+        return (PinnedActivityStack) mPinnedStack;
+    }
+
+    boolean hasPinnedStack() {
+        return mPinnedStack != null;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
+    }
+
+    @Override
+    protected int getChildCount() {
+        return mStacks.size();
+    }
+
+    @Override
+    protected ActivityStack getChildAt(int index) {
+        return mStacks.get(index);
+    }
+
+    @Override
+    protected ConfigurationContainer getParent() {
+        return mSupervisor;
+    }
+
+    boolean isPrivate() {
+        return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+    }
+
+    boolean isUidPresent(int uid) {
+        for (ActivityStack stack : mStacks) {
+            if (stack.isUidPresent(uid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Update and get all UIDs that are present on the display and have access to it. */
+    IntArray getPresentUIDs() {
+        mDisplayAccessUIDs.clear();
+        for (ActivityStack stack : mStacks) {
+            stack.getPresentUIDs(mDisplayAccessUIDs);
+        }
+        return mDisplayAccessUIDs;
+    }
+
+    boolean shouldDestroyContentOnRemove() {
+        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+    }
+
+    boolean shouldSleep() {
+        return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
+                && (mSupervisor.mService.mRunningVoice == null);
+    }
+
+    boolean isSleeping() {
+        return mSleeping;
+    }
+
+    void setIsSleeping(boolean asleep) {
+        mSleeping = asleep;
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "displayId=" + mDisplayId + " mStacks=" + mStacks);
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
+        proto.write(ID, mDisplayId);
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.writeToProto(proto, STACKS);
+        }
+        proto.end(token);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index 3a9bf12..ceb2ad6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -59,7 +59,6 @@
     static final boolean DEBUG_FOCUS = false;
     static final boolean DEBUG_IDLE = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_IMMERSIVE = DEBUG_ALL || false;
-    static final boolean DEBUG_LOCKSCREEN = DEBUG_ALL || false;
     static final boolean DEBUG_LOCKTASK = DEBUG_ALL || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
@@ -74,10 +73,10 @@
     static final boolean DEBUG_PROVIDER = DEBUG_ALL || false;
     static final boolean DEBUG_PSS = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
+    static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
     static final boolean DEBUG_RELEASE = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_RESULTS = DEBUG_ALL || false;
     static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
-    static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
     static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
@@ -85,7 +84,6 @@
     static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;
     static final boolean DEBUG_SWITCH = DEBUG_ALL || false;
     static final boolean DEBUG_TASKS = DEBUG_ALL || false;
-    static final boolean DEBUG_THUMBNAILS = DEBUG_ALL || false;
     static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
     static final boolean DEBUG_UID_OBSERVERS = DEBUG_ALL || false;
     static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false;
@@ -105,7 +103,6 @@
     static final String POSTFIX_FOCUS = (APPEND_CATEGORY_NAME) ? "_Focus" : "";
     static final String POSTFIX_IDLE = (APPEND_CATEGORY_NAME) ? "_Idle" : "";
     static final String POSTFIX_IMMERSIVE = (APPEND_CATEGORY_NAME) ? "_Immersive" : "";
-    static final String POSTFIX_LOCKSCREEN = (APPEND_CATEGORY_NAME) ? "_LockScreen" : "";
     static final String POSTFIX_LOCKTASK = (APPEND_CATEGORY_NAME) ? "_LockTask" : "";
     static final String POSTFIX_LRU = (APPEND_CATEGORY_NAME) ? "_LRU" : "";
     static final String POSTFIX_MU = "_MU";
@@ -122,7 +119,6 @@
     static final String POSTFIX_RELEASE = (APPEND_CATEGORY_NAME) ? "_Release" : "";
     static final String POSTFIX_RESULTS = (APPEND_CATEGORY_NAME) ? "_Results" : "";
     static final String POSTFIX_SAVED_STATE = (APPEND_CATEGORY_NAME) ? "_SavedState" : "";
-    static final String POSTFIX_SCREENSHOTS = (APPEND_CATEGORY_NAME) ? "_Screenshots" : "";
     static final String POSTFIX_SERVICE = (APPEND_CATEGORY_NAME) ? "_Service" : "";
     static final String POSTFIX_SERVICE_EXECUTING =
             (APPEND_CATEGORY_NAME) ? "_ServiceExecuting" : "";
@@ -130,13 +126,11 @@
     static final String POSTFIX_STATES = (APPEND_CATEGORY_NAME) ? "_States" : "";
     static final String POSTFIX_SWITCH = (APPEND_CATEGORY_NAME) ? "_Switch" : "";
     static final String POSTFIX_TASKS = (APPEND_CATEGORY_NAME) ? "_Tasks" : "";
-    static final String POSTFIX_THUMBNAILS = (APPEND_CATEGORY_NAME) ? "_Thumbnails" : "";
     static final String POSTFIX_TRANSITION = (APPEND_CATEGORY_NAME) ? "_Transition" : "";
     static final String POSTFIX_UID_OBSERVERS = (APPEND_CATEGORY_NAME)
             ? "_UidObservers" : "";
     static final String POSTFIX_URI_PERMISSION = (APPEND_CATEGORY_NAME) ? "_UriPermission" : "";
     static final String POSTFIX_USER_LEAVING = (APPEND_CATEGORY_NAME) ? "_UserLeaving" : "";
     static final String POSTFIX_VISIBILITY = (APPEND_CATEGORY_NAME) ? "_Visibility" : "";
-    static final String POSTFIX_VISIBLE_BEHIND = (APPEND_CATEGORY_NAME) ? "_VisibleBehind" : "";
 
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 12778d8..e98bb1a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
 import static android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -23,16 +24,27 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS;
 import static android.Manifest.permission.READ_FRAME_BUFFER;
+import static android.Manifest.permission.REMOVE_TASKS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -49,6 +61,10 @@
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
 import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground;
 import static android.os.Build.VERSION_CODES.N;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.os.Process.BLUETOOTH_UID;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.FIRST_ISOLATED_UID;
@@ -129,7 +145,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
@@ -164,7 +179,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY;
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
@@ -175,9 +189,7 @@
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
-import static com.android.server.am.proto.ActivityManagerServiceProto.ACTIVITIES;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
-import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
@@ -193,7 +205,6 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityManagerInternal;
@@ -210,7 +221,6 @@
 import android.app.Dialog;
 import android.app.IActivityController;
 import android.app.IActivityManager;
-import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.IInstrumentationWatcher;
 import android.app.INotificationManager;
@@ -229,6 +239,8 @@
 import android.app.ProfilerInfo;
 import android.app.RemoteAction;
 import android.app.WaitResult;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
 import android.app.admin.DevicePolicyManager;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -258,8 +270,8 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
 import android.content.pm.PermissionInfo;
@@ -327,7 +339,6 @@
 import android.provider.Settings;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
-import android.service.voice.VoiceInteractionSession;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -336,6 +347,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.StatsLog;
 import android.util.TimingsTraceLog;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
@@ -361,6 +373,7 @@
 import com.android.internal.app.DumpHeapActivity;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.SystemUserHomeActivity;
@@ -371,6 +384,7 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.BinderInternal;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.ProcessCpuTracker;
 import com.android.internal.os.TransferPipe;
@@ -398,6 +412,9 @@
 import com.android.server.ThreadPriorityBooster;
 import com.android.server.Watchdog;
 import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.proto.ActivityManagerServiceProto;
+import com.android.server.am.proto.BroadcastProto;
+import com.android.server.am.proto.StickyBroadcastProto;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
@@ -665,7 +682,7 @@
     /**
      * List of intents that were used to start the most recent tasks.
      */
-    final RecentTasks mRecentTasks;
+    private final RecentTasks mRecentTasks;
 
     /**
      * For addAppTask: cached of the last activity component that was added.
@@ -714,39 +731,44 @@
      */
     private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
         @Override
-        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, new String[] {"activities"});
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
+            if (asProto) return;
+            doDump(fd, pw, new String[]{"activities"}, asProto);
         }
 
         @Override
-        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, new String[] {"settings"});
-            doDump(fd, pw, new String[] {"intents"});
-            doDump(fd, pw, new String[] {"broadcasts"});
-            doDump(fd, pw, new String[] {"providers"});
-            doDump(fd, pw, new String[] {"permissions"});
-            doDump(fd, pw, new String[] {"services"});
-            doDump(fd, pw, new String[] {"recents"});
-            doDump(fd, pw, new String[] {"lastanr"});
-            doDump(fd, pw, new String[] {"starter"});
-            if (mAssociations.size() > 0) {
-                doDump(fd, pw, new String[] {"associations"});
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            if (asProto) {
+                doDump(fd, pw, new String[0], asProto);
+            } else {
+                doDump(fd, pw, new String[]{"settings"}, asProto);
+                doDump(fd, pw, new String[]{"intents"}, asProto);
+                doDump(fd, pw, new String[]{"broadcasts"}, asProto);
+                doDump(fd, pw, new String[]{"providers"}, asProto);
+                doDump(fd, pw, new String[]{"permissions"}, asProto);
+                doDump(fd, pw, new String[]{"services"}, asProto);
+                doDump(fd, pw, new String[]{"recents"}, asProto);
+                doDump(fd, pw, new String[]{"lastanr"}, asProto);
+                doDump(fd, pw, new String[]{"starter"}, asProto);
+                if (mAssociations.size() > 0) {
+                    doDump(fd, pw, new String[]{"associations"}, asProto);
+                }
+                doDump(fd, pw, new String[]{"processes"}, asProto);
             }
-            doDump(fd, pw, new String[] {"processes"});
-            doDump(fd, pw, new String[] {"-v", "all"});
-            doDump(fd, pw, new String[] {"service", "all"});
-            doDump(fd, pw, new String[] {"provider", "all"});
         }
 
         @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, args);
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, args, asProto);
         }
     };
 
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
                 && !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)
+                && !mUserController.hasUserRestriction(UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
+                        mUserController.getCurrentUserId())
                 && !(UserManager.isDeviceInDemoMode(mContext)
                         && mUserController.getCurrentUser().isDemo());
     }
@@ -768,7 +790,7 @@
         public final Bundle extras;
         public final Intent intent;
         public final String hint;
-        public final IResultReceiver receiver;
+        public final IAssistDataReceiver receiver;
         public final int userHandle;
         public boolean haveResult = false;
         public Bundle result = null;
@@ -777,7 +799,8 @@
         public Bundle receiverExtras;
 
         public PendingAssistExtras(ActivityRecord _activity, Bundle _extras, Intent _intent,
-                String _hint, IResultReceiver _receiver, Bundle _receiverExtras, int _userHandle) {
+                String _hint, IAssistDataReceiver _receiver, Bundle _receiverExtras,
+                int _userHandle) {
             activity = _activity;
             extras = _extras;
             intent = _intent;
@@ -798,8 +821,7 @@
         }
     }
 
-    final ArrayList<PendingAssistExtras> mPendingAssistExtras
-            = new ArrayList<PendingAssistExtras>();
+    final ArrayList<PendingAssistExtras> mPendingAssistExtras = new ArrayList<>();
 
     /**
      * Process management.
@@ -1711,6 +1733,7 @@
     static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
+    static final int NOTIFY_VR_KEYGUARD_MSG = 74;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1730,9 +1753,6 @@
      */
     private boolean mUserIsMonkey;
 
-    /** Flag whether the device has a Recents UI */
-    boolean mHasRecents;
-
     /** The dimensions of the thumbnails in the Recents UI. */
     int mThumbnailWidth;
     int mThumbnailHeight;
@@ -2033,7 +2053,9 @@
                 synchronized (ActivityManagerService.this) {
                     for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
                         ProcessRecord r = mLruProcesses.get(i);
-                        if (r.thread != null) {
+                        // Don't dispatch to isolated processes as they can't access
+                        // ConnectivityManager and don't have network privileges anyway.
+                        if (r.thread != null && !r.isolated) {
                             try {
                                 r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
                             } catch (RemoteException ex) {
@@ -2092,7 +2114,8 @@
                     String text = mContext.getString(R.string.heavy_weight_notification,
                             context.getApplicationInfo().loadLabel(context.getPackageManager()));
                     Notification notification =
-                            new Notification.Builder(context, SystemNotificationChannels.DEVELOPER)
+                            new Notification.Builder(context,
+                                    SystemNotificationChannels.HEAVY_WEIGHT_APP)
                             .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
                             .setWhen(0)
                             .setOngoing(true)
@@ -2126,7 +2149,7 @@
                 }
                 try {
                     inm.cancelNotificationWithTag("android", null,
-                            SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION,  msg.arg1);
+                            SystemMessage.NOTE_HEAVY_WEIGHT_NOTIFICATION, msg.arg1);
                 } catch (RuntimeException e) {
                     Slog.w(ActivityManagerService.TAG,
                             "Error canceling notification for service", e);
@@ -2360,17 +2383,16 @@
                     if (disableNonVrUi) {
                         // If we are in a VR mode where Picture-in-Picture mode is unsupported,
                         // then remove the pinned stack.
-                        final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
-                                PINNED_STACK_ID);
-                        if (pinnedStack != null) {
-                            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
-                        }
+                        mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
                     }
                 }
             } break;
             case NOTIFY_VR_SLEEPING_MSG: {
                 notifyVrManagerOfSleepState(msg.arg1 != 0);
             } break;
+            case NOTIFY_VR_KEYGUARD_MSG: {
+                notifyVrManagerOfKeyguardState(msg.arg1 != 0);
+            } break;
             case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
                 synchronized (ActivityManagerService.this) {
                     for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
@@ -2485,13 +2507,16 @@
 
     public void setSystemProcess() {
         try {
-            ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
+            ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
+                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
             ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
-            ServiceManager.addService("meminfo", new MemBinder(this));
+            ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
+                    DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
             ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
             ServiceManager.addService("dbinfo", new DbBinder(this));
             if (MONITOR_CPU_USAGE) {
-                ServiceManager.addService("cpuinfo", new CpuBinder(this));
+                ServiceManager.addService("cpuinfo", new CpuBinder(this),
+                        /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
             }
             ServiceManager.addService("permission", new PermissionController(this));
             ServiceManager.addService("processinfo", new ProcessInfoService(this));
@@ -2519,10 +2544,11 @@
     }
 
     public void setWindowManager(WindowManagerService wm) {
-        mWindowManager = wm;
-        mStackSupervisor.setWindowManager(wm);
-        mActivityStarter.setWindowManager(wm);
-        mLockTaskController.setWindowManager(wm);
+        synchronized (this) {
+            mWindowManager = wm;
+            mStackSupervisor.setWindowManager(wm);
+            mLockTaskController.setWindowManager(wm);
+        }
     }
 
     public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
@@ -2543,7 +2569,9 @@
         private final PriorityDump.PriorityDumper mPriorityDumper =
                 new PriorityDump.PriorityDumper() {
             @Override
-            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args,
+                    boolean asProto) {
+                if (asProto) return;
                 mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
             }
         };
@@ -2593,7 +2621,9 @@
         private final PriorityDump.PriorityDumper mPriorityDumper =
                 new PriorityDump.PriorityDumper() {
             @Override
-            public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+            public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                    boolean asProto) {
+                if (asProto) return;
                 if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                         "cpuinfo", pw)) return;
                 synchronized (mActivityManagerService.mProcessCpuTracker) {
@@ -2762,8 +2792,9 @@
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
-        mActivityStarter = new ActivityStarter(this, mStackSupervisor);
-        mRecentTasks = new RecentTasks(this, mStackSupervisor);
+        mActivityStarter = new ActivityStarter(this);
+        mRecentTasks = createRecentTasks();
+        mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
 
         mProcessCpuThread = new Thread("CpuTracker") {
@@ -2808,6 +2839,14 @@
         return new ActivityStackSupervisor(this, mHandler.getLooper());
     }
 
+    protected RecentTasks createRecentTasks() {
+        return new RecentTasks(this, mStackSupervisor);
+    }
+
+    RecentTasks getRecentTasks() {
+        return mRecentTasks;
+    }
+
     public void setSystemServiceManager(SystemServiceManager mgr) {
         mSystemServiceManager = mgr;
     }
@@ -3030,7 +3069,7 @@
     public void batterySendBroadcast(Intent intent) {
         synchronized (this) {
             broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                    AppOpsManager.OP_NONE, null, false, false,
+                    OP_NONE, null, false, false,
                     -1, SYSTEM_UID, UserHandle.USER_ALL);
         }
     }
@@ -3143,6 +3182,7 @@
             synchronized (this) {
                 final ActivityStack stack = mStackSupervisor.getStack(stackId);
                 if (stack == null) {
+                    Slog.w(TAG, "setFocusedStack: No stack with id=" + stackId);
                     return;
                 }
                 final ActivityRecord r = stack.topRunningActivityLocked();
@@ -3179,7 +3219,8 @@
     /** Sets the task stack listener that gets callbacks when a task stack changes. */
     @Override
     public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "registerTaskStackListener()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "registerTaskStackListener()");
         mTaskChangeNotificationController.registerTaskStackListener(listener);
     }
 
@@ -3188,7 +3229,8 @@
      */
     @Override
     public void unregisterTaskStackListener(ITaskStackListener listener) throws RemoteException {
-         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "unregisterTaskStackListener()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "unregisterTaskStackListener()");
          mTaskChangeNotificationController.unregisterTaskStackListener(listener);
      }
 
@@ -3221,10 +3263,12 @@
         // stack implementation changes in the future, keep in mind that the use of the fullscreen
         // stack is a means to move the activity to the main display and a moveActivityToDisplay()
         // option would be a better choice here.
-        if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) {
+        if (r.requestedVrComponent != null && r.getDisplayId() != DEFAULT_DISPLAY) {
             Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId()
                     + " to main stack for VR");
-            moveTaskToStack(r.getTask().taskId, FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */);
+            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getOrCreateStack(
+                    WINDOWING_MODE_FULLSCREEN, r.getActivityType(), true /* toTop */);
+            moveTaskToStack(r.getTask().taskId, stack.mStackId, true /* toTop */);
         }
         mHandler.sendMessage(
                 mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
@@ -3243,6 +3287,19 @@
         vrService.onSleepStateChanged(isSleeping);
     }
 
+    private void sendNotifyVrManagerOfKeyguardState(boolean isShowing) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(NOTIFY_VR_KEYGUARD_MSG, isShowing ? 1 : 0, 0));
+    }
+
+    private void notifyVrManagerOfKeyguardState(boolean isShowing) {
+        final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+        if (vrService == null) {
+            return;
+        }
+        vrService.onKeyguardStateChanged(isShowing);
+    }
+
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
         msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -4034,7 +4091,7 @@
             ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                     aInfo.applicationInfo.uid, true);
             if (app == null || app.instr == null) {
-                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
                 final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);
                 // For ANR debugging to verify if the user activity is the one that actually
                 // launched.
@@ -4106,7 +4163,7 @@
                 String lastVers = Settings.Secure.getString(
                         resolver, Settings.Secure.LAST_SETUP_SHOWN);
                 if (vers != null && !vers.equals(lastVers)) {
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
                     intent.setComponent(new ComponentName(
                             ri.activityInfo.packageName, ri.activityInfo.name));
                     mActivityStarter.startActivityLocked(null, intent, null /*ephemeralIntent*/,
@@ -4629,15 +4686,7 @@
             Intent intent, String resolvedType, IVoiceInteractionSession session,
             IVoiceInteractor interactor, int startFlags, ProfilerInfo profilerInfo,
             Bundle bOptions, int userId) {
-        if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: startVoiceActivity() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + android.Manifest.permission.BIND_VOICE_INTERACTION;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallingPermission(BIND_VOICE_INTERACTION, "startVoiceActivity()");
         if (session == null || interactor == null) {
             throw new NullPointerException("null session or interactor");
         }
@@ -4652,15 +4701,7 @@
     @Override
     public int startAssistantActivity(String callingPackage, int callingPid, int callingUid,
             Intent intent, String resolvedType, Bundle bOptions, int userId) {
-        if (checkCallingPermission(Manifest.permission.BIND_VOICE_INTERACTION)
-                != PackageManager.PERMISSION_GRANTED) {
-            final String msg = "Permission Denial: startAssistantActivity() from pid="
-                    + Binder.getCallingPid()
-                    + ", uid=" + Binder.getCallingUid()
-                    + " requires " + Manifest.permission.BIND_VOICE_INTERACTION;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallingPermission(BIND_VOICE_INTERACTION, "startAssistantActivity()");
         userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
                 ALLOW_FULL_ONLY, "startAssistantActivity", null);
         return mActivityStarter.startActivityMayWait(null, callingUid, callingPackage, intent,
@@ -4669,6 +4710,49 @@
     }
 
     @Override
+    public int startRecentsActivity(IAssistDataReceiver assistDataReceiver, Bundle bOptions,
+            int userId) {
+        if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+            String msg = "Permission Denial: startRecentsActivity() from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " not recent tasks package";
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final int recentsUid = mRecentTasks.getRecentsComponentUid();
+        final ComponentName recentsComponent = mRecentTasks.getRecentsComponent();
+        final String recentsPackage = recentsComponent.getPackageName();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                // If provided, kick off the request for the assist data in the background before
+                // starting the activity
+                if (assistDataReceiver != null) {
+                    final AppOpsManager appOpsManager = (AppOpsManager)
+                            mContext.getSystemService(Context.APP_OPS_SERVICE);
+                    final AssistDataReceiverProxy proxy = new AssistDataReceiverProxy(
+                            assistDataReceiver, recentsPackage);
+                    final AssistDataRequester requester = new AssistDataRequester(mContext, this,
+                            mWindowManager, appOpsManager, proxy, this,
+                            OP_ASSIST_STRUCTURE, OP_NONE);
+                    requester.requestAssistData(mStackSupervisor.getTopVisibleActivities(),
+                            true, false /* fetchScreenshots */, recentsUid, recentsPackage);
+                }
+
+                final Intent intent = new Intent();
+                intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+                intent.setComponent(recentsComponent);
+                return mActivityStarter.startActivityMayWait(null, recentsUid, recentsPackage,
+                        intent, null, null, null, null, null, 0, 0, null, null, null, bOptions,
+                        false, userId, null, "startRecentsActivity");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
     public void startLocalVoiceInteraction(IBinder callingActivity, Bundle options)
             throws RemoteException {
         Slog.i(TAG, "Activity tried to startVoiceInteraction");
@@ -4814,7 +4898,7 @@
                     Intent.FLAG_ACTIVITY_FORWARD_RESULT|
                     Intent.FLAG_ACTIVITY_CLEAR_TOP|
                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK|
-                    Intent.FLAG_ACTIVITY_NEW_TASK));
+                    FLAG_ACTIVITY_NEW_TASK));
 
             // Okay now we need to start the new activity, replacing the
             // currently running activity.  This is a little tricky because
@@ -4851,16 +4935,13 @@
 
     @Override
     public final int startActivityFromRecents(int taskId, Bundle bOptions) {
-        if (checkCallingPermission(START_TASKS_FROM_RECENTS) != PackageManager.PERMISSION_GRANTED) {
-            String msg = "Permission Denial: startActivityFromRecents called without " +
-                    START_TASKS_FROM_RECENTS;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
-        }
+        enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
+                "startActivityFromRecents()");
+
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                return mStackSupervisor.startActivityFromRecentsInner(taskId, bOptions);
+                return mStackSupervisor.startActivityFromRecents(taskId, bOptions);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -5061,11 +5142,12 @@
         }
 
         synchronized(this) {
-            if (mHeavyWeightProcess == null) {
+            final ProcessRecord proc = mHeavyWeightProcess;
+            if (proc == null) {
                 return;
             }
 
-            ArrayList<ActivityRecord> activities = new ArrayList<>(mHeavyWeightProcess.activities);
+            ArrayList<ActivityRecord> activities = new ArrayList<>(proc.activities);
             for (int i = 0; i < activities.size(); i++) {
                 ActivityRecord r = activities.get(i);
                 if (!r.finishing && r.isInStackLocked()) {
@@ -5075,7 +5157,7 @@
             }
 
             mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
-                    mHeavyWeightProcess.userId, 0));
+                    proc.userId, 0));
             mHeavyWeightProcess = null;
         }
     }
@@ -5911,16 +5993,7 @@
 
                 if (appInfo != null) {
                     forceStopPackageLocked(packageName, appInfo.uid, "clear data");
-                    // Remove all tasks match the cleared application package and user
-                    for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-                        final TaskRecord tr = mRecentTasks.get(i);
-                        final String taskPackageName =
-                                tr.getBaseIntent().getComponent().getPackageName();
-                        if (tr.userId != resolvedUserId) continue;
-                        if (!taskPackageName.equals(packageName)) continue;
-                        mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
-                                REMOVE_FROM_RECENTS);
-                    }
+                    mRecentTasks.removeTasksByPackageName(packageName, resolvedUserId);
                 }
             }
 
@@ -6150,7 +6223,7 @@
                         Slog.w(TAG, "Failed trying to unstop package "
                                 + packageName + ": " + e);
                     }
-                    if (mUserController.isUserRunningLocked(user, 0)) {
+                    if (mUserController.isUserRunning(user, 0)) {
                         forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                         finishForceStopPackageLocked(packageName, pkgUid);
                     }
@@ -6254,7 +6327,7 @@
         mStackSupervisor.closeSystemDialogsLocked();
 
         broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                AppOpsManager.OP_NONE, null, false, false,
+                OP_NONE, null, false, false,
                 -1, SYSTEM_UID, UserHandle.USER_ALL);
     }
 
@@ -6356,7 +6429,7 @@
         intent.putExtra(Intent.EXTRA_UID, uid);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
         broadcastIntentLocked(null, null, intent,
-                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                null, null, 0, null, null, null, OP_NONE,
                 null, false, false, MY_PID, SYSTEM_UID, UserHandle.getUserId(uid));
     }
 
@@ -6501,7 +6574,7 @@
         }
 
         // Clean-up disabled tasks
-        cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId);
+        mRecentTasks.cleanupDisabledPackageTasksLocked(packageName, disabledClasses, userId);
 
         // Clean-up disabled services.
         mServices.bringDownDisabledPackageServicesLocked(
@@ -7308,33 +7381,32 @@
                     startProcessLocked(procs.get(ip), "on-hold", null);
                 }
             }
-
-            if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-                // Start looking for apps that are abusing wake locks.
-                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
-                mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
-                // Tell anyone interested that we are done booting!
-                SystemProperties.set("sys.boot_completed", "1");
-
-                // And trigger dev.bootcomplete if we are not showing encryption progress
-                if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
-                    || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
-                    SystemProperties.set("dev.bootcomplete", "1");
-                }
-                mUserController.sendBootCompletedLocked(
-                        new IIntentReceiver.Stub() {
-                            @Override
-                            public void performReceive(Intent intent, int resultCode,
-                                    String data, Bundle extras, boolean ordered,
-                                    boolean sticky, int sendingUser) {
-                                synchronized (ActivityManagerService.this) {
-                                    requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-                                            true, false);
-                                }
-                            }
-                        });
-                mUserController.scheduleStartProfilesLocked();
+            if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+                return;
             }
+            // Start looking for apps that are abusing wake locks.
+            Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
+            mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+            // Tell anyone interested that we are done booting!
+            SystemProperties.set("sys.boot_completed", "1");
+
+            // And trigger dev.bootcomplete if we are not showing encryption progress
+            if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
+                    || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
+                SystemProperties.set("dev.bootcomplete", "1");
+            }
+            mUserController.sendBootCompleted(
+                    new IIntentReceiver.Stub() {
+                        @Override
+                        public void performReceive(Intent intent, int resultCode,
+                                String data, Bundle extras, boolean ordered,
+                                boolean sticky, int sendingUser) {
+                            synchronized (ActivityManagerService.this) {
+                                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                            }
+                        }
+                    });
+            mUserController.scheduleStartProfiles();
         }
     }
 
@@ -8033,8 +8105,8 @@
     }
 
     private boolean isInPictureInPictureMode(ActivityRecord r) {
-        if (r == null || r.getStack() == null || !r.getStack().isPinnedStack() ||
-                r.getStack().isInStackLocked(r) == null) {
+        if (r == null || r.getStack() == null || !r.inPinnedWindowingMode()
+                || r.getStack().isInStackLocked(r) == null) {
             return false;
         }
 
@@ -8073,8 +8145,8 @@
                     // Adjust the source bounds by the insets for the transition down
                     final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
-                            true /* moveHomeStackToFront */, "enterPictureInPictureMode");
-                    final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
+                            "enterPictureInPictureMode");
+                    final PinnedActivityStack stack = r.getStack();
                     stack.setPictureInPictureAspectRatio(aspectRatio);
                     stack.setPictureInPictureActions(actions);
 
@@ -8128,7 +8200,7 @@
 
                 // Only update the saved args from the args that are set
                 r.pictureInPictureArgs.copyOnlySet(params);
-                if (r.getStack().getStackId() == PINNED_STACK_ID) {
+                if (r.inPinnedWindowingMode()) {
                     // If the activity is already in picture-in-picture, update the pinned stack now
                     // if it is not already expanding to fullscreen. Otherwise, the arguments will
                     // be used the next time the activity enters PiP
@@ -8189,11 +8261,6 @@
                     + ": Current activity does not support picture-in-picture.");
         }
 
-        if (!StackId.isAllowedToEnterPictureInPicture(r.getStack().getStackId())) {
-            throw new IllegalStateException(caller
-                    + ": Activities on the home, assistant, or recents stack not supported");
-        }
-
         if (params.hasSetAspectRatio()
                 && !mWindowManager.isValidPictureInPictureAspectRatio(r.getStack().mDisplayId,
                         params.getAspectRatio())) {
@@ -8322,9 +8389,6 @@
         }
     }
 
-    /**
-     * This can be called with or without the global lock held.
-     */
     int checkComponentPermission(String permission, int pid, int uid,
             int owningUid, boolean exported) {
         if (pid == MY_PID) {
@@ -8399,6 +8463,15 @@
     }
 
     /**
+     * This can be called with or without the global lock held.
+     */
+    void enforceCallerIsRecentsOrHasPermission(String permission, String func) {
+        if (!mRecentTasks.isCallerRecents(Binder.getCallingUid())) {
+            enforceCallingPermission(permission, func);
+        }
+    }
+
+    /**
      * Determine if UID is holding permissions required to access {@link Uri} in
      * the given {@link ProviderInfo}. Final permission checking is always done
      * in {@link ContentProvider}.
@@ -9754,109 +9827,44 @@
     public List<IBinder> getAppTasks(String callingPackage) {
         int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
-
-        synchronized(this) {
-            ArrayList<IBinder> list = new ArrayList<IBinder>();
-            try {
-                if (DEBUG_ALL) Slog.v(TAG, "getAppTasks");
-
-                final int N = mRecentTasks.size();
-                for (int i = 0; i < N; i++) {
-                    TaskRecord tr = mRecentTasks.get(i);
-                    // Skip tasks that do not match the caller.  We don't need to verify
-                    // callingPackage, because we are also limiting to callingUid and know
-                    // that will limit to the correct security sandbox.
-                    if (tr.effectiveUid != callingUid) {
-                        continue;
-                    }
-                    Intent intent = tr.getBaseIntent();
-                    if (intent == null ||
-                            !callingPackage.equals(intent.getComponent().getPackageName())) {
-                        continue;
-                    }
-                    ActivityManager.RecentTaskInfo taskInfo =
-                            createRecentTaskInfoFromTaskRecord(tr);
-                    AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid);
-                    list.add(taskImpl.asBinder());
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+        try {
+            synchronized(this) {
+                return mRecentTasks.getAppTasksList(callingUid, callingPackage);
             }
-            return list;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
     @Override
-    public List<RunningTaskInfo> getTasks(int maxNum, int flags) {
+    public List<RunningTaskInfo> getTasks(int maxNum) {
+       return getFilteredTasks(maxNum, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED);
+    }
+
+    @Override
+    public List<RunningTaskInfo> getFilteredTasks(int maxNum, @ActivityType int ignoreActivityType,
+            @WindowingMode int ignoreWindowingMode) {
         final int callingUid = Binder.getCallingUid();
-        ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
+        ArrayList<RunningTaskInfo> list = new ArrayList<>();
 
         synchronized(this) {
-            if (DEBUG_ALL) Slog.v(
-                TAG, "getTasks: max=" + maxNum + ", flags=" + flags);
+            if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);
 
             final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
                     callingUid);
-
-            // TODO: Improve with MRU list from all ActivityStacks.
-            mStackSupervisor.getTasksLocked(maxNum, list, callingUid, allowed);
+            mStackSupervisor.getRunningTasks(maxNum, list, ignoreActivityType,
+                    ignoreWindowingMode, callingUid, allowed);
         }
 
         return list;
     }
 
-    /**
-     * Creates a new RecentTaskInfo from a TaskRecord.
-     */
-    private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) {
-        // Update the task description to reflect any changes in the task stack
-        tr.updateTaskDescription();
-
-        // Compose the recent task info
-        ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
-        rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
-        rti.persistentId = tr.taskId;
-        rti.baseIntent = new Intent(tr.getBaseIntent());
-        rti.origActivity = tr.origActivity;
-        rti.realActivity = tr.realActivity;
-        rti.description = tr.lastDescription;
-        rti.stackId = tr.getStackId();
-        rti.userId = tr.userId;
-        rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
-        rti.firstActiveTime = tr.firstActiveTime;
-        rti.lastActiveTime = tr.lastActiveTime;
-        rti.affiliatedTaskId = tr.mAffiliatedTaskId;
-        rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
-        rti.numActivities = 0;
-        if (tr.mBounds != null) {
-            rti.bounds = new Rect(tr.mBounds);
-        }
-        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen();
-        rti.resizeMode = tr.mResizeMode;
-
-        ActivityRecord base = null;
-        ActivityRecord top = null;
-        ActivityRecord tmp;
-
-        for (int i = tr.mActivities.size() - 1; i >= 0; --i) {
-            tmp = tr.mActivities.get(i);
-            if (tmp.finishing) {
-                continue;
-            }
-            base = tmp;
-            if (top == null || (top.state == ActivityState.INITIALIZING)) {
-                top = base;
-            }
-            rti.numActivities++;
-        }
-
-        rti.baseActivity = (base != null) ? base.intent.getComponent() : null;
-        rti.topActivity = (top != null) ? top.intent.getComponent() : null;
-
-        return rti;
-    }
-
     private boolean isGetTasksAllowed(String caller, int callingPid, int callingUid) {
+        if (mRecentTasks.isCallerRecents(callingUid)) {
+            // Always allow the recents component to get tasks
+            return true;
+        }
+
         boolean allowed = checkPermission(android.Manifest.permission.REAL_GET_TASKS,
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
         if (!allowed) {
@@ -9889,126 +9897,22 @@
         final int callingUid = Binder.getCallingUid();
         userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
                 false, ALLOW_FULL_ONLY, "getRecentTasks", null);
+        final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
+                callingUid);
+        final boolean detailed = checkCallingPermission(
+                android.Manifest.permission.GET_DETAILED_TASKS)
+                        == PackageManager.PERMISSION_GRANTED;
 
-        final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
-        final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;
         synchronized (this) {
-            final boolean allowed = isGetTasksAllowed("getRecentTasks", Binder.getCallingPid(),
+            return mRecentTasks.getRecentTasks(maxNum, flags, allowed, detailed, userId,
                     callingUid);
-            final boolean detailed = checkCallingPermission(
-                    android.Manifest.permission.GET_DETAILED_TASKS)
-                    == PackageManager.PERMISSION_GRANTED;
-
-            if (!isUserRunning(userId, ActivityManager.FLAG_AND_UNLOCKED)) {
-                Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
-                return ParceledListSlice.emptyList();
-            }
-            mRecentTasks.loadUserRecentsLocked(userId);
-
-            final int recentsCount = mRecentTasks.size();
-            ArrayList<ActivityManager.RecentTaskInfo> res =
-                    new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);
-
-            final Set<Integer> includedUsers;
-            if (includeProfiles) {
-                includedUsers = mUserController.getProfileIds(userId);
-            } else {
-                includedUsers = new HashSet<>();
-            }
-            includedUsers.add(Integer.valueOf(userId));
-
-            for (int i = 0; i < recentsCount && maxNum > 0; i++) {
-                TaskRecord tr = mRecentTasks.get(i);
-                // Only add calling user or related users recent tasks
-                if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
-                    continue;
-                }
-
-                if (tr.realActivitySuspended) {
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
-                    continue;
-                }
-
-                // Return the entry if desired by the caller.  We always return
-                // the first entry, because callers always expect this to be the
-                // foreground app.  We may filter others if the caller has
-                // not supplied RECENT_WITH_EXCLUDED and there is some reason
-                // we should exclude the entry.
-
-                if (i == 0
-                        || withExcluded
-                        || (tr.intent == null)
-                        || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-                                == 0)) {
-                    if (!allowed) {
-                        // If the caller doesn't have the GET_TASKS permission, then only
-                        // allow them to see a small subset of tasks -- their own and home.
-                        if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
-                            continue;
-                        }
-                    }
-                    final ActivityStack stack = tr.getStack();
-                    if ((flags & ActivityManager.RECENT_IGNORE_HOME_AND_RECENTS_STACK_TASKS) != 0) {
-                        if (stack != null && stack.isHomeOrRecentsStack()) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                    "Skipping, home or recents stack task: " + tr);
-                            continue;
-                        }
-                    }
-                    if ((flags & ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK) != 0) {
-                        if (stack != null && stack.isDockedStack() && stack.topTask() == tr) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                    "Skipping, top task in docked stack: " + tr);
-                            continue;
-                        }
-                    }
-                    if ((flags & ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS) != 0) {
-                        if (stack != null && stack.isPinnedStack()) {
-                            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                    "Skipping, pinned stack task: " + tr);
-                            continue;
-                        }
-                    }
-                    if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
-                        // Don't include auto remove tasks that are finished or finishing.
-                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                "Skipping, auto-remove without activity: " + tr);
-                        continue;
-                    }
-                    if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0
-                            && !tr.isAvailable) {
-                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                "Skipping, unavail real act: " + tr);
-                        continue;
-                    }
-
-                    if (!tr.mUserSetupComplete) {
-                        // Don't include task launched while user is not done setting-up.
-                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                                "Skipping, user setup not complete: " + tr);
-                        continue;
-                    }
-
-                    ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
-                    if (!detailed) {
-                        rti.baseIntent.replaceExtras((Bundle)null);
-                    }
-
-                    res.add(rti);
-                    maxNum--;
-                }
-            }
-            return new ParceledListSlice<>(res);
         }
     }
 
     @Override
     public ActivityManager.TaskDescription getTaskDescription(int id) {
         synchronized (this) {
-            enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                    "getTaskDescription()");
+            enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getTaskDescription()");
             final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
                     MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (tr != null) {
@@ -10072,23 +9976,10 @@
                 TaskRecord task = new TaskRecord(this,
                         mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
                         ainfo, intent, description);
-
-                int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
-                if (trimIdx >= 0) {
-                    // If this would have caused a trim, then we'll abort because that
-                    // means it would be added at the end of the list but then just removed.
+                if (!mRecentTasks.addToBottom(task)) {
                     return INVALID_TASK_ID;
                 }
-
-                final int N = mRecentTasks.size();
-                if (N >= (ActivityManager.getMaxRecentTasksStatic()-1)) {
-                    final TaskRecord tr = mRecentTasks.remove(N - 1);
-                    tr.removedFromRecents();
-                }
-
-                task.inRecents = true;
-                mRecentTasks.add(task);
-                r.getStack().addTask(task, false, "addAppTask");
+                r.getStack().addTask(task, !ON_TOP, "addAppTask");
 
                 // TODO: Send the thumbnail to WM to store it.
 
@@ -10150,21 +10041,23 @@
                 // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
                 //   that task to freeform
                 // - otherwise the task is not moved
-                int stackId = task.getStackId();
+                ActivityStack stack = task.getStack();
                 if (!task.getWindowConfiguration().canResizeTask()) {
                     throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
                 }
-                if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
-                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-                } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
-                    stackId = FREEFORM_WORKSPACE_STACK_ID;
+                if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                    stack = stack.getDisplay().getOrCreateStack(
+                            WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+                } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                    stack = stack.getDisplay().getOrCreateStack(
+                            WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP);
                 }
 
                 // Reparent the task to the right stack if necessary
                 boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
-                if (stackId != task.getStackId()) {
+                if (stack != task.getStack()) {
                     // Defer resume until the task is resized below
-                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+                    task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
                             DEFER_RESUME, "resizeTask");
                     preserveWindow = false;
                 }
@@ -10213,7 +10106,8 @@
 
     @Override
     public void cancelTaskWindowTransition(int taskId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskWindowTransition()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "cancelTaskWindowTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10232,7 +10126,8 @@
 
     @Override
     public void cancelTaskThumbnailTransition(int taskId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "cancelTaskThumbnailTransition()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "cancelTaskThumbnailTransition()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10251,7 +10146,7 @@
 
     @Override
     public TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) {
-        enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
+        enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
         final long ident = Binder.clearCallingIdentity();
         try {
             final TaskRecord task;
@@ -10302,49 +10197,54 @@
         mWindowManager.executeAppTransition();
     }
 
-    private void removeTasksByPackageNameLocked(String packageName, int userId) {
-        // Remove all tasks with activities in the specified package from the list of recent tasks
-        for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-            TaskRecord tr = mRecentTasks.get(i);
-            if (tr.userId != userId) continue;
-
-            ComponentName cn = tr.intent.getComponent();
-            if (cn != null && cn.getPackageName().equals(packageName)) {
-                // If the package name matches, remove the task.
-                mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+    @Override
+    public void removeStack(int stackId) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+                if (stack == null) {
+                    Slog.w(TAG, "removeStack: No stack with id=" + stackId);
+                    return;
+                }
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException(
+                            "Removing non-standard stack is not allowed.");
+                }
+                mStackSupervisor.removeStack(stack);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
         }
     }
 
-    private void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
-            int userId) {
-
-        for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
-            TaskRecord tr = mRecentTasks.get(i);
-            if (userId != UserHandle.USER_ALL && tr.userId != userId) {
-                continue;
-            }
-
-            ComponentName cn = tr.intent.getComponent();
-            final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
-                    && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
-            if (sameComponent) {
-                mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    @Override
+    public void removeStacksInWindowingModes(int[] windowingModes) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "removeStacksInWindowingModes()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mStackSupervisor.removeStacksInWindowingModes(windowingModes);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
             }
         }
     }
 
     @Override
-    public void removeStack(int stackId) {
-        enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException("Removing home or recents stack is not allowed.");
-        }
-
+    public void removeStacksWithActivityTypes(int[] activityTypes) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "removeStacksWithActivityTypes()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                mStackSupervisor.removeStackLocked(stackId);
+                mStackSupervisor.removeStacksWithActivityTypes(activityTypes);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10369,7 +10269,7 @@
 
     @Override
     public boolean removeTask(int taskId) {
-        enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
+        enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()");
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -10412,11 +10312,7 @@
                 Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
                 return;
             }
-            final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
-            if (prev != null) {
-                task.setTaskToReturnTo(prev);
-            }
-            mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options, "moveTaskToFront",
+            mStackSupervisor.findTaskToMoveToFront(task, flags, options, "moveTaskToFront",
                     false /* forceNonResizable */);
 
             final ActivityRecord topActivity = task.getTopActivity();
@@ -10494,13 +10390,16 @@
     public int createStackOnDisplay(int displayId) throws RemoteException {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "createStackOnDisplay()");
         synchronized (this) {
-            final int stackId = mStackSupervisor.getNextStackId();
-            final ActivityStack stack =
-                    mStackSupervisor.createStackOnDisplay(stackId, displayId, true /*onTop*/);
-            if (stack == null) {
+            final ActivityDisplay display =
+                    mStackSupervisor.getActivityDisplayOrCreateLocked(displayId);
+            if (display == null) {
                 return INVALID_STACK_ID;
             }
-            return stack.mStackId;
+            // TODO(multi-display): Have the caller pass in the windowing mode and activity type.
+            final ActivityStack stack = display.createStack(
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                    ON_TOP);
+            return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
         }
     }
 
@@ -10527,13 +10426,17 @@
                 }
 
                 final ActivityStack stack = r.getStack();
-                if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
+                if (stack == null || !stack.inFreeformWindowingMode()) {
                     throw new IllegalStateException(
                             "exitFreeformMode: You can only go fullscreen from freeform.");
                 }
 
+                final ActivityStack fullscreenStack = stack.getDisplay().getOrCreateStack(
+                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
-                r.getTask().reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+                // TODO: Should just change windowing mode vs. re-parenting...
+                r.getTask().reparent(fullscreenStack, ON_TOP,
                         REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "exitFreeformMode");
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10542,12 +10445,44 @@
     }
 
     @Override
-    public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException(
-                    "moveTaskToStack: Attempt to move task " + taskId + " to stack " + stackId);
+    public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task == null) {
+                    Slog.w(TAG, "setTaskWindowingMode: No task for id=" + taskId);
+                    return;
+                }
+
+                if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
+                        + " to windowingMode=" + windowingMode + " toTop=" + toTop);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                    mWindowManager.setDockedStackCreateState(SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+                            null /* initialBounds */);
+                }
+
+                if (!task.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move task "
+                            + taskId + " to non-standard windowin mode=" + windowingMode);
+                }
+                final ActivityDisplay display = task.getStack().getDisplay();
+                final ActivityStack stack = display.getOrCreateStack(windowingMode,
+                        task.getStack().getActivityType(), toTop);
+                // TODO: We should just change the windowing mode for the task vs. creating and
+                // moving it to a stack.
+                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+                        "moveTaskToStack");
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
         }
+    }
+
+    @Override
+    public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10559,12 +10494,22 @@
 
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
                         + " to stackId=" + stackId + " toTop=" + toTop);
-                if (stackId == DOCKED_STACK_ID) {
-                    mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
-                            null /* initialBounds */);
+
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+                if (stack == null) {
+                    throw new IllegalStateException(
+                            "moveTaskToStack: No stack for stackId=" + stackId);
                 }
-                task.reparent(stackId, toTop,
-                        REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
+                            + taskId + " to stack " + stackId);
+                }
+                if (stack.inSplitScreenPrimaryWindowingMode()) {
+                    mWindowManager.setDockedStackCreateState(
+                            SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, null /* initialBounds */);
+                }
+                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+                        "moveTaskToStack");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10572,40 +10517,47 @@
     }
 
     /**
-     * Moves the input task to the docked stack.
+     * Moves the input task to the primary-split-screen stack.
      *
      * @param taskId Id of task to move.
-     * @param createMode The mode the docked stack should be created in if it doesn't exist
-     *                   already. See
-     *                   {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT}
+     * @param createMode The mode the primary split screen stack should be created in if it doesn't
+     *                   exist already. See
+     *                   {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
      *                   and
-     *                   {@link android.app.ActivityManager#DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT}
+     *                   {@link android.app.ActivityManager#SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
      * @param toTop If the task and stack should be moved to the top.
      * @param animate Whether we should play an animation for the moving the task
-     * @param initialBounds If the docked stack gets created, it will use these bounds for the
-     *                      docked stack. Pass {@code null} to use default bounds.
+     * @param initialBounds If the primary stack gets created, it will use these bounds for the
+     *                      stack. Pass {@code null} to use default bounds.
      */
     @Override
-    public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,
-            Rect initialBounds) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
+    public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+            boolean animate, Rect initialBounds) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "setTaskWindowingModeSplitScreenPrimary()");
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
                 if (task == null) {
-                    Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
+                    Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
                     return false;
                 }
-
-                if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+                if (DEBUG_STACK) Slog.d(TAG_STACK,
+                        "setTaskWindowingModeSplitScreenPrimary: moving task=" + taskId
                         + " to createMode=" + createMode + " toTop=" + toTop);
                 mWindowManager.setDockedStackCreateState(createMode, initialBounds);
 
+                final ActivityDisplay display = task.getStack().getDisplay();
+                final ActivityStack stack = display.getOrCreateStack(
+                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, task.getStack().getActivityType(),
+                        toTop);
+
                 // Defer resuming until we move the home stack to the front below
-                final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+                // TODO: Should just change windowing mode vs. re-parenting...
+                final boolean moved = task.reparent(stack, toTop,
                         REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
-                        "moveTaskToDockedStack");
+                        "setTaskWindowingModeSplitScreenPrimary");
                 if (moved) {
                     mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 }
@@ -10617,6 +10569,70 @@
     }
 
     /**
+     * Dismisses split-screen multi-window mode.
+     * @param toTop If true the current primary split-screen stack will be placed or left on top.
+     */
+    @Override
+    public void dismissSplitScreenMode(boolean toTop) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ActivityStack stack =
+                        mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
+                if (stack == null) {
+                    Slog.w(TAG, "dismissSplitScreenMode: primary split-screen stack not found.");
+                    return;
+                }
+                if (toTop) {
+                    mStackSupervisor.resizeStackLocked(stack, null /* destBounds */,
+                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                            true /* preserveWindows */, true /* allowResizeInDockedMode */,
+                            !DEFER_RESUME);
+                } else {
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, false /* onTop */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Dismisses Pip
+     * @param animate True if the dismissal should be animated.
+     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
+     *                          default animation duration should be used.
+     */
+    @Override
+    public void dismissPip(boolean animate, int animationDuration) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final PinnedActivityStack stack =
+                        mStackSupervisor.getDefaultDisplay().getPinnedStack();
+                if (stack == null) {
+                    Slog.w(TAG, "dismissPip: pinned stack not found.");
+                    return;
+                }
+                if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    throw new IllegalArgumentException("Stack: " + stack
+                            + " doesn't support animated resize.");
+                }
+                if (animate) {
+                    stack.animateResizePinnedStack(null /* sourceHintBounds */,
+                            null /* destBounds */, animationDuration, false /* fromFullscreen */);
+                } else {
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
      * Moves the top activity in the input stackId to the pinned stack.
      *
      * @param stackId Id of stack to move the top activity to pinned stack.
@@ -10627,7 +10643,8 @@
      */
     @Override
     public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTopActivityToPinnedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "moveTopActivityToPinnedStack()");
         synchronized (this) {
             if (!mSupportsPictureInPicture) {
                 throw new IllegalStateException("moveTopActivityToPinnedStack:"
@@ -10646,24 +10663,29 @@
     @Override
     public void resizeStack(int stackId, Rect destBounds, boolean allowResizeInDockedMode,
             boolean preserveWindows, boolean animate, int animationDuration) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
                 if (animate) {
-                    if (stackId == PINNED_STACK_ID) {
-                        final PinnedActivityStack pinnedStack =
-                                mStackSupervisor.getStack(PINNED_STACK_ID);
-                        if (pinnedStack != null) {
-                            pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
-                                    destBounds, animationDuration, false /* fromFullscreen */);
-                        }
-                    } else {
+                    final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
+                    if (stack == null) {
+                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+                        return;
+                    }
+                    if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
                         throw new IllegalArgumentException("Stack: " + stackId
                                 + " doesn't support animated resize.");
                     }
+                    stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
+                            animationDuration, false /* fromFullscreen */);
                 } else {
-                    mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
+                    final ActivityStack stack = mStackSupervisor.getStack(stackId);
+                    if (stack == null) {
+                        Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
+                        return;
+                    }
+                    mStackSupervisor.resizeStackLocked(stack, destBounds, null /* tempTaskBounds */,
                             null /* tempTaskInsetBounds */, preserveWindows,
                             allowResizeInDockedMode, !DEFER_RESUME);
                 }
@@ -10677,8 +10699,7 @@
     public void resizeDockedStack(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds,
             Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds) {
-        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                "resizeDockedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizeDockedStack()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10693,8 +10714,7 @@
 
     @Override
     public void resizePinnedStack(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
-        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
-                "resizePinnedStack()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "resizePinnedStack()");
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10713,11 +10733,6 @@
     @Override
     public void positionTaskInStack(int taskId, int stackId, int position) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException(
-                    "positionTaskInStack: Attempt to change the position of task "
-                    + taskId + " in/to home/recents stack");
-        }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10729,8 +10744,16 @@
                             + taskId);
                 }
 
-                final ActivityStack stack = mStackSupervisor.getStack(stackId, CREATE_IF_NEEDED,
-                        !ON_TOP);
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+
+                if (stack == null) {
+                    throw new IllegalArgumentException("positionTaskInStack: no stack for id="
+                            + stackId);
+                }
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("positionTaskInStack: Attempt to change"
+                            + " the position of task " + taskId + " in/to non-standard stack");
+                }
 
                 // TODO: Have the callers of this API call a separate reparent method if that is
                 // what they intended to do vs. having this method also do reparenting.
@@ -10739,8 +10762,8 @@
                     stack.positionChildAt(task, position);
                 } else {
                     // Reparent to new stack.
-                    task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
-                            !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
+                    task.reparent(stack, position, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+                            !DEFER_RESUME, "positionTaskInStack");
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10750,7 +10773,7 @@
 
     @Override
     public List<StackInfo> getAllStackInfos() {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getAllStackInfos()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
@@ -10762,12 +10785,12 @@
     }
 
     @Override
-    public StackInfo getStackInfo(int stackId) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+    public StackInfo getStackInfo(int windowingMode, int activityType) {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                return mStackSupervisor.getStackInfoLocked(stackId);
+                return mStackSupervisor.getStackInfo(windowingMode, activityType);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10806,7 +10829,21 @@
         }
     }
 
-    private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isAppPinning) {
+    @Override
+    public void updateLockTaskFeatures(int userId, int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid != 0 && callingUid != SYSTEM_UID) {
+            enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
+                    "updateLockTaskFeatures()");
+        }
+        synchronized (this) {
+            if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Allowing features " + userId + ":0x" +
+                    Integer.toHexString(flags));
+            mLockTaskController.updateLockTaskFeatures(userId, flags);
+        }
+    }
+
+    private void startLockTaskModeLocked(@Nullable TaskRecord task, boolean isSystemCaller) {
         if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "startLockTaskModeLocked: " + task);
         if (task == null || task.mLockTaskAuth == LOCK_TASK_AUTH_DONT_LOCK) {
             return;
@@ -10818,19 +10855,18 @@
         }
 
         // When a task is locked, dismiss the pinned stack if it exists
-        final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
-                PINNED_STACK_ID);
-        if (pinnedStack != null) {
-            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
-        }
+        mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
-        // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
-        // is initiated by system after the pinning request was shown and locked mode is initiated
-        // by an authorized app directly
+        // {@code isSystemCaller} is used to distinguish whether this request is initiated by the
+        // system or a specific app.
+        // * System-initiated requests will only start the pinned mode (screen pinning)
+        // * App-initiated requests
+        //   - will put the device in fully locked mode (LockTask), if the app is whitelisted
+        //   - will start the pinned mode, otherwise
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
-            mLockTaskController.startLockTaskMode(task, isAppPinning, callingUid);
+            mLockTaskController.startLockTaskMode(task, isSystemCaller, callingUid);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -10843,7 +10879,7 @@
             if (r == null) {
                 return;
             }
-            startLockTaskModeLocked(r.getTask(), false /* not system initiated */);
+            startLockTaskModeLocked(r.getTask(), false /* isSystemCaller */);
         }
     }
 
@@ -10855,7 +10891,7 @@
         try {
             synchronized (this) {
                 startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId),
-                        true /* system initiated */);
+                        true /* isSystemCaller */);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10863,8 +10899,14 @@
     }
 
     @Override
-    public void stopLockTaskMode() {
-        stopLockTaskModeInternal(false /* not system initiated */);
+    public void stopLockTaskModeByToken(IBinder token) {
+        synchronized (this) {
+            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+            if (r == null) {
+                return;
+            }
+            stopLockTaskModeInternal(r.getTask(), false /* isSystemCaller */);
+        }
     }
 
     /**
@@ -10874,15 +10916,15 @@
     @Override
     public void stopSystemLockTaskMode() throws RemoteException {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "stopSystemLockTaskMode");
-        stopLockTaskModeInternal(true /* system initiated */);
+        stopLockTaskModeInternal(null, true /* isSystemCaller */);
     }
 
-    private void stopLockTaskModeInternal(boolean isSystemRequest) {
+    private void stopLockTaskModeInternal(@Nullable TaskRecord task, boolean isSystemCaller) {
         final int callingUid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                mLockTaskController.stopLockTaskMode(isSystemRequest, callingUid);
+                mLockTaskController.stopLockTaskMode(task, isSystemCaller, callingUid);
             }
             // Launch in-call UI if a call is ongoing. This is necessary to allow stopping the lock
             // task and jumping straight into a call in the case of emergency call back.
@@ -11049,7 +11091,7 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
+            int tmpTargetUserId = mUserController.unsafeConvertIncomingUser(userId);
             if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
                 if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
@@ -11437,7 +11479,7 @@
 
                 // Make sure that the user who owns this provider is running.  If not,
                 // we don't want to allow it to run.
-                if (!mUserController.isUserRunningLocked(userId, 0)) {
+                if (!mUserController.isUserRunning(userId, 0)) {
                     Slog.w(TAG, "Unable to launch app "
                             + cpi.applicationInfo.packageName + "/"
                             + cpi.applicationInfo.uid + " for provider "
@@ -11633,7 +11675,7 @@
             }
 
             final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSIONS);
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+            intent.addFlags(FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
             intent.putExtra(Intent.EXTRA_PACKAGE_NAME, cpi.packageName);
 
@@ -11928,8 +11970,7 @@
 
     @Override
     public void appNotRespondingViaProvider(IBinder connection) {
-        enforceCallingPermission(
-                android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+        enforceCallingPermission(REMOVE_TASKS, "appNotRespondingViaProvider()");
 
         final ContentProviderConnection conn = (ContentProviderConnection) connection;
         if (conn == null) {
@@ -12067,9 +12108,7 @@
         int callingPid = Binder.getCallingPid();
         long ident = 0;
         boolean clearedIdentity = false;
-        synchronized (this) {
-            userId = mUserController.unsafeConvertIncomingUserLocked(userId);
-        }
+        userId = mUserController.unsafeConvertIncomingUser(userId);
         if (canClearIdentity(callingPid, callingUid, userId)) {
             clearedIdentity = true;
             ident = Binder.clearCallingIdentity();
@@ -12399,6 +12438,9 @@
                     + android.Manifest.permission.SHUTDOWN);
         }
 
+        // TODO: Where should the corresponding '1' (start) write go?
+        StatsLog.write(StatsLog.DEVICE_ON_STATUS_CHANGED, 0);
+
         boolean timedout = false;
 
         synchronized(this) {
@@ -12469,6 +12511,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+        sendNotifyVrManagerOfKeyguardState(showing);
     }
 
     @Override
@@ -12487,7 +12530,7 @@
                 if (mUserController.shouldConfirmCredentials(userId)) {
                     if (mKeyguardController.isKeyguardLocked()) {
                         // Showing launcher to avoid user entering credential twice.
-                        final int currentUserId = mUserController.getCurrentUserIdLocked();
+                        final int currentUserId = mUserController.getCurrentUserId();
                         startHomeActivityLocked(currentUserId, "notifyLockedProfile");
                     }
                     mStackSupervisor.lockAllProfileTasks(userId);
@@ -12741,6 +12784,10 @@
                 throw new IllegalArgumentException("Provided bugreport type is not correct, value: "
                         + bugreportType);
         }
+        // Always log caller, even if it does not have permission to dump.
+        String type = extraOptions == null ? "bugreport" : extraOptions;
+        Slog.i(TAG, type + " requested by UID " + Binder.getCallingUid());
+
         enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
         if (extraOptions != null) {
             SystemProperties.set("dumpstate.options", extraOptions);
@@ -12937,7 +12984,7 @@
     }
 
     @Override
-    public boolean requestAssistContextExtras(int requestType, IResultReceiver receiver,
+    public boolean requestAssistContextExtras(int requestType, IAssistDataReceiver receiver,
             Bundle receiverExtras, IBinder activityToken, boolean focused, boolean newSessionId) {
         return enqueueAssistContext(requestType, null, null, receiver, receiverExtras,
                 activityToken, focused, newSessionId, UserHandle.getCallingUserId(), null,
@@ -12945,7 +12992,7 @@
     }
 
     @Override
-    public boolean requestAutofillData(IResultReceiver receiver, Bundle receiverExtras,
+    public boolean requestAutofillData(IAssistDataReceiver receiver, Bundle receiverExtras,
             IBinder activityToken, int flags) {
         return enqueueAssistContext(ActivityManager.ASSIST_CONTEXT_AUTOFILL, null, null,
                 receiver, receiverExtras, activityToken, true, true, UserHandle.getCallingUserId(),
@@ -12953,7 +13000,7 @@
     }
 
     private PendingAssistExtras enqueueAssistContext(int requestType, Intent intent, String hint,
-            IResultReceiver receiver, Bundle receiverExtras, IBinder activityToken,
+            IAssistDataReceiver receiver, Bundle receiverExtras, IBinder activityToken,
             boolean focused, boolean newSessionId, int userHandle, Bundle args, long timeout,
             int flags) {
         enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
@@ -13021,7 +13068,7 @@
     }
 
     void pendingAssistExtrasTimedOut(PendingAssistExtras pae) {
-        IResultReceiver receiver;
+        IAssistDataReceiver receiver;
         synchronized (this) {
             mPendingAssistExtras.remove(pae);
             receiver = pae.receiver;
@@ -13030,10 +13077,9 @@
             // Caller wants result sent back to them.
             Bundle sendBundle = new Bundle();
             // At least return the receiver extras
-            sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
-                    pae.receiverExtras);
+            sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
             try {
-                pae.receiver.send(0, sendBundle);
+                pae.receiver.onHandleAssistData(sendBundle);
             } catch (RemoteException e) {
             }
         }
@@ -13071,7 +13117,7 @@
             }
         }
         // We are now ready to launch the assist activity.
-        IResultReceiver sendReceiver = null;
+        IAssistDataReceiver sendReceiver = null;
         Bundle sendBundle = null;
         synchronized (this) {
             buildAssistBundleLocked(pae, extras);
@@ -13084,16 +13130,15 @@
             if ((sendReceiver=pae.receiver) != null) {
                 // Caller wants result sent back to them.
                 sendBundle = new Bundle();
-                sendBundle.putBundle(VoiceInteractionSession.KEY_DATA, pae.extras);
-                sendBundle.putParcelable(VoiceInteractionSession.KEY_STRUCTURE, pae.structure);
-                sendBundle.putParcelable(VoiceInteractionSession.KEY_CONTENT, pae.content);
-                sendBundle.putBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS,
-                        pae.receiverExtras);
+                sendBundle.putBundle(ASSIST_KEY_DATA, pae.extras);
+                sendBundle.putParcelable(ASSIST_KEY_STRUCTURE, pae.structure);
+                sendBundle.putParcelable(ASSIST_KEY_CONTENT, pae.content);
+                sendBundle.putBundle(ASSIST_KEY_RECEIVER_EXTRAS, pae.receiverExtras);
             }
         }
         if (sendReceiver != null) {
             try {
-                sendReceiver.send(0, sendBundle);
+                sendReceiver.onHandleAssistData(sendBundle);
             } catch (RemoteException e) {
             }
             return;
@@ -13107,7 +13152,7 @@
                 mContext.startServiceAsUser(pae.intent, new UserHandle(pae.userHandle));
             } else {
                 pae.intent.replaceExtras(pae.extras);
-                pae.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                pae.intent.setFlags(FLAG_ACTIVITY_NEW_TASK
                         | Intent.FLAG_ACTIVITY_SINGLE_TOP
                         | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 closeSystemDialogs("assist");
@@ -13576,6 +13621,7 @@
                     stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
                             sourcePkg != null ? sourcePkg : rec.key.packageName);
                 pkg.noteWakeupAlarmLocked(tag);
+                StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, sourceUid >= 0 ? sourceUid : uid);
             }
         }
     }
@@ -13873,7 +13919,7 @@
                     .setPackage("android")
                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             broadcastIntent(null, intent, null, null, 0, null, null, null,
-                    android.app.AppOpsManager.OP_NONE, null, true, false, UserHandle.USER_ALL);
+                    OP_NONE, null, true, false, UserHandle.USER_ALL);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -13948,7 +13994,6 @@
 
             // Load resources only after the current configuration has been set.
             final Resources res = mContext.getResources();
-            mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
             mThumbnailWidth = res.getDimensionPixelSize(
                     com.android.internal.R.dimen.thumbnail_width);
             mThumbnailHeight = res.getDimensionPixelSize(
@@ -13957,6 +14002,9 @@
                     com.android.internal.R.string.config_appsNotReportingCrashes));
             mUserController.mUserSwitchUiEnabled = !res.getBoolean(
                     com.android.internal.R.bool.config_customUserSwitchUi);
+            mUserController.mMaxRunningUsers = res.getInteger(
+                    com.android.internal.R.integer.config_multiuserMaxRunningUsers);
+
             if ((globalConfig.uiMode & UI_MODE_TYPE_TELEVISION) == UI_MODE_TYPE_TELEVISION) {
                 mFullscreenThumbnailScale = (float) res
                     .getInteger(com.android.internal.R.integer.thumbnail_width_tv) /
@@ -14067,9 +14115,8 @@
         }
 
         retrieveSettings();
-        final int currentUserId;
+        final int currentUserId = mUserController.getCurrentUserId();
         synchronized (this) {
-            currentUserId = mUserController.getCurrentUserIdLocked();
             readGrantedUriPermissionsLocked();
         }
 
@@ -14126,7 +14173,7 @@
                         | Intent.FLAG_RECEIVER_FOREGROUND);
                 intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
                 broadcastIntentLocked(null, null, intent,
-                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, null, 0, null, null, null, OP_NONE,
                         null, false, false, MY_PID, SYSTEM_UID,
                         currentUserId);
                 intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -14140,7 +14187,7 @@
                                     throws RemoteException {
                             }
                         }, 0, null, null,
-                        new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        new String[] {INTERACT_ACROSS_USERS}, OP_NONE,
                         null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
             } catch (Throwable t) {
                 Slog.wtf(TAG, "Failed sending first user broadcasts", t);
@@ -14148,7 +14195,24 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
-            mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
+            mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
+
+            BinderInternal.nSetBinderProxyCountEnabled(true);
+            BinderInternal.setBinderProxyCountCallback(
+                    new BinderInternal.BinderProxyLimitListener() {
+                        @Override
+                        public void onLimitReached(int uid) {
+                            Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+                                    + Process.myUid());
+                            if (uid == Process.SYSTEM_UID) {
+                                Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
+                            } else {
+                                killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+                                        "Too many Binders sent to SYSTEM");
+                            }
+                        }
+                    }, mHandler);
+
             traceLog.traceEnd(); // ActivityManagerStartApps
             traceLog.traceEnd(); // PhaseActivityManagerReady
         }
@@ -14305,12 +14369,12 @@
                 }
             }
             sb.append("\n");
-            if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
-                sb.append(info.crashInfo.stackTrace);
+            if (info.hasStackTrace()) {
+                sb.append(info.getStackTrace());
                 sb.append("\n");
             }
-            if (info.message != null) {
-                sb.append(info.message);
+            if (info.getViolationDetails() != null) {
+                sb.append(info.getViolationDetails());
                 sb.append("\n");
             }
 
@@ -14500,6 +14564,9 @@
                 }
                 sb.append("\n");
             }
+            if (process.info.isInstantApp()) {
+                sb.append("Instant-App: true\n");
+            }
         }
     }
 
@@ -14852,7 +14919,7 @@
     /**
      * Wrapper function to print out debug data filtered by specified arguments.
     */
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
         boolean dumpAll = false;
@@ -14861,7 +14928,6 @@
         boolean dumpCheckinFormat = false;
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
-        boolean useProto = false;
         String dumpPackage = null;
 
         int opti = 0;
@@ -14895,8 +14961,6 @@
             } else if ("-h".equals(opt)) {
                 ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
-            } else if ("--proto".equals(opt)) {
-                useProto = true;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
@@ -14905,10 +14969,31 @@
         long origId = Binder.clearCallingIdentity();
 
         if (useProto) {
-            //TODO: Options when dumping proto
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
-            synchronized (this) {
-                writeActivitiesToProtoLocked(proto);
+            String cmd = opti < args.length ? args[opti] : "";
+            opti++;
+
+            if ("activities".equals(cmd) || "a".equals(cmd)) {
+                // output proto is ActivityStackSupervisorProto
+                synchronized (this) {
+                    writeActivitiesToProtoLocked(proto);
+                }
+            } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
+                // output proto is BroadcastProto
+                synchronized (this) {
+                    writeBroadcastsToProtoLocked(proto);
+                }
+            } else {
+                // default option, dump everything, output is ActivityManagerServiceProto
+                synchronized (this) {
+                    long activityToken = proto.start(ActivityManagerServiceProto.ACTIVITIES);
+                    writeActivitiesToProtoLocked(proto);
+                    proto.end(activityToken);
+
+                    long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
+                    writeBroadcastsToProtoLocked(proto);
+                    proto.end(broadcastToken);
+                }
             }
             proto.flush();
             Binder.restoreCallingIdentity(origId);
@@ -14932,9 +15017,28 @@
                 synchronized (this) {
                     dumpActivityStarterLocked(pw, dumpPackage);
                 }
+            } else if ("containers".equals(cmd)) {
+                synchronized (this) {
+                    dumpActivityContainersLocked(pw);
+                }
             } else if ("recents".equals(cmd) || "r".equals(cmd)) {
                 synchronized (this) {
-                    dumpRecentsLocked(fd, pw, args, opti, true, dumpPackage);
+                    if (mRecentTasks != null) {
+                        mRecentTasks.dump(pw, true /* dumpAll */, dumpPackage);
+                    }
+                }
+            } else if ("binder-proxies".equals(cmd)) {
+                if (opti >= args.length) {
+                    dumpBinderProxiesCounts(pw, BinderInternal.nGetBinderProxyPerUidCounts(),
+                            "Counts of Binder Proxies held by SYSTEM");
+                } else {
+                    String uid = args[opti];
+                    opti++;
+                    // Ensure Binder Proxy Count is as up to date as possible
+                    System.gc();
+                    System.runFinalization();
+                    System.gc();
+                    pw.println(BinderInternal.nGetBinderProxyCount(Integer.parseInt(uid)));
                 }
             } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
                 String[] newArgs;
@@ -15155,7 +15259,9 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                if (mRecentTasks != null) {
+                    mRecentTasks.dump(pw, dumpAll, dumpPackage);
+                }
                 pw.println();
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
@@ -15170,6 +15276,11 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
+                dumpActivityContainersLocked(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
                 if (mAssociations.size() > 0) {
                     pw.println();
@@ -15225,7 +15336,9 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpRecentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                if (mRecentTasks != null) {
+                    mRecentTasks.dump(pw, dumpAll, dumpPackage);
+                }
                 pw.println();
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
@@ -15240,6 +15353,11 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
+                dumpActivityContainersLocked(pw);
+                pw.println();
+                if (dumpAll) {
+                    pw.println("-------------------------------------------------------------------------------");
+                }
                 dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
                 if (mAssociations.size() > 0) {
                     pw.println();
@@ -15259,7 +15377,8 @@
     }
 
     private void writeActivitiesToProtoLocked(ProtoOutputStream proto) {
-        mStackSupervisor.writeToProto(proto, ACTIVITIES);
+        // The output proto of "activity --proto activities" is ActivityStackSupervisorProto
+        mStackSupervisor.writeToProto(proto);
     }
 
     private void dumpLastANRLocked(PrintWriter pw) {
@@ -15271,6 +15390,12 @@
         }
     }
 
+    private void dumpActivityContainersLocked(PrintWriter pw) {
+        pw.println("ACTIVITY MANAGER STARTER (dumpsys activity containers)");
+        mStackSupervisor.dumpChildrenNames(pw, " ");
+        pw.println(" ");
+    }
+
     private void dumpActivityStarterLocked(PrintWriter pw, String dumpPackage) {
         pw.println("ACTIVITY MANAGER STARTER (dumpsys activity starter)");
         mActivityStarter.dump(pw, "", dumpPackage);
@@ -15311,42 +15436,6 @@
         }
     }
 
-    void dumpRecentsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
-        pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
-
-        boolean printedAnything = false;
-
-        if (mRecentTasks != null && mRecentTasks.size() > 0) {
-            boolean printedHeader = false;
-
-            final int N = mRecentTasks.size();
-            for (int i=0; i<N; i++) {
-                TaskRecord tr = mRecentTasks.get(i);
-                if (dumpPackage != null) {
-                    if (tr.realActivity == null ||
-                            !dumpPackage.equals(tr.realActivity.getPackageName())) {
-                        continue;
-                    }
-                }
-                if (!printedHeader) {
-                    pw.println("  Recent tasks:");
-                    printedHeader = true;
-                    printedAnything = true;
-                }
-                pw.print("  * Recent #"); pw.print(i); pw.print(": ");
-                        pw.println(tr);
-                if (dumpAll) {
-                    mRecentTasks.get(i).dump(pw, "    ");
-                }
-            }
-        }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
-    }
-
     void dumpAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
         pw.println("ACTIVITY MANAGER ASSOCIATIONS (dumpsys activity associations)");
@@ -15469,6 +15558,34 @@
         return printed;
     }
 
+    boolean dumpBinderProxiesCounts(PrintWriter pw, SparseIntArray counts, String header) {
+        if(counts != null) {
+            pw.println(header);
+            for (int i = 0; i < counts.size(); i++) {
+                final int uid = counts.keyAt(i);
+                final int binderCount = counts.valueAt(i);
+                pw.print("    UID ");
+                pw.print(uid);
+                pw.print(", binder count = ");
+                pw.print(binderCount);
+                pw.print(", package(s)= ");
+                final String[] pkgNames = mContext.getPackageManager().getPackagesForUid(uid);
+                if (pkgNames != null) {
+                    for (int j = 0; j < pkgNames.length; j++) {
+                        pw.print(pkgNames[j]);
+                        pw.print("; ");
+                    }
+                } else {
+                    pw.print("NO PACKAGE NAME FOUND");
+                }
+                pw.println();
+            }
+            pw.println();
+            return true;
+        }
+        return false;
+    }
+
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -15674,7 +15791,8 @@
             }
             pw.println("  mPreviousProcess: " + mPreviousProcess);
         }
-        if (dumpAll) {
+        if (dumpAll && (mPreviousProcess == null || dumpPackage == null
+                || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
             StringBuilder sb = new StringBuilder(128);
             sb.append("  mPreviousProcessVisibleTime: ");
             TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
@@ -15693,7 +15811,9 @@
             mStackSupervisor.dumpDisplayConfigs(pw, "  ");
         }
         if (dumpAll) {
-            pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            if (dumpPackage == null) {
+                pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            }
             if (mCompatModePackages.getPackages().size() > 0) {
                 boolean printed = false;
                 for (Map.Entry<String, Integer> entry
@@ -15773,8 +15893,8 @@
                 pw.println("  mRunningVoice=" + mRunningVoice);
                 pw.println("  mVoiceWakeLock" + mVoiceWakeLock);
             }
+            pw.println("  mVrController=" + mVrController);
         }
-        pw.println("  mVrController=" + mVrController);
         if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
                 || mOrigWaitForDebugger) {
             if (dumpPackage == null || dumpPackage.equals(mDebugApp)
@@ -16173,6 +16293,40 @@
         }
     }
 
+    void writeBroadcastsToProtoLocked(ProtoOutputStream proto) {
+        if (mRegisteredReceivers.size() > 0) {
+            Iterator it = mRegisteredReceivers.values().iterator();
+            while (it.hasNext()) {
+                ReceiverList r = (ReceiverList)it.next();
+                r.writeToProto(proto, BroadcastProto.RECEIVER_LIST);
+            }
+        }
+        mReceiverResolver.writeToProto(proto, BroadcastProto.RECEIVER_RESOLVER);
+        for (BroadcastQueue q : mBroadcastQueues) {
+            q.writeToProto(proto, BroadcastProto.BROADCAST_QUEUE);
+        }
+        for (int user=0; user<mStickyBroadcasts.size(); user++) {
+            long token = proto.start(BroadcastProto.STICKY_BROADCASTS);
+            proto.write(StickyBroadcastProto.USER, mStickyBroadcasts.keyAt(user));
+            for (Map.Entry<String, ArrayList<Intent>> ent
+                    : mStickyBroadcasts.valueAt(user).entrySet()) {
+                long actionToken = proto.start(StickyBroadcastProto.ACTIONS);
+                proto.write(StickyBroadcastProto.StickyAction.NAME, ent.getKey());
+                for (Intent intent : ent.getValue()) {
+                    intent.writeToProto(proto, StickyBroadcastProto.StickyAction.INTENTS,
+                            false, true, true, false);
+                }
+                proto.end(actionToken);
+            }
+            proto.end(token);
+        }
+
+        long handlerToken = proto.start(BroadcastProto.HANDLER);
+        proto.write(BroadcastProto.MainHandler.HANDLER, mHandler.toString());
+        mHandler.getLooper().writeToProto(proto, BroadcastProto.MainHandler.LOOPER);
+        proto.end(handlerToken);
+    }
+
     void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage) {
         boolean needSep = false;
@@ -16933,22 +17087,39 @@
         return stringifySize(size * 1024, 1024);
     }
 
-    // Update this version number in case you change the 'compact' format
+    // Update this version number if you change the 'compact' format.
     private static final int MEMINFO_COMPACT_VERSION = 1;
 
+    private static class MemoryUsageDumpOptions {
+        boolean dumpDetails;
+        boolean dumpFullDetails;
+        boolean dumpDalvik;
+        boolean dumpSummaryOnly;
+        boolean dumpUnreachable;
+        boolean oomOnly;
+        boolean isCompact;
+        boolean localOnly;
+        boolean packages;
+        boolean isCheckinRequest;
+        boolean dumpSwapPss;
+        boolean dumpProto;
+    }
+
     final void dumpApplicationMemoryUsage(FileDescriptor fd,
             PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
-        boolean dumpDetails = false;
-        boolean dumpFullDetails = false;
-        boolean dumpDalvik = false;
-        boolean dumpSummaryOnly = false;
-        boolean dumpUnreachable = false;
-        boolean oomOnly = false;
-        boolean isCompact = false;
-        boolean localOnly = false;
-        boolean packages = false;
-        boolean isCheckinRequest = false;
-        boolean dumpSwapPss = false;
+        MemoryUsageDumpOptions opts = new MemoryUsageDumpOptions();
+        opts.dumpDetails = false;
+        opts.dumpFullDetails = false;
+        opts.dumpDalvik = false;
+        opts.dumpSummaryOnly = false;
+        opts.dumpUnreachable = false;
+        opts.oomOnly = false;
+        opts.isCompact = false;
+        opts.localOnly = false;
+        opts.packages = false;
+        opts.isCheckinRequest = false;
+        opts.dumpSwapPss = false;
+        opts.dumpProto = false;
 
         int opti = 0;
         while (opti < args.length) {
@@ -16958,29 +17129,31 @@
             }
             opti++;
             if ("-a".equals(opt)) {
-                dumpDetails = true;
-                dumpFullDetails = true;
-                dumpDalvik = true;
-                dumpSwapPss = true;
+                opts.dumpDetails = true;
+                opts.dumpFullDetails = true;
+                opts.dumpDalvik = true;
+                opts.dumpSwapPss = true;
             } else if ("-d".equals(opt)) {
-                dumpDalvik = true;
+                opts.dumpDalvik = true;
             } else if ("-c".equals(opt)) {
-                isCompact = true;
+                opts.isCompact = true;
             } else if ("-s".equals(opt)) {
-                dumpDetails = true;
-                dumpSummaryOnly = true;
+                opts.dumpDetails = true;
+                opts.dumpSummaryOnly = true;
             } else if ("-S".equals(opt)) {
-                dumpSwapPss = true;
+                opts.dumpSwapPss = true;
             } else if ("--unreachable".equals(opt)) {
-                dumpUnreachable = true;
+                opts.dumpUnreachable = true;
             } else if ("--oom".equals(opt)) {
-                oomOnly = true;
+                opts.oomOnly = true;
             } else if ("--local".equals(opt)) {
-                localOnly = true;
+                opts.localOnly = true;
             } else if ("--package".equals(opt)) {
-                packages = true;
+                opts.packages = true;
             } else if ("--checkin".equals(opt)) {
-                isCheckinRequest = true;
+                opts.isCheckinRequest = true;
+            } else if ("--proto".equals(opt)) {
+                opts.dumpProto = true;
 
             } else if ("-h".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]");
@@ -16994,6 +17167,7 @@
                 pw.println("  --package: interpret process arg as package, dumping all");
                 pw.println("             processes that have loaded that package.");
                 pw.println("  --checkin: dump data for a checkin");
+                pw.println("  --proto: dump data to proto");
                 pw.println("If [process] is specified it can be the name or ");
                 pw.println("pid of a specific process to dump.");
                 return;
@@ -17002,21 +17176,33 @@
             }
         }
 
+        String[] innerArgs = new String[args.length-opti];
+        System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+
+        ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args);
+        if (opts.dumpProto) {
+            dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs);
+        } else {
+            dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw);
+        }
+    }
+
+    final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix,
+            MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+            ArrayList<ProcessRecord> procs, PrintWriter categoryPw) {
         long uptime = SystemClock.uptimeMillis();
         long realtime = SystemClock.elapsedRealtime();
         final long[] tmpLong = new long[1];
 
-        ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, packages, args);
         if (procs == null) {
             // No Java processes.  Maybe they want to print a native process.
-            if (args != null && args.length > opti
-                    && args[opti].charAt(0) != '-') {
+            if (innerArgs.length > 0 && innerArgs[0].charAt(0) != '-') {
                 ArrayList<ProcessCpuTracker.Stats> nativeProcs
                         = new ArrayList<ProcessCpuTracker.Stats>();
                 updateCpuStatsNow();
                 int findPid = -1;
                 try {
-                    findPid = Integer.parseInt(args[opti]);
+                    findPid = Integer.parseInt(innerArgs[0]);
                 } catch (NumberFormatException e) {
                 }
                 synchronized (mProcessCpuTracker) {
@@ -17024,51 +17210,48 @@
                     for (int i=0; i<N; i++) {
                         ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
                         if (st.pid == findPid || (st.baseName != null
-                                && st.baseName.equals(args[opti]))) {
+                                && st.baseName.equals(innerArgs[0]))) {
                             nativeProcs.add(st);
                         }
                     }
                 }
                 if (nativeProcs.size() > 0) {
-                    dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest,
-                            isCompact);
+                    dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest,
+                            opts.isCompact);
                     Debug.MemoryInfo mi = null;
                     for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
                         final ProcessCpuTracker.Stats r = nativeProcs.get(i);
                         final int pid = r.pid;
-                        if (!isCheckinRequest && dumpDetails) {
+                        if (!opts.isCheckinRequest && opts.dumpDetails) {
                             pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **");
                         }
                         if (mi == null) {
                             mi = new Debug.MemoryInfo();
                         }
-                        if (dumpDetails || (!brief && !oomOnly)) {
+                        if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
                             Debug.getMemoryInfo(pid, mi);
                         } else {
                             mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
                             mi.dalvikPrivateDirty = (int)tmpLong[0];
                         }
-                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
-                                dumpDalvik, dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
-                        if (isCheckinRequest) {
+                        ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+                                opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.baseName, 0, 0, 0, 0, 0, 0);
+                        if (opts.isCheckinRequest) {
                             pw.println();
                         }
                     }
                     return;
                 }
             }
-            pw.println("No process found for: " + args[opti]);
+            pw.println("No process found for: " + innerArgs[0]);
             return;
         }
 
-        if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest || packages)) {
-            dumpDetails = true;
+        if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) {
+            opts.dumpDetails = true;
         }
 
-        dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, isCompact);
-
-        String[] innerArgs = new String[args.length-opti];
-        System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+        dumpApplicationMemoryUsageHeader(pw, uptime, realtime, opts.isCheckinRequest, opts.isCompact);
 
         ArrayList<MemItem> procMems = new ArrayList<MemItem>();
         final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
@@ -17076,9 +17259,9 @@
         long nativeSwapPss = 0;
         long dalvikPss = 0;
         long dalvikSwapPss = 0;
-        long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+        long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
                 EmptyArray.LONG;
-        long[] dalvikSubitemSwapPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+        long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
                 EmptyArray.LONG;
         long otherPss = 0;
         long otherSwapPss = 0;
@@ -17110,24 +17293,24 @@
                 hasActivities = r.activities.size() > 0;
             }
             if (thread != null) {
-                if (!isCheckinRequest && dumpDetails) {
+                if (!opts.isCheckinRequest && opts.dumpDetails) {
                     pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
                 }
                 if (mi == null) {
                     mi = new Debug.MemoryInfo();
                 }
-                if (dumpDetails || (!brief && !oomOnly)) {
+                if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
                     Debug.getMemoryInfo(pid, mi);
                     hasSwapPss = mi.hasSwappedOutPss;
                 } else {
                     mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
                     mi.dalvikPrivateDirty = (int)tmpLong[0];
                 }
-                if (dumpDetails) {
-                    if (localOnly) {
-                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
-                                dumpDalvik, dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
-                        if (isCheckinRequest) {
+                if (opts.dumpDetails) {
+                    if (opts.localOnly) {
+                        ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
+                                opts.dumpDalvik, opts.dumpSummaryOnly, pid, r.processName, 0, 0, 0, 0, 0, 0);
+                        if (opts.isCheckinRequest) {
                             pw.println();
                         }
                     } else {
@@ -17136,19 +17319,19 @@
                             TransferPipe tp = new TransferPipe();
                             try {
                                 thread.dumpMemInfo(tp.getWriteFd(),
-                                        mi, isCheckinRequest, dumpFullDetails,
-                                        dumpDalvik, dumpSummaryOnly, dumpUnreachable, innerArgs);
+                                        mi, opts.isCheckinRequest, opts.dumpFullDetails,
+                                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
                                 tp.go(fd);
                             } finally {
                                 tp.kill();
                             }
                         } catch (IOException e) {
-                            if (!isCheckinRequest) {
+                            if (!opts.isCheckinRequest) {
                                 pw.println("Got IoException! " + e);
                                 pw.flush();
                             }
                         } catch (RemoteException e) {
-                            if (!isCheckinRequest) {
+                            if (!opts.isCheckinRequest) {
                                 pw.println("Got RemoteException! " + e);
                                 pw.flush();
                             }
@@ -17167,7 +17350,7 @@
                     }
                 }
 
-                if (!isCheckinRequest && mi != null) {
+                if (!opts.isCheckinRequest && mi != null) {
                     totalPss += myTotalPss;
                     totalSwapPss += myTotalSwapPss;
                     MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
@@ -17220,7 +17403,7 @@
 
         long nativeProcTotalPss = 0;
 
-        if (!isCheckinRequest && procs.size() > 1 && !packages) {
+        if (!opts.isCheckinRequest && procs.size() > 1 && !opts.packages) {
             // If we are showing aggregations, also look for native processes to
             // include so that our aggregations are more accurate.
             updateCpuStatsNow();
@@ -17233,7 +17416,7 @@
                         if (mi == null) {
                             mi = new Debug.MemoryInfo();
                         }
-                        if (!brief && !oomOnly) {
+                        if (!brief && !opts.oomOnly) {
                             Debug.getMemoryInfo(st.pid, mi);
                         } else {
                             mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
@@ -17320,7 +17503,7 @@
             ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
             for (int j=0; j<oomPss.length; j++) {
                 if (oomPss[j] != 0) {
-                    String label = isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+                    String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
                             : DUMP_MEM_OOM_LABEL[j];
                     MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j],
                             DUMP_MEM_OOM_ADJ[j]);
@@ -17329,26 +17512,26 @@
                 }
             }
 
-            dumpSwapPss = dumpSwapPss && hasSwapPss && totalSwapPss != 0;
-            if (!brief && !oomOnly && !isCompact) {
+            opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0;
+            if (!brief && !opts.oomOnly && !opts.isCompact) {
                 pw.println();
                 pw.println("Total PSS by process:");
-                dumpMemItems(pw, "  ", "proc", procMems, true, isCompact, dumpSwapPss);
+                dumpMemItems(pw, "  ", "proc", procMems, true, opts.isCompact, opts.dumpSwapPss);
                 pw.println();
             }
-            if (!isCompact) {
+            if (!opts.isCompact) {
                 pw.println("Total PSS by OOM adjustment:");
             }
-            dumpMemItems(pw, "  ", "oom", oomMems, false, isCompact, dumpSwapPss);
-            if (!brief && !oomOnly) {
+            dumpMemItems(pw, "  ", "oom", oomMems, false, opts.isCompact, opts.dumpSwapPss);
+            if (!brief && !opts.oomOnly) {
                 PrintWriter out = categoryPw != null ? categoryPw : pw;
-                if (!isCompact) {
+                if (!opts.isCompact) {
                     out.println();
                     out.println("Total PSS by category:");
                 }
-                dumpMemItems(out, "  ", "cat", catMems, true, isCompact, dumpSwapPss);
+                dumpMemItems(out, "  ", "cat", catMems, true, opts.isCompact, opts.dumpSwapPss);
             }
-            if (!isCompact) {
+            if (!opts.isCompact) {
                 pw.println();
             }
             MemInfoReader memInfo = new MemInfoReader();
@@ -17366,7 +17549,7 @@
                 }
             }
             if (!brief) {
-                if (!isCompact) {
+                if (!opts.isCompact) {
                     pw.print("Total RAM: "); pw.print(stringifyKBSize(memInfo.getTotalSizeKb()));
                     pw.print(" (status ");
                     switch (mLastMemoryLevel) {
@@ -17407,7 +17590,7 @@
             long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
                     - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
                     - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb();
-            if (!isCompact) {
+            if (!opts.isCompact) {
                 pw.print(" Used RAM: "); pw.print(stringifyKBSize(totalPss - cachedPss
                         + memInfo.getKernelUsedSizeKb())); pw.print(" (");
                 pw.print(stringifyKBSize(totalPss - cachedPss)); pw.print(" used pss + ");
@@ -17418,7 +17601,7 @@
             }
             if (!brief) {
                 if (memInfo.getZramTotalSizeKb() != 0) {
-                    if (!isCompact) {
+                    if (!opts.isCompact) {
                         pw.print("     ZRAM: ");
                         pw.print(stringifyKBSize(memInfo.getZramTotalSizeKb()));
                                 pw.print(" physical used for ");
@@ -17434,7 +17617,7 @@
                     }
                 }
                 final long[] ksm = getKsmInfo();
-                if (!isCompact) {
+                if (!opts.isCompact) {
                     if (ksm[KSM_SHARING] != 0 || ksm[KSM_SHARED] != 0 || ksm[KSM_UNSHARED] != 0
                             || ksm[KSM_VOLATILE] != 0) {
                         pw.print("      KSM: "); pw.print(stringifyKBSize(ksm[KSM_SHARING]));
@@ -17483,6 +17666,17 @@
         }
     }
 
+    final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw,
+            MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief,
+            ArrayList<ProcessRecord> procs) {
+        ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        // TODO: implement
+        pw.println("Not yet implemented. Have a cookie instead! :]");
+
+        proto.flush();
+    }
+
     private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss,
             long memtrack, String name) {
         sb.append("  ");
@@ -18077,8 +18271,7 @@
     // =========================================================
 
     @Override
-    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
-            int flags) {
+    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags) {
         enforceNotIsolatedCaller("getServices");
 
         final int callingUid = Binder.getCallingUid();
@@ -18707,7 +18900,7 @@
                     Intent intent = allSticky.get(i);
                     BroadcastQueue queue = broadcastQueueForIntent(intent);
                     BroadcastRecord r = new BroadcastRecord(queue, intent, null,
-                            null, -1, -1, false, null, null, AppOpsManager.OP_NONE, null, receivers,
+                            null, -1, -1, false, null, null, OP_NONE, null, receivers,
                             null, 0, null, null, false, true, true, -1);
                     queue.enqueueParallelBroadcastLocked(r);
                     queue.scheduleBroadcastsLocked();
@@ -18976,7 +19169,7 @@
         // If not, we will just skip it. Make an exception for shutdown broadcasts
         // and upgrade steps.
 
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, 0)) {
+        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
             if ((callingUid != SYSTEM_UID
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                     && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -19161,7 +19354,7 @@
                                         // Remove all permissions granted from/to this package
                                         removeUriPermissionsForPackageLocked(ssp, userId, true);
 
-                                        removeTasksByPackageNameLocked(ssp, userId);
+                                        mRecentTasks.removeTasksByPackageName(ssp, userId);
 
                                         mServices.forceStopPackageLocked(ssp, userId);
 
@@ -19395,7 +19588,7 @@
         int[] users;
         if (userId == UserHandle.USER_ALL) {
             // Caller wants broadcast to go to all started users.
-            users = mUserController.getStartedUserArrayLocked();
+            users = mUserController.getStartedUserArray();
         } else {
             // Caller wants broadcast to go to one specific user.
             users = new int[] {userId};
@@ -19703,7 +19896,7 @@
                     : new String[] {requiredPermission};
             int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
                     resultTo, resultCode, resultData, resultExtras,
-                    requiredPermissions, AppOpsManager.OP_NONE, bOptions, serialized,
+                    requiredPermissions, OP_NONE, bOptions, serialized,
                     sticky, -1, uid, userId);
             Binder.restoreCallingIdentity(origId);
             return res;
@@ -20021,12 +20214,20 @@
     }
 
     @Override
-    public int getFocusedStackId() throws RemoteException {
-        ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack != null) {
-            return focusedStack.getStackId();
+    public StackInfo getFocusedStackInfo() throws RemoteException {
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                ActivityStack focusedStack = getFocusedStack();
+                if (focusedStack != null) {
+                    return mStackSupervisor.getStackInfo(focusedStack.mStackId);
+                }
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
-        return -1;
     }
 
     public Configuration getConfiguration() {
@@ -20052,15 +20253,21 @@
      *       activity and clearing the task at the same time.
      */
     @Override
+    // TODO: API should just be about changing windowing modes...
     public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
-        if (StackId.isHomeOrRecentsStack(fromStackId)) {
-            throw new IllegalArgumentException("You can't move tasks from the home/recents stack.");
-        }
+        enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS,
+                "moveTasksToFullscreenStack()");
         synchronized (this) {
             final long origId = Binder.clearCallingIdentity();
             try {
-                mStackSupervisor.moveTasksToFullscreenStackLocked(fromStackId, onTop);
+                final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
+                if (stack != null){
+                    if (!stack.isActivityTypeStandardOrUndefined()) {
+                        throw new IllegalArgumentException(
+                                "You can't move tasks from non-standard stacks.");
+                    }
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop);
+                }
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -20158,7 +20365,7 @@
 
     void updateUserConfigurationLocked() {
         final Configuration configuration = new Configuration(getGlobalConfiguration());
-        final int currentUserId = mUserController.getCurrentUserIdLocked();
+        final int currentUserId = mUserController.getCurrentUserId();
         Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
                 currentUserId, Settings.System.canWrite(mContext));
         updateConfigurationLocked(configuration, null /* starting */, false /* initLocale */,
@@ -20269,7 +20476,7 @@
         Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
         // TODO(multi-display): Update UsageEvents#Event to include displayId.
         mUsageStatsService.reportConfigurationChange(mTempConfig,
-                mUserController.getCurrentUserIdLocked());
+                mUserController.getCurrentUserId());
 
         // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
         mShowDialogs = shouldShowDialogs(mTempConfig);
@@ -20312,7 +20519,7 @@
                 | Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
         broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                 UserHandle.USER_ALL);
         if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
             intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -20323,7 +20530,7 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             }
             broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
-                    AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                    OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
                     UserHandle.USER_ALL);
         }
 
@@ -20485,9 +20692,10 @@
     /** Helper method that requests bounds from WM and applies them to stack. */
     private void resizeStackWithBoundsFromWindowManager(int stackId, boolean deferResume) {
         final Rect newStackBounds = new Rect();
-        mStackSupervisor.getStack(stackId).getBoundsForNewConfiguration(newStackBounds);
+        final ActivityStack stack = mStackSupervisor.getStack(stackId);
+        stack.getBoundsForNewConfiguration(newStackBounds);
         mStackSupervisor.resizeStackLocked(
-                stackId, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
+                stack, !newStackBounds.isEmpty() ? newStackBounds : null /* bounds */,
                 null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
                 false /* preserveWindows */, false /* allowResizeInDockedMode */, deferResume);
     }
@@ -22200,7 +22408,7 @@
             String authority) {
         if (app == null) return;
         if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-            UserState userState = mUserController.getStartedUserStateLocked(app.userId);
+            UserState userState = mUserController.getStartedUserState(app.userId);
             if (userState == null) return;
             final long now = SystemClock.elapsedRealtime();
             Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -23509,10 +23717,8 @@
     }
 
     String getStartedUserState(int userId) {
-        synchronized (this) {
-            final UserState userState = mUserController.getStartedUserStateLocked(userId);
-            return UserState.stateToString(userState.state);
-        }
+        final UserState userState = mUserController.getStartedUserState(userId);
+        return UserState.stateToString(userState.state);
     }
 
     @Override
@@ -23527,9 +23733,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            return mUserController.isUserRunningLocked(userId, flags);
-        }
+        return mUserController.isUserRunning(userId, flags);
     }
 
     @Override
@@ -23543,9 +23747,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            return mUserController.getStartedUserArrayLocked();
-        }
+        return mUserController.getStartedUserArray();
     }
 
     @Override
@@ -23566,9 +23768,7 @@
     }
 
     public boolean isUserStopped(int userId) {
-        synchronized (this) {
-            return mUserController.getStartedUserStateLocked(userId) == null;
-        }
+        return mUserController.getStartedUserState(userId) == null;
     }
 
     ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
@@ -24156,132 +24356,13 @@
                 permission.INTERACT_ACROSS_USERS_FULL, "getLastResumedActivityUserId()");
         synchronized (this) {
             if (mLastResumedActivity == null) {
-                return mUserController.getCurrentUserIdLocked();
+                return mUserController.getCurrentUserId();
             }
             return mLastResumedActivity.userId;
         }
     }
 
     /**
-     * An implementation of IAppTask, that allows an app to manage its own tasks via
-     * {@link android.app.ActivityManager.AppTask}.  We keep track of the callingUid to ensure that
-     * only the process that calls getAppTasks() can call the AppTask methods.
-     */
-    class AppTaskImpl extends IAppTask.Stub {
-        private int mTaskId;
-        private int mCallingUid;
-
-        public AppTaskImpl(int taskId, int callingUid) {
-            mTaskId = taskId;
-            mCallingUid = callingUid;
-        }
-
-        private void checkCaller() {
-            if (mCallingUid != Binder.getCallingUid()) {
-                throw new SecurityException("Caller " + mCallingUid
-                        + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
-            }
-        }
-
-        @Override
-        public void finishAndRemoveTask() {
-            checkCaller();
-
-            synchronized (ActivityManagerService.this) {
-                long origId = Binder.clearCallingIdentity();
-                try {
-                    // We remove the task from recents to preserve backwards
-                    if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
-                            REMOVE_FROM_RECENTS)) {
-                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(origId);
-                }
-            }
-        }
-
-        @Override
-        public ActivityManager.RecentTaskInfo getTaskInfo() {
-            checkCaller();
-
-            synchronized (ActivityManagerService.this) {
-                long origId = Binder.clearCallingIdentity();
-                try {
-                    TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
-                    if (tr == null) {
-                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                    }
-                    return createRecentTaskInfoFromTaskRecord(tr);
-                } finally {
-                    Binder.restoreCallingIdentity(origId);
-                }
-            }
-        }
-
-        @Override
-        public void moveToFront() {
-            checkCaller();
-            // Will bring task to front if it already has a root activity.
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                synchronized (this) {
-                    mStackSupervisor.startActivityFromRecentsInner(mTaskId, null);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(origId);
-            }
-        }
-
-        @Override
-        public int startActivity(IBinder whoThread, String callingPackage,
-                Intent intent, String resolvedType, Bundle bOptions) {
-            checkCaller();
-
-            int callingUser = UserHandle.getCallingUserId();
-            TaskRecord tr;
-            IApplicationThread appThread;
-            synchronized (ActivityManagerService.this) {
-                tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
-                if (tr == null) {
-                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                }
-                appThread = IApplicationThread.Stub.asInterface(whoThread);
-                if (appThread == null) {
-                    throw new IllegalArgumentException("Bad app thread " + appThread);
-                }
-            }
-            return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent,
-                    resolvedType, null, null, null, null, 0, 0, null, null,
-                    null, bOptions, false, callingUser, tr, "AppTaskImpl");
-        }
-
-        @Override
-        public void setExcludeFromRecents(boolean exclude) {
-            checkCaller();
-
-            synchronized (ActivityManagerService.this) {
-                long origId = Binder.clearCallingIdentity();
-                try {
-                    TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(mTaskId);
-                    if (tr == null) {
-                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
-                    }
-                    Intent intent = tr.getBaseIntent();
-                    if (exclude) {
-                        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                    } else {
-                        intent.setFlags(intent.getFlags()
-                                & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(origId);
-                }
-            }
-        }
-    }
-
-    /**
      * Kill processes for the user with id userId and that depend on the package named packageName
      */
     @Override
@@ -24363,7 +24444,7 @@
                 if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                     try {
                         final ApplicationInfo ai = AppGlobals.getPackageManager()
-                                .getApplicationInfo(packageName, 0 /*flags*/, app.userId);
+                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
                         if (ai != null) {
                             app.thread.scheduleApplicationInfoChanged(ai);
                         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5e0724e..d6bd2b31 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -73,7 +73,6 @@
 
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -85,15 +84,6 @@
     public static final String NO_CLASS_ERROR_CODE = "Error type 3";
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
 
-    // Is the object moving in a positive direction?
-    private static final boolean MOVING_FORWARD = true;
-    // Is the object moving in the horizontal plan?
-    private static final boolean MOVING_HORIZONTALLY = true;
-    // Is the object current point great then its target point?
-    private static final boolean GREATER_THAN_TARGET = true;
-    // Amount we reduce the stack size by when testing a task re-size.
-    private static final int STACK_BOUNDS_INSET = 10;
-
     // IPC interface to activity manager -- don't need to do additional security checks.
     final IActivityManager mInterface;
 
@@ -232,6 +222,8 @@
                     return runSetInactive(pw);
                 case "get-inactive":
                     return runGetInactive(pw);
+                case "set-standby-bucket":
+                    return runSetStandbyBucket(pw);
                 case "send-trim-memory":
                     return runSendTrimMemory(pw);
                 case "display":
@@ -381,7 +373,7 @@
             if (mProfileFile != null || mAgent != null) {
                 ParcelFileDescriptor fd = null;
                 if (mProfileFile != null) {
-                    fd = openOutputFileForSystem(mProfileFile);
+                    fd = openFileForSystem(mProfileFile, "w");
                     if (fd == null) {
                         return 1;
                     }
@@ -676,7 +668,7 @@
 
         File file = new File(filename);
         file.delete();
-        ParcelFileDescriptor fd = openOutputFileForSystem(filename);
+        ParcelFileDescriptor fd = openFileForSystem(filename, "w");
         if (fd == null) {
             return -1;
         }
@@ -764,7 +756,7 @@
 
         if (start) {
             profileFile = getNextArgRequired();
-            fd = openOutputFileForSystem(profileFile);
+            fd = openFileForSystem(profileFile, "w");
             if (fd == null) {
                 return -1;
             }
@@ -828,7 +820,7 @@
 
         File file = new File(heapFile);
         file.delete();
-        ParcelFileDescriptor fd = openOutputFileForSystem(heapFile);
+        ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
         if (fd == null) {
             return -1;
         }
@@ -1834,6 +1826,27 @@
         return 0;
     }
 
+    int runSetStandbyBucket(PrintWriter pw) throws RemoteException {
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt=getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        String packageName = getNextArgRequired();
+        String value = getNextArgRequired();
+
+        IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
+                Context.USAGE_STATS_SERVICE));
+        usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId);
+        return 0;
+    }
+
     int runGetInactive(PrintWriter pw) throws RemoteException {
         int userId = UserHandle.USER_CURRENT;
 
@@ -1943,8 +1956,6 @@
                 return runStackInfo(pw);
             case "move-top-activity-to-pinned-stack":
                 return runMoveTopActivityToPinnedStack(pw);
-            case "size-docked-stack-test":
-                return runStackSizeDockedStackTest(pw);
             case "remove":
                 return runStackRemove(pw);
             default:
@@ -2113,9 +2124,9 @@
     }
 
     int runStackInfo(PrintWriter pw) throws RemoteException {
-        String stackIdStr = getNextArgRequired();
-        int stackId = Integer.parseInt(stackIdStr);
-        ActivityManager.StackInfo info = mInterface.getStackInfo(stackId);
+        int windowingMode = Integer.parseInt(getNextArgRequired());
+        int activityType = Integer.parseInt(getNextArgRequired());
+        ActivityManager.StackInfo info = mInterface.getStackInfo(windowingMode, activityType);
         pw.println(info);
         return 0;
     }
@@ -2142,88 +2153,6 @@
         return 0;
     }
 
-    int runStackSizeDockedStackTest(PrintWriter pw) throws RemoteException {
-        final PrintWriter err = getErrPrintWriter();
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String side = getNextArgRequired();
-        final String delayStr = getNextArg();
-        final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-
-        ActivityManager.StackInfo info = mInterface.getStackInfo(DOCKED_STACK_ID);
-        if (info == null) {
-            err.println("Docked stack doesn't exist");
-            return -1;
-        }
-        if (info.bounds == null) {
-            err.println("Docked stack doesn't have a bounds");
-            return -1;
-        }
-        Rect bounds = info.bounds;
-
-        final boolean horizontalGrowth = "l".equals(side) || "r".equals(side);
-        final int changeSize = (horizontalGrowth ? bounds.width() : bounds.height()) / 2;
-        int currentPoint;
-        switch (side) {
-            case "l":
-                currentPoint = bounds.left;
-                break;
-            case "r":
-                currentPoint = bounds.right;
-                break;
-            case "t":
-                currentPoint = bounds.top;
-                break;
-            case "b":
-                currentPoint = bounds.bottom;
-                break;
-            default:
-                err.println("Unknown growth side: " + side);
-                return -1;
-        }
-
-        final int startPoint = currentPoint;
-        final int minPoint = currentPoint - changeSize;
-        final int maxPoint = currentPoint + changeSize;
-
-        int maxChange;
-        pw.println("Shrinking docked stack side=" + side);
-        pw.flush();
-        while (currentPoint > minPoint) {
-            maxChange = Math.min(stepSize, currentPoint - minPoint);
-            currentPoint -= maxChange;
-            setBoundsSide(bounds, side, currentPoint);
-            int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
-            if (res < 0) {
-                return res;
-            }
-        }
-
-        pw.println("Growing docked stack side=" + side);
-        pw.flush();
-        while (currentPoint < maxPoint) {
-            maxChange = Math.min(stepSize, maxPoint - currentPoint);
-            currentPoint += maxChange;
-            setBoundsSide(bounds, side, currentPoint);
-            int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
-            if (res < 0) {
-                return res;
-            }
-        }
-
-        pw.println("Back to Original size side=" + side);
-        pw.flush();
-        while (currentPoint > startPoint) {
-            maxChange = Math.min(stepSize, currentPoint - startPoint);
-            currentPoint -= maxChange;
-            setBoundsSide(bounds, side, currentPoint);
-            int res = resizeStack(DOCKED_STACK_ID, bounds, delayMs);
-            if (res < 0) {
-                return res;
-            }
-        }
-        return 0;
-    }
-
     void setBoundsSide(Rect bounds, String side, int value) {
         switch (side) {
             case "l":
@@ -2252,10 +2181,6 @@
             return runTaskResizeable(pw);
         } else if (op.equals("resize")) {
             return runTaskResize(pw);
-        } else if (op.equals("drag-task-test")) {
-            return runTaskDragTaskTest(pw);
-        } else if (op.equals("size-task-test")) {
-            return runTaskSizeTaskTest(pw);
         } else if (op.equals("focus")) {
             return runTaskFocus(pw);
         } else {
@@ -2308,58 +2233,6 @@
         }
     }
 
-    int runTaskDragTaskTest(PrintWriter pw) throws RemoteException {
-        final int taskId = Integer.parseInt(getNextArgRequired());
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String delayStr = getNextArg();
-        final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-        final ActivityManager.StackInfo stackInfo;
-        Rect taskBounds;
-        stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
-        taskBounds = mInterface.getTaskBounds(taskId);
-        final Rect stackBounds = stackInfo.bounds;
-        int travelRight = stackBounds.width() - taskBounds.width();
-        int travelLeft = -travelRight;
-        int travelDown = stackBounds.height() - taskBounds.height();
-        int travelUp = -travelDown;
-        int passes = 0;
-
-        // We do 2 passes to get back to the original location of the task.
-        while (passes < 2) {
-            // Move right
-            pw.println("Moving right...");
-            pw.flush();
-            travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel right by " + travelRight);
-
-            // Move down
-            pw.println("Moving down...");
-            pw.flush();
-            travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel down by " + travelDown);
-
-            // Move left
-            pw.println("Moving left...");
-            pw.flush();
-            travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel left by " + travelLeft);
-
-            // Move up
-            pw.println("Moving up...");
-            pw.flush();
-            travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel up by " + travelUp);
-
-            taskBounds = mInterface.getTaskBounds(taskId);
-            passes++;
-        }
-        return 0;
-    }
-
     int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize,
             int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms)
             throws RemoteException {
@@ -2422,133 +2295,6 @@
         return stepSize;
     }
 
-    int runTaskSizeTaskTest(PrintWriter pw) throws RemoteException {
-        final int taskId = Integer.parseInt(getNextArgRequired());
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String delayStr = getNextArg();
-        final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-        final ActivityManager.StackInfo stackInfo;
-        final Rect initialTaskBounds;
-        stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
-        initialTaskBounds = mInterface.getTaskBounds(taskId);
-        final Rect stackBounds = stackInfo.bounds;
-        stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET);
-        final Rect currentTaskBounds = new Rect(initialTaskBounds);
-
-        // Size by top-left
-        pw.println("Growing top-left");
-        pw.flush();
-        do {
-            currentTaskBounds.top -= getStepSize(
-                    currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.left -= getStepSize(
-                    currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.top < currentTaskBounds.top
-                || stackBounds.left < currentTaskBounds.left);
-
-        // Back to original size
-        pw.println("Shrinking top-left");
-        pw.flush();
-        do {
-            currentTaskBounds.top += getStepSize(
-                    currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.left += getStepSize(
-                    currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.top > currentTaskBounds.top
-                || initialTaskBounds.left > currentTaskBounds.left);
-
-        // Size by top-right
-        pw.println("Growing top-right");
-        pw.flush();
-        do {
-            currentTaskBounds.top -= getStepSize(
-                    currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.right += getStepSize(
-                    currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.top < currentTaskBounds.top
-                || stackBounds.right > currentTaskBounds.right);
-
-        // Back to original size
-        pw.println("Shrinking top-right");
-        pw.flush();
-        do {
-            currentTaskBounds.top += getStepSize(
-                    currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
-                    stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.top > currentTaskBounds.top
-                || initialTaskBounds.right < currentTaskBounds.right);
-
-        // Size by bottom-left
-        pw.println("Growing bottom-left");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom += getStepSize(
-                    currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.left -= getStepSize(
-                    currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.bottom > currentTaskBounds.bottom
-                || stackBounds.left < currentTaskBounds.left);
-
-        // Back to original size
-        pw.println("Shrinking bottom-left");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
-                    initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.left += getStepSize(
-                    currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.bottom < currentTaskBounds.bottom
-                || initialTaskBounds.left > currentTaskBounds.left);
-
-        // Size by bottom-right
-        pw.println("Growing bottom-right");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom += getStepSize(
-                    currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.right += getStepSize(
-                    currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.bottom > currentTaskBounds.bottom
-                || stackBounds.right > currentTaskBounds.right);
-
-        // Back to original size
-        pw.println("Shrinking bottom-right");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
-                    initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
-                    stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.bottom < currentTaskBounds.bottom
-                || initialTaskBounds.right < currentTaskBounds.right);
-        return 0;
-    }
-
     int runTaskFocus(PrintWriter pw) throws RemoteException {
         final int taskId = Integer.parseInt(getNextArgRequired());
         pw.println("Setting focus to task " + taskId);
@@ -2559,7 +2305,7 @@
     int runWrite(PrintWriter pw) {
         mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                 "registerUidObserver()");
-        mInternal.mRecentTasks.flush();
+        mInternal.getRecentTasks().flush();
         pw.println("All tasks persisted.");
         return 0;
     }
@@ -2848,6 +2594,8 @@
             pw.println("      Sets the inactive state of an app.");
             pw.println("  get-inactive [--user <USER_ID>] <PACKAGE>");
             pw.println("      Returns the inactive state of an app.");
+            pw.println("  set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>");
+            pw.println("      Puts an app in the standby bucket.");
             pw.println("  send-trim-memory [--user <USER_ID>] <PROCESS>");
             pw.println("          [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
             pw.println("      Send a memory trim event to a <PROCESS>.  May also supply a raw trim int level.");
@@ -2868,10 +2616,6 @@
             pw.println("           Change docked stack to <LEFT,TOP,RIGHT,BOTTOM>");
             pw.println("           and supplying temporary different task bounds indicated by");
             pw.println("           <TASK_LEFT,TOP,RIGHT,BOTTOM>");
-            pw.println("       size-docked-stack-test: <STEP_SIZE> <l|t|r|b> [DELAY_MS]");
-            pw.println("           Test command for sizing docked stack by");
-            pw.println("           <STEP_SIZE> increments from the side <l>eft, <t>op, <r>ight, or <b>ottom");
-            pw.println("           applying the optional [DELAY_MS] between each step.");
             pw.println("       move-top-activity-to-pinned-stack: <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
             pw.println("           Moves the top activity from");
             pw.println("           <STACK_ID> to the pinned stack using <LEFT,TOP,RIGHT,BOTTOM> for the");
@@ -2880,8 +2624,8 @@
             pw.println("           Place <TASK_ID> in <STACK_ID> at <POSITION>");
             pw.println("       list");
             pw.println("           List all of the activity stacks and their sizes.");
-            pw.println("       info <STACK_ID>");
-            pw.println("           Display the information about activity stack <STACK_ID>.");
+            pw.println("       info <WINDOWING_MODE> <ACTIVITY_TYPE>");
+            pw.println("           Display the information about activity stack in <WINDOWING_MODE> and <ACTIVITY_TYPE>.");
             pw.println("       remove <STACK_ID>");
             pw.println("           Remove stack <STACK_ID>.");
             pw.println("  task [COMMAND] [...]: sub-commands for operating on activity tasks.");
@@ -2899,14 +2643,6 @@
             pw.println("           Makes sure <TASK_ID> is in a stack with the specified bounds.");
             pw.println("           Forces the task to be resizeable and creates a stack if no existing stack");
             pw.println("           has the specified bounds.");
-            pw.println("       drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
-            pw.println("           Test command for dragging/moving <TASK_ID> by");
-            pw.println("           <STEP_SIZE> increments around the screen applying the optional [DELAY_MS]");
-            pw.println("           between each step.");
-            pw.println("       size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
-            pw.println("           Test command for sizing <TASK_ID> by <STEP_SIZE>");
-            pw.println("           increments within the screen applying the optional [DELAY_MS] between");
-            pw.println("           each step.");
             pw.println("  update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
             pw.println("      Update the ApplicationInfo objects of the listed packages for <USER_ID>");
             pw.println("      without restarting any processes.");
diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
index 0c8321d..93c0f77 100644
--- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java
@@ -2,8 +2,6 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -32,13 +30,10 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.metrics.LogMaker;
 import android.os.SystemClock;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -133,7 +128,7 @@
             case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
                 mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
                 break;
-            case WINDOW_STATE_FREEFORM:
+            case WINDOWING_MODE_FREEFORM:
                 mWindowState = WINDOW_STATE_FREEFORM;
                 break;
             default:
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 142c97b..2f0b649 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -17,10 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.app.ActivityManager.TaskDescription.ATTR_TASKDESCRIPTION_PREFIX;
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
@@ -61,6 +58,10 @@
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.PERSIST_ACROSS_REBOOTS;
 import static android.content.pm.ActivityInfo.PERSIST_ROOT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
@@ -103,7 +104,6 @@
 import static com.android.server.am.ActivityStack.LAUNCH_TICK;
 import static com.android.server.am.ActivityStack.LAUNCH_TICK_MSG;
 import static com.android.server.am.ActivityStack.PAUSE_TIMEOUT_MSG;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 import static com.android.server.am.ActivityStack.STOP_TIMEOUT_MSG;
 import static com.android.server.am.EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME;
 import static com.android.server.am.EventLogTags.AM_ACTIVITY_LAUNCH_TIME;
@@ -165,6 +165,7 @@
 import android.view.IApplicationToken;
 import android.view.WindowManager.LayoutParams;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
@@ -203,7 +204,6 @@
     private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
 
     private static final boolean SHOW_ACTIVITY_START_TIME = true;
-    private static final String RECENTS_PACKAGE_NAME = "com.android.systemui.recents";
 
     private static final String ATTR_ID = "id";
     private static final String TAG_INTENT = "intent";
@@ -232,7 +232,9 @@
     final String processName; // process where this component wants to run
     final String taskAffinity; // as per ActivityInfo.taskAffinity
     final boolean stateNotNeeded; // As per ActivityInfo.flags
-    boolean fullscreen; // covers the full screen?
+    boolean fullscreen; // The activity is opaque and fills the entire space of this task.
+    // TODO: See if it possible to combine this with the fullscreen field.
+    final boolean hasWallpaper; // Has a wallpaper window as a background.
     final boolean noDisplay;  // activity is not displayed?
     private final boolean componentSpecified;  // did caller specify an explicit component?
     final boolean rootVoiceInteraction;  // was this the root activity of a voice interaction?
@@ -274,6 +276,7 @@
     ActivityState state;    // current state we are in
     Bundle  icicle;         // last saved activity state
     PersistableBundle persistentState; // last persistently saved activity state
+    // TODO: See if this is still needed.
     boolean frontOfTask;    // is this the root activity of its task?
     boolean launchFailed;   // set if a launched failed, to abort on 2nd try
     boolean haveState;      // have we gotten the last activity state?
@@ -286,6 +289,7 @@
     int configChangeFlags;  // which config values have changed
     private boolean keysPaused;     // has key dispatching been paused for it?
     int launchMode;         // the launch mode activity attribute.
+    int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
     boolean visible;        // does this activity's window need to be shown?
     boolean visibleIgnoringKeyguard; // is this activity visible, ignoring the fact that Keyguard
                                      // might hide this activity?
@@ -422,9 +426,13 @@
             if (iconFilename != null || taskDescription.getLabel() != null ||
                     taskDescription.getPrimaryColor() != 0) {
                 pw.print(prefix); pw.print("taskDescription:");
-                        pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
                         pw.print(" label=\""); pw.print(taskDescription.getLabel());
                                 pw.print("\"");
+                        pw.print(" icon="); pw.print(taskDescription.getInMemoryIcon() != null
+                                ? taskDescription.getInMemoryIcon().getByteCount() + " bytes"
+                                : "null");
+                        pw.print(" iconResource="); pw.print(taskDescription.getIconResource());
+                        pw.print(" iconFilename="); pw.print(taskDescription.getIconFilename());
                         pw.print(" primaryColor=");
                         pw.println(Integer.toHexString(taskDescription.getPrimaryColor()));
                         pw.print(prefix + " backgroundColor=");
@@ -434,9 +442,6 @@
                         pw.print(prefix + " navigationBarColor=");
                         pw.println(Integer.toHexString(taskDescription.getNavigationBarColor()));
             }
-            if (iconFilename == null && taskDescription.getIcon() != null) {
-                pw.print(prefix); pw.println("taskDescription contains Bitmap");
-            }
         }
         if (results != null) {
             pw.print(prefix); pw.print("results="); pw.println(results);
@@ -663,8 +668,7 @@
             return;
         }
 
-        final boolean inPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID) &&
-                (targetStackBounds != null);
+        final boolean inPictureInPictureMode = inPinnedWindowingMode() && targetStackBounds != null;
         if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
             // Picture-in-picture mode changes also trigger a multi-window mode change as well, so
             // update that here in order
@@ -686,10 +690,6 @@
         }
     }
 
-    boolean isFreeform() {
-        return task != null && task.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
     @Override
     protected int getChildCount() {
         // {@link ActivityRecord} is a leaf node and has no children.
@@ -833,23 +833,6 @@
         hasBeenLaunched = false;
         mStackSupervisor = supervisor;
 
-        mRotationAnimationHint = aInfo.rotationAnimation;
-
-        if (options != null) {
-            pendingOptions = options;
-            mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind();
-
-            final int rotationAnimation = pendingOptions.getRotationAnimationHint();
-            // Only override manifest supplied option if set.
-            if (rotationAnimation >= 0) {
-                mRotationAnimationHint = rotationAnimation;
-            }
-            PendingIntent usageReport = pendingOptions.getUsageTimeReport();
-            if (usageReport != null) {
-                appTimeTracker = new AppTimeTracker(usageReport);
-            }
-        }
-
         // This starts out true, since the initial state of an activity is that we have everything,
         // and we shouldn't never consider it lacking in state to be removed if it dies.
         haveState = true;
@@ -903,9 +886,14 @@
 
         Entry ent = AttributeCache.instance().get(packageName,
                 realTheme, com.android.internal.R.styleable.Window, userId);
-        fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
-        noDisplay = ent != null && ent.array.getBoolean(
-                com.android.internal.R.styleable.Window_windowNoDisplay, false);
+        if (ent != null) {
+            fullscreen = !ActivityInfo.isTranslucentOrFloating(ent.array);
+            hasWallpaper = ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+            noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+        } else {
+            hasWallpaper = false;
+            noDisplay = false;
+        }
 
         setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
 
@@ -916,6 +904,32 @@
 
         mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
         mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) != 0;
+
+        mRotationAnimationHint = aInfo.rotationAnimation;
+        lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+        if (appInfo.isPrivilegedApp() && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+                || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+            lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        }
+
+        if (options != null) {
+            pendingOptions = options;
+            mLaunchTaskBehind = options.getLaunchTaskBehind();
+
+            final int rotationAnimation = pendingOptions.getRotationAnimationHint();
+            // Only override manifest supplied option if set.
+            if (rotationAnimation >= 0) {
+                mRotationAnimationHint = rotationAnimation;
+            }
+            final PendingIntent usageReport = pendingOptions.getUsageTimeReport();
+            if (usageReport != null) {
+                appTimeTracker = new AppTimeTracker(usageReport);
+            }
+            final boolean useLockTask = pendingOptions.getLockTaskMode();
+            if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+                lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+            }
+        }
     }
 
     AppWindowContainerController getWindowContainerController() {
@@ -950,7 +964,7 @@
         // update the initial multi-window modes so that the callbacks are scheduled correctly when
         // the user leaves that mode.
         mLastReportedMultiWindowMode = !task.mFullscreen;
-        mLastReportedPictureInPictureMode = (task.getStackId() == PINNED_STACK_ID);
+        mLastReportedPictureInPictureMode = inPinnedWindowingMode();
     }
 
     void removeWindowContainer() {
@@ -1043,7 +1057,7 @@
                 // We only allow home activities to be resizeable if they explicitly requested it.
                 info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
             }
-        } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
+        } else if (service.getRecentTasks().isRecentsComponent(realActivity, appInfo.uid)) {
             activityType = ACTIVITY_TYPE_RECENTS;
         } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
@@ -1069,6 +1083,11 @@
         return getStack() != null ? getStack().mStackId : INVALID_STACK_ID;
     }
 
+    ActivityDisplay getDisplay() {
+        final ActivityStack stack = getStack();
+        return stack != null ? stack.getDisplay() : null;
+    }
+
     boolean changeWindowTranslucency(boolean toOpaque) {
         if (fullscreen == toOpaque) {
             return false;
@@ -1134,10 +1153,12 @@
      * @return whether this activity supports split-screen multi-window and can be put in the docked
      *         stack.
      */
-    boolean supportsSplitScreen() {
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
         // An activity can not be docked even if it is considered resizeable because it only
         // supports picture-in-picture mode but has a non-resizeable resizeMode
-        return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
+        return super.supportsSplitScreenWindowingMode()
+                && service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
     }
 
     /**
@@ -1198,7 +1219,8 @@
 
         boolean isKeyguardLocked = service.isKeyguardLocked();
         boolean isCurrentAppLocked = service.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
-        boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+        final ActivityDisplay display = getDisplay();
+        boolean hasPinnedStack = display != null && display.hasPinnedStack();
         // Don't return early if !isNotLocked, since we want to throw an exception if the activity
         // is in an incorrect state
         boolean isNotLockedOrOnKeyguard = !isKeyguardLocked && !isCurrentAppLocked;
@@ -1539,16 +1561,7 @@
             return false;
         }
 
-        boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind;
-
-        if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
-            // On devices that support leanback only (Android TV), Recents activity can only be
-            // visible if the home stack is the focused stack or we are in split-screen mode.
-            isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null
-                    || mStackSupervisor.isFocusedStack(getStack());
-        }
-
-        return isVisible;
+        return !behindFullscreenActivity || mLaunchTaskBehind;
     }
 
     void makeVisibleIfNeeded(ActivityRecord starting) {
@@ -1948,7 +1961,7 @@
 
         return (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0
                 || (mStackSupervisor.isCurrentProfileLocked(userId)
-                && service.mUserController.isUserRunningLocked(userId, 0 /* flags */));
+                && service.mUserController.isUserRunning(userId, 0 /* flags */));
     }
 
     /**
@@ -2050,7 +2063,7 @@
             final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId),
                     iconFilename);
             final String iconFilePath = iconFile.getAbsolutePath();
-            service.mRecentTasks.saveImage(icon, iconFilePath);
+            service.getRecentTasks().saveImage(icon, iconFilePath);
             _taskDescription.setIconFilename(iconFilePath);
         }
         taskDescription = _taskDescription;
@@ -2312,7 +2325,7 @@
         // be visible based on the stack, task, and lockscreen state and use that here instead. The
         // method should be based on the logic in ActivityStack.ensureActivitiesVisibleLocked().
         // Skip updating configuration for activity is a stack that shouldn't be visible.
-        if (stack.shouldBeVisible(null /* starting */) == STACK_INVISIBLE) {
+        if (!stack.shouldBeVisible(null /* starting */)) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                     "Skipping config check invisible stack: " + this);
             return true;
@@ -2732,6 +2745,8 @@
 
     void setShowWhenLocked(boolean showWhenLocked) {
         mShowWhenLocked = showWhenLocked;
+        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0 /* configChanges */,
+                false /* preserveWindows */);
     }
 
     /**
@@ -2795,7 +2810,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(STATE, state.toString());
         proto.write(VISIBLE, visible);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8d21862..88403fc 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,27 +16,24 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.getActivityTypeForStackId;
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
-
 import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
+import static com.android.server.am.ActivityDisplay.POSITION_TOP;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
@@ -74,7 +71,6 @@
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
@@ -99,17 +95,17 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
 import android.app.ResultInfo;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
@@ -192,10 +188,6 @@
     // finished destroying itself.
     private static final int DESTROY_TIMEOUT = 10 * 1000;
 
-    // How long until we reset a task when the user returns to it.  Currently
-    // disabled.
-    private static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
-
     // Set to false to disable the preview that is shown while a new activity
     // is being started.
     private static final boolean SHOW_APP_STARTING_PREVIEW = true;
@@ -240,11 +232,6 @@
         DESTROYED
     }
 
-    // Stack is not considered visible.
-    static final int STACK_INVISIBLE = 0;
-    // Stack is considered visible
-    static final int STACK_VISIBLE = 1;
-
     @VisibleForTesting
     /* The various modes for the method {@link #removeTask}. */
     // Task is being completely removed from all stacks in the system.
@@ -267,7 +254,6 @@
     final ActivityManagerService mService;
     private final WindowManagerService mWindowManager;
     T mWindowContainerController;
-    private final RecentTasks mRecentTasks;
 
     /**
      * The back history of all previous (and possibly still
@@ -350,9 +336,6 @@
     int mCurrentUser;
 
     final int mStackId;
-    /** The other stacks, in order, on the attached display. Updated at attach/detach time. */
-    // TODO: This list doesn't belong here...
-    ArrayList<ActivityStack> mStacks;
     /** The attached Display's unique identifier, or -1 if detached */
     int mDisplayId;
 
@@ -365,8 +348,6 @@
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
 
-    private final LaunchingTaskPositioner mTaskPositioner;
-
     private boolean mTopActivityOccludesKeyguard;
     private ActivityRecord mTopDismissingKeyguardActivity;
 
@@ -461,53 +442,67 @@
         return count;
     }
 
-    ActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-            ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
+    ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+            int windowingMode, int activityType, boolean onTop) {
         mStackSupervisor = supervisor;
         mService = supervisor.mService;
         mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
         mWindowManager = mService.mWindowManager;
         mStackId = stackId;
-        mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
-        mRecentTasks = recentTasks;
-        mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
-                ? new LaunchingTaskPositioner() : null;
+        mCurrentUser = mService.mUserController.getCurrentUserId();
         mTmpRect2.setEmpty();
-        updateOverrideConfiguration();
+        setWindowingMode(windowingMode);
+        setActivityType(activityType);
         mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
                 mTmpRect2);
-        mStackSupervisor.mStacks.put(mStackId, this);
         postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
     }
 
     T createStackWindowController(int displayId, boolean onTop, Rect outBounds) {
-        return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds);
+        return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds,
+                mStackSupervisor.mWindowManager);
     }
 
     T getWindowContainerController() {
         return mWindowContainerController;
     }
 
-    // TODO: Not needed once we are no longer using stack ids as the override config. can be passed
-    // in.
-    private void updateOverrideConfiguration() {
-        final int windowingMode = getWindowingModeForStackId(
-                mStackId, mStackSupervisor.getStack(DOCKED_STACK_ID) != null);
-        if (windowingMode != WINDOWING_MODE_UNDEFINED) {
-            setWindowingMode(windowingMode);
-        }
-        final int activityType = getActivityTypeForStackId(mStackId);
-        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
-            setActivityType(activityType);
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final int prevWindowingMode = getWindowingMode();
+        super.onConfigurationChanged(newParentConfig);
+        final ActivityDisplay display = getDisplay();
+        if (display != null && prevWindowingMode != getWindowingMode()) {
+            display.onStackWindowingModeChanged(this);
         }
     }
 
+    @Override
+    public boolean isCompatible(int windowingMode, int activityType) {
+        // TODO: Should we just move this to ConfigurationContainer?
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Undefined activity types end up in a standard stack once the stack is created on a
+            // display, so they should be considered compatible.
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+        final ActivityDisplay display = getDisplay();
+        if (display != null && activityType == ACTIVITY_TYPE_STANDARD
+                    && windowingMode == WINDOWING_MODE_UNDEFINED) {
+            // Standard activity types will mostly take on the windowing mode of the display if one
+            // isn't specified, so look-up a compatible stack based on the display's windowing mode.
+            windowingMode = display.getWindowingMode();
+        }
+        return super.isCompatible(windowingMode, activityType);
+    }
+
     /** Adds the stack to specified display and calls WindowManager to do the same. */
-    void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+    void reparent(ActivityDisplay activityDisplay, boolean onTop) {
+        // TODO: We should probably resolve the windowing mode for the stack on the new display here
+        // so that it end up in a compatible mode in the new display. e.g. split-screen secondary.
         removeFromDisplay();
         mTmpRect2.setEmpty();
         postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
-        adjustFocusToNextFocusableStackLocked("reparent", true /* allowFocusSelf */);
+        adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */);
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         // Update visibility of activities before notifying WM. This way it won't try to resize
         // windows that are no longer visible.
@@ -521,20 +516,15 @@
      * @param activityDisplay New display to which this stack was attached.
      * @param bounds Updated bounds.
      */
-    private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
-            Rect bounds, boolean onTop) {
+    private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
         mDisplayId = activityDisplay.mDisplayId;
-        mStacks = activityDisplay.mStacks;
         mBounds = bounds != null ? new Rect(bounds) : null;
         mFullscreen = mBounds == null;
-        if (mTaskPositioner != null) {
-            mTaskPositioner.setDisplay(activityDisplay.mDisplay);
-            mTaskPositioner.configure(mBounds);
-        }
+
         onParentChanged();
 
-        activityDisplay.attachStack(this, findStackInsertIndex(onTop));
-        if (mStackId == DOCKED_STACK_ID) {
+        activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
+        if (inSplitScreenPrimaryWindowingMode()) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
             mStackSupervisor.resizeDockedStackLocked(
@@ -547,42 +537,33 @@
      * either destroyed completely or re-parented.
      */
     private void removeFromDisplay() {
-        final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
-        if (display != null) {
-            display.detachStack(this);
-        }
-        mDisplayId = INVALID_DISPLAY;
-        mStacks = null;
-        if (mTaskPositioner != null) {
-            mTaskPositioner.reset();
-        }
-        if (mStackId == DOCKED_STACK_ID) {
+        if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If we removed a docked stack we want to resize it so it resizes all other stacks
             // in the system to fullscreen.
             mStackSupervisor.resizeDockedStackLocked(
                     null, null, null, null, null, PRESERVE_WINDOWS);
         }
+        final ActivityDisplay display = getDisplay();
+        if (display != null) {
+            display.removeChild(this);
+        }
+        mDisplayId = INVALID_DISPLAY;
     }
 
     /** Removes the stack completely. Also calls WindowManager to do the same on its side. */
     void remove() {
         removeFromDisplay();
-        mStackSupervisor.mStacks.remove(mStackId);
         mWindowContainerController.removeContainer();
         mWindowContainerController = null;
         onParentChanged();
     }
 
-    ActivityStackSupervisor.ActivityDisplay getDisplay() {
+    ActivityDisplay getDisplay() {
         return mStackSupervisor.getActivityDisplay(mDisplayId);
     }
 
-    void getDisplaySize(Point out) {
-        getDisplay().mDisplay.getSize(out);
-    }
-
     /**
-     * @see ActivityStack.getStackDockedModeBounds(Rect, Rect, Rect, boolean)
+     * @see #getStackDockedModeBounds(Rect, Rect, Rect, boolean)
      */
     void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
             Rect outTempTaskBounds, boolean ignoreVisibility) {
@@ -670,9 +651,6 @@
 
     void setBounds(Rect bounds) {
         mBounds = mFullscreen ? null : new Rect(bounds);
-        if (mTaskPositioner != null) {
-            mTaskPositioner.configure(bounds);
-        }
     }
 
     ActivityRecord topRunningActivityLocked() {
@@ -846,19 +824,17 @@
     }
 
     final boolean isHomeOrRecentsStack() {
-        return StackId.isHomeOrRecentsStack(mStackId);
-    }
-
-    final boolean isDockedStack() {
-        return mStackId == DOCKED_STACK_ID;
-    }
-
-    final boolean isPinnedStack() {
-        return mStackId == PINNED_STACK_ID;
+        return isActivityTypeHome() || isActivityTypeRecents();
     }
 
     final boolean isOnHomeDisplay() {
-        return isAttached() && mDisplayId == DEFAULT_DISPLAY;
+        return mDisplayId == DEFAULT_DISPLAY;
+    }
+
+    private boolean returnsToHomeStack() {
+        return !inMultiWindowMode()
+                && !mTaskHistory.isEmpty()
+                && mTaskHistory.get(0).returnsToHomeStack();
     }
 
     void moveToFront(String reason) {
@@ -874,8 +850,13 @@
             return;
         }
 
-        mStacks.remove(this);
-        mStacks.add(findStackInsertIndex(ON_TOP), this);
+        if (!isActivityTypeHome() && returnsToHomeStack()) {
+            // Make sure the home stack is behind this stack since that is where we should return to
+            // when this stack is no longer visible.
+            mStackSupervisor.moveHomeStackToFront(reason + " returnToHome");
+        }
+
+        getDisplay().positionChildAtTop(this);
         mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
             insertTaskAtTop(task, null);
@@ -889,45 +870,6 @@
         }
     }
 
-    /**
-     * @param task If non-null, the task will be moved to the back of the stack.
-     * */
-    private void moveToBack(TaskRecord task) {
-        if (!isAttached()) {
-            return;
-        }
-
-        mStacks.remove(this);
-        mStacks.add(0, this);
-
-        if (task != null) {
-            mTaskHistory.remove(task);
-            mTaskHistory.add(0, task);
-            updateTaskMovement(task, false);
-            mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
-        }
-    }
-
-    /**
-     * @return the index to insert a new stack into, taking the always-on-top stacks into account.
-     */
-    private int findStackInsertIndex(boolean onTop) {
-        if (onTop) {
-            int addIndex = mStacks.size();
-            if (addIndex > 0) {
-                final ActivityStack topStack = mStacks.get(addIndex - 1);
-                if (topStack.getWindowConfiguration().isAlwaysOnTop()
-                        && topStack != this) {
-                    // If the top stack is always on top, we move this stack just below it.
-                    addIndex--;
-                }
-            }
-            return addIndex;
-        } else {
-            return 0;
-        }
-    }
-
     boolean isFocusable() {
         if (getWindowConfiguration().canReceiveKeys()) {
             return true;
@@ -939,7 +881,7 @@
     }
 
     final boolean isAttached() {
-        return mStacks != null;
+        return getParent() != null;
     }
 
     /**
@@ -1114,14 +1056,6 @@
                 "Launch completed; removing icicle of " + r.icicle);
     }
 
-    void addRecentActivityLocked(ActivityRecord r) {
-        if (r != null) {
-            final TaskRecord task = r.getTask();
-            mRecentTasks.addLocked(task);
-            task.touchActiveTime();
-        }
-    }
-
     private void startLaunchTraces(String packageName) {
         if (mFullyDrawnStartTime != 0)  {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
@@ -1536,7 +1470,7 @@
         // focus). Also if there is an active pinned stack - we always want to notify it about
         // task stack changes, because its positioning may depend on it.
         if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || mService.mStackSupervisor.getStack(PINNED_STACK_ID) != null) {
+                || getDisplay().hasPinnedStack()) {
             mService.mTaskChangeNotificationController.notifyTaskStackChanged();
             mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
         }
@@ -1568,73 +1502,17 @@
         }
     }
 
-    // Find the first visible activity above the passed activity and if it is translucent return it
-    // otherwise return null;
-    ActivityRecord findNextTranslucentActivity(ActivityRecord r) {
-        TaskRecord task = r.getTask();
-        if (task == null) {
-            return null;
-        }
-
-        final ActivityStack stack = task.getStack();
-        if (stack == null) {
-            return null;
-        }
-
-        int stackNdx = mStacks.indexOf(stack);
-
-        ArrayList<TaskRecord> tasks = stack.mTaskHistory;
-        int taskNdx = tasks.indexOf(task);
-
-        ArrayList<ActivityRecord> activities = task.mActivities;
-        int activityNdx = activities.indexOf(r) + 1;
-
-        final int numStacks = mStacks.size();
-        while (stackNdx < numStacks) {
-            final ActivityStack historyStack = mStacks.get(stackNdx);
-            tasks = historyStack.mTaskHistory;
-            final int numTasks = tasks.size();
-            while (taskNdx < numTasks) {
-                final TaskRecord currentTask = tasks.get(taskNdx);
-                activities = currentTask.mActivities;
-                final int numActivities = activities.size();
-                while (activityNdx < numActivities) {
-                    final ActivityRecord activity = activities.get(activityNdx);
-                    if (!activity.finishing) {
-                        return historyStack.mFullscreen
-                                && currentTask.mFullscreen && activity.fullscreen ? null : activity;
-                    }
-                    ++activityNdx;
-                }
-                activityNdx = 0;
-                ++taskNdx;
-            }
-            taskNdx = 0;
-            ++stackNdx;
-        }
-
-        return null;
-    }
-
-    /** Returns true if the stack contains a fullscreen task. */
-    private boolean hasFullscreenTask() {
-        for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
-            final TaskRecord task = mTaskHistory.get(i);
-            if (task.mFullscreen) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     /**
      * Returns true if the stack is translucent and can have other contents visible behind it if
      * needed. A stack is considered translucent if it don't contain a visible or
      * starting (about to be visible) activity that is fullscreen (opaque).
      * @param starting The currently starting activity or null if there is none.
-     * @param stackBehindId The id of the stack directly behind this one.
      */
-    private boolean isStackTranslucent(ActivityRecord starting, int stackBehindId) {
+    @VisibleForTesting
+    boolean isStackTranslucent(ActivityRecord starting) {
+        if (!isAttached() || mForceHidden) {
+            return true;
+        }
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1653,20 +1531,11 @@
                     continue;
                 }
 
-                if (r.fullscreen) {
+                if (r.fullscreen || r.hasWallpaper) {
                     // Stack isn't translucent if it has at least one fullscreen activity
                     // that is visible.
                     return false;
                 }
-
-                if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
-                        && !StackId.isHomeOrRecentsStack(stackBehindId)
-                        && !isActivityTypeAssistant()) {
-                    // Stack isn't translucent if it's top activity should have the home stack
-                    // behind it and the stack currently behind it isn't the home or recents stack
-                    // or the assistant stack.
-                    return false;
-                }
             }
         }
         return true;
@@ -1678,120 +1547,79 @@
                 && !mForceHidden;
     }
 
+    boolean isTopStackOnDisplay() {
+        return getDisplay().isTopStack(this);
+    }
+
     /**
-     * Returns what the stack visibility should be: {@link #STACK_INVISIBLE} or
-     * {@link #STACK_VISIBLE}.
+     * Returns true if the stack should be visible.
      *
      * @param starting The currently starting activity or null if there is none.
      */
-    int shouldBeVisible(ActivityRecord starting) {
+    boolean shouldBeVisible(ActivityRecord starting) {
         if (!isAttached() || mForceHidden) {
-            return STACK_INVISIBLE;
+            return false;
+        }
+        if (mStackSupervisor.isFocusedStack(this)) {
+            return true;
         }
 
-        if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) {
-            return STACK_VISIBLE;
+        final ActivityRecord top = topRunningActivityLocked();
+        if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) {
+            // Shouldn't be visible if you don't have any running activities, not starting one, and
+            // not the top stack on display.
+            return false;
         }
 
-        final int stackIndex = mStacks.indexOf(this);
-
-        if (stackIndex == mStacks.size() - 1) {
-            Slog.wtf(TAG,
-                    "Stack=" + this + " isn't front stack but is at the top of the stack list");
-            return STACK_INVISIBLE;
-        }
-
-        // Check position and visibility of this stack relative to the front stack on its display.
-        final ActivityStack topStack = getTopStackOnDisplay();
-        final int topStackId = topStack.mStackId;
-
-        if (mStackId == DOCKED_STACK_ID) {
-            // If the assistant stack is focused and translucent, then the docked stack is always
-            // visible
-            if (topStack.isActivityTypeAssistant()) {
-                return (topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) ? STACK_VISIBLE
-                        : STACK_INVISIBLE;
+        final ActivityDisplay display = getDisplay();
+        boolean gotOpaqueSplitScreenPrimary = false;
+        boolean gotOpaqueSplitScreenSecondary = false;
+        final int windowingMode = getWindowingMode();
+        for (int i = display.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack other = display.getChildAt(i);
+            if (other == this) {
+                // Should be visible if there is no other stack occluding it.
+                return true;
             }
-            return STACK_VISIBLE;
-        }
 
-        // Set home stack to invisible when it is below but not immediately below the docked stack
-        // A case would be if recents stack exists but has no tasks and is below the docked stack
-        // and home stack is below recents
-        if (mStackId == HOME_STACK_ID) {
-            int dockedStackIndex = mStacks.indexOf(mStackSupervisor.getStack(DOCKED_STACK_ID));
-            if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
-                return STACK_INVISIBLE;
-            }
-        }
+            final int otherWindowingMode = other.getWindowingMode();
+            // TODO: Can be removed once we are no longer using returnToType for back functionality
+            final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
 
-        // Find the first stack behind front stack that actually got something visible.
-        int stackBehindTopIndex = mStacks.indexOf(topStack) - 1;
-        while (stackBehindTopIndex >= 0 &&
-                mStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
-            stackBehindTopIndex--;
-        }
-        final int stackBehindTopId = (stackBehindTopIndex >= 0)
-                ? mStacks.get(stackBehindTopIndex).mStackId : INVALID_STACK_ID;
-        final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
-        if (topStackId == DOCKED_STACK_ID || alwaysOnTop) {
-            if (stackIndex == stackBehindTopIndex) {
-                // Stacks directly behind the docked or pinned stack are always visible.
-                return STACK_VISIBLE;
-            } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
-                // Otherwise, this stack can also be visible if it is directly behind a docked stack
-                // or translucent assistant stack behind an always-on-top top-most stack
-                if (stackBehindTopId == DOCKED_STACK_ID) {
-                    return STACK_VISIBLE;
-                } else if (stackBehindTopId == ASSISTANT_STACK_ID) {
-                    return mStacks.get(stackBehindTopIndex).isStackTranslucent(starting, mStackId)
-                            ? STACK_VISIBLE : STACK_INVISIBLE;
+            if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+                if (other.isStackTranslucent(starting)) {
+                    // Can be visible behind a translucent fullscreen stack.
+                    continue;
+                }
+                return false;
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    && !gotOpaqueSplitScreenPrimary) {
+                gotOpaqueSplitScreenPrimary =
+                        !other.isStackTranslucent(starting);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                        && gotOpaqueSplitScreenPrimary) {
+                    // Can not be visible behind another opaque stack in split-screen-primary mode.
+                    return false;
+                }
+            } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    && !gotOpaqueSplitScreenSecondary) {
+                gotOpaqueSplitScreenSecondary =
+                        !other.isStackTranslucent(starting);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                        && gotOpaqueSplitScreenSecondary) {
+                    // Can not be visible behind another opaque stack in split-screen-secondary mode.
+                    return false;
                 }
             }
-        }
-
-        if (StackId.isBackdropToTranslucentActivity(topStackId)
-                && topStack.isStackTranslucent(starting, stackBehindTopId)) {
-            // Stacks behind the fullscreen or assistant stack with a translucent activity are
-            // always visible so they can act as a backdrop to the translucent activity.
-            // For example, dialog activities
-            if (stackIndex == stackBehindTopIndex) {
-                return STACK_VISIBLE;
-            }
-            if (stackBehindTopIndex >= 0) {
-                if ((stackBehindTopId == DOCKED_STACK_ID
-                        || stackBehindTopId == PINNED_STACK_ID)
-                        && stackIndex == (stackBehindTopIndex - 1)) {
-                    // The stack behind the docked or pinned stack is also visible so we can have a
-                    // complete backdrop to the translucent activity when the docked stack is up.
-                    return STACK_VISIBLE;
-                }
+            if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
+                // Can not be visible if we are in split-screen windowing mode and both halves of
+                // the screen are opaque.
+                return false;
             }
         }
 
-        if (StackId.isStaticStack(mStackId)) {
-            // Visibility of any static stack should have been determined by the conditions above.
-            return STACK_INVISIBLE;
-        }
-
-        for (int i = stackIndex + 1; i < mStacks.size(); i++) {
-            final ActivityStack stack = mStacks.get(i);
-
-            if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
-                continue;
-            }
-
-            if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
-                // These stacks can't have any dynamic stacks visible behind them.
-                return STACK_INVISIBLE;
-            }
-
-            if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
-                return STACK_INVISIBLE;
-            }
-        }
-
-        return STACK_VISIBLE;
+        // Well, nothing is stopping you from being visible...
+        return true;
     }
 
     final int rankTaskLayers(int baseLayer) {
@@ -1812,6 +1640,7 @@
      * Make sure that all activities that need to be visible (that is, they
      * currently can be seen by the user) actually are.
      */
+    // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
     final void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
             boolean preserveWindows) {
         mTopActivityOccludesKeyguard = false;
@@ -1828,12 +1657,10 @@
             // If the top activity is not fullscreen, then we need to
             // make sure any activities under it are now visible.
             boolean aboveTop = top != null;
-            final int stackVisibility = shouldBeVisible(starting);
-            final boolean stackInvisible = stackVisibility != STACK_VISIBLE;
-            boolean behindFullscreenActivity = stackInvisible;
+            final boolean stackShouldBeVisible = shouldBeVisible(starting);
+            boolean behindFullscreenActivity = !stackShouldBeVisible;
             boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this)
                     && (isInStackLocked(starting) == null);
-            boolean behindTranslucentActivity = false;
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = mTaskHistory.get(taskNdx);
                 final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1857,11 +1684,8 @@
                     final boolean reallyVisible = checkKeyguardVisibility(r,
                             visibleIgnoringKeyguard, isTop);
                     if (visibleIgnoringKeyguard) {
-                        behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
-                                behindFullscreenActivity, task, r);
-                        if (behindFullscreenActivity && !r.fullscreen) {
-                            behindTranslucentActivity = true;
-                        }
+                        behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
+                                behindFullscreenActivity, r);
                     }
                     if (reallyVisible) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1897,21 +1721,22 @@
                         configChanges |= r.configChangeFlags;
                     } else {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
-                                + " finishing=" + r.finishing + " state=" + r.state + " stackInvisible="
-                                + stackInvisible + " behindFullscreenActivity="
-                                + behindFullscreenActivity + " mLaunchTaskBehind="
-                                + r.mLaunchTaskBehind);
+                                + " finishing=" + r.finishing + " state=" + r.state
+                                + " stackShouldBeVisible=" + stackShouldBeVisible
+                                + " behindFullscreenActivity=" + behindFullscreenActivity
+                                + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
                         makeInvisible(r);
                     }
                 }
-                if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                final int windowingMode = getWindowingMode();
+                if (windowingMode == WINDOWING_MODE_FREEFORM) {
                     // The visibility of tasks and the activities they contain in freeform stack are
                     // determined individually unlike other stacks where the visibility or fullscreen
                     // status of an activity in a previous task affects other.
-                    behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
-                } else if (mStackId == HOME_STACK_ID) {
+                    behindFullscreenActivity = !stackShouldBeVisible;
+                } else if (isActivityTypeHome()) {
                     if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task
-                            + " stackInvisible=" + stackInvisible
+                            + " stackShouldBeVisible=" + stackShouldBeVisible
                             + " behindFullscreenActivity=" + behindFullscreenActivity);
                     // No other task in the home stack should be visible behind the home activity.
                     // Home activities is usually a translucent activity with the wallpaper behind
@@ -1919,15 +1744,6 @@
                     // show activities in the next application stack behind them vs. another
                     // task in the home stack like recents.
                     behindFullscreenActivity = true;
-                } else if (mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
-                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Skipping after task=" + task
-                            + " returning to non-application type=" + task.getTaskToReturnTo());
-                    // Once we reach a fullscreen stack task that has a running activity and should
-                    // return to another stack task, then no other activities behind that one should
-                    // be visible.
-                    if (task.topRunningActivityLocked() != null && !task.returnsToStandardTask()) {
-                        behindFullscreenActivity = true;
-                    }
                 }
             }
 
@@ -1955,6 +1771,14 @@
     }
 
     /**
+     * Returns true if this stack should be resized to match the bounds specified by
+     * {@link ActivityOptions#setLaunchBounds} when launching an activity into the stack.
+     */
+    boolean resizeStackWithLaunchBounds() {
+        return inPinnedWindowingMode();
+    }
+
+    /**
      * @return the top most visible activity that wants to dismiss Keyguard
      */
     ActivityRecord getTopDismissingKeyguardActivity() {
@@ -1970,7 +1794,7 @@
      */
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
             boolean isTop) {
-        final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID;
+        final boolean isInPinnedStack = r.inPinnedWindowingMode();
         final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
                 mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
@@ -2012,7 +1836,7 @@
      * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
      */
     private boolean canShowWithInsecureKeyguard() {
-        final ActivityStackSupervisor.ActivityDisplay activityDisplay = getDisplay();
+        final ActivityDisplay activityDisplay = getDisplay();
         if (activityDisplay == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
                     + mStackId);
@@ -2115,18 +1939,13 @@
     }
 
     private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
-            TaskRecord task, ActivityRecord r) {
+            ActivityRecord r) {
         if (r.fullscreen) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                         + " stackInvisible=" + stackInvisible
                         + " behindFullscreenActivity=" + behindFullscreenActivity);
             // At this point, nothing else needs to be shown in this task.
             behindFullscreenActivity = true;
-        } else if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()) {
-            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
-                    + " stackInvisible=" + stackInvisible
-                    + " behindFullscreenActivity=" + behindFullscreenActivity);
-            behindFullscreenActivity = true;
         }
         return behindFullscreenActivity;
     }
@@ -2192,7 +2011,7 @@
         // activities as we need to display their starting window until they are done initializing.
         boolean behindFullscreenActivity = false;
 
-        if (shouldBeVisible(null) == STACK_INVISIBLE) {
+        if (!shouldBeVisible(null)) {
             // The stack is not visible, so no activity in it should be displaying a starting
             // window. Mark all activities below top and behind fullscreen.
             aboveTop = false;
@@ -2268,9 +2087,7 @@
         mResumedActivity = r;
         r.state = ActivityState.RESUMED;
         mService.setResumedActivityUncheckLocked(r, reason);
-        final TaskRecord task = r.getTask();
-        task.touchActiveTime();
-        mRecentTasks.addLocked(task);
+        mStackSupervisor.mRecentTasks.add(r.getTask());
     }
 
     private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
@@ -2287,7 +2104,7 @@
         final boolean hasRunningActivity = next != null;
 
         // TODO: Maybe this entire condition can get removed?
-        if (hasRunningActivity && getDisplay() == null) {
+        if (hasRunningActivity && !isAttached()) {
             return false;
         }
 
@@ -2317,28 +2134,6 @@
             return false;
         }
 
-        final TaskRecord nextTask = next.getTask();
-        final TaskRecord prevTask = prev != null ? prev.getTask() : null;
-        if (prevTask != null && prevTask.getStack() == this &&
-                prevTask.isOverHomeStack() && prev.finishing && prev.frontOfTask) {
-            if (DEBUG_STACK)  mStackSupervisor.validateTopActivitiesLocked();
-            if (prevTask == nextTask) {
-                prevTask.setFrontOfTask();
-            } else if (prevTask != topTask()) {
-                // This task is going away but it was supposed to return to the home stack.
-                // Now the task above it has to return to the home task instead.
-                final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
-                mTaskHistory.get(taskNdx).setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            } else if (!isOnHomeDisplay()) {
-                return false;
-            } else if (!isActivityTypeHome()){
-                if (DEBUG_STATES) Slog.d(TAG_STATES,
-                        "resumeTopActivityLocked: Launching home next");
-                return isOnHomeDisplay() &&
-                        mStackSupervisor.resumeHomeStackTask(prev, "prevFinished");
-            }
-        }
-
         // If we are sleeping, and there is no resumed activity, and the top
         // activity is paused, well that is the state we want.
         if (shouldSleepOrShutDownActivities()
@@ -2553,128 +2348,139 @@
                     || (lastStack.mLastPausedActivity != null
                     && !lastStack.mLastPausedActivity.fullscreen));
 
-            // This activity is now becoming visible.
-            if (!next.visible || next.stopped || lastActivityTranslucent) {
-                next.setVisibility(true);
-            }
-
-            // schedule launch ticks to collect information about slow apps.
-            next.startLaunchTickingLocked();
-
-            ActivityRecord lastResumedActivity =
-                    lastStack == null ? null :lastStack.mResumedActivity;
-            ActivityState lastState = next.state;
-
-            mService.updateCpuStats();
-
-            if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");
-
-            setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
-
-            mService.updateLruProcessLocked(next.app, true, null);
-            updateLRUListLocked(next);
-            mService.updateOomAdjLocked();
-
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-            if (mStackSupervisor.isFocusedStack(this)) {
-
-                // We have special rotation behavior when Keyguard is locked. Make sure all activity
-                // visibilities are set correctly as well as the transition is updated if needed to
-                // get the correct rotation behavior.
-                // TODO: Remove this once visibilities are set correctly immediately when starting
-                // an activity.
-                if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
-                    mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
-                            0 /* configChanges */, false /* preserveWindows */);
-                }
-                final Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                        mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
-                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
-                if (config != null) {
-                    next.frozenBeforeDestroy = true;
-                }
-                notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
-                        false /* deferResume */, mDisplayId);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivityLocked();
-                if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
-                        "Activity config changed during resume: " + next
-                        + ", new next: " + nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mStackSupervisor.scheduleResumeTopActivities();
-                }
-                if (!next.visible || next.stopped) {
+            // The contained logic must be synchronized, since we are both changing the visibility
+            // and updating the {@link Configuration}. {@link ActivityRecord#setVisibility} will
+            // ultimately cause the client code to schedule a layout. Since layouts retrieve the
+            // current {@link Configuration}, we must ensure that the below code updates it before
+            // the layout can occur.
+            synchronized(mWindowManager.getWindowManagerLock()) {
+                // This activity is now becoming visible.
+                if (!next.visible || next.stopped || lastActivityTranslucent) {
                     next.setVisibility(true);
                 }
-                next.completeResumeLocked();
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
-            }
 
-            try {
-                // Deliver all pending results.
-                ArrayList<ResultInfo> a = next.results;
-                if (a != null) {
-                    final int N = a.size();
-                    if (!next.finishing && N > 0) {
-                        if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
-                                "Delivering results to " + next + ": " + a);
-                        next.app.thread.scheduleSendResult(next.appToken, a);
+                // schedule launch ticks to collect information about slow apps.
+                next.startLaunchTickingLocked();
+
+                ActivityRecord lastResumedActivity =
+                        lastStack == null ? null :lastStack.mResumedActivity;
+                ActivityState lastState = next.state;
+
+                mService.updateCpuStats();
+
+                if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next
+                        + " (in existing)");
+
+                setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
+
+                mService.updateLruProcessLocked(next.app, true, null);
+                updateLRUListLocked(next);
+                mService.updateOomAdjLocked();
+
+                // Have the window manager re-evaluate the orientation of
+                // the screen based on the new activity order.
+                boolean notUpdated = true;
+
+                if (mStackSupervisor.isFocusedStack(this)) {
+
+                    // We have special rotation behavior when Keyguard is locked. Make sure all
+                    // activity visibilities are set correctly as well as the transition is updated
+                    // if needed to get the correct rotation behavior.
+                    // TODO: Remove this once visibilities are set correctly immediately when
+                    // starting an activity.
+                    if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+                        mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
+                                0 /* configChanges */, false /* preserveWindows */);
                     }
+                    final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                            mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+                            next.mayFreezeScreenLocked(next.app) ? next.appToken : null,
+                                    mDisplayId);
+                    if (config != null) {
+                        next.frozenBeforeDestroy = true;
+                    }
+                    notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+                            false /* deferResume */, mDisplayId);
                 }
 
-                if (next.newIntents != null) {
-                    next.app.thread.scheduleNewIntent(
-                            next.newIntents, next.appToken, false /* andPause */);
+                if (notUpdated) {
+                    // The configuration update wasn't able to keep the existing
+                    // instance of the activity, and instead started a new one.
+                    // We should be all done, but let's just make sure our activity
+                    // is still at the top and schedule another run if something
+                    // weird happened.
+                    ActivityRecord nextNext = topRunningActivityLocked();
+                    if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
+                            "Activity config changed during resume: " + next
+                                    + ", new next: " + nextNext);
+                    if (nextNext != next) {
+                        // Do over!
+                        mStackSupervisor.scheduleResumeTopActivities();
+                    }
+                    if (!next.visible || next.stopped) {
+                        next.setVisibility(true);
+                    }
+                    next.completeResumeLocked();
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
 
-                // Well the app will no longer be stopped.
-                // Clear app token stopped state in window manager if needed.
-                next.notifyAppResumed(next.stopped);
+                try {
+                    // Deliver all pending results.
+                    ArrayList<ResultInfo> a = next.results;
+                    if (a != null) {
+                        final int N = a.size();
+                        if (!next.finishing && N > 0) {
+                            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+                                    "Delivering results to " + next + ": " + a);
+                            next.app.thread.scheduleSendResult(next.appToken, a);
+                        }
+                    }
 
-                EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
-                        System.identityHashCode(next), next.getTask().taskId,
-                        next.shortComponentName);
+                    if (next.newIntents != null) {
+                        next.app.thread.scheduleNewIntent(
+                                next.newIntents, next.appToken, false /* andPause */);
+                    }
 
-                next.sleeping = false;
-                mService.showUnsupportedZoomDialogIfNeededLocked(next);
-                mService.showAskCompatModeDialogLocked(next);
-                next.app.pendingUiClean = true;
-                next.app.forceProcessStateUpTo(mService.mTopProcessState);
-                next.clearOptionsLocked();
-                next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
-                        mService.isNextTransitionForward(), resumeAnimOptions);
+                    // Well the app will no longer be stopped.
+                    // Clear app token stopped state in window manager if needed.
+                    next.notifyAppResumed(next.stopped);
 
-                if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next);
-            } catch (Exception e) {
-                // Whoops, need to restart this activity!
-                if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
-                        + lastState + ": " + next);
-                next.state = lastState;
-                if (lastStack != null) {
-                    lastStack.mResumedActivity = lastResumedActivity;
+                    EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
+                            System.identityHashCode(next), next.getTask().taskId,
+                            next.shortComponentName);
+
+                    next.sleeping = false;
+                    mService.showUnsupportedZoomDialogIfNeededLocked(next);
+                    mService.showAskCompatModeDialogLocked(next);
+                    next.app.pendingUiClean = true;
+                    next.app.forceProcessStateUpTo(mService.mTopProcessState);
+                    next.clearOptionsLocked();
+                    next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
+                            mService.isNextTransitionForward(), resumeAnimOptions);
+
+                    if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+                            + next);
+                } catch (Exception e) {
+                    // Whoops, need to restart this activity!
+                    if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
+                            + lastState + ": " + next);
+                    next.state = lastState;
+                    if (lastStack != null) {
+                        lastStack.mResumedActivity = lastResumedActivity;
+                    }
+                    Slog.i(TAG, "Restarting because process died: " + next);
+                    if (!next.hasBeenLaunched) {
+                        next.hasBeenLaunched = true;
+                    } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null
+                            && lastStack.isTopStackOnDisplay()) {
+                        next.showStartingWindow(null /* prev */, false /* newTask */,
+                                false /* taskSwitch */);
+                    }
+                    mStackSupervisor.startSpecificActivityLocked(next, true, false);
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
-                Slog.i(TAG, "Restarting because process died: " + next);
-                if (!next.hasBeenLaunched) {
-                    next.hasBeenLaunched = true;
-                } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
-                        mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwitch */);
-                }
-                mStackSupervisor.startSpecificActivityLocked(next, true, false);
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
             }
 
             // From this point on, if something goes wrong there is no way
@@ -2711,7 +2517,7 @@
 
     private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev,
             ActivityOptions options, String reason) {
-        if ((!mFullscreen || !isOnHomeDisplay()) && adjustFocusToNextFocusableStackLocked(reason)) {
+        if (adjustFocusToNextFocusableStack(reason)) {
             // Try to move focus to the next visible stack with a running activity if this
             // stack is not covering the entire screen or is on a secondary display (with no home
             // stack).
@@ -2785,7 +2591,6 @@
     }
 
     private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) {
-        updateTaskReturnToForTopInsertion(task);
         // TODO: Better place to put all the code below...may be addTask...
         mTaskHistory.remove(task);
         // Now put task at top.
@@ -2796,58 +2601,6 @@
                 true /* includingParents */);
     }
 
-    /**
-     * Updates the {@param task}'s return type before it is moved to the top.
-     */
-    private void updateTaskReturnToForTopInsertion(TaskRecord task) {
-        boolean isLastTaskOverHome = false;
-        // If the moving task is over the home or assistant stack, transfer its return type to next
-        // task so that they return to the same stack
-        if (task.isOverHomeStack() || task.isOverAssistantStack()) {
-            final TaskRecord nextTask = getNextTask(task);
-            if (nextTask != null) {
-                nextTask.setTaskToReturnTo(task.getTaskToReturnTo());
-            } else {
-                isLastTaskOverHome = true;
-            }
-        }
-
-        // If this is not on the default display, then just set the return type to application
-        if (!isOnHomeDisplay()) {
-            task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-            return;
-        }
-
-        final ActivityStack lastStack = mStackSupervisor.getLastStack();
-
-        // If there is no last task, do not set task to return to
-        if (lastStack == null) {
-            return;
-        }
-
-        // If the task was launched from the assistant stack, set the return type to assistant
-        if (lastStack.isActivityTypeAssistant()) {
-            task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-            return;
-        }
-
-        // If this is being moved to the top by another activity or being launched from the home
-        // activity, set mTaskToReturnTo accordingly.
-        final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack();
-        final TaskRecord topTask = lastStack.topTask();
-        if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
-            // If it's a last task over home - we default to keep its return to type not to
-            // make underlying task focused when this one will be finished.
-            int returnToType = isLastTaskOverHome
-                    ? task.getTaskToReturnTo() : ACTIVITY_TYPE_STANDARD;
-            if (fromHomeOrRecents && StackId.allowTopTaskToReturnHome(mStackId)) {
-                returnToType = topTask == null
-                        ? ACTIVITY_TYPE_HOME : topTask.getActivityType();
-            }
-            task.setTaskToReturnTo(returnToType);
-        }
-    }
-
     final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
             boolean newTask, boolean keepCurTransition, ActivityOptions options) {
         TaskRecord rTask = r.getTask();
@@ -2995,13 +2748,13 @@
             // Ensure the caller has requested not to trigger auto-enter PiP
             return false;
         }
-        if (pipCandidate == null || pipCandidate.getStackId() == PINNED_STACK_ID) {
+        if (pipCandidate == null || pipCandidate.inPinnedWindowingMode()) {
             // Ensure that we do not trigger entering PiP an activity on the pinned stack
             return false;
         }
-        final int targetStackId = toFrontTask != null ? toFrontTask.getStackId()
-                : toFrontActivity.getStackId();
-        if (targetStackId == ASSISTANT_STACK_ID) {
+        final ActivityStack targetStack = toFrontTask != null
+                ? toFrontTask.getStack() : toFrontActivity.getStack();
+        if (targetStack != null && targetStack.isActivityTypeAssistant()) {
             // Ensure the task/activity being brought forward is not the assistant
             return false;
         }
@@ -3117,7 +2870,7 @@
                 }
 
                 mWindowContainerController.positionChildAtBottom(
-                        targetTask.getWindowContainerController());
+                        targetTask.getWindowContainerController(), false /* includingParents */);
                 replyChainEnd = -1;
             } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
                 // If the activity should just be removed -- either
@@ -3280,15 +3033,8 @@
 
     final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop,
             ActivityRecord newActivity) {
-        boolean forceReset =
+        final boolean forceReset =
                 (newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0;
-        if (ACTIVITY_INACTIVE_RESET_TIME > 0
-                && taskTop.getTask().getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) {
-            if ((newActivity.info.flags & ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) {
-                forceReset = true;
-            }
-        }
-
         final TaskRecord task = taskTop.getTask();
 
         /** False until we evaluate the TaskRecord associated with taskTop. Switches to true
@@ -3360,7 +3106,7 @@
     }
 
     /** Returns true if the task is one of the task finishing on-top of the top running task. */
-    boolean isATopFinishingTask(TaskRecord task) {
+    private boolean isATopFinishingTask(TaskRecord task) {
         for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
             final TaskRecord current = mTaskHistory.get(i);
             final ActivityRecord r = current.topRunningActivityLocked();
@@ -3375,7 +3121,7 @@
         return false;
     }
 
-    private void adjustFocusedActivityStackLocked(ActivityRecord r, String reason) {
+    private void adjustFocusedActivityStack(ActivityRecord r, String reason) {
         if (!mStackSupervisor.isFocusedStack(this) ||
                 ((mResumedActivity != r) && (mResumedActivity != null))) {
             return;
@@ -3384,66 +3130,44 @@
         final ActivityRecord next = topRunningActivityLocked();
         final String myReason = reason + " adjustFocus";
 
-        if (next != r) {
-            if (next != null && StackId.keepFocusInStackIfPossible(mStackId) && isFocusable()) {
-                // For freeform, docked, and pinned stacks we always keep the focus within the
-                // stack as long as there is a running activity.
-                return;
-            } else {
-                // Task is not guaranteed to be non-null. For example, destroying the
-                // {@link ActivityRecord} will disassociate the task from the activity.
-                final TaskRecord task = r.getTask();
-
-                if (task == null) {
-                    throw new IllegalStateException("activity no longer associated with task:" + r);
-                }
-
-                final boolean isAssistantOrOverAssistant =
-                        task.getStack().isActivityTypeAssistant() || task.isOverAssistantStack();
-                if (r.frontOfTask && isATopFinishingTask(task)
-                        && (task.isOverHomeStack() || isAssistantOrOverAssistant)) {
-                    // For non-fullscreen or assistant stack, we want to move the focus to the next
-                    // visible stack to prevent the home screen from moving to the top and obscuring
-                    // other visible stacks.
-                    if ((!mFullscreen || isAssistantOrOverAssistant)
-                            && adjustFocusToNextFocusableStackLocked(myReason)) {
-                        return;
-                    }
-                    // Move the home stack to the top if this stack is fullscreen or there is no
-                    // other visible stack.
-                    if (task.isOverHomeStack() &&
-                            mStackSupervisor.moveHomeStackTaskToTop(myReason)) {
-                        // Activity focus was already adjusted. Nothing else to do...
-                        return;
-                    }
-                }
-            }
+        if (next == r) {
+            mStackSupervisor.moveFocusableActivityStackToFrontLocked(
+                    mStackSupervisor.topRunningActivityLocked(), myReason);
+            return;
         }
 
-        mStackSupervisor.moveFocusableActivityStackToFrontLocked(
-                mStackSupervisor.topRunningActivityLocked(), myReason);
+        if (next != null && isFocusable()) {
+            // Keep focus in stack if we have a top running activity and are focusable.
+            return;
+        }
+
+        // Task is not guaranteed to be non-null. For example, destroying the
+        // {@link ActivityRecord} will disassociate the task from the activity.
+        final TaskRecord task = r.getTask();
+
+        if (task == null) {
+            throw new IllegalStateException("activity no longer associated with task:" + r);
+        }
+
+        // Move focus to next focusable stack if possible.
+        if (adjustFocusToNextFocusableStack(myReason)) {
+            return;
+        }
+
+        // Whatever...go home.
+        mStackSupervisor.moveHomeStackTaskToTop(myReason);
     }
 
     /** Find next proper focusable stack and make it focused. */
-    private boolean adjustFocusToNextFocusableStackLocked(String reason) {
-        return adjustFocusToNextFocusableStackLocked(reason, false /* allowFocusSelf */);
+    private boolean adjustFocusToNextFocusableStack(String reason) {
+        return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */);
     }
 
     /**
      * Find next proper focusable stack and make it focused.
      * @param allowFocusSelf Is the focus allowed to remain on the same stack.
      */
-    private boolean adjustFocusToNextFocusableStackLocked(String reason, boolean allowFocusSelf) {
-        if (isActivityTypeAssistant() && bottomTask() != null
-                && bottomTask().returnsToHomeTask()) {
-            // If the current stack is the assistant stack, then use the return-to type to determine
-            // whether to return to the home screen. This is needed to workaround an issue where
-            // launching a fullscreen task (and subequently returning from that task) will cause
-            // the fullscreen stack to be found as the next focusable stack below, even if the
-            // assistant was launched over home.
-            return mStackSupervisor.moveHomeStackTaskToTop(reason);
-        }
-
+    private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) {
         final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked(
                 allowFocusSelf ? null : this);
         final String myReason = reason + " adjustFocusToNextFocusableStack";
@@ -3453,22 +3177,12 @@
 
         final ActivityRecord top = stack.topRunningActivityLocked();
 
-        if (stack.isHomeOrRecentsStack() && (top == null || !top.visible)) {
+        if (stack.isActivityTypeHome() && (top == null || !top.visible)) {
             // If we will be focusing on the home stack next and its current top activity isn't
-            // visible, then use the task return to value to determine the home task to display
-            // next.
+            // visible, then use the move the home stack task to top to make the activity visible.
             return mStackSupervisor.moveHomeStackTaskToTop(reason);
         }
 
-        if (stack.isActivityTypeAssistant() && top != null
-                && top.getTask().returnsToHomeTask()) {
-            // It is possible for the home stack to not be directly underneath the assistant stack.
-            // For example, the assistant may start an activity in the fullscreen stack. Upon
-            // returning to the assistant stack, we must ensure that the home stack is underneath
-            // when appropriate.
-            mStackSupervisor.moveHomeStackTaskToTop("adjustAssistantReturnToHome");
-        }
-
         stack.moveToFront(myReason);
         return true;
     }
@@ -3483,7 +3197,7 @@
                     if (requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                             "stop-no-history", false)) {
                         // If {@link requestFinishActivityLocked} returns {@code true},
-                        // {@link adjustFocusedActivityStackLocked} would have been already called.
+                        // {@link adjustFocusedActivityStack} would have been already called.
                         r.resumeKeyDispatchingLocked();
                         return;
                     }
@@ -3495,7 +3209,7 @@
         }
 
         if (r.app != null && r.app.thread != null) {
-            adjustFocusedActivityStackLocked(r, "stopActivity");
+            adjustFocusedActivityStack(r, "stopActivity");
             r.resumeKeyDispatchingLocked();
             try {
                 r.stopped = false;
@@ -3734,7 +3448,7 @@
 
             r.pauseKeyDispatchingLocked();
 
-            adjustFocusedActivityStackLocked(r, "finishActivity");
+            adjustFocusedActivityStack(r, "finishActivity");
 
             finishActivityResultsLocked(r, resultCode, resultData);
 
@@ -3760,7 +3474,7 @@
                 }
 
                 if (endTask) {
-                    mService.mLockTaskController.removeLockedTask(task);
+                    mService.mLockTaskController.clearLockedTask(task);
                 }
             } else if (r.state != ActivityState.PAUSING) {
                 // If the activity is PAUSING, we will complete the finish once
@@ -3851,7 +3565,7 @@
 
         if (mode == FINISH_IMMEDIATELY
                 || (prevState == ActivityState.PAUSED
-                    && (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
+                    && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode()))
                 || finishingActivityInNonFocusedStack
                 || prevState == STOPPING
                 || prevState == STOPPED
@@ -3901,7 +3615,21 @@
         }
     }
 
-    final boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
+    /** @return true if the stack behind this one is a standard activity type. */
+    boolean inFrontOfStandardStack() {
+        final ActivityDisplay display = getDisplay();
+        if (display == null) {
+            return false;
+        }
+        final int index = display.getIndexOf(this);
+        if (index == 0) {
+            return false;
+        }
+        final ActivityStack stackBehind = display.getChildAt(index - 1);
+        return stackBehind.isActivityTypeStandard();
+    }
+
+    boolean shouldUpRecreateTaskLocked(ActivityRecord srec, String destAffinity) {
         // Basic case: for simple app-centric recents, we need to recreate
         // the task if the affinity has changed.
         if (srec == null || srec.getTask().affinity == null ||
@@ -3913,10 +3641,9 @@
         // of a document, unless simply finishing it will return them to the the
         // correct app behind.
         final TaskRecord task = srec.getTask();
-        if (srec.frontOfTask && task != null && task.getBaseIntent() != null
-                && task.getBaseIntent().isDocument()) {
+        if (srec.frontOfTask && task.getBaseIntent() != null && task.getBaseIntent().isDocument()) {
             // Okay, this activity is at the root of its task.  What to do, what to do...
-            if (!task.returnsToStandardTask()) {
+            if (!inFrontOfStandardStack()) {
                 // Finishing won't return to an application, so we need to recreate.
                 return true;
             }
@@ -4116,11 +3843,6 @@
                                 + " onlyHasTaskOverlays=" + onlyHasTaskOverlays);
             }
 
-            if (mStackSupervisor.isFocusedStack(this) && task == topTask() &&
-                    task.isOverHomeStack()) {
-                mStackSupervisor.moveHomeStackTaskToTop(reason);
-            }
-
             // The following block can be executed multiple times if there is more than one overlay.
             // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup
             // of the task by id and exiting early if not found.
@@ -4530,7 +4252,7 @@
             AppTimeTracker timeTracker, String reason) {
         if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
 
-        final ActivityStack topStack = getTopStackOnDisplay();
+        final ActivityStack topStack = getDisplay().getTopStack();
         final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
         final int numTasks = mTaskHistory.size();
         final int index = mTaskHistory.indexOf(tr);
@@ -4558,7 +4280,9 @@
         // Don't refocus if invisible to current user
         final ActivityRecord top = tr.getTopActivity();
         if (top == null || !top.okToShowLocked()) {
-            addRecentActivityLocked(top);
+            if (top != null) {
+                mStackSupervisor.mRecentTasks.add(top.getTask());
+            }
             ActivityOptions.abort(options);
             return;
         }
@@ -4610,15 +4334,16 @@
         }
         Slog.i(TAG, "moveTaskToBack: " + tr);
 
-        // If the task is locked, then show the lock task toast
-        if (!mService.mLockTaskController.checkLockedTask(tr)) {
+        // In LockTask mode, moving a locked task to the back of the stack may expose unlocked
+        // ones. Therefore we need to check if this operation is allowed.
+        if (!mService.mLockTaskController.canMoveTaskToBack(tr)) {
             return false;
         }
 
         // If we have a watcher, preflight the move before committing to it.  First check
         // for *other* available tasks, but if none are available, then try again allowing the
         // current task to be selected.
-        if (mStackSupervisor.isFrontStackOnDisplay(this) && mService.mController != null) {
+        if (isTopStackOnDisplay() && mService.mController != null) {
             ActivityRecord next = topRunningActivityLocked(null, taskId);
             if (next == null) {
                 next = topRunningActivityLocked(null, 0);
@@ -4640,73 +4365,23 @@
 
         if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId);
 
-        boolean prevIsHome = false;
+        mTaskHistory.remove(tr);
+        mTaskHistory.add(0, tr);
+        updateTaskMovement(tr, false);
 
-        // If true, we should resume the home activity next if the task we are moving to the
-        // back is over the home stack. We force to false if the task we are moving to back
-        // is the home task and we don't want it resumed after moving to the back.
-        final boolean canGoHome = !tr.isActivityTypeHome() && tr.isOverHomeStack();
-        if (canGoHome) {
-            final TaskRecord nextTask = getNextTask(tr);
-            if (nextTask != null) {
-                nextTask.setTaskToReturnTo(tr.getTaskToReturnTo());
-            } else {
-                prevIsHome = true;
-            }
-        }
+        mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
+        mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
+                true /* includingParents */);
 
-        boolean requiresMove = mTaskHistory.indexOf(tr) != 0;
-        if (requiresMove) {
-            mTaskHistory.remove(tr);
-            mTaskHistory.add(0, tr);
-            updateTaskMovement(tr, false);
-
-            mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
-            mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController());
-        }
-
-        if (mStackId == PINNED_STACK_ID) {
-            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
+        if (inPinnedWindowingMode()) {
+            mStackSupervisor.removeStack(this);
             return true;
         }
 
-        // Otherwise, there is an assumption that moving a task to the back moves it behind the
-        // home activity. We make sure here that some activity in the stack will launch home.
-        int numTasks = mTaskHistory.size();
-        for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
-            final TaskRecord task = mTaskHistory.get(taskNdx);
-            if (task.isOverHomeStack()) {
-                break;
-            }
-            if (taskNdx == 1) {
-                // Set the last task before tr to go to home.
-                task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            }
-        }
-
-        final TaskRecord task = mResumedActivity != null ? mResumedActivity.getTask() : null;
-        if (prevIsHome || (task == tr && canGoHome) || (numTasks <= 1 && isOnHomeDisplay())) {
-            if (!mService.mBooting && !mService.mBooted) {
-                // Not ready yet!
-                return false;
-            }
-            tr.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-            return mStackSupervisor.resumeHomeStackTask(null, "moveTaskToBack");
-        }
-
         mStackSupervisor.resumeFocusedStackTopActivityLocked();
         return true;
     }
 
-    /**
-     * Get the topmost stack on the current display. It may be different from focused stack, because
-     * focus may be on another display.
-     */
-    private ActivityStack getTopStackOnDisplay() {
-        final ArrayList<ActivityStack> stacks = getDisplay().mStacks;
-        return stacks.isEmpty() ? null : stacks.get(stacks.size() - 1);
-    }
-
     static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) {
         final Uri data = r.intent.getData();
         final String strData = data != null ? data.toSafeString() : null;
@@ -4781,7 +4456,7 @@
         for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
             final TaskRecord task = mTaskHistory.get(i);
             if (task.isResizeable()) {
-                if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                if (inFreeformWindowingMode()) {
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.
@@ -4933,68 +4608,43 @@
         return didSomething;
     }
 
-    void getTasksLocked(List<RunningTaskInfo> list, int callingUid, boolean allowed) {
+    /**
+     * @return The set of running tasks through {@param tasksOut} that are available to the caller.
+     *         If {@param ignoreActivityType} or {@param ignoreWindowingMode} are not undefined,
+     *         then skip running tasks that match those types.
+     */
+    void getRunningTasks(List<TaskRecord> tasksOut, @ActivityType int ignoreActivityType,
+            @WindowingMode int ignoreWindowingMode, int callingUid, boolean allowed) {
         boolean focusedStack = mStackSupervisor.getFocusedStack() == this;
         boolean topTask = true;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
             if (task.getTopActivity() == null) {
+                // Skip if there are no activities in the task
                 continue;
             }
-            ActivityRecord r = null;
-            ActivityRecord top = null;
-            ActivityRecord tmp;
-            int numActivities = 0;
-            int numRunning = 0;
-            final ArrayList<ActivityRecord> activities = task.mActivities;
             if (!allowed && !task.isActivityTypeHome() && task.effectiveUid != callingUid) {
+                // Skip if the caller can't fetch this task
                 continue;
             }
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                tmp = activities.get(activityNdx);
-                if (tmp.finishing) {
-                    continue;
-                }
-                r = tmp;
-
-                // Initialize state for next task if needed.
-                if (top == null || (top.state == ActivityState.INITIALIZING)) {
-                    top = r;
-                    numActivities = numRunning = 0;
-                }
-
-                // Add 'r' into the current task.
-                numActivities++;
-                if (r.app != null && r.app.thread != null) {
-                    numRunning++;
-                }
-
-                if (DEBUG_ALL) Slog.v(
-                    TAG, r.intent.getComponent().flattenToShortString()
-                    + ": task=" + r.getTask());
+            if (ignoreActivityType != ACTIVITY_TYPE_UNDEFINED
+                    && task.getActivityType() == ignoreActivityType) {
+                // Skip ignored activity type
+                continue;
             }
-
-            RunningTaskInfo ci = new RunningTaskInfo();
-            ci.id = task.taskId;
-            ci.stackId = mStackId;
-            ci.baseActivity = r.intent.getComponent();
-            ci.topActivity = top.intent.getComponent();
-            ci.lastActiveTime = task.lastActiveTime;
+            if (ignoreWindowingMode != WINDOWING_MODE_UNDEFINED
+                    && task.getWindowingMode() == ignoreWindowingMode) {
+                // Skip ignored windowing mode
+                continue;
+            }
             if (focusedStack && topTask) {
-                // Give the latest time to ensure foreground task can be sorted
-                // at the first, because lastActiveTime of creating task is 0.
-                ci.lastActiveTime = System.currentTimeMillis();
+                // For the focused stack top task, update the last stack active time so that it can
+                // be used to determine the order of the tasks (it may not be set for newly created
+                // tasks)
+                task.lastActiveTime = SystemClock.elapsedRealtime();
                 topTask = false;
             }
-
-            if (top.getTask() != null) {
-                ci.description = top.getTask().lastDescription;
-            }
-            ci.numActivities = numActivities;
-            ci.numRunning = numRunning;
-            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
-            ci.resizeMode = task.mResizeMode;
-            list.add(ci);
+            tasksOut.add(task);
         }
     }
 
@@ -5138,14 +4788,6 @@
             onActivityRemovedFromStack(record);
         }
 
-        final int taskNdx = mTaskHistory.indexOf(task);
-        final int topTaskNdx = mTaskHistory.size() - 1;
-        if (task.isOverHomeStack() && taskNdx < topTaskNdx) {
-            final TaskRecord nextTask = mTaskHistory.get(taskNdx + 1);
-            if (!nextTask.isOverHomeStack() && !nextTask.isOverAssistantStack()) {
-                nextTask.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            }
-        }
         mTaskHistory.remove(task);
         removeActivitiesFromLRUListLocked(task);
         updateTaskMovement(task, true);
@@ -5162,8 +4804,7 @@
             if (task.autoRemoveFromRecents() || isVoiceSession) {
                 // Task creator asked to remove this when done, or this task was a voice
                 // interaction, so it should not remain on the recent tasks list.
-                mRecentTasks.remove(task);
-                task.removedFromRecents();
+                mStackSupervisor.mRecentTasks.remove(task);
             }
 
             task.removeWindowContainer();
@@ -5176,15 +4817,14 @@
             if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
                     && mStackSupervisor.isFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
-                if (mFullscreen || !adjustFocusToNextFocusableStackLocked(myReason)) {
+                if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
-            if (mStacks != null) {
-                mStacks.remove(this);
-                mStacks.add(0, this);
+            if (isAttached()) {
+                getDisplay().positionChildAtBottom(this);
             }
-            if (!isHomeOrRecentsStack()) {
+            if (!isActivityTypeHome()) {
                 remove();
             }
         }
@@ -5192,7 +4832,7 @@
         task.setStack(null);
 
         // Notify if a task from the pinned stack is being removed (or moved depending on the mode)
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             mService.mTaskChangeNotificationController.notifyActivityUnpinned();
         }
     }
@@ -5206,22 +4846,14 @@
         addTask(task, toTop, "createTaskRecord");
         final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
                 .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
-        if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
-                && !isLockscreenShown) {
+        if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
+                && mBounds != null && task.isResizeable() && !isLockscreenShown) {
             task.updateOverrideConfiguration(mBounds);
         }
         task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
     }
 
-    boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) {
-        if (mTaskPositioner == null) {
-            return false;
-        }
-        mTaskPositioner.updateDefaultBounds(task, mTaskHistory, windowLayout);
-        return true;
-    }
-
     ArrayList<TaskRecord> getAllTasks() {
         return new ArrayList<>(mTaskHistory);
     }
@@ -5248,10 +4880,6 @@
         mTaskHistory.add(position, task);
         task.setStack(this);
 
-        if (toTop) {
-            updateTaskReturnToForTopInsertion(task);
-        }
-
         updateTaskMovement(task, toTop);
 
         postAddTask(task, prevStack, schedulePictureInPictureModeChange);
@@ -5343,10 +4971,13 @@
     @Override
     public String toString() {
         return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
-                + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}";
+                + " stackId=" + mStackId + " type=" + activityTypeToString(getActivityType())
+                + " mode=" + windowingModeToString(getWindowingMode())
+                + " visible=" + shouldBeVisible(null /* starting */) + ", "
+                + mTaskHistory.size() + " tasks}";
     }
 
-    void onLockTaskPackagesUpdatedLocked() {
+    void onLockTaskPackagesUpdated() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             mTaskHistory.get(taskNdx).setLockTaskAuth();
         }
@@ -5359,7 +4990,7 @@
     }
 
     boolean shouldSleepActivities() {
-        final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
+        final ActivityDisplay display = getDisplay();
         return display != null ? display.isSleeping() : mService.isSleepingLocked();
     }
 
@@ -5369,7 +5000,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         proto.write(ID, mStackId);
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = mTaskHistory.get(taskNdx);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 45c4df9..43a2a9f 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -20,42 +20,34 @@
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.START_ANY_ACTIVITY;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
-import static android.app.ActivityManager.StackId.getStackIdForActivityType;
-import static android.app.ActivityManager.StackId.getStackIdForWindowingMode;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.Display.TYPE_VIRTUAL;
+
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
@@ -89,22 +81,20 @@
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStack.STACK_VISIBLE;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER;
 import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY;
-import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
-import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
-import static com.android.server.am.proto.ActivityDisplayProto.ID;
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
+
 import static java.lang.Integer.MAX_VALUE;
 
 import android.Manifest;
@@ -115,7 +105,6 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManagerInternal.SleepToken;
 import android.app.ActivityOptions;
@@ -123,6 +112,8 @@
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.WaitResult;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -174,7 +165,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.proto.ActivityDisplayProto;
 import com.android.server.wm.ConfigurationContainer;
 import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.WindowManagerService;
@@ -189,7 +179,8 @@
 import java.util.List;
 import java.util.Set;
 
-public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener {
+public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener,
+        RecentTasks.Callbacks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStackSupervisor" : TAG_AM;
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
     private static final String TAG_IDLE = TAG + POSTFIX_IDLE;
@@ -235,13 +226,6 @@
     // at the top of its container (e.g. stack).
     static final boolean ON_TOP = true;
 
-    // Used to indicate that an objects (e.g. task) removal from its container
-    // (e.g. stack) is due to it moving to another container.
-    static final boolean MOVING = true;
-
-    // Force the focus to change to the stack we are moving a task to..
-    static final boolean FORCE_FOCUS = true;
-
     // Don't execute any calls to resume.
     static final boolean DEFER_RESUME = true;
 
@@ -299,7 +283,11 @@
 
     final ActivityManagerService mService;
 
-    private RecentTasks mRecentTasks;
+    /** The historial list of recent tasks including inactive tasks */
+    RecentTasks mRecentTasks;
+
+    /** Helper class to abstract out logic for fetching the set of currently running tasks */
+    private RunningTasks mRunningTasks;
 
     final ActivityStackSupervisorHandler mHandler;
 
@@ -307,8 +295,10 @@
     WindowManagerService mWindowManager;
     DisplayManager mDisplayManager;
 
+    private final LaunchingBoundsController mLaunchingBoundsController;
+
     /** Counter for next free stack ID to use for dynamic activity stacks. */
-    private int mNextFreeStackId = FIRST_DYNAMIC_STACK_ID;
+    private int mNextFreeStackId = 0;
 
     /**
      * Maps the task identifier that activities are currently being started in to the userId of the
@@ -393,15 +383,11 @@
      * They are used by components that may hide and block interaction with underlying
      * activities.
      */
-    final ArrayList<SleepToken> mSleepTokens = new ArrayList<SleepToken>();
+    final ArrayList<SleepToken> mSleepTokens = new ArrayList<>();
 
     /** Stack id of the front stack when user switched, indexed by userId. */
     SparseIntArray mUserStackInFront = new SparseIntArray(2);
 
-    // TODO: Add listener for removal of references.
-    /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
-    SparseArray<ActivityStack> mStacks = new SparseArray<>();
-
     // TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -419,6 +405,7 @@
      * object each time.
      */
     private final Rect tempRect = new Rect();
+    private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
@@ -587,12 +574,22 @@
     public ActivityStackSupervisor(ActivityManagerService service, Looper looper) {
         mService = service;
         mHandler = new ActivityStackSupervisorHandler(looper);
+        mRunningTasks = createRunningTasks();
         mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
         mKeyguardController = new KeyguardController(service, this);
+
+        mLaunchingBoundsController = new LaunchingBoundsController();
+        mLaunchingBoundsController.registerDefaultPositioners(this);
     }
 
     void setRecentTasks(RecentTasks recentTasks) {
         mRecentTasks = recentTasks;
+        mRecentTasks.registerCallback(this);
+    }
+
+    @VisibleForTesting
+    RunningTasks createRunningTasks() {
+        return new RunningTasks();
     }
 
     /**
@@ -620,16 +617,13 @@
             Display[] displays = mDisplayManager.getDisplays();
             for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
                 final int displayId = displays[displayNdx].getDisplayId();
-                ActivityDisplay activityDisplay = new ActivityDisplay(displayId);
-                if (activityDisplay.mDisplay == null) {
-                    throw new IllegalStateException("Default Display does not exist");
-                }
+                ActivityDisplay activityDisplay = new ActivityDisplay(this, displayId);
                 mActivityDisplays.put(displayId, activityDisplay);
                 calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
 
-            mHomeStack = mFocusedStack = mLastFocusedStack =
-                    getStack(HOME_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+            mHomeStack = mFocusedStack = mLastFocusedStack = getDefaultDisplay().getOrCreateStack(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
             mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         }
@@ -647,15 +641,6 @@
         return stack != null && stack == mFocusedStack;
     }
 
-    /** The top most stack on its display. */
-    boolean isFrontStackOnDisplay(ActivityStack stack) {
-        return isFrontOfStackList(stack, stack.getDisplay().mStacks);
-    }
-
-    private boolean isFrontOfStackList(ActivityStack stack, List<ActivityStack> stackList) {
-        return stack == stackList.get((stackList.size() - 1));
-    }
-
     /** NOTE: Should only be called from {@link ActivityStack#moveToFront} */
     void setFocusStackUnchecked(String reason, ActivityStack focusCandidate) {
         if (!focusCandidate.isFocusable()) {
@@ -685,7 +670,8 @@
     }
 
     void moveRecentsStackToFront(String reason) {
-        final ActivityStack recentsStack = getStack(RECENTS_STACK_ID);
+        final ActivityStack recentsStack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
         if (recentsStack != null) {
             recentsStack.moveToFront(reason);
         }
@@ -709,10 +695,6 @@
             return false;
         }
 
-        if (prev != null) {
-            prev.getTask().setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-        }
-
         mHomeStack.moveHomeStackTaskToTop();
         ActivityRecord r = getHomeActivity();
         final String myReason = reason + " resumeHomeStackTask";
@@ -730,7 +712,7 @@
     }
 
     TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
-        return anyTaskForIdLocked(id, matchMode, null);
+        return anyTaskForIdLocked(id, matchMode, null, !ON_TOP);
     }
 
     /**
@@ -738,11 +720,11 @@
      * @param id Id of the task we would like returned.
      * @param matchMode The mode to match the given task id in.
      * @param aOptions The activity options to use for restoration. Can be null.
+     * @param onTop If the stack for the task should be the topmost on the display.
      */
     TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode,
-            @Nullable ActivityOptions aOptions) {
-        // If there is a stack id set, ensure that we are attempting to actually restore a task
-        // TODO: Don't really know if this is needed...
+            @Nullable ActivityOptions aOptions, boolean onTop) {
+        // If options are set, ensure that we are attempting to actually restore a task
         if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
             throw new IllegalArgumentException("Should not specify activity options for non-restore"
                     + " lookup");
@@ -750,13 +732,25 @@
 
         int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final TaskRecord task = stack.taskForIdLocked(id);
-                if (task != null) {
-                    return task;
+                if (task == null) {
+                    continue;
                 }
+                if (aOptions != null) {
+                    // Resolve the stack the task should be placed in now based on options
+                    // and reparent if needed.
+                    final ActivityStack launchStack = getLaunchStack(null, aOptions, task, onTop);
+                    if (launchStack != null && stack != launchStack) {
+                        final int reparentMode = onTop
+                                ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
+                        task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
+                                "anyTaskForIdLocked");
+                    }
+                }
+                return task;
             }
         }
 
@@ -768,7 +762,7 @@
         // Otherwise, check the recent tasks and return if we find it there and we are not restoring
         // the task from recents
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS, "Looking for task id=" + id + " in recents");
-        final TaskRecord task = mRecentTasks.taskForIdLocked(id);
+        final TaskRecord task = mRecentTasks.getTask(id);
 
         if (task == null) {
             if (DEBUG_RECENTS) {
@@ -783,7 +777,7 @@
         }
 
         // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
-        if (!restoreRecentTaskLocked(task, aOptions)) {
+        if (!restoreRecentTaskLocked(task, aOptions, onTop)) {
             if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
                     "Couldn't restore task id=" + id + " found in recents");
             return null;
@@ -795,9 +789,10 @@
     ActivityRecord isInAnyStackLocked(IBinder token) {
         int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityRecord r = stacks.get(stackNdx).isInStackLocked(token);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                final ActivityRecord r = stack.isInStackLocked(token);
                 if (r != null) {
                     return r;
                 }
@@ -835,18 +830,21 @@
     void lockAllProfileTasks(@UserIdInt int userId) {
         mWindowManager.deferSurfaceLayout();
         try {
-            final List<ActivityStack> stacks = getStacks();
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; stackNdx--) {
-                final List<TaskRecord> tasks = stacks.get(stackNdx).getAllTasks();
-                for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
-                    final TaskRecord task = tasks.get(taskNdx);
+            for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
+                final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                    final ActivityStack stack = display.getChildAt(stackNdx);
+                    final List<TaskRecord> tasks = stack.getAllTasks();
+                    for (int taskNdx = tasks.size() - 1; taskNdx >= 0; taskNdx--) {
+                        final TaskRecord task = tasks.get(taskNdx);
 
-                    // Check the task for a top activity belonging to userId, or returning a result
-                    // to an activity belonging to userId. Example case: a document picker for
-                    // personal files, opened by a work app, should still get locked.
-                    if (taskTopActivityIsUser(task, userId)) {
-                        mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
-                                task.taskId, userId);
+                        // Check the task for a top activity belonging to userId, or returning a
+                        // result to an activity belonging to userId. Example case: a document
+                        // picker for personal files, opened by a work app, should still get locked.
+                        if (taskTopActivityIsUser(task, userId)) {
+                            mService.mTaskChangeNotificationController.notifyTaskProfileLocked(
+                                    task.taskId, userId);
+                        }
                     }
                 }
             }
@@ -877,7 +875,7 @@
         // [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
         // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
         int candidateTaskId = nextTaskIdForUser(currentTaskId, userId);
-        while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId)
+        while (mRecentTasks.containsTaskId(candidateTaskId, userId)
                 || anyTaskForIdLocked(
                         candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
             candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
@@ -912,9 +910,9 @@
         final String processName = app.processName;
         boolean didSomething = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!isFocusedStack(stack)) {
                     continue;
                 }
@@ -947,9 +945,9 @@
 
     boolean allResumedActivitiesIdle() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!isFocusedStack(stack) || stack.numActivities() == 0) {
                     continue;
                 }
@@ -968,9 +966,9 @@
 
     boolean allResumedActivitiesComplete() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (isFocusedStack(stack)) {
                     final ActivityRecord r = stack.mResumedActivity;
                     if (r != null && r.state != RESUMED) {
@@ -987,12 +985,12 @@
         return true;
     }
 
-    boolean allResumedActivitiesVisible() {
+    private boolean allResumedActivitiesVisible() {
         boolean foundResumed = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final ActivityRecord r = stack.mResumedActivity;
                 if (r != null) {
                     if (!r.nowVisible || mActivitiesWaitingForVisibleActivity.contains(r)) {
@@ -1016,9 +1014,9 @@
     boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) {
         boolean someActivityPaused = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!isFocusedStack(stack) && stack.mResumedActivity != null) {
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack +
                             " mResumedActivity=" + stack.mResumedActivity);
@@ -1033,9 +1031,9 @@
     boolean allPausedActivitiesComplete() {
         boolean pausing = true;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final ActivityRecord r = stack.mPausingActivity;
                 if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) {
                     if (DEBUG_STATES) {
@@ -1053,9 +1051,10 @@
 
     void cancelInitializingActivities() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stacks.get(stackNdx).cancelInitializingActivities();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.cancelInitializingActivities();
             }
         }
     }
@@ -1153,13 +1152,10 @@
 
         for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
             final int displayId = mTmpOrderedDisplayIds.get(i);
-            final List<ActivityStack> stacks = mActivityDisplays.get(displayId).mStacks;
-            if (stacks == null) {
-                continue;
-            }
-            for (int j = stacks.size() - 1; j >= 0; --j) {
-                final ActivityStack stack = stacks.get(j);
-                if (stack != focusedStack && isFrontStackOnDisplay(stack) && stack.isFocusable()) {
+            final ActivityDisplay display = mActivityDisplays.get(displayId);
+            for (int j = display.getChildCount() - 1; j >= 0; --j) {
+                final ActivityStack stack = display.getChildAt(j);
+                if (stack != focusedStack && stack.isTopStackOnDisplay() && stack.isFocusable()) {
                     r = stack.topRunningActivityLocked();
                     if (r != null) {
                         return r;
@@ -1170,44 +1166,12 @@
         return null;
     }
 
-    void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
-        // Gather all of the running tasks for each stack into runningTaskLists.
-        ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists =
-                new ArrayList<ArrayList<RunningTaskInfo>>();
-        final int numDisplays = mActivityDisplays.size();
-        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
-                ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<>();
-                runningTaskLists.add(stackTaskList);
-                stack.getTasksLocked(stackTaskList, callingUid, allowed);
-            }
-        }
-
-        // The lists are already sorted from most recent to oldest. Just pull the most recent off
-        // each list and add it to list. Stop when all lists are empty or maxNum reached.
-        while (maxNum > 0) {
-            long mostRecentActiveTime = Long.MIN_VALUE;
-            ArrayList<RunningTaskInfo> selectedStackList = null;
-            final int numTaskLists = runningTaskLists.size();
-            for (int stackNdx = 0; stackNdx < numTaskLists; ++stackNdx) {
-                ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists.get(stackNdx);
-                if (!stackTaskList.isEmpty()) {
-                    final long lastActiveTime = stackTaskList.get(0).lastActiveTime;
-                    if (lastActiveTime > mostRecentActiveTime) {
-                        mostRecentActiveTime = lastActiveTime;
-                        selectedStackList = stackTaskList;
-                    }
-                }
-            }
-            if (selectedStackList != null) {
-                list.add(selectedStackList.remove(0));
-                --maxNum;
-            } else {
-                break;
-            }
-        }
+    @VisibleForTesting
+    void getRunningTasks(int maxNum, List<RunningTaskInfo> list,
+            @ActivityType int ignoreActivityType, @WindowingMode int ignoreWindowingMode,
+            int callingUid, boolean allowed) {
+        mRunningTasks.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode,
+                mActivityDisplays, callingUid, allowed);
     }
 
     ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags,
@@ -1255,7 +1219,7 @@
         synchronized (mService) {
             return mService.getPackageManagerInternalLocked().resolveIntent(intent, resolvedType,
                     PackageManager.MATCH_INSTANT | PackageManager.MATCH_DEFAULT_ONLY | flags
-                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
+                    | ActivityManagerService.STOCK_PM_FLAGS, userId, true);
         }
     }
 
@@ -1344,8 +1308,11 @@
             mService.updateLruProcessLocked(app, true, null);
             mService.updateOomAdjLocked();
 
-            if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE ||
-                    task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) {
+            if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+                    || (task.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED
+                            && mService.mLockTaskController.getLockTaskModeState()
+                            == LOCK_TASK_MODE_LOCKED)) {
                 mService.mLockTaskController.startLockTaskMode(task, false, 0 /* blank UID */);
             }
 
@@ -1603,7 +1570,10 @@
             return false;
         }
         if (options != null) {
-            if (options.getLaunchTaskId() != INVALID_STACK_ID) {
+            // If a launch task id is specified, then ensure that the caller is the recents
+            // component or has the START_TASKS_FROM_RECENTS permission
+            if (options.getLaunchTaskId() != INVALID_TASK_ID
+                    && !mRecentTasks.isCallerRecents(callingUid)) {
                 final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
                         callingPid, callingUid);
                 if (startInTaskPerm == PERMISSION_DENIED) {
@@ -1627,6 +1597,16 @@
                 Slog.w(TAG, msg);
                 throw new SecurityException(msg);
             }
+            // Check if someone tries to launch an unwhitelisted activity into LockTask mode.
+            final boolean lockTaskMode = options.getLockTaskMode();
+            if (lockTaskMode && !mService.mLockTaskController.isPackageWhitelisted(
+                    UserHandle.getUserId(callingUid), aInfo.packageName)) {
+                final String msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ") with lockTaskMode=true";
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
         }
 
         return true;
@@ -1948,9 +1928,10 @@
     boolean handleAppDiedLocked(ProcessRecord app) {
         boolean hasVisibleActivities = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                hasVisibleActivities |= stacks.get(stackNdx).handleAppDiedLocked(app);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                hasVisibleActivities |= stack.handleAppDiedLocked(app);
             }
         }
         return hasVisibleActivities;
@@ -1958,9 +1939,10 @@
 
     void closeSystemDialogsLocked() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stacks.get(stackNdx).closeSystemDialogsLocked();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.closeSystemDialogsLocked();
             }
         }
     }
@@ -1975,7 +1957,7 @@
      */
     void updateUserStackLocked(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
-            mUserStackInFront.put(userId, stack != null ? stack.getStackId() : HOME_STACK_ID);
+            mUserStackInFront.put(userId, stack != null ? stack.getStackId() : mHomeStack.mStackId);
         }
     }
 
@@ -1986,9 +1968,9 @@
             boolean doit, boolean evenPersistent, int userId) {
         boolean didSomething = false;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (stack.finishDisabledPackageActivitiesLocked(
                         packageName, filterByClasses, doit, evenPersistent, userId)) {
                     didSomething = true;
@@ -2008,9 +1990,9 @@
         // hosted by the process that is actually still the foreground.
         ProcessRecord fgApp = null;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (isFocusedStack(stack)) {
                     if (stack.mResumedActivity != null) {
                         fgApp = stack.mResumedActivity.app;
@@ -2060,9 +2042,10 @@
 
     void updateActivityApplicationInfoLocked(ApplicationInfo aInfo) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stacks.get(stackNdx).updateActivityApplicationInfoLocked(aInfo);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.updateActivityApplicationInfoLocked(aInfo);
             }
         }
     }
@@ -2071,10 +2054,10 @@
         TaskRecord finishedTask = null;
         ActivityStack focusedStack = getFocusedStack();
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            final int numStacks = display.getChildCount();
             for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 TaskRecord t = stack.finishTopRunningActivityLocked(app, reason);
                 if (stack == focusedStack || finishedTask == null) {
                     finishedTask = t;
@@ -2086,49 +2069,54 @@
 
     void finishVoiceTask(IVoiceInteractionSession session) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            final int numStacks = display.getChildCount();
             for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.finishVoiceTask(session);
             }
         }
     }
 
-    void findTaskToMoveToFrontLocked(TaskRecord task, int flags, ActivityOptions options,
+    void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options,
             String reason, boolean forceNonResizeable) {
+        final ActivityStack currentStack = task.getStack();
+        if (currentStack == null) {
+            Slog.e(TAG, "findTaskToMoveToFront: can't move task="
+                    + task + " to front. Stack is null");
+            return;
+        }
+
         if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
             mUserLeaving = true;
         }
-        if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
-            // Caller wants the home activity moved with it.  To accomplish this,
-            // we'll just indicate that this task returns to the home task.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-        }
-        final ActivityStack currentStack = task.getStack();
-        if (currentStack == null) {
-            Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
-                    + task + " to front. Stack is null");
-            return;
+
+        final ActivityRecord prev = topRunningActivityLocked();
+
+        if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0
+                || (prev != null && prev.isActivityTypeRecents())) {
+            // Caller wants the home activity moved with it or the previous task is recents in which
+            // case we always return home from the task we are moving to the front.
+            moveHomeStackToFront("findTaskToMoveToFront");
         }
 
         if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
             final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
             task.updateOverrideConfiguration(bounds);
 
-            int stackId = getLaunchStackId(null, options, task);
+            ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
 
-            if (stackId != currentStack.mStackId) {
-                task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                        DEFER_RESUME, "findTaskToMoveToFrontLocked");
-                stackId = currentStack.mStackId;
+            if (stack != currentStack) {
+                task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
+                        "findTaskToMoveToFront");
+                stack = currentStack;
                 // moveTaskToStackUncheckedLocked() should already placed the task on top,
                 // still need moveTaskToFrontLocked() below for any transition settings.
             }
-            if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                resizeStackLocked(stackId, bounds,
-                        null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                        !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            if (stack.resizeStackWithLaunchBounds()) {
+                resizeStackLocked(stack, bounds, null /* tempTaskBounds */,
+                        null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+                        true /* allowResizeInDockedMode */, !DEFER_RESUME);
             } else {
                 // WM resizeTask must be done after the task is moved to the correct stack,
                 // because Task's setBounds() also updates dim layer's bounds, but that has
@@ -2145,7 +2133,7 @@
                 "findTaskToMoveToFront: moved to front of stack=" + currentStack);
 
         handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY,
-                currentStack.mStackId, forceNonResizeable);
+                currentStack, forceNonResizeable);
     }
 
     boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
@@ -2159,87 +2147,51 @@
                 || mService.mSupportsFreeformWindowManagement;
     }
 
+    LaunchingBoundsController getLaunchingBoundsController() {
+        return mLaunchingBoundsController;
+    }
+
     protected <T extends ActivityStack> T getStack(int stackId) {
-        return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
-    }
-
-    protected <T extends ActivityStack> T getStack(int stackId, boolean createStaticStackIfNeeded,
-            boolean createOnTop) {
-        final ActivityStack stack = mStacks.get(stackId);
-        if (stack != null) {
-            return (T) stack;
-        }
-        if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
-            return null;
-        }
-        if (stackId == DOCKED_STACK_ID) {
-            // Make sure recents stack exist when creating a dock stack as it normally need to be on
-            // the other side of the docked stack and we make visibility decisions based on that.
-            getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, createOnTop);
-        }
-        return (T) createStackOnDisplay(stackId, DEFAULT_DISPLAY, createOnTop);
-    }
-
-    private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable TaskRecord task) {
-
-        // First preference if the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
-
-        // If windowing mode is unset, then next preference is the candidate task, then the
-        // activity record.
-        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
-            if (task != null) {
-                windowingMode = task.getWindowingMode();
-            }
-            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
-                windowingMode = r.getWindowingMode();
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final T stack = mActivityDisplays.valueAt(i).getStack(stackId);
+            if (stack != null) {
+                return stack;
             }
         }
-
-        // Make sure the windowing mode we are trying to use makes sense for what is supported.
-        if (!mService.mSupportsMultiWindow && windowingMode != WINDOWING_MODE_FULLSCREEN) {
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        }
-
-        if (!mService.mSupportsSplitScreenMultiWindow
-                && (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        }
-
-        if (windowingMode == WINDOWING_MODE_FREEFORM
-                && !mService.mSupportsFreeformWindowManagement) {
-            windowingMode = WINDOWING_MODE_FULLSCREEN;
-        }
-
-        return windowingMode;
+        return null;
     }
 
-    private int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable TaskRecord task) {
-        // First preference if the activity type in the activity options if set.
-        int activityType = (options != null)
-                ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+    /** @see ActivityDisplay#getStack(int, int) */
+    private <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final T stack = mActivityDisplays.valueAt(i).getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
 
+    int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task) {
+        // Preference is given to the activity type for the activity then the task since the type
+        // once set shouldn't change.
+        int activityType = r != null ? r.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+        if (activityType == ACTIVITY_TYPE_UNDEFINED && task != null) {
+            activityType = task.getActivityType();
+        }
         if (activityType != ACTIVITY_TYPE_UNDEFINED) {
             return activityType;
         }
-
-        // If activity type is unset, then next preference is the task, then the activity record.
-        if (task != null) {
-            activityType = task.getActivityType();
+        if (options != null) {
+            activityType = options.getLaunchActivityType();
         }
-        if (activityType == ACTIVITY_TYPE_UNDEFINED && r != null) {
-            activityType = r.getActivityType();
-        }
-        return activityType;
+        return activityType != ACTIVITY_TYPE_UNDEFINED ? activityType : ACTIVITY_TYPE_STANDARD;
     }
 
-    int getLaunchStackId(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable TaskRecord candidateTask) {
-        return getLaunchStackId(r, options, candidateTask, INVALID_DISPLAY);
+    <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
+        return getLaunchStack(r, options, candidateTask, onTop, INVALID_DISPLAY);
     }
 
     /**
@@ -2251,8 +2203,9 @@
      *
      * @return The stack to use for the launch or INVALID_STACK_ID.
      */
-    int getLaunchStackId(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
-            @Nullable TaskRecord candidateTask, int candidateDisplayId) {
+    <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
+            int candidateDisplayId) {
         int taskId = INVALID_TASK_ID;
         int displayId = INVALID_DISPLAY;
         //Rect bounds = null;
@@ -2271,16 +2224,15 @@
             // Temporarily set the task id to invalid in case in re-entry.
             options.setLaunchTaskId(INVALID_TASK_ID);
             final TaskRecord task = anyTaskForIdLocked(taskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options);
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options, onTop);
             options.setLaunchTaskId(taskId);
             if (task != null) {
-                return task.getStack().mStackId;
+                return task.getStack();
             }
         }
 
-        final int windowingMode = resolveWindowingMode(r, options, candidateTask);
         final int activityType = resolveActivityType(r, options, candidateTask);
-        ActivityStack stack = null;
+        T stack = null;
 
         // Next preference for stack goes to the display Id set in the activity options or the
         // candidate display.
@@ -2290,26 +2242,24 @@
         if (displayId != INVALID_DISPLAY) {
             if (r != null) {
                 // TODO: This should also take in the windowing mode and activity type into account.
-                stack = getValidLaunchStackOnDisplay(displayId, r);
+                stack = (T) getValidLaunchStackOnDisplay(displayId, r);
                 if (stack != null) {
-                    return stack.mStackId;
+                    return stack;
                 }
             }
             final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
             if (display != null) {
-                for (int i = display.mStacks.size() - 1; i >= 0; --i) {
-                    stack = display.mStacks.get(i);
-                    if (stack.getWindowingMode() == windowingMode
-                            && stack.getActivityType() == activityType) {
-                        return stack.mStackId;
-                    }
+                stack = display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
+                if (stack != null) {
+                    return stack;
                 }
-                // TODO: We should create the stack we want on the display at this point.
             }
         }
 
         // Give preference to the stack and display of the input task and activity if they match the
         // mode we want to launch into.
+        stack = null;
+        ActivityDisplay display = null;
         if (candidateTask != null) {
             stack = candidateTask.getStack();
         }
@@ -2317,40 +2267,25 @@
             stack = r.getStack();
         }
         if (stack != null) {
-            if (stack.getWindowingMode() == windowingMode
-                    && stack.getActivityType() == activityType) {
-                return stack.mStackId;
-            }
-            ActivityDisplay display = stack.getDisplay();
-
+            display = stack.getDisplay();
             if (display != null) {
-                for (int i = display.mStacks.size() - 1; i >= 0; --i) {
-                    stack = display.mStacks.get(i);
-                    if (stack.getWindowingMode() == windowingMode
-                            && stack.getActivityType() == activityType) {
-                        return stack.mStackId;
-                    }
+                final int windowingMode =
+                        display.resolveWindowingMode(r, options, candidateTask, activityType);
+                if (stack.isCompatible(windowingMode, activityType)) {
+                    return stack;
                 }
             }
         }
 
-        // Give preference to the type of activity we are trying to launch followed by the windowing
-        // mode.
-        int stackId = getStackIdForActivityType(activityType);
-        if (stackId != INVALID_STACK_ID) {
-            return stackId;
-        }
-        stackId = getStackIdForWindowingMode(windowingMode);
-        if (stackId != INVALID_STACK_ID) {
-            return stackId;
+        if (display == null
+                // TODO: Can be removed once we figure-out how non-standard types should launch
+                // outside the default display.
+                || (activityType != ACTIVITY_TYPE_STANDARD
+                && activityType != ACTIVITY_TYPE_UNDEFINED)) {
+            display = getDefaultDisplay();
         }
 
-        // Whatever...return some default for now.
-        if (candidateTask != null && candidateTask.mBounds != null
-                && mService.mSupportsFreeformWindowManagement) {
-            return FREEFORM_WORKSPACE_STACK_ID;
-        }
-        return FULLSCREEN_WORKSPACE_STACK_ID;
+        return display.getOrCreateStack(r, options, candidateTask, activityType, onTop);
     }
 
     /**
@@ -2367,62 +2302,48 @@
                     "Display with displayId=" + displayId + " not found.");
         }
 
+        if (!r.canBeLaunchedOnDisplay(displayId)) {
+            return null;
+        }
+
         // Return the topmost valid stack on the display.
-        for (int i = activityDisplay.mStacks.size() - 1; i >= 0; --i) {
-            final ActivityStack stack = activityDisplay.mStacks.get(i);
-            if (isValidLaunchStackId(stack.mStackId, displayId, r)) {
+        for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) {
+            final ActivityStack stack = activityDisplay.getChildAt(i);
+            if (isValidLaunchStack(stack, displayId, r)) {
                 return stack;
             }
         }
 
         // If there is no valid stack on the external display - check if new dynamic stack will do.
-        if (displayId != Display.DEFAULT_DISPLAY) {
-            final int newDynamicStackId = getNextStackId();
-            if (isValidLaunchStackId(newDynamicStackId, displayId, r)) {
-                return createStackOnDisplay(newDynamicStackId, displayId, true /*onTop*/);
-            }
+        if (displayId != DEFAULT_DISPLAY) {
+            return activityDisplay.createStack(
+                    r.getWindowingMode(), r.getActivityType(), true /*onTop*/);
         }
 
         Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId);
         return null;
     }
 
-    boolean isValidLaunchStackId(int stackId, int displayId, ActivityRecord r) {
-        switch (stackId) {
-            case INVALID_STACK_ID:
-            case HOME_STACK_ID:
-                return false;
-            case FULLSCREEN_WORKSPACE_STACK_ID:
-                return true;
-            case FREEFORM_WORKSPACE_STACK_ID:
-                return r.supportsFreeform();
-            case DOCKED_STACK_ID:
-                return r.supportsSplitScreen();
-            case PINNED_STACK_ID:
-                return r.supportsPictureInPicture();
-            case RECENTS_STACK_ID:
-                return r.isActivityTypeRecents();
-            case ASSISTANT_STACK_ID:
-                return r.isActivityTypeAssistant();
-            default:
-                if (StackId.isDynamicStack(stackId)) {
-                    return r.canBeLaunchedOnDisplay(displayId);
-                }
-                Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
-                return false;
+    // TODO: Can probably be consolidated into getLaunchStack()...
+    private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) {
+        switch (stack.getActivityType()) {
+            case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
+            case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
+            case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant();
         }
-    }
-
-    ArrayList<ActivityStack> getStacks() {
-        ArrayList<ActivityStack> allStacks = new ArrayList<>();
-        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            allStacks.addAll(mActivityDisplays.valueAt(displayNdx).mStacks);
+        switch (stack.getWindowingMode()) {
+            case WINDOWING_MODE_FULLSCREEN: return true;
+            case WINDOWING_MODE_FREEFORM: return r.supportsFreeform();
+            case WINDOWING_MODE_PINNED: return r.supportsPictureInPicture();
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return r.supportsSplitScreenWindowingMode();
+            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode();
         }
-        return allStacks;
-    }
 
-    ArrayList<ActivityStack> getStacksOnDefaultDisplay() {
-        return mActivityDisplays.valueAt(DEFAULT_DISPLAY).mStacks;
+        if (!stack.isOnHomeDisplay()) {
+            return r.canBeLaunchedOnDisplay(displayId);
+        }
+        Slog.e(TAG, "isValidLaunchStack: Unexpected stack=" + stack);
+        return false;
     }
 
     /**
@@ -2439,12 +2360,11 @@
         for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) {
             final int displayId = mTmpOrderedDisplayIds.get(i);
             // If a display is registered in WM, it must also be available in AM.
-            @SuppressWarnings("ConstantConditions")
-            final List<ActivityStack> stacks = getActivityDisplayOrCreateLocked(displayId).mStacks;
-            for (int j = stacks.size() - 1; j >= 0; --j) {
-                final ActivityStack stack = stacks.get(j);
+            final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
+            for (int j = display.getChildCount() - 1; j >= 0; --j) {
+                final ActivityStack stack = display.getChildAt(j);
                 if (stack != currentFocus && stack.isFocusable()
-                        && stack.shouldBeVisible(null) != STACK_INVISIBLE) {
+                        && stack.shouldBeVisible(null)) {
                     return stack;
                 }
             }
@@ -2500,20 +2420,17 @@
         return null;
     }
 
-    void resizeStackLocked(int stackId, Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds,
-            boolean preserveWindows, boolean allowResizeInDockedMode, boolean deferResume) {
-        if (stackId == DOCKED_STACK_ID) {
+    void resizeStackLocked(ActivityStack stack, Rect bounds, Rect tempTaskBounds,
+            Rect tempTaskInsetBounds, boolean preserveWindows, boolean allowResizeInDockedMode,
+            boolean deferResume) {
+
+        if (stack.inSplitScreenPrimaryWindowingMode()) {
             resizeDockedStackLocked(bounds, tempTaskBounds, tempTaskInsetBounds, null, null,
                     preserveWindows, deferResume);
             return;
         }
-        final ActivityStack stack = getStack(stackId);
-        if (stack == null) {
-            Slog.w(TAG, "resizeStack: stackId " + stackId + " not found.");
-            return;
-        }
 
-        final boolean splitScreenActive = getStack(DOCKED_STACK_ID) != null;
+        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenPrimaryStack();
         if (!allowResizeInDockedMode
                 && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
             // If the docked stack exists, don't resize non-floating stacks independently of the
@@ -2521,10 +2438,10 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stack.mStackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            if (stack.supportSplitScreenWindowingMode()) {
+            if (stack.supportsSplitScreenWindowingMode()) {
                 if (bounds == null && stack.inSplitScreenWindowingMode()) {
                     // null bounds = fullscreen windowing mode...at least for now.
                     stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2545,22 +2462,22 @@
         }
     }
 
-    private void deferUpdateBounds(int stackId) {
-        final ActivityStack stack = getStack(stackId);
+    private void deferUpdateBounds(int activityType) {
+        final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
         if (stack != null) {
             stack.deferUpdateBounds();
         }
     }
 
-    private void continueUpdateBounds(int stackId) {
-        final ActivityStack stack = getStack(stackId);
+    private void continueUpdateBounds(int activityType) {
+        final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
         if (stack != null) {
             stack.continueUpdateBounds();
         }
     }
 
     void notifyAppTransitionDone() {
-        continueUpdateBounds(RECENTS_STACK_ID);
+        continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
         for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
             final int taskId = mResizingTasksDuringAnimation.valueAt(i);
             final TaskRecord task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY);
@@ -2571,29 +2488,31 @@
         mResizingTasksDuringAnimation.clear();
     }
 
-    private void moveTasksToFullscreenStackInSurfaceTransaction(int fromStackId,
-            boolean onTop) {
-
-        final ActivityStack stack = getStack(fromStackId);
-        if (stack == null) {
-            return;
-        }
+    /**
+     * TODO: This should just change the windowing mode and resize vs. actually moving task around.
+     * Can do that once we are no longer using static stack ids.
+     */
+    private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
+            int toDisplayId, boolean onTop) {
 
         mWindowManager.deferSurfaceLayout();
         try {
-            if (fromStackId == DOCKED_STACK_ID) {
+            final int windowingMode = fromStack.getWindowingMode();
+            final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
+            final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
+
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // Tell the display we are exiting split-screen mode.
+                toDisplay.onExitingSplitScreenMode();
                 // We are moving all tasks from the docked stack to the fullscreen stack,
                 // which is dismissing the docked stack, so resize all other stacks to
                 // fullscreen here already so we don't end up with resize trashing.
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    final ActivityStack otherStack = getStack(i);
-                    if (otherStack == null) {
-                        continue;
-                    }
+                for (int i = toDisplay.getChildCount() - 1; i >= 0; --i) {
+                    final ActivityStack otherStack = toDisplay.getChildAt(i);
                     if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
                         continue;
                     }
-                    resizeStackLocked(i, null, null, null, PRESERVE_WINDOWS,
+                    resizeStackLocked(otherStack, null, null, null, PRESERVE_WINDOWS,
                             true /* allowResizeInDockedMode */, DEFER_RESUME);
                 }
 
@@ -2602,48 +2521,43 @@
                 // resize when we remove task from it below and it is detached from the
                 // display because it no longer contains any tasks.
                 mAllowDockedStackResize = false;
-            } else if (fromStackId == PINNED_STACK_ID) {
-                if (onTop) {
-                    // Log if we are expanding the PiP to fullscreen
-                    MetricsLogger.action(mService.mContext,
-                            ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
-                }
+            } else if (inPinnedWindowingMode && onTop) {
+                // Log if we are expanding the PiP to fullscreen
+                MetricsLogger.action(mService.mContext,
+                        ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
             }
-            ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
-            final boolean isFullscreenStackVisible = fullscreenStack != null &&
-                    fullscreenStack.shouldBeVisible(null) == STACK_VISIBLE;
+
             // If we are moving from the pinned stack, then the animation takes care of updating
             // the picture-in-picture mode.
-            final boolean schedulePictureInPictureModeChange = (fromStackId == PINNED_STACK_ID);
-            final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-            final int size = tasks.size();
-            if (onTop) {
-                for (int i = 0; i < size; i++) {
+            final boolean schedulePictureInPictureModeChange = inPinnedWindowingMode;
+            final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
+
+            if (!tasks.isEmpty()) {
+                mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                final int size = tasks.size();
+                for (int i = 0; i < size; ++i) {
                     final TaskRecord task = tasks.get(i);
-                    final boolean isTopTask = i == (size - 1);
-                    if (fromStackId == PINNED_STACK_ID) {
-                        // Update the return-to to reflect where the pinned stack task was moved
-                        // from so that we retain the stack that was previously visible if the
-                        // pinned stack is recreated. See moveActivityToPinnedStackLocked().
-                        task.setTaskToReturnTo(isFullscreenStackVisible ?
-                                ACTIVITY_TYPE_STANDARD : ACTIVITY_TYPE_HOME);
+                    final ActivityStack toStack = toDisplay.getOrCreateStack(
+                                null, mTmpOptions, task, task.getActivityType(), onTop);
+
+                    if (onTop) {
+                        final int returnToType =
+                                toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
+                        final boolean isTopTask = i == (size - 1);
+                        // Defer resume until all the tasks have been moved to the fullscreen stack
+                        task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+                                isTopTask /* animate */, DEFER_RESUME,
+                                schedulePictureInPictureModeChange,
+                                "moveTasksToFullscreenStack - onTop");
+                    } else {
+                        // Position the tasks in the fullscreen stack in order at the bottom of the
+                        // stack. Also defer resume until all the tasks have been moved to the
+                        // fullscreen stack.
+                        task.reparent(toStack, ON_TOP,
+                                REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
+                                schedulePictureInPictureModeChange,
+                                "moveTasksToFullscreenStack - NOT_onTop");
                     }
-                    // Defer resume until all the tasks have been moved to the fullscreen stack
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
-                            REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
-                            schedulePictureInPictureModeChange,
-                            "moveTasksToFullscreenStack - onTop");
-                }
-            } else {
-                for (int i = 0; i < size; i++) {
-                    final TaskRecord task = tasks.get(i);
-                    // Position the tasks in the fullscreen stack in order at the bottom of the
-                    // stack. Also defer resume until all the tasks have been moved to the
-                    // fullscreen stack.
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, i /* position */,
-                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
-                            schedulePictureInPictureModeChange,
-                            "moveTasksToFullscreenStack - NOT_onTop");
                 }
             }
 
@@ -2655,9 +2569,13 @@
         }
     }
 
-    void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
-        mWindowManager.inSurfaceTransaction(
-                () -> moveTasksToFullscreenStackInSurfaceTransaction(fromStackId, onTop));
+    void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) {
+        moveTasksToFullscreenStackLocked(fromStack, DEFAULT_DISPLAY, onTop);
+    }
+
+    void moveTasksToFullscreenStackLocked(ActivityStack fromStack, int toDisplayId, boolean onTop) {
+        mWindowManager.inSurfaceTransaction(() ->
+                moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop));
     }
 
     void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
@@ -2668,7 +2586,7 @@
                 false /* deferResume */);
     }
 
-    void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
+    private void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
             boolean preserveWindows, boolean deferResume) {
 
@@ -2677,7 +2595,7 @@
             return;
         }
 
-        final ActivityStack stack = getStack(DOCKED_STACK_ID);
+        final ActivityStack stack = getDefaultDisplay().getSplitScreenPrimaryStack();
         if (stack == null) {
             Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
             return;
@@ -2697,7 +2615,7 @@
                 // The dock stack either was dismissed or went fullscreen, which is kinda the same.
                 // In this case we make all other static stacks fullscreen and move all
                 // docked stack tasks to the fullscreen stack.
-                moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, ON_TOP);
+                moveTasksToFullscreenStackLocked(stack, ON_TOP);
 
                 // stack shouldn't contain anymore activities, so nothing to resume.
                 r = null;
@@ -2706,13 +2624,14 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
+                final ActivityDisplay display = getDefaultDisplay();
                 final Rect otherTaskRect = new Rect();
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (i == DOCKED_STACK_ID) {
+                for (int i = display.getChildCount() - 1; i >= 0; --i) {
+                    final ActivityStack current = display.getChildAt(i);
+                    if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                         continue;
                     }
-                    final ActivityStack current = getStack(i);
-                    if (current == null || !current.supportSplitScreenWindowingMode()) {
+                    if (!current.supportsSplitScreenWindowingMode()) {
                         continue;
                     }
                     // Need to set windowing mode here before we try to get the dock bounds.
@@ -2722,7 +2641,7 @@
                             tempRect /* outStackBounds */,
                             otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */);
 
-                    resizeStackLocked(i, !tempRect.isEmpty() ? tempRect : null,
+                    resizeStackLocked(current, !tempRect.isEmpty() ? tempRect : null,
                             !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
                             tempOtherTaskInsetBounds, preserveWindows,
                             true /* allowResizeInDockedMode */, deferResume);
@@ -2739,7 +2658,8 @@
     }
 
     void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
-        final PinnedActivityStack stack = getStack(PINNED_STACK_ID);
+        // TODO(multi-display): Pinned stack display should be passed in.
+        final PinnedActivityStack stack = getDefaultDisplay().getPinnedStack();
         if (stack == null) {
             Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
             return;
@@ -2777,32 +2697,9 @@
         }
     }
 
-    ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
-        if (activityDisplay == null) {
-            return null;
-        }
-        return createStack(stackId, activityDisplay, onTop);
-
-    }
-
-    ActivityStack createStack(int stackId, ActivityDisplay display, boolean onTop) {
-        switch (stackId) {
-            case PINNED_STACK_ID:
-                return new PinnedActivityStack(display, stackId, this, mRecentTasks, onTop);
-            default:
-                return new ActivityStack(display, stackId, this, mRecentTasks, onTop);
-        }
-    }
-
-    void removeStackInSurfaceTransaction(int stackId) {
-        final ActivityStack stack = getStack(stackId);
-        if (stack == null) {
-            return;
-        }
-
+    private void removeStackInSurfaceTransaction(ActivityStack stack) {
         final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-        if (stack.getStackId() == PINNED_STACK_ID) {
+        if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
             /**
              * Workaround: Force-stop all the activities in the pinned stack before we reparent them
              * to the fullscreen stack.  This is to guarantee that when we are removing a stack,
@@ -2820,7 +2717,7 @@
                     true /* processPausingActivites */, null /* configuration */);
 
             // Move all the tasks to the bottom of the fullscreen stack
-            moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+            moveTasksToFullscreenStackLocked(pinnedStack, !ON_TOP);
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
@@ -2830,13 +2727,28 @@
     }
 
     /**
-     * Removes the stack associated with the given {@param stackId}.  If the {@param stackId} is the
+     * Removes the stack associated with the given {@param stack}. If the {@param stack} is the
      * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
      * instead moved back onto the fullscreen stack.
      */
-    void removeStackLocked(int stackId) {
-        mWindowManager.inSurfaceTransaction(
-                () -> removeStackInSurfaceTransaction(stackId));
+    void removeStack(ActivityStack stack) {
+        mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stack));
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            mActivityDisplays.valueAt(i).removeStacksInWindowingModes(windowingModes);
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            mActivityDisplays.valueAt(i).removeStacksWithActivityTypes(activityTypes);
+        }
     }
 
     /**
@@ -2862,6 +2774,7 @@
         if (tr != null) {
             tr.removeTaskActivitiesLocked(pauseImmediately);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
+            mService.mLockTaskController.clearLockedTask(tr);
             if (tr.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
@@ -2874,7 +2787,6 @@
     void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
         if (removeFromRecents) {
             mRecentTasks.remove(tr);
-            tr.removedFromRecents();
         }
         ComponentName component = tr.getBaseIntent().getComponent();
         if (component == null) {
@@ -2945,8 +2857,7 @@
 
     int getNextStackId() {
         while (true) {
-            if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID
-                    && getStack(mNextFreeStackId) == null) {
+            if (getStack(mNextFreeStackId) == null) {
                 break;
             }
             mNextFreeStackId++;
@@ -2955,17 +2866,19 @@
     }
 
     /**
-     * Restores a recent task to a stack
+     * Called to restore the state of the task into the stack that it's supposed to go into.
+     *
      * @param task The recent task to be restored.
      * @param aOptions The activity options to use for restoration.
+     * @param onTop If the stack for the task should be the topmost on the display.
      * @return true if the task has been restored successfully.
      */
-    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
-        final int stackId = getLaunchStackId(null, aOptions, task);
+    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions, boolean onTop) {
+        final ActivityStack stack = getLaunchStack(null, aOptions, task, onTop);
         final ActivityStack currentStack = task.getStack();
         if (currentStack != null) {
             // Task has already been restored once. See if we need to do anything more
-            if (currentStack.mStackId == stackId) {
+            if (currentStack == stack) {
                 // Nothing else to do since it is already restored in the right stack.
                 return true;
             }
@@ -2974,11 +2887,9 @@
             currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
         }
 
-        final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
-
-        stack.addTask(task, false /* toTop */, "restoreRecentTask");
+        stack.addTask(task, onTop, "restoreRecentTask");
         // TODO: move call for creation here and other place into Stack.addTask()
-        task.createWindowContainer(false /* toTop */, true /* showForAllUsers */);
+        task.createWindowContainer(onTop, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
         final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -2988,6 +2899,17 @@
         return true;
     }
 
+    @Override
+    public void onRecentTaskAdded(TaskRecord task) {
+        task.touchActiveTime();
+    }
+
+    @Override
+    public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+        // TODO: Trim active task once b/68045330 is fixed
+        task.removedFromRecents();
+    }
+
     /**
      * Move stack with all its existing content to specified display.
      * @param stackId Id of stack to move.
@@ -3000,7 +2922,7 @@
             throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
                     + displayId);
         }
-        final ActivityStack stack = mStacks.get(stackId);
+        final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
                     + stackId);
@@ -3025,8 +2947,10 @@
      * Returns the reparent target stack, creating the stack if necessary.  This call also enforces
      * the various checks on tasks that are going to be reparented from one stack to another.
      */
-    ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
+    ActivityStack getReparentTargetStack(TaskRecord task, ActivityStack stack, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
+        final int stackId = stack.mStackId;
+        final boolean inMultiWindowMode = stack.inMultiWindowMode();
 
         // Check that we aren't reparenting to the same stack that the task is already in
         if (prevStack != null && prevStack.mStackId == stackId) {
@@ -3037,49 +2961,42 @@
 
         // Ensure that we aren't trying to move into a multi-window stack without multi-window
         // support
-        if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+        if (inMultiWindowMode && !mService.mSupportsMultiWindow) {
             throw new IllegalArgumentException("Device doesn't support multi-window, can not"
-                    + " reparent task=" + task + " to stackId=" + stackId);
+                    + " reparent task=" + task + " to stack=" + stack);
         }
 
         // Ensure that we're not moving a task to a dynamic stack if device doesn't support
         // multi-display.
-        // TODO(multi-display): Support non-dynamic stacks on secondary displays.
-        if (StackId.isDynamicStack(stackId) && !mService.mSupportsMultiDisplay) {
+        if (stack.mDisplayId != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
             throw new IllegalArgumentException("Device doesn't support multi-display, can not"
                     + " reparent task=" + task + " to stackId=" + stackId);
         }
 
         // Ensure that we aren't trying to move into a freeform stack without freeform
         // support
-        if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
+        if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                && !mService.mSupportsFreeformWindowManagement) {
             throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
                     + " task=" + task);
         }
 
-        // We don't allow moving a unresizeable task to the docked stack since the docked stack is
-        // used for split-screen mode and will cause things like the docked divider to show up. We
-        // instead leave the task in its current stack or move it to the fullscreen stack if it
-        // isn't currently in a stack.
-        if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
-            stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
-            Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
-                    + " Moving to stackId=" + stackId + " instead.");
+        // Leave the task in its current stack or a fullscreen stack if it isn't resizeable and the
+        // preferred stack is in multi-window mode.
+        if (inMultiWindowMode && !task.isResizeable()) {
+            Slog.w(TAG, "Can not move unresizeable task=" + task + " to multi-window stack=" + stack
+                    + " Moving to a fullscreen stack instead.");
+            if (prevStack != null) {
+                return prevStack;
+            }
+            stack = stack.getDisplay().createStack(
+                    WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
         }
-
-        // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
-        // example, a docked stack is created which will lead to the stack we are moving from being
-        // resized and and its resizeable tasks being resized.
-        try {
-            task.mTemporarilyUnresizable = true;
-            return getStack(stackId, CREATE_IF_NEEDED, toTop);
-        } finally {
-            task.mTemporarilyUnresizable = false;
-        }
+        return stack;
     }
 
     boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect destBounds) {
-        final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+        final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             throw new IllegalArgumentException(
                     "moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId);
@@ -3100,46 +3017,40 @@
         }
 
         moveActivityToPinnedStackLocked(r, null /* sourceBounds */, 0f /* aspectRatio */,
-                true /* moveHomeStackToFront */, "moveTopActivityToPinnedStack");
+                "moveTopActivityToPinnedStack");
         return true;
     }
 
     void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
-            boolean moveHomeStackToFront, String reason) {
+            String reason) {
 
         mWindowManager.deferSurfaceLayout();
 
+        final ActivityDisplay display = r.getStack().getDisplay();
+        PinnedActivityStack stack = display.getPinnedStack();
+
         // This will clear the pinned stack by moving an existing task to the full screen stack,
         // ensuring only one task is present.
-        moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+        if (stack != null) {
+            moveTasksToFullscreenStackLocked(stack, !ON_TOP);
+        }
 
         // Need to make sure the pinned stack exist so we can resize it below...
-        final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+        stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
         try {
             final TaskRecord task = r.getTask();
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
-            resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */,
+            resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */,
                     null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
                     true /* allowResizeInDockedMode */, !DEFER_RESUME);
 
             if (task.mActivities.size() == 1) {
-                // There is only one activity in the task. So, we can just move the task over to
-                // the stack without re-parenting the activity in a different task.  We don't
-                // move the home stack forward if we are currently entering picture-in-picture
-                // while pausing because that changes the focused stack and may prevent the new
-                // starting activity from resuming.
-                if (moveHomeStackToFront && task.returnsToHomeTask()
-                        && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) {
-                    // Move the home stack forward if the task we just moved to the pinned stack
-                    // was launched from home so home should be visible behind it.
-                    moveHomeStackToFront(reason);
-                }
                 // Defer resume until below, and do not schedule PiP changes until we animate below
-                task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                        DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
+                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
+                        false /* schedulePictureInPictureModeChange */, reason);
             } else {
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
@@ -3154,7 +3065,7 @@
                 r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
 
                 // Defer resume until below, and do not schedule PiP changes until we animate below
-                newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
                         DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
             }
 
@@ -3180,8 +3091,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName, r.userId,
-                r.getTask().taskId);
+        mService.mTaskChangeNotificationController.notifyActivityPinned(r);
     }
 
     /** Move activity with its stack to front and make the stack focused. */
@@ -3219,9 +3129,9 @@
         ActivityRecord affinityMatch = null;
         if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Looking for task of " + r);
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (!r.hasCompatibleActivityType(stack)) {
                     if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Skipping stack: (mismatch activity/stack) "
                             + stack);
@@ -3254,12 +3164,13 @@
     }
 
     ActivityRecord findActivityLocked(Intent intent, ActivityInfo info,
-                                      boolean compareIntentFilters) {
+            boolean compareIntentFilters) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityRecord ar = stacks.get(stackNdx)
-                        .findActivityLocked(intent, info, compareIntentFilters);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                final ActivityRecord ar = stack.findActivityLocked(
+                        intent, info, compareIntentFilters);
                 if (ar != null) {
                     return ar;
                 }
@@ -3353,9 +3264,8 @@
             }
 
             // Set the sleeping state of the stacks on the display.
-            final ArrayList<ActivityStack> stacks = display.mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (displayShouldSleep) {
                     stack.goToSleepIfPossible(false /* shuttingDown */);
                 } else {
@@ -3417,12 +3327,13 @@
     private boolean putStacksToSleepLocked(boolean allowDelay, boolean shuttingDown) {
         boolean allSleep = true;
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 if (allowDelay) {
-                    allSleep &= stacks.get(stackNdx).goToSleepIfPossible(shuttingDown);
+                    allSleep &= stack.goToSleepIfPossible(shuttingDown);
                 } else {
-                    stacks.get(stackNdx).goToSleep();
+                    stack.goToSleep();
                 }
             }
         }
@@ -3447,11 +3358,10 @@
 
     void handleAppCrashLocked(ProcessRecord app) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            int stackNdx = stacks.size() - 1;
-            while (stackNdx >= 0) {
-                stacks.get(stackNdx).handleAppCrashLocked(app);
-                stackNdx--;
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.handleAppCrashLocked(app);
             }
         }
     }
@@ -3462,7 +3372,7 @@
         final ActivityStack stack = task.getStack();
 
         r.mLaunchTaskBehind = false;
-        mRecentTasks.addLocked(task);
+        mRecentTasks.add(task);
         mService.mTaskChangeNotificationController.notifyTaskStackChanged();
         r.setVisibility(false);
 
@@ -3484,10 +3394,9 @@
         try {
             // First the front stacks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-                final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-                final int topStackNdx = stacks.size() - 1;
-                for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
-                    final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                    final ActivityStack stack = display.getChildAt(stackNdx);
                     stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows);
                 }
             }
@@ -3498,10 +3407,9 @@
 
     void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int topStackNdx = stacks.size() - 1;
-            for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.addStartingWindowsForVisibleActivities(taskSwitch);
             }
         }
@@ -3517,20 +3425,20 @@
         }
         mTaskLayersChanged = false;
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); displayNdx++) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
             int baseLayer = 0;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                baseLayer += stacks.get(stackNdx).rankTaskLayers(baseLayer);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                baseLayer += stack.rankTaskLayers(baseLayer);
             }
         }
     }
 
     void clearOtherAppTimeTrackers(AppTimeTracker except) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int topStackNdx = stacks.size() - 1;
-            for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.clearOtherAppTimeTrackers(except);
             }
         }
@@ -3538,10 +3446,9 @@
 
     void scheduleDestroyAllActivities(ProcessRecord app, String reason) {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            final int numStacks = stacks.size();
-            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.scheduleDestroyActivities(app, reason);
             }
         }
@@ -3593,10 +3500,11 @@
         // let's iterate through the tasks and release the oldest one.
         final int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            final int stackCount = display.getChildCount();
             // Step through all stacks starting from behind, to hit the oldest things first.
-            for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            for (int stackNdx = 0; stackNdx < stackCount; stackNdx++) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 // Try to release activities in this stack; if we manage to, we are done.
                 if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
                     return;
@@ -3608,21 +3516,24 @@
     boolean switchUserLocked(int userId, UserState uss) {
         final int focusStackId = mFocusedStack.getStackId();
         // We dismiss the docked stack whenever we switch users.
-        moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, focusStackId == DOCKED_STACK_ID);
+        final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenPrimaryStack();
+        if (dockedStack != null) {
+            moveTasksToFullscreenStackLocked(dockedStack, mFocusedStack == dockedStack);
+        }
         // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
         // also cause all tasks to be moved to the fullscreen stack at a position that is
         // appropriate.
-        removeStackLocked(PINNED_STACK_ID);
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
         mUserStackInFront.put(mCurrentUser, focusStackId);
-        final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
+        final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId);
         mCurrentUser = userId;
 
         mStartingUsers.add(uss);
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 stack.switchUserLocked(userId);
                 TaskRecord task = stack.topTask();
                 if (task != null) {
@@ -3648,7 +3559,7 @@
     /** Checks whether the userid is a profile of the current user. */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUser) return true;
-        return mService.mUserController.isCurrentProfileLocked(userId);
+        return mService.mUserController.isCurrentProfile(userId);
     }
 
     /**
@@ -3720,9 +3631,9 @@
 
     void validateTopActivitiesLocked() {
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
-            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 final ActivityRecord r = stack.topRunningActivityLocked();
                 final ActivityState state = r == null ? DESTROYED : r.state;
                 if (isFocusedStack(stack)) {
@@ -3755,7 +3666,10 @@
         pw.print(prefix);
         pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
         pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
-        pw.print(prefix); pw.println("mStacks=" + mStacks);
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final ActivityDisplay display = mActivityDisplays.valueAt(i);
+            display.dump(pw, prefix);
+        }
         if (!mWaitingForActivityVisible.isEmpty()) {
             pw.print(prefix); pw.println("mWaitingForActivityVisible=");
             for (int i = 0; i < mWaitingForActivityVisible.size(); ++i) {
@@ -3767,9 +3681,8 @@
         mService.mLockTaskController.dump(pw, prefix);
     }
 
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
-        final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+    public void writeToProto(ProtoOutputStream proto) {
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
             ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
             activityDisplay.writeToProto(proto, DISPLAYS);
@@ -3784,7 +3697,6 @@
         } else {
             proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
         }
-        proto.end(token);
     }
 
     /**
@@ -3813,11 +3725,10 @@
             ArrayList<ActivityRecord> activities = new ArrayList<>();
             int numDisplays = mActivityDisplays.size();
             for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
-                ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-                for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                    ActivityStack stack = stacks.get(stackNdx);
-                    if (!dumpVisibleStacksOnly ||
-                            stack.shouldBeVisible(null) == STACK_VISIBLE) {
+                final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                    final ActivityStack stack = display.getChildAt(stackNdx);
+                    if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
                         activities.addAll(stack.getDumpActivitiesLocked(name));
                     }
                 }
@@ -3849,11 +3760,13 @@
             ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
             pw.print("Display #"); pw.print(activityDisplay.mDisplayId);
                     pw.println(" (activities from top to bottom):");
-            ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
                 pw.println();
-                pw.println("  Stack #" + stack.mStackId + ":");
+                pw.println("  Stack #" + stack.mStackId
+                        + ": type=" + activityTypeToString(stack.getActivityType())
+                        + " mode=" + windowingModeToString(stack.getWindowingMode()));
                 pw.println("  mFullscreen=" + stack.mFullscreen);
                 pw.println("  isSleeping=" + stack.shouldSleepActivities());
                 pw.println("  mBounds=" + stack.mBounds);
@@ -4043,15 +3956,22 @@
         return getActivityDisplayOrCreateLocked(displayId) != null;
     }
 
+    // TODO: Look into consolidating with getActivityDisplayOrCreateLocked()
     ActivityDisplay getActivityDisplay(int displayId) {
         return mActivityDisplays.get(displayId);
     }
 
+    // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
+    ActivityDisplay getDefaultDisplay() {
+        return mActivityDisplays.get(DEFAULT_DISPLAY);
+    }
+
     /**
      * Get an existing instance of {@link ActivityDisplay} or create new if there is a
      * corresponding record in display manager.
      */
-    private ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
+    // TODO: Look into consolidating with getActivityDisplay()
+    ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
         ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay != null) {
             return activityDisplay;
@@ -4066,17 +3986,18 @@
             return null;
         }
         // The display hasn't been added to ActivityManager yet, create a new record now.
-        activityDisplay = new ActivityDisplay(displayId);
-        if (activityDisplay.mDisplay == null) {
-            Slog.w(TAG, "Display " + displayId + " gone before initialization complete");
-            return null;
-        }
-        mActivityDisplays.put(displayId, activityDisplay);
+        activityDisplay = new ActivityDisplay(this, displayId);
+        attachDisplay(activityDisplay);
         calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
         mWindowManager.onDisplayAdded(displayId);
         return activityDisplay;
     }
 
+    @VisibleForTesting
+    void attachDisplay(ActivityDisplay display) {
+        mActivityDisplays.put(display.mDisplayId, display);
+    }
+
     private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) {
         mDefaultMinSizeOfResizeableTask =
                 mService.mContext.getResources().getDimensionPixelSize(
@@ -4089,30 +4010,29 @@
         }
 
         synchronized (mService) {
-            ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
-            if (activityDisplay != null) {
-                final boolean destroyContentOnRemoval
-                        = activityDisplay.shouldDestroyContentOnRemove();
-                final ArrayList<ActivityStack> stacks = activityDisplay.mStacks;
-                while (!stacks.isEmpty()) {
-                    final ActivityStack stack = stacks.get(0);
-                    if (destroyContentOnRemoval) {
-                        moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY,
-                                false /* onTop */);
-                        stack.finishAllActivitiesLocked(true /* immediately */);
-                    } else {
-                        // Moving all tasks to fullscreen stack, because it's guaranteed to be
-                        // a valid launch stack for all activities. This way the task history from
-                        // external display will be preserved on primary after move.
-                        moveTasksToFullscreenStackLocked(stack.getStackId(), true /* onTop */);
-                    }
-                }
-
-                releaseSleepTokens(activityDisplay);
-
-                mActivityDisplays.remove(displayId);
-                mWindowManager.onDisplayRemoved(displayId);
+            final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+            if (activityDisplay == null) {
+                return;
             }
+            final boolean destroyContentOnRemoval
+                    = activityDisplay.shouldDestroyContentOnRemove();
+            while (activityDisplay.getChildCount() > 0) {
+                final ActivityStack stack = activityDisplay.getChildAt(0);
+                if (destroyContentOnRemoval) {
+                    moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, false /* onTop */);
+                    stack.finishAllActivitiesLocked(true /* immediately */);
+                } else {
+                    // Moving all tasks to fullscreen stack, because it's guaranteed to be
+                    // a valid launch stack for all activities. This way the task history from
+                    // external display will be preserved on primary after move.
+                    moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+                }
+            }
+
+            releaseSleepTokens(activityDisplay);
+
+            mActivityDisplays.remove(displayId);
+            mWindowManager.onDisplayRemoved(displayId);
         }
     }
 
@@ -4166,7 +4086,7 @@
         if (display.mAllSleepTokens.isEmpty()) {
             return;
         }
-        for (SleepTokenImpl token : display.mAllSleepTokens) {
+        for (SleepToken token : display.mAllSleepTokens) {
             mSleepTokens.remove(token);
         }
         display.mAllSleepTokens.clear();
@@ -4174,7 +4094,7 @@
         mService.updateSleepIfNeededLocked();
     }
 
-    private StackInfo getStackInfoLocked(ActivityStack stack) {
+    private StackInfo getStackInfo(ActivityStack stack) {
         final int displayId = stack.mDisplayId;
         final ActivityDisplay display = mActivityDisplays.get(displayId);
         StackInfo info = new StackInfo();
@@ -4182,11 +4102,10 @@
         info.displayId = displayId;
         info.stackId = stack.mStackId;
         info.userId = stack.mCurrentUser;
-        info.visible = stack.shouldBeVisible(null) == STACK_VISIBLE;
+        info.visible = stack.shouldBeVisible(null);
         // A stack might be not attached to a display.
-        info.position = display != null
-                ? display.mStacks.indexOf(stack)
-                : 0;
+        info.position = display != null ? display.getIndexOf(stack) : 0;
+        info.configuration.setTo(stack.getConfiguration());
 
         ArrayList<TaskRecord> tasks = stack.getAllTasks();
         final int numTasks = tasks.size();
@@ -4215,40 +4134,45 @@
         return info;
     }
 
-    StackInfo getStackInfoLocked(int stackId) {
+    StackInfo getStackInfo(int stackId) {
         ActivityStack stack = getStack(stackId);
         if (stack != null) {
-            return getStackInfoLocked(stack);
+            return getStackInfo(stack);
         }
         return null;
     }
 
+    StackInfo getStackInfo(int windowingMode, int activityType) {
+        final ActivityStack stack = getStack(windowingMode, activityType);
+        return (stack != null) ? getStackInfo(stack) : null;
+    }
+
     ArrayList<StackInfo> getAllStackInfosLocked() {
         ArrayList<StackInfo> list = new ArrayList<>();
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
-            ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
-            for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) {
-                list.add(getStackInfoLocked(stacks.get(ndx)));
+            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                list.add(getStackInfo(stack));
             }
         }
         return list;
     }
 
     void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
-            int preferredDisplayId, int actualStackId) {
+            int preferredDisplayId, ActivityStack actualStack) {
         handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
-                actualStackId, false /* forceNonResizable */);
+                actualStack, false /* forceNonResizable */);
     }
 
     void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
-            int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
+            int preferredDisplayId, ActivityStack actualStack, boolean forceNonResizable) {
         final boolean isSecondaryDisplayPreferred =
                 (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
-        final ActivityStack actualStack = getStack(actualStackId);
         final boolean inSplitScreenMode = actualStack != null
-                && actualStack.inSplitScreenWindowingMode();
+                && actualStack.getDisplay().hasSplitScreenPrimaryStack();
         if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
-                && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
+                && !isSecondaryDisplayPreferred) || !task.isActivityTypeStandardOrUndefined()) {
             return;
         }
 
@@ -4260,8 +4184,8 @@
                 // The task landed on an inappropriate display somehow, move it to the default
                 // display.
                 // TODO(multi-display): Find proper stack for the task on the default display.
-                mService.moveTaskToStack(task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
-                        true /* toTop */);
+                mService.setTaskWindowingMode(task.taskId,
+                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
                 launchOnSecondaryDisplayFailed = true;
             } else {
                 // The task might have landed on a display different from requested.
@@ -4275,7 +4199,8 @@
         }
 
         final ActivityRecord topActivity = task.getTopActivity();
-        if (launchOnSecondaryDisplayFailed || !task.supportsSplitScreen() || forceNonResizable) {
+        if (launchOnSecondaryDisplayFailed
+                || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             if (launchOnSecondaryDisplayFailed) {
                 // Display a warning toast that we tried to put a non-resizeable task on a secondary
                 // display with config different from global config.
@@ -4289,7 +4214,11 @@
 
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
-            moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
+
+            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenPrimaryStack();
+            if (dockedStack != null) {
+                moveTasksToFullscreenStackLocked(dockedStack, actualStack == dockedStack);
+            }
         } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
                 && !topActivity.noDisplay) {
             final String packageName = topActivity.appInfo.packageName;
@@ -4341,7 +4270,7 @@
     void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, ActivityStack prevStack) {
         final ActivityStack stack = task.getStack();
         if (prevStack == null || prevStack == stack
-                || (prevStack.mStackId != PINNED_STACK_ID && stack.mStackId != PINNED_STACK_ID)) {
+                || (!prevStack.inPinnedWindowingMode() && !stack.inPinnedWindowingMode())) {
             return;
         }
 
@@ -4494,147 +4423,19 @@
         }
     }
 
-    // TODO: Move to its own file.
-    /** Exactly one of these classes per Display in the system. Capable of holding zero or more
-     * attached {@link ActivityStack}s */
-    class ActivityDisplay extends ConfigurationContainer {
-        /** Actual Display this object tracks. */
-        int mDisplayId;
-        Display mDisplay;
-
-        /** All of the stacks on this display. Order matters, topmost stack is in front of all other
-         * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
-        final ArrayList<ActivityStack> mStacks = new ArrayList<>();
-
-        /** Array of all UIDs that are present on the display. */
-        private IntArray mDisplayAccessUIDs = new IntArray();
-
-        /** All tokens used to put activities on this stack to sleep (including mOffToken) */
-        final ArrayList<SleepTokenImpl> mAllSleepTokens = new ArrayList<>();
-        /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
-        SleepToken mOffToken;
-
-        private boolean mSleeping;
-
-        @VisibleForTesting
-        ActivityDisplay() {
-            mActivityDisplays.put(mDisplayId, this);
-        }
-
-        // After instantiation, check that mDisplay is not null before using this. The alternative
-        // is for this to throw an exception if mDisplayManager.getDisplay() returns null.
-        ActivityDisplay(int displayId) {
-            final Display display = mDisplayManager.getDisplay(displayId);
-            if (display == null) {
-                return;
-            }
-            init(display);
-        }
-
-        void init(Display display) {
-            mDisplay = display;
-            mDisplayId = display.getDisplayId();
-        }
-
-        void attachStack(ActivityStack stack, int position) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
-                    + " to displayId=" + mDisplayId + " position=" + position);
-            mStacks.add(position, stack);
-            mService.updateSleepIfNeededLocked();
-        }
-
-        void detachStack(ActivityStack stack) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
-                    + " from displayId=" + mDisplayId);
-            mStacks.remove(stack);
-            mService.updateSleepIfNeededLocked();
-        }
-
-        @Override
-        public String toString() {
-            return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
-        }
-
-        @Override
-        protected int getChildCount() {
-            return mStacks.size();
-        }
-
-        @Override
-        protected ConfigurationContainer getChildAt(int index) {
-            return mStacks.get(index);
-        }
-
-        @Override
-        protected ConfigurationContainer getParent() {
-            return ActivityStackSupervisor.this;
-        }
-
-        boolean isPrivate() {
-            return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
-        }
-
-        boolean isUidPresent(int uid) {
-            for (ActivityStack stack : mStacks) {
-                if (stack.isUidPresent(uid)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        /** Update and get all UIDs that are present on the display and have access to it. */
-        private IntArray getPresentUIDs() {
-            mDisplayAccessUIDs.clear();
-            for (ActivityStack stack : mStacks) {
-                stack.getPresentUIDs(mDisplayAccessUIDs);
-            }
-            return mDisplayAccessUIDs;
-        }
-
-        boolean shouldDestroyContentOnRemove() {
-            return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
-        }
-
-        boolean shouldSleep() {
-            return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
-                    && (mService.mRunningVoice == null);
-        }
-
-        boolean isSleeping() {
-            return mSleeping;
-        }
-
-        void setIsSleeping(boolean asleep) {
-            mSleeping = asleep;
-        }
-
-        public void writeToProto(ProtoOutputStream proto, long fieldId) {
-            final long token = proto.start(fieldId);
-            super.writeToProto(proto, ActivityDisplayProto.CONFIGURATION_CONTAINER);
-            proto.write(ID, mDisplayId);
-            for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = mStacks.get(stackNdx);
-                stack.writeToProto(proto, STACKS);
-            }
-            proto.end(token);
-        }
-    }
-
     ActivityStack findStackBehind(ActivityStack stack) {
         // TODO(multi-display): We are only looking for stacks on the default display.
         final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
         if (display == null) {
             return null;
         }
-        final ArrayList<ActivityStack> stacks = display.mStacks;
-        for (int i = stacks.size() - 1; i >= 0; i--) {
-            if (stacks.get(i) == stack && i > 0) {
-                return stacks.get(i - 1);
+        for (int i = display.getChildCount() - 1; i >= 0; i--) {
+            if (display.getChildAt(i) == stack && i > 0) {
+                return display.getChildAt(i - 1);
             }
         }
         throw new IllegalStateException("Failed to find a stack behind stack=" + stack
-                + " in=" + stacks);
+                + " in=" + display);
     }
 
     /**
@@ -4647,7 +4448,7 @@
         task.setTaskDockedResizing(true);
     }
 
-    final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {
+    int startActivityFromRecents(int taskId, Bundle bOptions) {
         final TaskRecord task;
         final int callingUid;
         final String callingPackage;
@@ -4662,7 +4463,7 @@
             windowingMode = activityOptions.getLaunchWindowingMode();
         }
         if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
-            throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
+            throw new IllegalArgumentException("startActivityFromRecents: Task "
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
@@ -4670,35 +4471,27 @@
         try {
             if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateState(
-                        activityOptions.getDockCreateMode(), null /* initialBounds */);
+                        activityOptions.getSplitScreenCreateMode(), null /* initialBounds */);
 
                 // Defer updating the stack in which recents is until the app transition is done, to
                 // not run into issues where we still need to draw the task in recents but the
                 // docked stack is already created.
-                deferUpdateBounds(RECENTS_STACK_ID);
+                deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
             }
 
             task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                    activityOptions);
+                    activityOptions, ON_TOP);
             if (task == null) {
-                continueUpdateBounds(RECENTS_STACK_ID);
+                continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.executeAppTransition();
                 throw new IllegalArgumentException(
-                        "startActivityFromRecentsInner: Task " + taskId + " not found.");
+                        "startActivityFromRecents: Task " + taskId + " not found.");
             }
 
-            // Since we don't have an actual source record here, we assume that the currently
-            // focused activity was the source.
-            final ActivityStack focusedStack = getFocusedStack();
-            final ActivityRecord sourceRecord = focusedStack != null
-                    ? focusedStack.topActivity() : null;
-            final int stackId = getLaunchStackId(null, activityOptions, task);
-
-            if (stackId != INVALID_STACK_ID && task.getStackId() != stackId) {
-                task.reparent(stackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
-                        "startActivityFromRecents");
-            }
+            // We always want to return to the home activity instead of the recents activity from
+            // whatever is started from the recents activity, so move the home stack forward.
+            moveHomeStackToFront("startActivityFromRecents");
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
             // Work Challenge is present) let startActivityInPackage handle the intercepting.
@@ -4721,10 +4514,7 @@
                 }
 
                 mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
-                        ActivityManager.START_TASK_TO_FRONT,
-                        sourceRecord != null
-                                ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,
-                        sourceRecord, task.getStack());
+                        ActivityManager.START_TASK_TO_FRONT, task.getStack());
                 return ActivityManager.START_TASK_TO_FRONT;
             }
             callingUid = task.mCallingUid;
@@ -4753,11 +4543,10 @@
         for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
             final ActivityDisplay display = mActivityDisplays.valueAt(i);
             // Traverse all stacks on a display.
-            for (int j = display.mStacks.size() - 1; j >= 0; j--) {
-                final ActivityStack stack = display.mStacks.get(j);
+            for (int j = display.getChildCount() - 1; j >= 0; --j) {
+                final ActivityStack stack = display.getChildAt(j);
                 // Get top activity from a visible stack and add it to the list.
-                if (stack.shouldBeVisible(null /* starting */)
-                        == ActivityStack.STACK_VISIBLE) {
+                if (stack.shouldBeVisible(null /* starting */)) {
                     final ActivityRecord top = stack.topActivity();
                     if (top != null) {
                         if (stack == mFocusedStack) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index fab4d0d..1c80282 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -26,20 +26,10 @@
 import static android.app.ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
-import static android.app.ActivityManager.StackId.isDynamicStack;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
@@ -78,8 +68,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.ANIMATE;
 import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -124,7 +112,6 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch;
 import com.android.server.pm.InstantAppResolver;
-import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
 import java.text.DateFormat;
@@ -143,11 +130,11 @@
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
     private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
     private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
+    private static final int INVALID_LAUNCH_MODE = -1;
 
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
-    private WindowManagerService mWindowManager;
 
     final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<>();
 
@@ -157,18 +144,17 @@
     private int mCallingUid;
     private ActivityOptions mOptions;
 
-    private boolean mLaunchSingleTop;
-    private boolean mLaunchSingleInstance;
-    private boolean mLaunchSingleTask;
+    private int mLaunchMode;
     private boolean mLaunchTaskBehind;
     private int mLaunchFlags;
 
-    private Rect mLaunchBounds;
+    private Rect mLaunchBounds = new Rect();
 
     private ActivityRecord mNotTop;
     private boolean mDoResume;
     private int mStartFlags;
     private ActivityRecord mSourceRecord;
+
     // The display to launch the activity onto, barring any strong reason to do otherwise.
     private int mPreferredDisplayId;
 
@@ -180,20 +166,21 @@
     private Intent mNewTaskIntent;
     private ActivityStack mSourceStack;
     private ActivityStack mTargetStack;
-    // Indicates that we moved other task and are going to put something on top soon, so
-    // we don't want to show it redundantly or accidentally change what's shown below.
-    private boolean mMovedOtherTask;
     private boolean mMovedToFront;
     private boolean mNoAnimation;
     private boolean mKeepCurTransition;
     private boolean mAvoidMoveToFront;
     private boolean mPowerHintSent;
 
+    // We must track when we deliver the new intent since multiple code paths invoke
+    // {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used
+    // inside {@link #deliverNewIntent} to suppress duplicate requests and ensure the intent is
+    // delivered at most once.
+    private boolean mIntentDelivered;
+
     private IVoiceInteractionSession mVoiceSession;
     private IVoiceInteractor mVoiceInteractor;
 
-    private boolean mUsingVr2dDisplay;
-
     // Last home activity record we attempted to start
     private final ActivityRecord[] mLastHomeActivityStartRecord = new ActivityRecord[1];
     // The result of the last home activity we attempted to start.
@@ -213,13 +200,11 @@
         mCallingUid = -1;
         mOptions = null;
 
-        mLaunchSingleTop = false;
-        mLaunchSingleInstance = false;
-        mLaunchSingleTask = false;
         mLaunchTaskBehind = false;
         mLaunchFlags = 0;
+        mLaunchMode = INVALID_LAUNCH_MODE;
 
-        mLaunchBounds = null;
+        mLaunchBounds.setEmpty();
 
         mNotTop = null;
         mDoResume = false;
@@ -236,7 +221,6 @@
         mSourceStack = null;
 
         mTargetStack = null;
-        mMovedOtherTask = false;
         mMovedToFront = false;
         mNoAnimation = false;
         mKeepCurTransition = false;
@@ -245,14 +229,13 @@
         mVoiceSession = null;
         mVoiceInteractor = null;
 
-        mUsingVr2dDisplay = false;
+        mIntentDelivered = false;
     }
 
-    ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
+    ActivityStarter(ActivityManagerService service) {
         mService = service;
-        mSupervisor = supervisor;
+        mSupervisor = mService.mStackSupervisor;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
-        mUsingVr2dDisplay = false;
     }
 
     int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
@@ -592,9 +575,7 @@
                 auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
     }
 
-    void postStartActivityProcessing(
-            ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord,
-            ActivityStack targetStack) {
+    void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
 
         if (ActivityManager.isStartResultFatalError(result)) {
             return;
@@ -607,37 +588,41 @@
             mSupervisor.reportTaskToFrontNoLaunch(mStartActivity);
         }
 
-        int startedActivityStackId = INVALID_STACK_ID;
+        ActivityStack startedActivityStack = null;
         final ActivityStack currentStack = r.getStack();
         if (currentStack != null) {
-            startedActivityStackId = currentStack.mStackId;
+            startedActivityStack = currentStack;
         } else if (mTargetStack != null) {
-            startedActivityStackId = targetStack.mStackId;
+            startedActivityStack = targetStack;
         }
 
-        if (startedActivityStackId == DOCKED_STACK_ID) {
-            final ActivityStack homeStack = mSupervisor.getStack(HOME_STACK_ID);
+        if (startedActivityStack == null) {
+            return;
+        }
+
+        if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) {
+            final ActivityStack homeStack = mSupervisor.mHomeStack;
             final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
             if (homeStackVisible) {
                 // We launch an activity while being in home stack, which means either launcher or
                 // recents into docked stack. We don't want the launched activity to be alone in a
                 // docked stack, so we want to immediately launch recents too.
                 if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
-                mWindowManager.showRecentApps(true /* fromHome */);
+                mService.mWindowManager.showRecentApps(true /* fromHome */);
             }
             return;
         }
 
         boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                 == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null);
-        if (startedActivityStackId == PINNED_STACK_ID && (result == START_TASK_TO_FRONT
-                || result == START_DELIVERED_TO_TOP || clearedTask)) {
+        if (startedActivityStack.inPinnedWindowingMode()
+                && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
+                || clearedTask)) {
             // The activity was already running in the pinned stack so it wasn't started, but either
             // brought to the front or the new intent was delivered to it since it was already in
             // front. Notify anyone interested in this piece of information.
             mService.mTaskChangeNotificationController.notifyPinnedActivityRestartAttempt(
                     clearedTask);
-            return;
         }
     }
 
@@ -1004,8 +989,7 @@
             mService.mWindowManager.continueSurfaceLayout();
         }
 
-        postStartActivityProcessing(r, result, mSupervisor.getLastStack().mStackId,  mSourceRecord,
-                mTargetStack);
+        postStartActivityProcessing(r, result, mTargetStack);
 
         return result;
     }
@@ -1059,7 +1043,7 @@
             // operations.
             if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
                     || isDocumentLaunchesIntoExisting(mLaunchFlags)
-                    || mLaunchSingleInstance || mLaunchSingleTask) {
+                    || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                 final TaskRecord task = reusedActivity.getTask();
 
                 // In this situation we want to remove all activities from the task up to the one
@@ -1082,9 +1066,7 @@
                         // so make sure the task now has the identity of the new intent.
                         top.getTask().setIntent(mStartActivity);
                     }
-                    ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
-                    top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                            mStartActivity.launchedFromPackage);
+                    deliverNewIntent(top);
                 }
             }
 
@@ -1144,9 +1126,8 @@
                 && top.userId == mStartActivity.userId
                 && top.app != null && top.app.thread != null
                 && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                || mLaunchSingleTop || mLaunchSingleTask);
+                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));
         if (dontStart) {
-            ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
             // For paranoia, make sure we have correctly resumed the top activity.
             topStack.mLastPausedActivity = null;
             if (mDoResume) {
@@ -1158,13 +1139,13 @@
                 // anything if that is the case, so this is it!
                 return START_RETURN_INTENT_TO_CALLER;
             }
-            top.deliverNewIntentLocked(
-                    mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+
+            deliverNewIntent(top);
 
             // Don't use mStartActivity.task to show the toast. We're not starting a new activity
             // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
             mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
-                    preferredLaunchDisplayId, topStack.mStackId);
+                    preferredLaunchDisplayId, topStack);
 
             return START_DELIVERED_TO_TOP;
         }
@@ -1196,12 +1177,8 @@
                 mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
         mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
                 mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
-        if (mSourceRecord != null) {
-            mStartActivity.getTask().setTaskToReturnTo(mSourceRecord);
-        }
         if (newTask) {
-            EventLog.writeEvent(
-                    EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
+            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.userId,
                     mStartActivity.getTask().taskId);
         }
         ActivityStack.logStartActivity(
@@ -1227,7 +1204,7 @@
                 mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
-                mWindowManager.executeAppTransition();
+                mService.mWindowManager.executeAppTransition();
             } else {
                 // If the target stack was not previously focusable (previous top running activity
                 // on that stack was not visible) then any prior calls to move the stack to the
@@ -1240,13 +1217,13 @@
                 mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                         mOptions);
             }
-        } else {
-            mTargetStack.addRecentActivityLocked(mStartActivity);
+        } else if (mStartActivity != null) {
+            mSupervisor.mRecentTasks.add(mStartActivity.getTask());
         }
         mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
 
         mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
-                preferredLaunchDisplayId, mTargetStack.mStackId);
+                preferredLaunchDisplayId, mTargetStack);
 
         return START_SUCCESS;
     }
@@ -1266,15 +1243,18 @@
 
         mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
 
-        mLaunchBounds = getOverrideBounds(r, options, inTask);
+        mLaunchBounds.setEmpty();
 
-        mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
-        mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
-        mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
+        mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r,
+                sourceRecord, options, mLaunchBounds);
+
+        mLaunchMode = r.launchMode;
+
         mLaunchFlags = adjustLaunchFlagsToDocumentMode(
-                r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
+                r, LAUNCH_SINGLE_INSTANCE == mLaunchMode,
+                LAUNCH_SINGLE_TASK == mLaunchMode, mIntent.getFlags());
         mLaunchTaskBehind = r.mLaunchTaskBehind
-                && !mLaunchSingleTask && !mLaunchSingleInstance
+                && !isLaunchModeOneOf(LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE)
                 && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
 
         sendNewTaskResultRequestIfNeeded();
@@ -1384,7 +1364,7 @@
 
             // If this task is empty, then we are adding the first activity -- it
             // determines the root, and must be launching as a NEW_TASK.
-            if (mLaunchSingleInstance || mLaunchSingleTask) {
+            if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                 if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
                     ActivityOptions.abort(mOptions);
                     throw new IllegalArgumentException("Trying to launch singleInstance/Task "
@@ -1427,7 +1407,7 @@
             // in any task/stack, however it could launch other activities like ResolverActivity,
             // and we want those to stay in the original task.
             if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
-                    && mSourceRecord.isFreeform())  {
+                    && mSourceRecord.inFreeformWindowingMode())  {
                 mAddingToTask = true;
             }
         }
@@ -1446,7 +1426,7 @@
                 // instance...  this new activity it is starting must go on its
                 // own task.
                 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
-            } else if (mLaunchSingleInstance || mLaunchSingleTask) {
+            } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
                 // The activity being started is a single instance...  it always
                 // gets launched into its own task.
                 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
@@ -1497,7 +1477,7 @@
         // launch this as a new task behind the current one.
         boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
                 (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
-                || mLaunchSingleInstance || mLaunchSingleTask;
+                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
         // If bring to front is requested, and no result is requested and we have not been given
         // an explicit task to launch in to, and we can find a task that was started with this
         // same component, then instead of launching bring that one to the front.
@@ -1507,7 +1487,7 @@
             final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
             intentActivity = task != null ? task.getTopActivity() : null;
         } else if (putIntoExistingTask) {
-            if (mLaunchSingleInstance) {
+            if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
                 // There can be one and only one instance of single instance activity in the
                 // history, and it is always in its own unique task, so we do a special search.
                intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
@@ -1516,7 +1496,7 @@
                 // For the launch adjacent case we only want to put the activity in an existing
                 // task if the activity already exists in the history.
                 intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
-                        !mLaunchSingleTask);
+                        !(LAUNCH_SINGLE_TASK == mLaunchMode));
             } else {
                 // Otherwise find the best task to put the activity in.
                 intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
@@ -1545,7 +1525,6 @@
             if (DEBUG_STACK) {
                 Slog.d(TAG, "getSourceDisplayId :" + displayId);
             }
-            mUsingVr2dDisplay = true;
             return displayId;
         }
 
@@ -1592,7 +1571,6 @@
                 if (mLaunchTaskBehind && mSourceRecord != null) {
                     intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask());
                 }
-                mMovedOtherTask = true;
 
                 // If the launch flags carry both NEW_TASK and CLEAR_TASK, the task's activities
                 // will be cleared soon by ActivityStarter in setTaskFromIntentActivity().
@@ -1614,12 +1592,11 @@
                         mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
                                 mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
                         mMovedToFront = true;
-                    } else if (launchStack.mStackId == DOCKED_STACK_ID
-                            || launchStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                    } else if (launchStack.inSplitScreenWindowingMode()) {
                         if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                             // If we want to launch adjacent and mTargetStack is not the computed
                             // launch stack - move task to top of computed stack.
-                            intentTask.reparent(launchStack.mStackId, ON_TOP,
+                            intentTask.reparent(launchStack, ON_TOP,
                                     REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                     "launchToSide");
                         } else {
@@ -1636,17 +1613,17 @@
                         // Target and computed stacks are on different displays and we've
                         // found a matching task - move the existing instance to that display and
                         // move it to front.
-                        intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+                        intentActivity.getTask().reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                 "reparentToDisplay");
                         mMovedToFront = true;
-                    } else if (launchStack.getStackId() == StackId.HOME_STACK_ID
-                        && mTargetStack.getStackId() != StackId.HOME_STACK_ID) {
+                    } else if (launchStack.isActivityTypeHome()
+                            && !mTargetStack.isActivityTypeHome()) {
                         // It is possible for the home activity to be in another stack initially.
                         // For example, the activity may have been initially started with an intent
                         // which placed it in the fullscreen stack. To ensure the proper handling of
                         // the activity based on home stack assumptions, we must move it over.
-                        intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+                        intentActivity.getTask().reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                 "reparentingHome");
                         mMovedToFront = true;
@@ -1658,7 +1635,6 @@
                     intentActivity.showStartingWindow(null /* prev */, false /* newTask */,
                             true /* taskSwitch */);
                 }
-                updateTaskReturnToType(intentActivity.getTask(), mLaunchFlags, focusStack);
             }
         }
         if (!mMovedToFront && mDoResume) {
@@ -1668,7 +1644,7 @@
         }
 
         mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
-                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId);
+                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack);
 
         // If the caller has requested that the target task be reset, then do so.
         if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -1677,27 +1653,6 @@
         return intentActivity;
     }
 
-    private void updateTaskReturnToType(
-            TaskRecord task, int launchFlags, ActivityStack focusedStack) {
-        if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
-            // Caller wants to appear on home activity.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            return;
-        } else if (focusedStack == null || focusedStack.isActivityTypeHome()) {
-            // Task will be launched over the home stack, so return home.
-            task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
-            return;
-        } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
-            // Task was launched over the assistant stack, so return there
-            task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-            return;
-        }
-
-        // Else we are coming from an application stack so return to an application.
-        task.setTaskToReturnTo(ACTIVITY_TYPE_STANDARD);
-    }
-
     private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
         if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                 == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
@@ -1714,13 +1669,8 @@
             task.performClearTaskLocked();
             mReuseTask = task;
             mReuseTask.setIntent(mStartActivity);
-
-            // When we clear the task - focus will be adjusted, which will bring another task
-            // to top before we launch the activity we need. This will temporary swap their
-            // mTaskToReturnTo values and we don't want to overwrite them accidentally.
-            mMovedOtherTask = true;
         } else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
-                || mLaunchSingleInstance || mLaunchSingleTask) {
+                || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
             ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
                     mLaunchFlags);
             if (top == null) {
@@ -1739,7 +1689,7 @@
                     // Target stack got cleared when we all activities were removed above.
                     // Go ahead and reset it.
                     mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
-                            null /* bounds */, mLaunchFlags, mOptions);
+                            mLaunchFlags, mOptions);
                     mTargetStack.addTask(task,
                             !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
                 }
@@ -1749,15 +1699,13 @@
             // so we take that as a request to bring the task to the foreground. If the top
             // activity in the task is the root activity, deliver this new intent to it if it
             // desires.
-            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
+            if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
+                        || LAUNCH_SINGLE_TOP == mLaunchMode)
                     && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
-                ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
-                        intentActivity.getTask());
                 if (intentActivity.frontOfTask) {
                     intentActivity.getTask().setIntent(mStartActivity);
                 }
-                intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                        mStartActivity.launchedFromPackage);
+                deliverNewIntent(intentActivity);
             } else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
                 // In this case we are launching the root activity of the task, but with a
                 // different intent. We should start a new instance on top.
@@ -1792,8 +1740,7 @@
 
     private int setTaskFromReuseOrCreateNewTask(
             TaskRecord taskToAffiliate, ActivityStack topStack) {
-        mTargetStack = computeStackFocus(
-                mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
+        mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
 
         // Do no move the target stack to front yet, as we might bail if
         // isLockTaskModeViolation fails below.
@@ -1805,15 +1752,8 @@
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
                     mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
             addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
-            if (mLaunchBounds != null) {
-                final int stackId = mTargetStack.mStackId;
-                if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                    mService.resizeStack(
-                            stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-                } else {
-                    mStartActivity.getTask().updateOverrideConfiguration(mLaunchBounds);
-                }
-            }
+            updateBounds(mStartActivity.getTask(), mLaunchBounds);
+
             if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                     + " in new task " + mStartActivity.getTask());
         } else {
@@ -1829,21 +1769,23 @@
             return START_RETURN_LOCK_TASK_MODE_VIOLATION;
         }
 
-        if (!mMovedOtherTask) {
-            // If stack id is specified in activity options, usually it means that activity is
-            // launched not from currently focused stack (e.g. from SysUI or from shell) - in
-            // that case we check the target stack.
-            // TODO: Not sure I understand the value or use of the commented out code and the
-            // comment above. See if this causes any issues and why...
-            updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
-                    /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
-        }
         if (mDoResume) {
             mTargetStack.moveToFront("reuseOrNewTask");
         }
         return START_SUCCESS;
     }
 
+    private void deliverNewIntent(ActivityRecord activity) {
+        if (mIntentDelivered) {
+            return;
+        }
+
+        ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTask());
+        activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
+                mStartActivity.launchedFromPackage);
+        mIntentDelivered = true;
+    }
+
     private int setTaskFromSourceRecord() {
         if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
@@ -1881,8 +1823,8 @@
         if (mTargetStack == null) {
             mTargetStack = sourceStack;
         } else if (mTargetStack != sourceStack) {
-            sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
-                    !ANIMATE, DEFER_RESUME, "launchToSide");
+            sourceTask.reparent(mTargetStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                    DEFER_RESUME, "launchToSide");
         }
 
         final TaskRecord topTask = mTargetStack.topTask();
@@ -1900,7 +1842,7 @@
             mKeepCurTransition = true;
             if (top != null) {
                 ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 // For paranoia, make sure we have correctly resumed the top activity.
                 mTargetStack.mLastPausedActivity = null;
                 if (mDoResume) {
@@ -1919,7 +1861,7 @@
                 task.moveActivityToFrontLocked(top);
                 top.updateOptionsLocked(mOptions);
                 ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task);
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 mTargetStack.mLastPausedActivity = null;
                 if (mDoResume) {
                     mSupervisor.resumeFocusedStackTopActivityLocked();
@@ -1952,17 +1894,15 @@
         if (top != null && top.realActivity.equals(mStartActivity.realActivity)
                 && top.userId == mStartActivity.userId) {
             if ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                    || mLaunchSingleTop || mLaunchSingleTask) {
+                    || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK)) {
                 mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
                         mStartActivity.appTimeTracker, "inTaskToFront");
-                ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
                 if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                     // We don't need to start a new activity, and the client said not to do
                     // anything if that is the case, so this is it!
                     return START_RETURN_INTENT_TO_CALLER;
                 }
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                        mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 return START_DELIVERED_TO_TOP;
             }
         }
@@ -1976,19 +1916,16 @@
             return START_TASK_TO_FRONT;
         }
 
-        if (mLaunchBounds != null) {
-            mInTask.updateOverrideConfiguration(mLaunchBounds);
+        if (!mLaunchBounds.isEmpty()) {
             // TODO: Shouldn't we already know what stack to use by the time we get here?
-            int stackId = mSupervisor.getLaunchStackId(null, null, mInTask);
-            if (stackId != mInTask.getStackId()) {
-                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+            ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
+            if (stack != mInTask.getStack()) {
+                mInTask.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
                         DEFER_RESUME, "inTaskToFront");
-                stackId = mInTask.getStackId();
                 mTargetStack = mInTask.getStack();
             }
-            if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-            }
+
+            updateBounds(mInTask, mLaunchBounds);
         }
 
         mTargetStack.moveTaskToFrontLocked(
@@ -2001,9 +1938,21 @@
         return START_SUCCESS;
     }
 
+    void updateBounds(TaskRecord task, Rect bounds) {
+        if (bounds.isEmpty()) {
+            return;
+        }
+
+        final ActivityStack stack = task.getStack();
+        if (stack != null && stack.resizeStackWithLaunchBounds()) {
+            mService.resizeStack(stack.mStackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+        } else {
+            task.updateOverrideConfiguration(bounds);
+        }
+    }
+
     private void setTaskToCurrentTopOrCreateNewTask() {
-        mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
-                mOptions);
+        mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions);
         if (mDoResume) {
             mTargetStack.moveToFront("addingToTopTask");
         }
@@ -2066,8 +2015,8 @@
         }
     }
 
-    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
-            int launchFlags, ActivityOptions aOptions) {
+    private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags,
+            ActivityOptions aOptions) {
         final TaskRecord task = r.getTask();
         ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions);
         if (stack != null) {
@@ -2107,21 +2056,17 @@
         }
         if (stack == null) {
             // We first try to put the task in the first dynamic stack on home display.
-            final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
-            for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                stack = homeDisplayStacks.get(stackNdx);
-                if (isDynamicStack(stack.mStackId)) {
+            final ActivityDisplay display = mSupervisor.getDefaultDisplay();
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                stack = display.getChildAt(stackNdx);
+                if (!stack.isOnHomeDisplay()) {
                     if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
                             "computeStackFocus: Setting focused stack=" + stack);
                     return stack;
                 }
             }
             // If there is no suitable dynamic stack then we figure out which static stack to use.
-            final int stackId = task != null ? mSupervisor.getLaunchStackId(r, aOptions, task)
-                    // TODO: This should go in mSupervisor.getLaunchStackId method...
-                    : bounds != null && mService.mSupportsFreeformWindowManagement
-                            ? FREEFORM_WORKSPACE_STACK_ID : FULLSCREEN_WORKSPACE_STACK_ID;
-            stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+            stack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
         }
         if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
                 + r + " stackId=" + stack.mStackId);
@@ -2129,36 +2074,38 @@
     }
 
     /** Check if provided activity record can launch in currently focused stack. */
+    // TODO: This method can probably be consolidated into getLaunchStack() below.
     private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
         final ActivityStack focusedStack = mSupervisor.mFocusedStack;
-        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
         final boolean canUseFocusedStack;
-        switch (focusedStackId) {
-            case FULLSCREEN_WORKSPACE_STACK_ID:
-                // The fullscreen stack can contain any task regardless of if the task is resizeable
-                // or not. So, we let the task go in the fullscreen task if it is the focus stack.
-                canUseFocusedStack = true;
-                break;
-            case ASSISTANT_STACK_ID:
-                canUseFocusedStack = r.isActivityTypeAssistant();
-                break;
-            case DOCKED_STACK_ID:
-                // Any activity which supports split screen can go in the docked stack.
-                canUseFocusedStack = r.supportsSplitScreen();
-                break;
-            case FREEFORM_WORKSPACE_STACK_ID:
-                // Any activity which supports freeform can go in the freeform stack.
-                canUseFocusedStack = r.supportsFreeform();
-                break;
-            default:
-                // Dynamic stacks behave similarly to the fullscreen stack and can contain any
-                // resizeable task.
-                canUseFocusedStack = isDynamicStack(focusedStackId)
-                        && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+        if (focusedStack.isActivityTypeAssistant()) {
+            canUseFocusedStack = r.isActivityTypeAssistant();
+        } else {
+            switch (focusedStack.getWindowingMode()) {
+                case WINDOWING_MODE_FULLSCREEN:
+                    // The fullscreen stack can contain any task regardless of if the task is
+                    // resizeable or not. So, we let the task go in the fullscreen task if it is the
+                    // focus stack.
+                    canUseFocusedStack = true;
+                    break;
+                case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+                    // Any activity which supports split screen can go in the docked stack.
+                    canUseFocusedStack = r.supportsSplitScreenWindowingMode();
+                    break;
+                case WINDOWING_MODE_FREEFORM:
+                    // Any activity which supports freeform can go in the freeform stack.
+                    canUseFocusedStack = r.supportsFreeform();
+                    break;
+                default:
+                    // Dynamic stacks behave similarly to the fullscreen stack and can contain any
+                    // resizeable task.
+                    canUseFocusedStack = !focusedStack.isOnHomeDisplay()
+                            && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+            }
         }
-
         return canUseFocusedStack && !newTask
-                // Using the focus stack isn't important enough to override the prefered display.
+                // Using the focus stack isn't important enough to override the preferred display.
                 && (mPreferredDisplayId == focusedStack.mDisplayId);
     }
 
@@ -2169,50 +2116,13 @@
             return mReuseTask.getStack();
         }
 
-        // If the activity is of a specific type, return the associated stack, creating it if
-        // necessary
-        if (r.isActivityTypeHome()) {
-            return mSupervisor.mHomeStack;
-        }
-        if (r.isActivityTypeRecents()) {
-            return mSupervisor.getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (r.isActivityTypeAssistant()) {
-            return mSupervisor.getStack(ASSISTANT_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
+        final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
+                ? mPreferredDisplayId : INVALID_DISPLAY;
+        final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
+                vrDisplayId);
 
-        int launchDisplayId = INVALID_DISPLAY;
-        int launchStackId = INVALID_STACK_ID;
-        if (aOptions != null) {
-            launchDisplayId = aOptions.getLaunchDisplayId();
-            final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY;
-            launchStackId = mSupervisor.getLaunchStackId(r, aOptions, task, vrDisplayId);
-        }
-
-        // TODO: Will no longer be needed once we are on longer using static stack ids.
-        if (mSupervisor.isValidLaunchStackId(launchStackId, launchDisplayId, r)) {
-            return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (launchStackId == DOCKED_STACK_ID) {
-            // The preferred launch stack is the docked stack, but it isn't a valid launch stack
-            // for this activity, so we put the activity in the fullscreen stack.
-            return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
-        // TODO: Can probably be removed since ASS.getLaunchStackId() does display resolution.
-        if (launchDisplayId != INVALID_DISPLAY) {
-            // Stack id has higher priority than display id.
-            return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
-        }
-
-        // If we are using Vr2d display, find the virtual display stack.
-        // TODO: Can be removed.
-        if (mUsingVr2dDisplay) {
-            ActivityStack as = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r);
-            if (DEBUG_STACK) {
-                Slog.v(TAG, "Launch stack for app: " + r.toString() +
-                    ", on virtual display stack:" + as.toString());
-            }
-            return as;
+        if (launchStack != null) {
+            return launchStack;
         }
 
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
@@ -2235,18 +2145,19 @@
                 return mSupervisor.mFocusedStack;
             }
 
-            if (parentStack != null && parentStack.isDockedStack()) {
+            if (parentStack != null && parentStack.inSplitScreenPrimaryWindowingMode()) {
                 // If parent was in docked stack, the natural place to launch another activity
                 // will be fullscreen, so it can appear alongside the docked window.
-                return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED,
-                        ON_TOP);
+                final int activityType = mSupervisor.resolveActivityType(r, mOptions, task);
+                return parentStack.getDisplay().getOrCreateStack(
+                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, activityType, ON_TOP);
             } else {
                 // If the parent is not in the docked stack, we check if there is docked window
                 // and if yes, we will launch into that stack. If not, we just put the new
                 // activity into parent's stack, because we can't find a better place.
-                final ActivityStack dockedStack = mSupervisor.getStack(DOCKED_STACK_ID);
-                if (dockedStack != null
-                        && dockedStack.shouldBeVisible(r) == STACK_INVISIBLE) {
+                final ActivityStack dockedStack =
+                        mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
+                if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
                     // There is a docked stack, but it isn't visible, so we can't launch into that.
                     return null;
                 } else {
@@ -2256,26 +2167,8 @@
         }
     }
 
-    private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
-        Rect newBounds = null;
-        if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
-                && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
-            newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
-        }
-        return newBounds;
-    }
-
-    void setWindowManager(WindowManagerService wm) {
-        mWindowManager = wm;
-    }
-
-    void removePendingActivityLaunchesLocked(ActivityStack stack) {
-        for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
-            PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
-            if (pal.stack == stack) {
-                mPendingActivityLaunches.remove(palNdx);
-            }
-        }
+    private boolean isLaunchModeOneOf(int mode1, int mode2) {
+        return mode1 == mLaunchMode || mode2 == mLaunchMode;
     }
 
     static boolean isDocumentLaunchesIntoExisting(int flags) {
@@ -2356,11 +2249,11 @@
         }
         pw.print(prefix);
         pw.print("mLaunchSingleTop=");
-        pw.print(mLaunchSingleTop);
+        pw.print(LAUNCH_SINGLE_TOP == mLaunchMode);
         pw.print(" mLaunchSingleInstance=");
-        pw.print(mLaunchSingleInstance);
+        pw.print(LAUNCH_SINGLE_INSTANCE == mLaunchMode);
         pw.print(" mLaunchSingleTask=");
-        pw.println(mLaunchSingleTask);
+        pw.println(LAUNCH_SINGLE_TASK == mLaunchMode);
         pw.print(prefix);
         pw.print("mLaunchFlags=0x");
         pw.print(Integer.toHexString(mLaunchFlags));
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index 440b3d3b..fe38097 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -507,7 +507,7 @@
         // launching the report UI under a different user.
         app.errorReportReceiver = null;
 
-        for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+        for (int userId : mService.mUserController.getCurrentProfileIds()) {
             if (app.userId == userId) {
                 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                         mContext, app.info.packageName, app.info.flags);
@@ -728,7 +728,7 @@
             boolean isBackground = (UserHandle.getAppId(proc.uid)
                     >= Process.FIRST_APPLICATION_UID
                     && proc.pid != MY_PID);
-            for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+            for (int userId : mService.mUserController.getCurrentProfileIds()) {
                 isBackground &= (proc.userId != userId);
             }
             if (isBackground && !showBackground) {
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
new file mode 100644
index 0000000..17626ea
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -0,0 +1,154 @@
+/*
+ * 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.server.am;
+
+import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+
+import android.app.ActivityManager;
+import android.app.IAppTask;
+import android.app.IApplicationThread;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+
+/**
+ * An implementation of IAppTask, that allows an app to manage its own tasks via
+ * {@link android.app.ActivityManager.AppTask}.  We keep track of the callingUid to ensure that
+ * only the process that calls getAppTasks() can call the AppTask methods.
+ */
+class AppTaskImpl extends IAppTask.Stub {
+    private ActivityManagerService mService;
+
+    private int mTaskId;
+    private int mCallingUid;
+
+    public AppTaskImpl(ActivityManagerService service, int taskId, int callingUid) {
+        mService = service;
+        mTaskId = taskId;
+        mCallingUid = callingUid;
+    }
+
+    private void checkCaller() {
+        if (mCallingUid != Binder.getCallingUid()) {
+            throw new SecurityException("Caller " + mCallingUid
+                    + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+        }
+    }
+
+    @Override
+    public void finishAndRemoveTask() {
+        checkCaller();
+
+        synchronized (mService) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                // We remove the task from recents to preserve backwards
+                if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
+                        REMOVE_FROM_RECENTS)) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public ActivityManager.RecentTaskInfo getTaskInfo() {
+        checkCaller();
+
+        synchronized (mService) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                if (tr == null) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+                return mService.getRecentTasks().createRecentTaskInfo(tr);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public void moveToFront() {
+        checkCaller();
+        // Will bring task to front if it already has a root activity.
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                mService.mStackSupervisor.startActivityFromRecents(mTaskId, null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public int startActivity(IBinder whoThread, String callingPackage,
+            Intent intent, String resolvedType, Bundle bOptions) {
+        checkCaller();
+
+        int callingUser = UserHandle.getCallingUserId();
+        TaskRecord tr;
+        IApplicationThread appThread;
+        synchronized (mService) {
+            tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+            if (tr == null) {
+                throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+            }
+            appThread = IApplicationThread.Stub.asInterface(whoThread);
+            if (appThread == null) {
+                throw new IllegalArgumentException("Bad app thread " + appThread);
+            }
+        }
+        return mService.mActivityStarter.startActivityMayWait(appThread, -1, callingPackage,
+                intent, resolvedType, null, null, null, null, 0, 0, null, null,
+                null, bOptions, false, callingUser, tr, "AppTaskImpl");
+    }
+
+    @Override
+    public void setExcludeFromRecents(boolean exclude) {
+        checkCaller();
+
+        synchronized (mService) {
+            long origId = Binder.clearCallingIdentity();
+            try {
+                TaskRecord tr = mService.mStackSupervisor.anyTaskForIdLocked(mTaskId,
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                if (tr == null) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+                Intent intent = tr.getBaseIntent();
+                if (exclude) {
+                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                } else {
+                    intent.setFlags(intent.getFlags()
+                            & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AssistDataReceiverProxy.java b/services/core/java/com/android/server/am/AssistDataReceiverProxy.java
new file mode 100644
index 0000000..8306731
--- /dev/null
+++ b/services/core/java/com/android/server/am/AssistDataReceiverProxy.java
@@ -0,0 +1,87 @@
+/*
+ * 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.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.app.IAssistDataReceiver;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+/**
+ * Proxies assist data to the given receiver, skipping all callbacks if the receiver dies.
+ */
+class AssistDataReceiverProxy implements AssistDataRequesterCallbacks,
+        Binder.DeathRecipient {
+
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "AssistDataReceiverProxy" : TAG_AM;
+
+    private String mCallerPackage;
+    private boolean mBinderDied;
+    private IAssistDataReceiver mReceiver;
+
+    public AssistDataReceiverProxy(IAssistDataReceiver receiver, String callerPackage) {
+        try {
+            receiver.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Could not link to client death", e);
+        }
+        mReceiver = receiver;
+        mCallerPackage = callerPackage;
+    }
+
+    @Override
+    public boolean canHandleReceivedAssistDataLocked() {
+        // We are forwarding, so we can always receive this data
+        return true;
+    }
+
+    @Override
+    public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+        if (!mBinderDied) {
+            try {
+                mReceiver.onHandleAssistData(data);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to proxy assist data to receiver in package="
+                        + mCallerPackage, e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+        if (!mBinderDied) {
+            try {
+                mReceiver.onHandleAssistScreenshot(screenshot);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to proxy assist screenshot to receiver in package="
+                        + mCallerPackage, e);
+            }
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        mBinderDied = true;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/AssistDataRequester.java b/services/core/java/com/android/server/am/AssistDataRequester.java
new file mode 100644
index 0000000..e32ff6e
--- /dev/null
+++ b/services/core/java/com/android/server/am/AssistDataRequester.java
@@ -0,0 +1,307 @@
+/*
+ * 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.server.am;
+
+import static android.app.ActivityManager.ASSIST_CONTEXT_FULL;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_NONE;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAssistDataReceiver;
+import com.android.internal.logging.MetricsLogger;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to asynchronously fetch the assist data and screenshot from the current running
+ * activities. It manages received data and calls back to the owner when the owner is ready to
+ * receive the data itself.
+ */
+public class AssistDataRequester extends IAssistDataReceiver.Stub {
+
+    public static final String KEY_RECEIVER_EXTRA_COUNT = "count";
+    public static final String KEY_RECEIVER_EXTRA_INDEX = "index";
+
+    private IActivityManager mService;
+    private IWindowManager mWindowManager;
+    private Context mContext;
+    private AppOpsManager mAppOpsManager;
+
+    private AssistDataRequesterCallbacks mCallbacks;
+    private Object mCallbacksLock;
+
+    private int mRequestStructureAppOps;
+    private int mRequestScreenshotAppOps;
+    private boolean mCanceled;
+    private int mPendingDataCount;
+    private int mPendingScreenshotCount;
+    private final ArrayList<Bundle> mAssistData = new ArrayList<>();
+    private final ArrayList<Bitmap> mAssistScreenshot = new ArrayList<>();
+
+
+    /**
+     * Interface to handle the events from the fetcher.
+     */
+    public interface AssistDataRequesterCallbacks {
+        /**
+         * @return whether the currently received assist data can be handled by the callbacks.
+         */
+        @GuardedBy("mCallbacksLock")
+        boolean canHandleReceivedAssistDataLocked();
+
+        /**
+         * Called when we receive asynchronous assist data. This call is only made if the
+         * {@param fetchData} argument to requestAssistData() is true, and if the current activity
+         * allows assist data to be fetched.  In addition, the callback will be made with the
+         * {@param mCallbacksLock} held, and only if {@link #canHandleReceivedAssistDataLocked()}
+         * is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount);
+
+        /**
+         * Called when we receive asynchronous assist screenshot. This call is only made if
+         * {@param fetchScreenshot} argument to requestAssistData() is true, and if the current
+         * activity allows assist data to be fetched.  In addition, the callback will be made with
+         * the {@param mCallbacksLock} held, and only if
+         * {@link #canHandleReceivedAssistDataLocked()} is true.
+         */
+        @GuardedBy("mCallbacksLock")
+        void onAssistScreenshotReceivedLocked(Bitmap screenshot);
+    }
+
+    /**
+     * @param callbacks The callbacks to handle the asynchronous reply with the assist data.
+     * @param callbacksLock The lock for the requester to hold when calling any of the
+     *                     {@param callbacks}. The owner should also take care in locking
+     *                     appropriately when calling into this requester.
+     * @param requestStructureAppOps The app ops to check before requesting the assist structure
+     * @param requestScreenshotAppOps The app ops to check before requesting the assist screenshot.
+     *                                This can be {@link AppOpsManager#OP_NONE} to indicate that
+     *                                screenshots should never be fetched.
+     */
+    public AssistDataRequester(Context context, IActivityManager service,
+            IWindowManager windowManager, AppOpsManager appOpsManager,
+            AssistDataRequesterCallbacks callbacks, Object callbacksLock,
+            int requestStructureAppOps, int requestScreenshotAppOps) {
+        mCallbacks = callbacks;
+        mCallbacksLock = callbacksLock;
+        mWindowManager = windowManager;
+        mService = service;
+        mContext = context;
+        mAppOpsManager = appOpsManager;
+        mRequestStructureAppOps = requestStructureAppOps;
+        mRequestScreenshotAppOps = requestScreenshotAppOps;
+    }
+
+    /**
+     * Request that assist data be loaded asynchronously. The resulting data will be provided
+     * through the {@link AssistDataRequesterCallbacks}.
+     *
+     * @param activityTokens the list of visible activities
+     * @param fetchData whether or not to fetch the assist data, only applies if the caller is
+     *     allowed to fetch the assist data, and the current activity allows assist data to be
+     *     fetched from it
+     * @param fetchScreenshot whether or not to fetch the screenshot, only applies if fetchData is
+     *     true, the caller is allowed to fetch the assist data, and the current activity allows
+     *     assist data to be fetched from it
+     */
+    public void requestAssistData(List<IBinder> activityTokens, boolean fetchData,
+            boolean fetchScreenshot, int callingUid, String callingPackage) {
+        // TODO: Better handle the cancel case if a request can be reused
+        // TODO: Known issue, if the assist data is not allowed on the current activity, then no
+        //       assist data is requested for any of the other activities
+
+        // Early exit if there are no activity to fetch for
+        if (activityTokens.isEmpty()) {
+            return;
+        }
+
+        // Ensure that the current activity supports assist data
+        boolean isAssistDataAllowed = false;
+        try {
+            isAssistDataAllowed = mService.isAssistDataAllowedOnCurrentActivity();
+        } catch (RemoteException e) {
+            // Should never happen
+        }
+        fetchData &= isAssistDataAllowed;
+        fetchScreenshot &= fetchData && isAssistDataAllowed
+                && (mRequestScreenshotAppOps != OP_NONE);
+
+        mCanceled = false;
+        mPendingDataCount = 0;
+        mPendingScreenshotCount = 0;
+        mAssistData.clear();
+        mAssistScreenshot.clear();
+
+        if (fetchData) {
+            if (mAppOpsManager.checkOpNoThrow(mRequestStructureAppOps, callingUid, callingPackage)
+                    == MODE_ALLOWED) {
+                final int numActivities = activityTokens.size();
+                for (int i = 0; i < numActivities; i++) {
+                    IBinder topActivity = activityTokens.get(i);
+                    try {
+                        MetricsLogger.count(mContext, "assist_with_context", 1);
+                        Bundle receiverExtras = new Bundle();
+                        receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
+                        receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, numActivities);
+                        if (mService.requestAssistContextExtras(ASSIST_CONTEXT_FULL, this,
+                                receiverExtras, topActivity, /* focused= */ i == 0,
+                                    /* newSessionId= */ i == 0)) {
+                            mPendingDataCount++;
+                        } else if (i == 0) {
+                            // Wasn't allowed... given that, let's not do the screenshot either.
+                            dispatchAssistDataReceived(null);
+                            fetchScreenshot = false;
+                            break;
+                        }
+                    } catch (RemoteException e) {
+                        // Can't happen
+                    }
+                }
+            } else {
+                // Wasn't allowed... given that, let's not do the screenshot either.
+                dispatchAssistDataReceived(null);
+                fetchScreenshot = false;
+            }
+        }
+
+        if (fetchScreenshot) {
+            if (mAppOpsManager.checkOpNoThrow(mRequestScreenshotAppOps, callingUid, callingPackage)
+                    == MODE_ALLOWED) {
+                try {
+                    MetricsLogger.count(mContext, "assist_with_screen", 1);
+                    mPendingScreenshotCount++;
+                    mWindowManager.requestAssistScreenshot(this);
+                } catch (RemoteException e) {
+                    // Can't happen
+                }
+            } else {
+                dispatchAssistScreenshotReceived(null);
+            }
+        }
+    }
+
+    /**
+     * This call should only be made when the callbacks are capable of handling the received assist
+     * data.
+     */
+    public void processPendingAssistData() {
+        final int dataCount = mAssistData.size();
+        for (int i = 0; i < dataCount; i++) {
+            dispatchAssistDataReceived(mAssistData.get(i));
+        }
+        final int screenshotsCount = mAssistScreenshot.size();
+        for (int i = 0; i < screenshotsCount; i++) {
+            dispatchAssistScreenshotReceived(mAssistScreenshot.get(i));
+        }
+    }
+
+    public int getPendingDataCount() {
+        return mPendingDataCount;
+    }
+
+    public int getPendingScreenshotCount() {
+        return mPendingScreenshotCount;
+    }
+
+    /**
+     * Cancels the current request for the assist data.
+     */
+    public void cancel() {
+        // Reset the pending data count, if we receive new assist data after this point, it will
+        // be ignored
+        mCanceled = true;
+        mPendingDataCount = 0;
+        mPendingScreenshotCount = 0;
+        mAssistData.clear();
+        mAssistScreenshot.clear();
+    }
+
+    @Override
+    public void onHandleAssistData(Bundle data) {
+        synchronized (mCallbacksLock) {
+            if (mCanceled) {
+                return;
+            }
+            mPendingDataCount--;
+
+            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                // Process any pending data and dispatch the new data as well
+                processPendingAssistData();
+                dispatchAssistDataReceived(data);
+            } else {
+                // Queue up the data for processing later
+                mAssistData.add(data);
+            }
+        }
+    }
+
+    @Override
+    public void onHandleAssistScreenshot(Bitmap screenshot) {
+        synchronized (mCallbacksLock) {
+            if (mCanceled) {
+                return;
+            }
+            mPendingScreenshotCount--;
+
+            if (mCallbacks.canHandleReceivedAssistDataLocked()) {
+                // Process any pending data and dispatch the new data as well
+                processPendingAssistData();
+                dispatchAssistScreenshotReceived(screenshot);
+            } else {
+                // Queue up the data for processing later
+                mAssistScreenshot.add(screenshot);
+            }
+        }
+    }
+
+    private void dispatchAssistDataReceived(Bundle data) {
+        int activityIndex = 0;
+        int activityCount = 0;
+        final Bundle receiverExtras = data != null
+                ? data.getBundle(ASSIST_KEY_RECEIVER_EXTRAS) : null;
+        if (receiverExtras != null) {
+            activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
+            activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
+        }
+        mCallbacks.onAssistDataReceivedLocked(data, activityIndex, activityCount);
+    }
+
+    private void dispatchAssistScreenshotReceived(Bitmap screenshot) {
+        mCallbacks.onAssistScreenshotReceivedLocked(screenshot);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mPendingDataCount="); pw.println(mPendingDataCount);
+        pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
+        pw.print(prefix); pw.print("mPendingScreenshotCount="); pw.println(mPendingScreenshotCount);
+        pw.print(prefix); pw.print("mAssistScreenshot="); pw.println(mAssistScreenshot);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index e839003..db12ae2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -220,7 +220,7 @@
         // Shutdown the thread we made.
         mWorker.shutdown();
     }
-    
+
     public static IBatteryStats getService() {
         if (sService != null) {
             return sService;
@@ -297,43 +297,40 @@
     void noteProcessStart(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessStartLocked(name, uid);
-
-            // TODO: remove this once we figure out properly where and how
-            // PROCESS_EVENT = 1112
-            // KEY_STATE = 1
-            // KEY_PACKAGE_NAME: 1002
-            // KEY_UID: 2
-            StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 1);
         }
     }
 
     void noteProcessCrash(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessCrashLocked(name, uid);
-
-            // TODO: remove this once we figure out properly where and how
-            // PROCESS_EVENT = 1112
-            // KEY_STATE = 1
-            // KEY_PACKAGE_NAME: 1002
-            // KEY_UID: 2
-            StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 2);
         }
     }
 
     void noteProcessAnr(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessAnrLocked(name, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 3);
         }
     }
 
     void noteProcessFinish(String name, int uid) {
         synchronized (mStats) {
             mStats.noteProcessFinishLocked(name, uid);
+            // TODO: decide where this should be and use a constant instead of a literal.
+            StatsLog.write(StatsLog.PROCESS_LIFE_CYCLE_STATE_CHANGED, uid, name, 0);
         }
     }
 
     void noteUidProcessState(int uid, int state) {
         synchronized (mStats) {
+            // TODO: remove this once we figure out properly where and how
+            StatsLog.write(StatsLog.UID_PROCESS_STATE_CHANGED, uid, state);
+
             mStats.noteUidProcessStateLocked(uid, state);
         }
     }
@@ -548,12 +545,10 @@
         enforceCallingPermission();
         if (DBG) Slog.d(TAG, "begin noteScreenState");
         synchronized (mStats) {
-            mStats.noteScreenStateLocked(state);
             // TODO: remove this once we figure out properly where and how
-            // SCREEN_EVENT = 2
-            // KEY_STATE: 1
-            // State value: state. We can change this to our own def later.
-            StatsLog.writeArray(2, 1, state);
+            StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, state);
+
+            mStats.noteScreenStateLocked(state);
         }
         if (DBG) Slog.d(TAG, "end noteScreenState");
     }
@@ -561,6 +556,7 @@
     public void noteScreenBrightness(int brightness) {
         enforceCallingPermission();
         synchronized (mStats) {
+            StatsLog.write(StatsLog.SCREEN_BRIGHTNESS_CHANGED, brightness);
             mStats.noteScreenBrightnessLocked(brightness);
         }
     }
@@ -611,21 +607,21 @@
             mStats.notePhoneOnLocked();
         }
     }
-    
+
     public void notePhoneOff() {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.notePhoneOffLocked();
         }
     }
-    
+
     public void notePhoneSignalStrength(SignalStrength signalStrength) {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.notePhoneSignalStrengthLocked(signalStrength);
         }
     }
-    
+
     public void notePhoneDataConnectionState(int dataType, boolean hasData) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -647,7 +643,7 @@
             mStats.noteWifiOnLocked();
         }
     }
-    
+
     public void noteWifiOff() {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -806,7 +802,7 @@
             mStats.noteFullWifiLockAcquiredLocked(uid);
         }
     }
-    
+
     public void noteFullWifiLockReleased(int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -922,6 +918,7 @@
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.noteDeviceIdleModeLocked(mode, activeReason, activeUid);
+            StatsLog.write(StatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mode);
         }
     }
 
@@ -1043,7 +1040,7 @@
             });
         });
     }
-    
+
     public long getAwakeTimeBattery() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
@@ -1186,6 +1183,7 @@
 
         int flags = 0;
         boolean useCheckinFormat = false;
+        boolean toProto = false;
         boolean isRealCheckin = false;
         boolean noOutput = false;
         boolean writeData = false;
@@ -1212,6 +1210,8 @@
                 } else if ("-c".equals(arg)) {
                     useCheckinFormat = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
+                } else if ("--proto".equals(arg)) {
+                    toProto = true;
                 } else if ("--charged".equals(arg)) {
                     flags |= BatteryStats.DUMP_CHARGED_ONLY;
                 } else if ("--daily".equals(arg)) {
@@ -1304,7 +1304,45 @@
             }
         }
 
-        if (useCheckinFormat) {
+        if (toProto) {
+            List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+                    PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
+            if (isRealCheckin) {
+                // For a real checkin, first we want to prefer to use the last complete checkin
+                // file if there is one.
+                synchronized (mStats.mCheckinFile) {
+                    if (mStats.mCheckinFile.exists()) {
+                        try {
+                            byte[] raw = mStats.mCheckinFile.readFully();
+                            if (raw != null) {
+                                Parcel in = Parcel.obtain();
+                                in.unmarshall(raw, 0, raw.length);
+                                in.setDataPosition(0);
+                                BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+                                        null, mStats.mHandler, null, mUserManagerUserInfoProvider);
+                                checkinStats.readSummaryFromParcel(in);
+                                in.recycle();
+                                checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
+                                        historyStart);
+                                mStats.mCheckinFile.delete();
+                                return;
+                            }
+                        } catch (IOException | ParcelFormatException e) {
+                            Slog.w(TAG, "Failure reading checkin file "
+                                    + mStats.mCheckinFile.getBaseFile(), e);
+                        }
+                    }
+                }
+            }
+            if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid());
+            synchronized (mStats) {
+                mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart);
+                if (writeData) {
+                    mStats.writeAsyncLocked();
+                }
+            }
+            if (DBG) Slog.d(TAG, "end dumpProtoLocked");
+        } else if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
                     PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
             if (isRealCheckin) {
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index f96b06f..7ff227f 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -19,6 +19,9 @@
 import android.content.IntentFilter;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.BroadcastFilterProto;
 
 import java.io.PrintWriter;
 
@@ -44,27 +47,38 @@
         instantApp = _instantApp;
         visibleToInstantApp = _visibleToInstantApp;
     }
-    
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        super.writeToProto(proto, BroadcastFilterProto.INTENT_FILTER);
+        if (requiredPermission != null) {
+            proto.write(BroadcastFilterProto.REQUIRED_PERMISSION, requiredPermission);
+        }
+        proto.write(BroadcastFilterProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+        proto.write(BroadcastFilterProto.OWNING_USER_ID, owningUserId);
+        proto.end(token);
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix);
         receiverList.dumpLocal(pw, prefix);
     }
-    
+
     public void dumpBrief(PrintWriter pw, String prefix) {
         dumpBroadcastFilterState(pw, prefix);
     }
-    
+
     public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) {
         super.dump(pr, prefix);
         dumpBroadcastFilterState(pw, prefix);
     }
-    
+
     void dumpBroadcastFilterState(PrintWriter pw, String prefix) {
         if (requiredPermission != null) {
             pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
         }
     }
-    
+
     public String toString() {
         StringBuilder sb = new StringBuilder();
         sb.append("BroadcastFilter{");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index d835454..aa82d00 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -51,9 +51,12 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import static com.android.server.am.ActivityManagerDebugConfig.*;
 
+import com.android.server.am.proto.BroadcastQueueProto;
+
 /**
  * BROADCASTS
  *
@@ -1195,11 +1198,6 @@
                         + " (uid " + r.callingUid + ")");
                 skip = true;
             }
-            if (!skip) {
-                r.manifestCount++;
-            } else {
-                r.manifestSkipCount++;
-            }
             if (r.curApp != null && r.curApp.crashing) {
                 // If the target process is crashing, just skip it.
                 Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
@@ -1280,6 +1278,16 @@
                 }
             }
 
+            if (!skip && !Intent.ACTION_SHUTDOWN.equals(r.intent.getAction())
+                    && !mService.mUserController
+                    .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
+                            0 /* flags */)) {
+                skip = true;
+                Slog.w(TAG,
+                        "Skipping delivery to " + info.activityInfo.packageName + " / "
+                                + info.activityInfo.applicationInfo.uid + " : user is not running");
+            }
+
             if (skip) {
                 if (DEBUG_BROADCAST)  Slog.v(TAG_BROADCAST,
                         "Skipping delivery of ordered [" + mQueueName + "] "
@@ -1288,9 +1296,11 @@
                 r.receiver = null;
                 r.curFilter = null;
                 r.state = BroadcastRecord.IDLE;
+                r.manifestSkipCount++;
                 scheduleBroadcastsLocked();
                 return;
             }
+            r.manifestCount++;
 
             r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
             r.state = BroadcastRecord.APP_RECEIVE;
@@ -1299,7 +1309,7 @@
             if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
                 Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
                         + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
-                        + info.activityInfo.applicationInfo.uid);
+                        + receiverUid);
             }
 
             if (brOptions != null && brOptions.getTemporaryAppWhitelistDuration() > 0) {
@@ -1362,7 +1372,7 @@
                 // and mark the broadcast record as ready for the next.
                 Slog.w(TAG, "Unable to launch app "
                         + info.activityInfo.applicationInfo.packageName + "/"
-                        + info.activityInfo.applicationInfo.uid + " for broadcast "
+                        + receiverUid + " for broadcast "
                         + r.intent + ": process is bad");
                 logBroadcastReceiverDiscardLocked(r);
                 finishReceiverLocked(r, r.resultCode, r.resultData,
@@ -1585,6 +1595,55 @@
                 && (mPendingBroadcast == null);
     }
 
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+        int N;
+        N = mParallelBroadcasts.size();
+        for (int i = N - 1; i >= 0; i--) {
+            mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
+        }
+        N = mOrderedBroadcasts.size();
+        for (int i = N - 1; i >= 0; i--) {
+            mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
+        }
+        if (mPendingBroadcast != null) {
+            mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST);
+        }
+
+        int lastIndex = mHistoryNext;
+        int ringIndex = lastIndex;
+        do {
+            // increasing index = more recent entry, and we want to print the most
+            // recent first and work backwards, so we roll through the ring backwards.
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_HISTORY);
+            BroadcastRecord r = mBroadcastHistory[ringIndex];
+            if (r != null) {
+                r.writeToProto(proto, BroadcastQueueProto.HISTORICAL_BROADCASTS);
+            }
+        } while (ringIndex != lastIndex);
+
+        lastIndex = ringIndex = mSummaryHistoryNext;
+        do {
+            ringIndex = ringAdvance(ringIndex, -1, MAX_BROADCAST_SUMMARY_HISTORY);
+            Intent intent = mBroadcastSummaryHistory[ringIndex];
+            if (intent == null) {
+                continue;
+            }
+            long summaryToken = proto.start(BroadcastQueueProto.HISTORICAL_BROADCASTS_SUMMARY);
+            intent.writeToProto(proto, BroadcastQueueProto.BroadcastSummary.INTENT,
+                    false, true, true, false);
+            proto.write(BroadcastQueueProto.BroadcastSummary.ENQUEUE_CLOCK_TIME_MS,
+                    mSummaryHistoryEnqueueTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.DISPATCH_CLOCK_TIME_MS,
+                    mSummaryHistoryDispatchTime[ringIndex]);
+            proto.write(BroadcastQueueProto.BroadcastSummary.FINISH_CLOCK_TIME_MS,
+                    mSummaryHistoryFinishTime[ringIndex]);
+            proto.end(summaryToken);
+        } while (ringIndex != lastIndex);
+        proto.end(token);
+    }
+
     final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6bc0744..5b3b2a8 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -30,6 +30,9 @@
 import android.os.UserHandle;
 import android.util.PrintWriterPrinter;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.BroadcastRecordProto;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -331,9 +334,17 @@
         return didSomething;
     }
 
+    @Override
     public String toString() {
         return "BroadcastRecord{"
             + Integer.toHexString(System.identityHashCode(this))
             + " u" + userId + " " + intent.getAction() + "}";
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastRecordProto.USER_ID, userId);
+        proto.write(BroadcastRecordProto.INTENT_ACTION, intent.getAction());
+        proto.end(token);
+    }
 }
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index e03c530..c3fed17 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -47,7 +46,6 @@
 import com.android.server.wm.WindowManagerService;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
 /**
  * Controls Keyguard occluding, dismissing and transitions depending on what kind of activities are
@@ -238,9 +236,9 @@
         final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
         mOccluded = false;
         mDismissingKeyguardActivity = null;
-        final ArrayList<ActivityStack> stacks = mStackSupervisor.getStacksOnDefaultDisplay();
-        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-            final ActivityStack stack = stacks.get(stackNdx);
+        final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
+        for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = display.getChildAt(stackNdx);
 
             // Only the focused stack top activity may control occluded state
             if (mStackSupervisor.isFocusedStack(stack)) {
@@ -342,8 +340,12 @@
             // show on top of the lock screen. In this can we want to dismiss the docked
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
-            mStackSupervisor.moveTasksToFullscreenStackLocked(DOCKED_STACK_ID,
-                    mStackSupervisor.mFocusedStack.getStackId() == DOCKED_STACK_ID);
+            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
+            if (stack == null) {
+                return;
+            }
+            mStackSupervisor.moveTasksToFullscreenStackLocked(stack,
+                    mStackSupervisor.mFocusedStack == stack);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/LaunchingActivityPositioner.java b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java
new file mode 100644
index 0000000..d5f9cf3
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java
@@ -0,0 +1,63 @@
+/*
+ * 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.server.am;
+
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+/**
+ * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified
+ * inside {@link ActivityOptions#getLaunchBounds()}.
+ */
+public class LaunchingActivityPositioner implements LaunchingBoundsPositioner {
+    private final ActivityStackSupervisor mSupervisor;
+
+    LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) {
+        mSupervisor = activityStackSupervisor;
+    }
+
+    @Override
+    public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+            ActivityRecord activity, ActivityRecord source,
+            ActivityOptions options, Rect current, Rect result) {
+        // We only care about figuring out bounds for activities.
+        if (activity == null) {
+            return RESULT_SKIP;
+        }
+
+        // Activity must be resizeable in the specified task.
+        if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options)
+            && (activity.isResizeable() || (task != null && task.isResizeable())))) {
+            return RESULT_SKIP;
+        }
+
+        final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+
+        // Bounds weren't valid.
+        if (bounds == null) {
+            return RESULT_SKIP;
+        }
+
+        result.set(bounds);
+
+        // When this is the most explicit position specification so we should not allow further
+        // modification of the position.
+        return RESULT_DONE;
+    }
+}
diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java
new file mode 100644
index 0000000..c762f7f
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java
@@ -0,0 +1,163 @@
+/*
+ * 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.server.am;
+
+import android.annotation.IntDef;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered
+ * {@link LaunchingBoundsPositioner}.
+ */
+class LaunchingBoundsController {
+    private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>();
+
+    // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and
+    // {@code mTmpResult} to prevent clobbering values.
+    private final Rect mTmpRect = new Rect();
+
+    private final Rect mTmpCurrent = new Rect();
+    private final Rect mTmpResult = new Rect();
+
+    /**
+     * Creates a {@link LaunchingBoundsController} with default registered
+     * {@link LaunchingBoundsPositioner}s.
+     */
+    void registerDefaultPositioners(ActivityStackSupervisor supervisor) {
+        // {@link LaunchingTaskPositioner} handles window layout preferences.
+        registerPositioner(new LaunchingTaskPositioner());
+
+        // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be
+        // registered last (applied first) out of the defaults.
+        registerPositioner(new LaunchingActivityPositioner(supervisor));
+    }
+
+    /**
+     * Returns the position calculated by the registered positioners
+     * @param task      The {@link TaskRecord} currently being positioned.
+     * @param layout    The specified {@link WindowLayout}.
+     * @param activity  The {@link ActivityRecord} currently being positioned.
+     * @param source    The {@link ActivityRecord} from which activity was started from.
+     * @param options   The {@link ActivityOptions} specified for the activity.
+     * @param result    The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be
+     *                  {@code true}.
+     */
+    void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+            ActivityRecord source, ActivityOptions options, Rect result) {
+        result.setEmpty();
+
+        // We start at the last registered {@link LaunchingBoundsPositioner} as this represents
+        // The positioner closest to the product level. Moving back through the list moves closer to
+        // the platform logic.
+        for (int i = mPositioners.size() - 1; i >= 0; --i) {
+            mTmpResult.setEmpty();
+            mTmpCurrent.set(result);
+            final LaunchingBoundsPositioner positioner = mPositioners.get(i);
+
+            switch(positioner.onCalculateBounds(task, layout, activity, source, options,
+                    mTmpCurrent, mTmpResult)) {
+                case RESULT_SKIP:
+                    // Do not apply any results when we are told to skip
+                    continue;
+                case RESULT_DONE:
+                    // Set result and return immediately.
+                    result.set(mTmpResult);
+                    return;
+                case RESULT_CONTINUE:
+                    // Set result and continue
+                    result.set(mTmpResult);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * A convenience method for laying out a task.
+     * @return {@code true} if bounds were set on the task. {@code false} otherwise.
+     */
+    boolean layoutTask(TaskRecord task, WindowLayout layout) {
+        calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
+                mTmpRect);
+
+        if (mTmpRect.isEmpty()) {
+            return false;
+        }
+
+        task.updateOverrideConfiguration(mTmpRect);
+
+        return true;
+    }
+
+    /**
+     * Adds a positioner to participate in future bounds calculation. Note that the last registered
+     * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds.
+     */
+    void registerPositioner(LaunchingBoundsPositioner positioner) {
+        if (mPositioners.contains(positioner)) {
+            return;
+        }
+
+        mPositioners.add(positioner);
+    }
+
+    /**
+     * An interface implemented by those wanting to participate in bounds calculation.
+     */
+    interface LaunchingBoundsPositioner {
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
+        @interface Result {}
+
+        // Returned when the positioner does not want to influence the bounds calculation
+        int RESULT_SKIP = 0;
+        // Returned when the positioner has changed the bounds and would like its results to be the
+        // final bounds applied.
+        int RESULT_DONE = 1;
+        // Returned when the positioner has changed the bounds but is okay with other positioners
+        // influencing the bounds.
+        int RESULT_CONTINUE = 2;
+
+        /**
+         * Called when asked to calculate bounds.
+         * @param task      The {@link TaskRecord} currently being positioned.
+         * @param layout    The specified {@link WindowLayout}.
+         * @param activity  The {@link ActivityRecord} currently being positioned.
+         * @param source    The {@link ActivityRecord} activity was started from.
+         * @param options   The {@link ActivityOptions} specified for the activity.
+         * @param current   The current bounds. This can differ from the initial bounds as it
+         *                  represents the modified bounds up to this point.
+         * @param result    The {@link Rect} which the positioner should return its modified bounds.
+         *                  Any merging of the current bounds should be already applied to this
+         *                  value as well before returning.
+         * @return          A {@link Result} representing the result of the bounds calculation.
+         */
+        @Result
+        int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+                ActivityRecord source, ActivityOptions options, Rect current, Rect result);
+    }
+}
diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
index d652341..c958fca 100644
--- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
+++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
@@ -19,13 +19,13 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.annotation.Nullable;
+import android.app.ActivityOptions;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Slog;
-import android.view.Display;
 import android.view.Gravity;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 
@@ -36,8 +36,10 @@
  * and compares corners of the task with corners of existing tasks. If some two pairs of corners are
  * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts
  * all possible shifts, it gives up and puts the task in the original position.
+ *
+ * Note that the only gravities of concern are the corners and the center.
  */
-class LaunchingTaskPositioner {
+class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM;
 
     // Determines how close window frames/corners have to be to call them colliding.
@@ -64,154 +66,179 @@
     private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2;
     private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3;
 
-    private boolean mDefaultStartBoundsConfigurationSet = false;
     private final Rect mAvailableRect = new Rect();
     private final Rect mTmpProposal = new Rect();
     private final Rect mTmpOriginal = new Rect();
 
-    private int mDefaultFreeformStartX;
-    private int mDefaultFreeformStartY;
-    private int mDefaultFreeformWidth;
-    private int mDefaultFreeformHeight;
-    private int mDefaultFreeformStepHorizontal;
-    private int mDefaultFreeformStepVertical;
-    private int mDisplayWidth;
-    private int mDisplayHeight;
-
-    void setDisplay(Display display) {
-        Point size = new Point();
-        display.getSize(size);
-        mDisplayWidth = size.x;
-        mDisplayHeight = size.y;
-    }
-
-    void configure(Rect stackBounds) {
-        if (stackBounds == null) {
-            mAvailableRect.set(0, 0, mDisplayWidth, mDisplayHeight);
-        } else {
-            mAvailableRect.set(stackBounds);
-        }
-        int width = mAvailableRect.width();
-        int height = mAvailableRect.height();
-        mDefaultFreeformStartX = mAvailableRect.left + width / MARGIN_SIZE_DENOMINATOR;
-        mDefaultFreeformStartY = mAvailableRect.top + height / MARGIN_SIZE_DENOMINATOR;
-        mDefaultFreeformWidth = width / WINDOW_SIZE_DENOMINATOR;
-        mDefaultFreeformHeight = height / WINDOW_SIZE_DENOMINATOR;
-        mDefaultFreeformStepHorizontal = Math.max(width / STEP_DENOMINATOR, MINIMAL_STEP);
-        mDefaultFreeformStepVertical = Math.max(height / STEP_DENOMINATOR, MINIMAL_STEP);
-        mDefaultStartBoundsConfigurationSet = true;
-    }
+    private final Point mDisplaySize = new Point();
 
     /**
      * Tries to set task's bound in a way that it won't collide with any other task. By colliding
      * we mean that two tasks have left-top corner very close to each other, so one might get
      * obfuscated by the other one.
-     *
-     * @param task Task for which we want to find bounds that won't collide with other.
-     * @param tasks Existing tasks with which we don't want to collide.
-     * @param windowLayout Optional information from the client about how it would like to be sized
-     *                      and positioned.
      */
-    void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks,
-            @Nullable ActivityInfo.WindowLayout windowLayout) {
-        if (!mDefaultStartBoundsConfigurationSet) {
-            return;
+    @Override
+    public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+            ActivityRecord activity, ActivityRecord source,
+            ActivityOptions options, Rect current, Rect result) {
+        // We can only apply positioning if we're in a freeform stack.
+        if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) {
+            return RESULT_SKIP;
         }
-        if (windowLayout == null) {
-            positionCenter(task, tasks, mDefaultFreeformWidth, mDefaultFreeformHeight);
-            return;
+
+        final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
+
+        updateAvailableRect(task, mAvailableRect);
+
+        if (layout == null) {
+            positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
+                    getFreeformHeight(mAvailableRect), result);
+            return RESULT_CONTINUE;
         }
-        int width = getFinalWidth(windowLayout);
-        int height = getFinalHeight(windowLayout);
-        int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
-        int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+
+        int width = getFinalWidth(layout, mAvailableRect);
+        int height = getFinalHeight(layout, mAvailableRect);
+        int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
         if (verticalGravity == Gravity.TOP) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionTopRight(task, tasks, width, height);
+                positionTopRight(tasks, mAvailableRect, width, height, result);
             } else {
-                positionTopLeft(task, tasks, width, height);
+                positionTopLeft(tasks, mAvailableRect, width, height, result);
             }
         } else if (verticalGravity == Gravity.BOTTOM) {
             if (horizontalGravity == Gravity.RIGHT) {
-                positionBottomRight(task, tasks, width, height);
+                positionBottomRight(tasks, mAvailableRect, width, height, result);
             } else {
-                positionBottomLeft(task, tasks, width, height);
+                positionBottomLeft(tasks, mAvailableRect, width, height, result);
             }
         } else {
             // Some fancy gravity setting that we don't support yet. We just put the activity in the
             // center.
-            Slog.w(TAG, "Received unsupported gravity: " + windowLayout.gravity
+            Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
                     + ", positioning in the center instead.");
-            positionCenter(task, tasks, width, height);
+            positionCenter(tasks, mAvailableRect, width, height, result);
+        }
+
+        return RESULT_CONTINUE;
+    }
+
+    private void updateAvailableRect(TaskRecord task, Rect availableRect) {
+        final Rect stackBounds = task.getStack().mBounds;
+
+        if (stackBounds != null) {
+            availableRect.set(stackBounds);
+        } else {
+            task.getStack().getDisplay().mDisplay.getSize(mDisplaySize);
+            availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
         }
     }
 
-    private int getFinalWidth(ActivityInfo.WindowLayout windowLayout) {
-        int width = mDefaultFreeformWidth;
+    @VisibleForTesting
+    static int getFreeformStartLeft(Rect bounds) {
+        return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getFreeformStartTop(Rect bounds) {
+        return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getFreeformWidth(Rect bounds) {
+        return bounds.width() / WINDOW_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getFreeformHeight(Rect bounds) {
+        return bounds.height() / WINDOW_SIZE_DENOMINATOR;
+    }
+
+    @VisibleForTesting
+    static int getHorizontalStep(Rect bounds) {
+        return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP);
+    }
+
+    @VisibleForTesting
+    static int getVerticalStep(Rect bounds) {
+        return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP);
+    }
+
+
+
+    private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
+        int width = getFreeformWidth(availableRect);
         if (windowLayout.width > 0) {
             width = windowLayout.width;
         }
         if (windowLayout.widthFraction > 0) {
-            width = (int) (mAvailableRect.width() * windowLayout.widthFraction);
+            width = (int) (availableRect.width() * windowLayout.widthFraction);
         }
         return width;
     }
 
-    private int getFinalHeight(ActivityInfo.WindowLayout windowLayout) {
-        int height = mDefaultFreeformHeight;
+    private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
+        int height = getFreeformHeight(availableRect);
         if (windowLayout.height > 0) {
             height = windowLayout.height;
         }
         if (windowLayout.heightFraction > 0) {
-            height = (int) (mAvailableRect.height() * windowLayout.heightFraction);
+            height = (int) (availableRect.height() * windowLayout.heightFraction);
         }
         return height;
     }
 
-    private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.left, mAvailableRect.bottom - height,
-                mAvailableRect.left + width, mAvailableRect.bottom);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT);
+    private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
+        mTmpProposal.set(availableRect.left, availableRect.bottom - height,
+                availableRect.left + width, availableRect.bottom);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+                result);
     }
 
-    private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.bottom - height,
-                mAvailableRect.right, mAvailableRect.bottom);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT);
+    private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
+        mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
+                availableRect.right, availableRect.bottom);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+                result);
     }
 
-    private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.left, mAvailableRect.top,
-                mAvailableRect.left + width, mAvailableRect.top + height);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT);
+    private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
+        mTmpProposal.set(availableRect.left, availableRect.top,
+                availableRect.left + width, availableRect.top + height);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
+                result);
     }
 
-    private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mAvailableRect.right - width, mAvailableRect.top,
-                mAvailableRect.right, mAvailableRect.top + height);
-        position(task, tasks, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT);
+    private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
+        mTmpProposal.set(availableRect.right - width, availableRect.top,
+                availableRect.right, availableRect.top + height);
+        position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
+                result);
     }
 
-    private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, int width,
-            int height) {
-        mTmpProposal.set(mDefaultFreeformStartX, mDefaultFreeformStartY,
-                mDefaultFreeformStartX + width, mDefaultFreeformStartY + height);
-        position(task, tasks, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN);
+    private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
+            int height, Rect result) {
+        final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
+        final int defaultFreeformTop = getFreeformStartTop(availableRect);
+        mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
+                defaultFreeformLeft + width, defaultFreeformTop + height);
+        position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN,
+                result);
     }
 
-    private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect proposal,
-            boolean allowRestart, int shiftPolicy) {
+    private void position(ArrayList<TaskRecord> tasks, Rect availableRect,
+            Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) {
         mTmpOriginal.set(proposal);
         boolean restarted = false;
         while (boundsConflict(proposal, tasks)) {
             // Unfortunately there is already a task at that spot, so we need to look for some
             // other place.
-            shiftStartingPoint(proposal, shiftPolicy);
-            if (shiftedToFar(proposal, shiftPolicy)) {
+            shiftStartingPoint(proposal, availableRect, shiftPolicy);
+            if (shiftedTooFar(proposal, availableRect, shiftPolicy)) {
                 // We don't want the task to go outside of the stack, because it won't look
                 // nice. Depending on the starting point we either restart, or immediately give up.
                 if (!allowRestart) {
@@ -220,13 +247,13 @@
                 }
                 // We must have started not from the top. Let's restart from there because there
                 // might be some space there.
-                proposal.set(mAvailableRect.left, mAvailableRect.top,
-                        mAvailableRect.left + proposal.width(),
-                        mAvailableRect.top + proposal.height());
+                proposal.set(availableRect.left, availableRect.top,
+                        availableRect.left + proposal.width(),
+                        availableRect.top + proposal.height());
                 restarted = true;
             }
-            if (restarted && (proposal.left > mDefaultFreeformStartX
-                    || proposal.top > mDefaultFreeformStartY)) {
+            if (restarted && (proposal.left > getFreeformStartLeft(availableRect)
+                    || proposal.top > getFreeformStartTop(availableRect))) {
                 // If we restarted and crossed the initial position, let's not struggle anymore.
                 // The user already must have ton of tasks visible, we can just smack the new
                 // one in the center.
@@ -234,30 +261,33 @@
                 break;
             }
         }
-        task.updateOverrideConfiguration(proposal);
+        result.set(proposal);
     }
 
-    private boolean shiftedToFar(Rect start, int shiftPolicy) {
+    private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
         switch (shiftPolicy) {
             case SHIFT_POLICY_HORIZONTAL_LEFT:
-                return start.left < mAvailableRect.left;
+                return start.left < availableRect.left;
             case SHIFT_POLICY_HORIZONTAL_RIGHT:
-                return start.right > mAvailableRect.right;
+                return start.right > availableRect.right;
             default: // SHIFT_POLICY_DIAGONAL_DOWN
-                return start.right > mAvailableRect.right || start.bottom > mAvailableRect.bottom;
+                return start.right > availableRect.right || start.bottom > availableRect.bottom;
         }
     }
 
-    private void shiftStartingPoint(Rect posposal, int shiftPolicy) {
+    private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) {
+        final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect);
+        final int defaultFreeformStepVertical = getVerticalStep(availableRect);
+
         switch (shiftPolicy) {
             case SHIFT_POLICY_HORIZONTAL_LEFT:
-                posposal.offset(-mDefaultFreeformStepHorizontal, 0);
+                posposal.offset(-defaultFreeformStepHorizontal, 0);
                 break;
             case SHIFT_POLICY_HORIZONTAL_RIGHT:
-                posposal.offset(mDefaultFreeformStepHorizontal, 0);
+                posposal.offset(defaultFreeformStepHorizontal, 0);
                 break;
             default: // SHIFT_POLICY_DIAGONAL_DOWN:
-                posposal.offset(mDefaultFreeformStepHorizontal, mDefaultFreeformStepVertical);
+                posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical);
                 break;
         }
     }
@@ -296,8 +326,4 @@
         return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE
                 && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE;
     }
-
-    void reset() {
-        mDefaultStartBoundsConfigurationSet = false;
-    }
 }
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index 72b5de8..e87b4e6 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -19,18 +19,11 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.StatusBarManager.DISABLE_BACK;
-import static android.app.StatusBarManager.DISABLE_HOME;
-import static android.app.StatusBarManager.DISABLE_MASK;
-import static android.app.StatusBarManager.DISABLE_NONE;
-import static android.app.StatusBarManager.DISABLE_RECENT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
-import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
@@ -47,7 +40,10 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Binder;
 import android.os.Debug;
@@ -56,10 +52,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
@@ -86,13 +82,39 @@
     private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK;
 
     @VisibleForTesting
-    static final int STATUS_BAR_MASK_LOCKED = DISABLE_MASK
-            & (~DISABLE_BACK);
+    static final int STATUS_BAR_MASK_LOCKED = StatusBarManager.DISABLE_MASK
+            & (~StatusBarManager.DISABLE_EXPAND)
+            & (~StatusBarManager.DISABLE_NOTIFICATION_TICKER)
+            & (~StatusBarManager.DISABLE_SYSTEM_INFO)
+            & (~StatusBarManager.DISABLE_BACK);
     @VisibleForTesting
-    static final int STATUS_BAR_MASK_PINNED = DISABLE_MASK
-            & (~DISABLE_BACK)
-            & (~DISABLE_HOME)
-            & (~DISABLE_RECENT);
+    static final int STATUS_BAR_MASK_PINNED = StatusBarManager.DISABLE_MASK
+            & (~StatusBarManager.DISABLE_BACK)
+            & (~StatusBarManager.DISABLE_HOME)
+            & (~StatusBarManager.DISABLE_RECENT);
+
+    private static final SparseArray<Pair<Integer, Integer>> STATUS_BAR_FLAG_MAP_LOCKED;
+    static {
+        STATUS_BAR_FLAG_MAP_LOCKED = new SparseArray<>();
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO,
+                new Pair<>(StatusBarManager.DISABLE_CLOCK, StatusBarManager.DISABLE2_SYSTEM_ICONS));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS,
+                new Pair<>(StatusBarManager.DISABLE_NOTIFICATION_ICONS
+                        | StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+                        StatusBarManager.DISABLE2_NOTIFICATION_SHADE));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_HOME,
+                new Pair<>(StatusBarManager.DISABLE_HOME, StatusBarManager.DISABLE2_NONE));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS,
+                new Pair<>(StatusBarManager.DISABLE_RECENT, StatusBarManager.DISABLE2_NONE));
+
+        STATUS_BAR_FLAG_MAP_LOCKED.append(DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                new Pair<>(StatusBarManager.DISABLE_NONE,
+                        StatusBarManager.DISABLE2_GLOBAL_ACTIONS));
+    }
 
     /** Tag used for disabling of keyguard */
     private static final String LOCK_TASK_TAG = "Lock-to-App";
@@ -121,9 +143,17 @@
     LockTaskNotify mLockTaskNotify;
 
     /**
-     * The chain of tasks in lockTask mode. The current frontmost task is at the top, and tasks
-     * may be finished until there is only one entry left. If this is empty the system is not
-     * in lockTask mode.
+     * The chain of tasks in LockTask mode, in the order of when they first entered LockTask mode.
+     *
+     * The first task in the list, which started the current LockTask session, is called the root
+     * task. It coincides with the Home task in a typical multi-app kiosk deployment. When there are
+     * more than one locked tasks, the root task can't be finished. Nor can it be moved to the back
+     * of the stack by {@link ActivityStack#moveTaskToBackLocked(int)};
+     *
+     * Calling {@link Activity#stopLockTask()} on the root task will finish all tasks but itself in
+     * this list, and the device will exit LockTask mode.
+     *
+     * The list is empty if LockTask is inactive.
      */
     private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
 
@@ -133,11 +163,16 @@
     private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
 
     /**
+     * Features that are allowed by DPC to show during LockTask mode.
+     */
+    private final SparseArray<Integer> mLockTaskFeatures = new SparseArray<>();
+
+    /**
      * Store the current lock task mode. Possible values:
      * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
      * {@link ActivityManager#LOCK_TASK_MODE_PINNED}
      */
-    private int mLockTaskModeState;
+    private int mLockTaskModeState = LOCK_TASK_MODE_NONE;
 
     /**
      * This is ActivityStackSupervisor's Handler.
@@ -172,8 +207,29 @@
      * @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
      * back of the stack.
      */
-    boolean checkLockedTask(TaskRecord task) {
-        if (mLockTaskModeTasks.contains(task)) {
+    @VisibleForTesting
+    boolean isTaskLocked(TaskRecord task) {
+        return mLockTaskModeTasks.contains(task);
+    }
+
+    /**
+     * @return {@code true} whether this task first started the current LockTask session.
+     */
+    private boolean isRootTask(TaskRecord task) {
+        return mLockTaskModeTasks.indexOf(task) == 0;
+    }
+
+    /**
+     * @return whether the given activity is blocked from finishing, because it is the only activity
+     * of the last locked task and finishing it would mean that lock task mode is ended illegally.
+     */
+    boolean activityBlockedFromFinish(ActivityRecord activity) {
+        final TaskRecord task = activity.getTask();
+        if (activity == task.getRootActivity()
+                && activity == task.getTopActivity()
+                && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
+                && isRootTask(task)) {
+            Slog.i(TAG, "Not finishing task in lock task mode");
             showLockTaskToast();
             return true;
         }
@@ -181,20 +237,16 @@
     }
 
     /**
-     * @return whether the given activity is blocked from finishing, because it is the root activity
-     * of the last locked task and finishing it would mean that lock task mode is ended illegally.
+     * @return whether the given task can be moved to the back of the stack with
+     * {@link ActivityStack#moveTaskToBackLocked(int)}
+     * @see #mLockTaskModeTasks
      */
-    boolean activityBlockedFromFinish(ActivityRecord activity) {
-        TaskRecord task = activity.getTask();
-        if (activity == task.getRootActivity()
-                && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
-                && mLockTaskModeTasks.size() == 1
-                && mLockTaskModeTasks.contains(task)) {
-            Slog.i(TAG, "Not finishing task in lock task mode");
+    boolean canMoveTaskToBack(TaskRecord task) {
+        if (isRootTask(task)) {
             showLockTaskToast();
-            return true;
+            return false;
         }
-        return false;
+        return true;
     }
 
     /**
@@ -219,7 +271,7 @@
     private boolean isLockTaskModeViolationInternal(TaskRecord task, boolean isNewClearTask) {
         // TODO: Double check what's going on here. If the task is already in lock task mode, it's
         // likely whitelisted, so will return false below.
-        if (getLockedTask() == task && !isNewClearTask) {
+        if (isTaskLocked(task) && !isNewClearTask) {
             // If the task is already at the top and won't be cleared, then allow the operation
             return false;
         }
@@ -243,105 +295,130 @@
     /**
      * Stop the current lock task mode.
      *
-     * @param isSystemInitiated indicates whether this request was initiated by the system via
-     *                          {@link ActivityManagerService#stopSystemLockTaskMode()}.
+     * This is called by {@link ActivityManagerService} and performs various checks before actually
+     * finishing the locked task.
+     *
+     * @param task the task that requested the end of lock task mode ({@code null} for quitting app
+     *             pinning mode)
+     * @param isSystemCaller indicates whether this request comes from the system via
+     *                       {@link ActivityManagerService#stopSystemLockTaskMode()}. If
+     *                       {@code true}, it means the user intends to stop pinned mode through UI;
+     *                       otherwise, it's called by an app and we need to stop locked or pinned
+     *                       mode, subject to checks.
      * @param callingUid the caller that requested the end of lock task mode.
+     * @throws IllegalArgumentException if the calling task is invalid (e.g., {@code null} or not in
+     *                                  foreground)
      * @throws SecurityException if the caller is not authorized to stop the lock task mode, i.e. if
      *                           they differ from the one that launched lock task mode.
      */
-    void stopLockTaskMode(boolean isSystemInitiated, int callingUid) {
-        final TaskRecord lockTask = getLockedTask();
-        if (lockTask == null || mLockTaskModeState == LOCK_TASK_MODE_NONE) {
-            // Our work here is done.
+    void stopLockTaskMode(@Nullable TaskRecord task, boolean isSystemCaller, int callingUid) {
+        if (mLockTaskModeState == LOCK_TASK_MODE_NONE) {
             return;
         }
 
-        if (isSystemInitiated && mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
-            // As system can only start app pinning, we also only let it unlock in this mode.
-            showLockTaskToast();
+        if (isSystemCaller) {
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                clearLockedTasks("stopAppPinning");
+            } else {
+                Slog.e(TAG_LOCKTASK, "Attempted to stop LockTask with isSystemCaller=true");
+                showLockTaskToast();
+            }
+
+        } else {
+            // Ensure calling activity is not null
+            if (task == null) {
+                throw new IllegalArgumentException("can't stop LockTask for null task");
+            }
+
+            // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
+            // It is possible lockTaskMode was started by the system process because
+            // android:lockTaskMode is set to a locking value in the application manifest
+            // instead of the app calling startLockTaskMode. In this case
+            // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
+            // {@link TaskRecord.effectiveUid} instead. Also caller with
+            // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
+            if (callingUid != task.mLockTaskUid
+                    && (task.mLockTaskUid != 0 || callingUid != task.effectiveUid)) {
+                throw new SecurityException("Invalid uid, expected " + task.mLockTaskUid
+                        + " callingUid=" + callingUid + " effectiveUid=" + task.effectiveUid);
+            }
+
+            // We don't care if it's pinned or locked mode; this will stop it anyways.
+            clearLockedTask(task);
+        }
+    }
+
+    /**
+     * Clear all locked tasks and request the end of LockTask mode.
+     *
+     * This method is called by {@link UserController} when starting a new foreground user, and,
+     * unlike {@link #stopLockTaskMode(TaskRecord, boolean, int)}, it doesn't perform the checks.
+     */
+    void clearLockedTasks(String reason) {
+        if (DEBUG_LOCKTASK) Slog.i(TAG_LOCKTASK, "clearLockedTasks: " + reason);
+        if (!mLockTaskModeTasks.isEmpty()) {
+            clearLockedTask(mLockTaskModeTasks.get(0));
+        }
+    }
+
+    /**
+     * Clear one locked task from LockTask mode.
+     *
+     * If the requested task is the root task (see {@link #mLockTaskModeTasks}), then all locked
+     * tasks are cleared. Otherwise, only the requested task is cleared. LockTask mode is stopped
+     * when the last locked task is cleared.
+     *
+     * @param task the task to be cleared from LockTask mode.
+     */
+    void clearLockedTask(final TaskRecord task) {
+        if (task == null || mLockTaskModeTasks.isEmpty()) return;
+
+        if (task == mLockTaskModeTasks.get(0)) {
+            // We're removing the root task while there are other locked tasks. Therefore we should
+            // clear all locked tasks in reverse order.
+            for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx > 0; --taskNdx) {
+                clearLockedTask(mLockTaskModeTasks.get(taskNdx));
+            }
+        }
+
+        removeLockedTask(task);
+        if (mLockTaskModeTasks.isEmpty()) {
             return;
         }
-
-        // Ensure the same caller for startLockTaskMode and stopLockTaskMode.
-        // It is possible lockTaskMode was started by the system process because
-        // android:lockTaskMode is set to a locking value in the application manifest
-        // instead of the app calling startLockTaskMode. In this case
-        // {@link TaskRecord.mLockTaskUid} will be 0, so we compare the callingUid to the
-        // {@link TaskRecord.effectiveUid} instead. Also caller with
-        // {@link MANAGE_ACTIVITY_STACKS} can stop any lock task.
-        if (!isSystemInitiated && callingUid != lockTask.mLockTaskUid
-                && (lockTask.mLockTaskUid != 0 || callingUid != lockTask.effectiveUid)) {
-            throw new SecurityException("Invalid uid, expected " + lockTask.mLockTaskUid
-                    + " callingUid=" + callingUid + " effectiveUid=" + lockTask.effectiveUid);
-        }
-
-        clearLockTaskMode("stopLockTask");
+        task.performClearTaskLocked();
+        mSupervisor.resumeFocusedStackTopActivityLocked();
     }
 
     /**
      * Remove the given task from the locked task list. If this was the last task in the list,
      * lock task mode is stopped.
      */
-    void removeLockedTask(final TaskRecord task) {
+    private void removeLockedTask(final TaskRecord task) {
         if (!mLockTaskModeTasks.remove(task)) {
             return;
         }
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "removeLockedTask: removed " + task);
+        if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: removed " + task);
         if (mLockTaskModeTasks.isEmpty()) {
-            // Last one.
             if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "removeLockedTask: task=" + task +
                     " last task, reverting locktask mode. Callers=" + Debug.getCallers(3));
             mHandler.post(() -> performStopLockTask(task.userId));
         }
     }
 
-    /**
-     * Remove the topmost task from the locked task list. If this is the last task in the list, it
-     * will result in the end of locked task mode.
-     */
-    void clearLockTaskMode(String reason) {
-        // Take out of lock task mode if necessary
-        final TaskRecord lockedTask = getLockedTask();
-        if (lockedTask != null) {
-            removeLockedTask(lockedTask);
-            if (!mLockTaskModeTasks.isEmpty()) {
-                // There are locked tasks remaining, can only finish this task, not unlock it.
-                if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
-                        "setLockTaskMode: Tasks remaining, can't unlock");
-                lockedTask.performClearTaskLocked();
-                mSupervisor.resumeFocusedStackTopActivityLocked();
-                return;
-            }
-        }
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
-                "setLockTaskMode: No tasks to unlock. Callers=" + Debug.getCallers(4));
-    }
-
     // This method should only be called on the handler thread
     private void performStopLockTask(int userId) {
         // When lock task ends, we enable the status bars.
         try {
-            if (getStatusBarService() != null) {
-                getStatusBarService().disable(DISABLE_NONE, mToken,
-                        mContext.getPackageName());
+            setStatusBarState(LOCK_TASK_MODE_NONE, userId);
+            setKeyguardState(LOCK_TASK_MODE_NONE, userId);
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                lockKeyguardIfNeeded();
             }
-            mWindowManager.reenableKeyguard(mToken);
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
             }
-            getLockTaskNotify().show(false);
-            try {
-                boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
-                        mContext.getContentResolver(),
-                        LOCK_TO_APP_EXIT_LOCKED,
-                        USER_CURRENT) != 0;
-                if (mLockTaskModeState == LOCK_TASK_MODE_PINNED && shouldLockKeyguard) {
-                    mWindowManager.lockNow(null);
-                    mWindowManager.dismissKeyguard(null /* callback */);
-                    getLockPatternUtils().requireCredentialEntry(USER_ALL);
-                }
-            } catch (Settings.SettingNotFoundException e) {
-                // No setting, don't lock.
+            if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                getLockTaskNotify().showPinningExitToast();
             }
         } catch (RemoteException ex) {
             throw new RuntimeException(ex);
@@ -351,10 +428,13 @@
     }
 
     /**
-     * Show the lock task violation toast.
+     * Show the lock task violation toast. Currently we only show toast for screen pinning mode, and
+     * no-op if the device is in locked mode.
      */
     void showLockTaskToast() {
-        mHandler.post(() -> getLockTaskNotify().showToast(mLockTaskModeState));
+        if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
+            mHandler.post(() -> getLockTaskNotify().showEscapeToast());
+        }
     }
 
     // Starting lock task
@@ -363,17 +443,18 @@
      * Method to start lock task mode on a given task.
      *
      * @param task the task that should be locked.
-     * @param isSystemInitiated indicates whether this request was initiated by the system via
-     *                          {@link ActivityManagerService#startSystemLockTaskMode(int)}.
+     * @param isSystemCaller indicates whether this request was initiated by the system via
+     *                       {@link ActivityManagerService#startSystemLockTaskMode(int)}. If
+     *                       {@code true}, this intends to start pinned mode; otherwise, we look
+     *                       at the calling task's mLockTaskAuth to decide which mode to start.
      * @param callingUid the caller that requested the launch of lock task mode.
      */
-    void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemInitiated,
-            int callingUid) {
-        if (!isSystemInitiated) {
+    void startLockTaskMode(@NonNull TaskRecord task, boolean isSystemCaller, int callingUid) {
+        if (!isSystemCaller) {
             task.mLockTaskUid = callingUid;
             if (task.mLockTaskAuth == LOCK_TASK_AUTH_PINNABLE) {
                 // startLockTask() called by app, but app is not part of lock task whitelist. Show
-                // app pinning request. We will come back here with isSystemInitiated true.
+                // app pinning request. We will come back here with isSystemCaller true.
                 if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Mode default, asking user");
                 StatusBarManagerInternal statusBarManager = LocalServices.getService(
                         StatusBarManagerInternal.class);
@@ -385,8 +466,9 @@
         }
 
         // System can only initiate screen pinning, not full lock task mode
-        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, isSystemInitiated ? "Locking pinned" : "Locking fully");
-        setLockTaskMode(task, isSystemInitiated ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
+        if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK,
+                isSystemCaller ? "Locking pinned" : "Locking fully");
+        setLockTaskMode(task, isSystemCaller ? LOCK_TASK_MODE_PINNED : LOCK_TASK_MODE_LOCKED,
                 "startLockTask", true);
     }
 
@@ -415,25 +497,25 @@
                     task.userId,
                     lockTaskModeState));
         }
-
-        // Add it or move it to the top.
         if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "setLockTaskMode: Locking to " + task +
                 " Callers=" + Debug.getCallers(4));
-        mLockTaskModeTasks.remove(task);
-        mLockTaskModeTasks.add(task);
+
+        if (!mLockTaskModeTasks.contains(task)) {
+            mLockTaskModeTasks.add(task);
+        }
 
         if (task.mLockTaskUid == -1) {
             task.mLockTaskUid = task.effectiveUid;
         }
 
         if (andResume) {
-            mSupervisor.findTaskToMoveToFrontLocked(task, 0, null, reason,
+            mSupervisor.findTaskToMoveToFront(task, 0, null, reason,
                     lockTaskModeState != LOCK_TASK_MODE_NONE);
             mSupervisor.resumeFocusedStackTopActivityLocked();
             mWindowManager.executeAppTransition();
         } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
             mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
-                    DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */);
+                    DEFAULT_DISPLAY, task.getStack(), true /* forceNonResizable */);
         }
     }
 
@@ -441,18 +523,12 @@
     private void performStartLockTask(String packageName, int userId, int lockTaskModeState) {
         // When lock task starts, we disable the status bars.
         try {
-            getLockTaskNotify().show(true);
-            mLockTaskModeState = lockTaskModeState;
-            if (getStatusBarService() != null) {
-                int flags = 0;
-                if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
-                    flags = STATUS_BAR_MASK_LOCKED;
-                } else if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                    flags = STATUS_BAR_MASK_PINNED;
-                }
-                getStatusBarService().disable(flags, mToken, mContext.getPackageName());
+            if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+                getLockTaskNotify().showPinningStartToast();
             }
-            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+            mLockTaskModeState = lockTaskModeState;
+            setStatusBarState(lockTaskModeState, userId);
+            setKeyguardState(lockTaskModeState, userId);
             if (getDevicePolicyManager() != null) {
                 getDevicePolicyManager().notifyLockTaskModeChanged(true, packageName, userId);
             }
@@ -494,11 +570,7 @@
         }
 
         for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) {
-            ArrayList<ActivityStack> stacks = mSupervisor.getChildAt(displayNdx).mStacks;
-            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
-                final ActivityStack stack = stacks.get(stackNdx);
-                stack.onLockTaskPackagesUpdatedLocked();
-            }
+            mSupervisor.getChildAt(displayNdx).onLockTaskPackagesUpdated();
         }
 
         final ActivityRecord r = mSupervisor.topRunningActivityLocked();
@@ -535,14 +607,131 @@
     }
 
     /**
-     * @return the topmost locked task
+     * Update the UI features that are enabled for LockTask mode.
+     * @param userId Which user these feature flags are associated with
+     * @param flags Bitfield of feature flags
+     * @see DevicePolicyManager#setLockTaskFeatures(ComponentName, int)
      */
-    private TaskRecord getLockedTask() {
-        final int top = mLockTaskModeTasks.size() - 1;
-        if (top >= 0) {
-            return mLockTaskModeTasks.get(top);
+    void updateLockTaskFeatures(int userId, int flags) {
+        int oldFlags = getLockTaskFeaturesForUser(userId);
+        if (flags == oldFlags) {
+            return;
         }
-        return null;
+
+        mLockTaskFeatures.put(userId, flags);
+        if (!mLockTaskModeTasks.isEmpty() && userId == mLockTaskModeTasks.get(0).userId) {
+            mHandler.post(() -> {
+                if (mLockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+                    setStatusBarState(mLockTaskModeState, userId);
+                    setKeyguardState(mLockTaskModeState, userId);
+                }
+            });
+        }
+    }
+
+    /**
+     * Helper method for configuring the status bar disabled state.
+     * Should only be called on the handler thread to avoid race.
+     */
+    private void setStatusBarState(int lockTaskModeState, int userId) {
+        IStatusBarService statusBar = getStatusBarService();
+        if (statusBar == null) {
+            Slog.e(TAG, "Can't find StatusBarService");
+            return;
+        }
+
+        // Default state, when lockTaskModeState == LOCK_TASK_MODE_NONE
+        int flags1 = StatusBarManager.DISABLE_NONE;
+        int flags2 = StatusBarManager.DISABLE2_NONE;
+
+        if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
+            flags1 = STATUS_BAR_MASK_PINNED;
+
+        } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+            int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
+            Pair<Integer, Integer> statusBarFlags = getStatusBarDisableFlags(lockTaskFeatures);
+            flags1 = statusBarFlags.first;
+            flags2 = statusBarFlags.second;
+        }
+
+        try {
+            statusBar.disable(flags1, mToken, mContext.getPackageName());
+            statusBar.disable2(flags2, mToken, mContext.getPackageName());
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to set status bar flags", e);
+        }
+    }
+
+    /**
+     * Helper method for configuring the keyguard disabled state.
+     * Should only be called on the handler thread to avoid race.
+     */
+    private void setKeyguardState(int lockTaskModeState, int userId) {
+        if (lockTaskModeState == LOCK_TASK_MODE_NONE) {
+            mWindowManager.reenableKeyguard(mToken);
+
+        } else if (lockTaskModeState == LOCK_TASK_MODE_LOCKED) {
+            int lockTaskFeatures = getLockTaskFeaturesForUser(userId);
+            if ((DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD & lockTaskFeatures) == 0) {
+                mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+            } else {
+                mWindowManager.reenableKeyguard(mToken);
+            }
+
+        } else { // lockTaskModeState == LOCK_TASK_MODE_PINNED
+            mWindowManager.disableKeyguard(mToken, LOCK_TASK_TAG);
+        }
+    }
+
+    /**
+     * Helper method for locking the device immediately. This may be necessary when the device
+     * leaves the pinned mode.
+     */
+    private void lockKeyguardIfNeeded() {
+        try {
+            boolean shouldLockKeyguard = Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
+                    USER_CURRENT) != 0;
+            if (shouldLockKeyguard) {
+                mWindowManager.lockNow(null);
+                mWindowManager.dismissKeyguard(null /* callback */);
+                getLockPatternUtils().requireCredentialEntry(USER_ALL);
+            }
+        } catch (Settings.SettingNotFoundException e) {
+            // No setting, don't lock.
+        }
+    }
+
+    /**
+     * Translates from LockTask feature flags to StatusBarManager disable and disable2 flags.
+     * @param lockTaskFlags Bitfield of flags as per
+     *                      {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)}
+     * @return A {@link Pair} of {@link StatusBarManager#disable(int)} and
+     *         {@link StatusBarManager#disable2(int)} flags
+     */
+    @VisibleForTesting
+    Pair<Integer, Integer> getStatusBarDisableFlags(int lockTaskFlags) {
+        // Everything is disabled by default
+        int flags1 = StatusBarManager.DISABLE_MASK;
+        int flags2 = StatusBarManager.DISABLE2_MASK;
+        for (int i = STATUS_BAR_FLAG_MAP_LOCKED.size() - 1; i >= 0; i--) {
+            Pair<Integer, Integer> statusBarFlags = STATUS_BAR_FLAG_MAP_LOCKED.valueAt(i);
+            if ((STATUS_BAR_FLAG_MAP_LOCKED.keyAt(i) & lockTaskFlags) != 0) {
+                flags1 &= ~statusBarFlags.first;
+                flags2 &= ~statusBarFlags.second;
+            }
+        }
+        // Some flags are not used for LockTask purposes, so we mask them
+        flags1 &= STATUS_BAR_MASK_LOCKED;
+        return new Pair<>(flags1, flags2);
+    }
+
+    /**
+     * Gets the cached value of LockTask feature flags for a specific user.
+     */
+    private int getLockTaskFeaturesForUser(int userId) {
+        return mLockTaskFeatures.get(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
     }
 
     // Should only be called on the handler thread
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index 0412db5..1dcb0ad 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -16,10 +16,7 @@
 
 package com.android.server.am;
 
-import android.app.ActivityManager;
 import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
 import android.os.SystemClock;
 import android.util.Slog;
 import android.view.WindowManager;
@@ -28,37 +25,33 @@
 import com.android.internal.R;
 
 /**
- *  Helper to manage showing/hiding a image to notify them that they are entering
- *  or exiting lock-to-app mode.
+ *  Helper to manage showing/hiding a image to notify them that they are entering or exiting screen
+ *  pinning mode. All exposed methods should be called from a handler thread.
  */
 public class LockTaskNotify {
     private static final String TAG = "LockTaskNotify";
     private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
 
     private final Context mContext;
-    private final H mHandler;
     private Toast mLastToast;
     private long mLastShowToastTime;
 
     public LockTaskNotify(Context context) {
         mContext = context;
-        mHandler = new H();
     }
 
-    public void showToast(int lockTaskModeState) {
-        mHandler.obtainMessage(H.SHOW_TOAST, lockTaskModeState, 0 /* Not used */).sendToTarget();
+    /** Show "Screen pinned" toast. */
+    void showPinningStartToast() {
+        makeAllUserToastAndShow(R.string.lock_to_app_start);
     }
 
-    public void handleShowToast(int lockTaskModeState) {
-        String text = null;
-        if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_LOCKED) {
-            text = mContext.getString(R.string.lock_to_app_toast_locked);
-        } else if (lockTaskModeState == ActivityManager.LOCK_TASK_MODE_PINNED) {
-            text = mContext.getString(R.string.lock_to_app_toast);
-        }
-        if (text == null) {
-            return;
-        }
+    /** Show "Screen unpinned" toast. */
+    void showPinningExitToast() {
+        makeAllUserToastAndShow(R.string.lock_to_app_exit);
+    }
+
+    /** Show a toast that describes the gesture the user should use to escape pinned mode. */
+    void showEscapeToast() {
         long showToastTime = SystemClock.elapsedRealtime();
         if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
             Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
@@ -67,36 +60,15 @@
         if (mLastToast != null) {
             mLastToast.cancel();
         }
-        mLastToast = makeAllUserToastAndShow(text);
+        mLastToast = makeAllUserToastAndShow(R.string.lock_to_app_toast);
         mLastShowToastTime = showToastTime;
     }
 
-    public void show(boolean starting) {
-        int showString = R.string.lock_to_app_exit;
-        if (starting) {
-            showString = R.string.lock_to_app_start;
-        }
-        makeAllUserToastAndShow(mContext.getString(showString));
-    }
-
-    private Toast makeAllUserToastAndShow(String text) {
-        Toast toast = Toast.makeText(mContext, text, Toast.LENGTH_LONG);
+    private Toast makeAllUserToastAndShow(int resId) {
+        Toast toast = Toast.makeText(mContext, resId, Toast.LENGTH_LONG);
         toast.getWindowParams().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
         toast.show();
         return toast;
     }
-
-    private final class H extends Handler {
-        private static final int SHOW_TOAST = 3;
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch(msg.what) {
-                case SHOW_TOAST:
-                    handleShowToast(msg.arg1);
-                    break;
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index ee59386..7930f53 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -308,7 +308,7 @@
                 boolean sendFinish = finishedReceiver != null;
                 int userId = key.userId;
                 if (userId == UserHandle.USER_CURRENT) {
-                    userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
+                    userId = owner.mUserController.getCurrentOrTargetUserId();
                 }
                 int res = 0;
                 switch (key.type) {
diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java
index a601ee1..2726979 100644
--- a/services/core/java/com/android/server/am/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/am/PinnedActivityStack.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.app.RemoteAction;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -32,15 +35,16 @@
 class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
         implements PinnedStackWindowListener {
 
-    PinnedActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-            ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
-        super(display, stackId, supervisor, recentTasks, onTop);
+    PinnedActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+            boolean onTop) {
+        super(display, stackId, supervisor, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, onTop);
     }
 
     @Override
     PinnedStackWindowController createStackWindowController(int displayId, boolean onTop,
             Rect outBounds) {
-        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds);
+        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds,
+                mStackSupervisor.mWindowManager);
     }
 
     Rect getDefaultPictureInPictureBounds(float aspectRatio) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0e318d9..e847723 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -27,6 +27,7 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ProcessState;
 import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.am.proto.ProcessRecordProto;
 
 import android.app.ActivityManager;
 import android.app.Dialog;
@@ -44,6 +45,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -621,6 +623,22 @@
         }
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(ProcessRecordProto.PID, pid);
+        proto.write(ProcessRecordProto.PROCESS_NAME, processName);
+        if (info.uid < Process.FIRST_APPLICATION_UID) {
+            proto.write(ProcessRecordProto.UID, uid);
+        } else {
+            proto.write(ProcessRecordProto.USER_ID, userId);
+            proto.write(ProcessRecordProto.APP_ID, UserHandle.getAppId(info.uid));
+            if (uid != info.uid) {
+                proto.write(ProcessRecordProto.ISOLATED_APP_ID, UserHandle.getAppId(uid));
+            }
+        }
+        proto.end(token);
+    }
+
     public String toShortString() {
         if (shortStringName != null) {
             return shortStringName;
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 39aed7c..effb86c 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -23,11 +23,14 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.DumpUtils;
@@ -622,13 +625,17 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
-            dumpInner(fd, pw, args);
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpInner(pw, args);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void dumpInner(PrintWriter pw, String[] args) {
         final long now = SystemClock.uptimeMillis();
 
         boolean isCheckin = false;
@@ -1038,4 +1045,44 @@
             }
         }
     }
+
+    private void dumpAggregatedStats(ProtoOutputStream proto, int aggregateHours, long now) {
+        ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
+                - (ProcessStats.COMMIT_PERIOD/2));
+        if (pfd == null) {
+            return;
+        }
+        ProcessStats stats = new ProcessStats(false);
+        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        stats.read(stream);
+        if (stats.mReadError != null) {
+            return;
+        }
+        stats.toProto(proto, now);
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        // dump current procstats
+        long nowToken = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_NOW);
+        long now;
+        synchronized (mAm) {
+            now = SystemClock.uptimeMillis();
+            mProcessStats.toProto(proto, now);
+        }
+        proto.end(nowToken);
+
+        // aggregated over last 3 hours procstats
+        long tokenOf3Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_3HRS);
+        dumpAggregatedStats(proto, 3, now);
+        proto.end(tokenOf3Hrs);
+
+        // aggregated over last 24 hours procstats
+        long tokenOf24Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_24HRS);
+        dumpAggregatedStats(proto, 24, now);
+        proto.end(tokenOf24Hrs);
+
+        proto.flush();
+    }
 }
diff --git a/services/core/java/com/android/server/am/ReceiverList.java b/services/core/java/com/android/server/am/ReceiverList.java
index 6ade736..a989063 100644
--- a/services/core/java/com/android/server/am/ReceiverList.java
+++ b/services/core/java/com/android/server/am/ReceiverList.java
@@ -21,6 +21,8 @@
 import android.os.IBinder;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import com.android.server.am.proto.ReceiverListProto;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -41,7 +43,7 @@
     boolean linkedToDeath = false;
 
     String stringName;
-    
+
     ReceiverList(ActivityManagerService _owner, ProcessRecord _app,
             int _pid, int _uid, int _userId, IIntentReceiver _receiver) {
         owner = _owner;
@@ -59,12 +61,31 @@
     public int hashCode() {
         return System.identityHashCode(this);
     }
-    
+
     public void binderDied() {
         linkedToDeath = false;
         owner.unregisterReceiver(receiver);
     }
-    
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        app.writeToProto(proto, ReceiverListProto.APP);
+        proto.write(ReceiverListProto.PID, pid);
+        proto.write(ReceiverListProto.UID, uid);
+        proto.write(ReceiverListProto.USER, userId);
+        if (curBroadcast != null) {
+            curBroadcast.writeToProto(proto, ReceiverListProto.CURRENT);
+        }
+        proto.write(ReceiverListProto.LINKED_TO_DEATH, linkedToDeath);
+        final int N = size();
+        for (int i=0; i<N; i++) {
+            BroadcastFilter bf = get(i);
+            bf.writeToProto(proto, ReceiverListProto.FILTERS);
+        }
+        proto.write(ReceiverListProto.HEX_HASH, Integer.toHexString(System.identityHashCode(this)));
+        proto.end(token);
+    }
+
     void dumpLocal(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("app="); pw.print(app != null ? app.toShortString() : null);
             pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid);
@@ -74,7 +95,7 @@
                 pw.print(" linkedToDeath="); pw.println(linkedToDeath);
         }
     }
-    
+
     void dump(PrintWriter pw, String prefix) {
         Printer pr = new PrintWriterPrinter(pw);
         dumpLocal(pw, prefix);
@@ -89,7 +110,7 @@
             bf.dumpInReceiverList(pw, pr, p2);
         }
     }
-    
+
     public String toString() {
         if (stringName != null) {
             return stringName;
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 365c5b1..d35c37b 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -16,19 +16,27 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.FLAG_AND_UNLOCKED;
+import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
+import static android.app.ActivityManager.RECENT_WITH_EXCLUDED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 
-import com.google.android.collect.Sets;
-
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.ComponentName;
@@ -37,43 +45,94 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Environment;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.am.TaskRecord.TaskActivitiesReport;
+
+import com.google.android.collect.Sets;
+
 import java.io.File;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
- * Class for managing the recent tasks list.
+ * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
+ * least recent.
  */
-class RecentTasks extends ArrayList<TaskRecord> {
+class RecentTasks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
     private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+    private static final boolean TRIMMED = true;
 
-    // Maximum number recent bitmaps to keep in memory.
-    private static final int MAX_RECENT_BITMAPS = 3;
     private static final int DEFAULT_INITIAL_CAPACITY = 5;
 
     // Whether or not to move all affiliated tasks to the front when one of the tasks is launched
     private static final boolean MOVE_AFFILIATED_TASKS_TO_FRONT = false;
 
+    // Comparator to sort by taskId
+    private static final Comparator<TaskRecord> TASK_ID_COMPARATOR =
+            (lhs, rhs) -> rhs.taskId - lhs.taskId;
+
+    // Placeholder variables to keep track of activities/apps that are no longer avialble while
+    // iterating through the recents list
+    private static final ActivityInfo NO_ACTIVITY_INFO_TOKEN = new ActivityInfo();
+    private static final ApplicationInfo NO_APPLICATION_INFO_TOKEN = new ApplicationInfo();
+
+    /**
+     * Callbacks made when manipulating the list.
+     */
+    interface Callbacks {
+        /**
+         * Called when a task is added to the recent tasks list.
+         */
+        void onRecentTaskAdded(TaskRecord task);
+
+        /**
+         * Called when a task is removed from the recent tasks list.
+         */
+        void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed);
+    }
+
     /**
      * Save recent tasks information across reboots.
      */
     private final TaskPersister mTaskPersister;
     private final ActivityManagerService mService;
+    private final UserController mUserController;
+
+    /**
+     * Keeps track of the static recents package/component which is granted additional permissions
+     * to call recents-related APIs.
+     */
+    private int mRecentsUid = -1;
+    private ComponentName mRecentsComponent = null;
+
+    /**
+     * Mapping of user id -> whether recent tasks have been loaded for that user.
+     */
     private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(
             DEFAULT_INITIAL_CAPACITY);
 
@@ -81,21 +140,162 @@
      * Stores for each user task ids that are taken by tasks residing in persistent storage. These
      * tasks may or may not currently be in memory.
      */
-    final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
+    private final SparseArray<SparseBooleanArray> mPersistedTaskIds = new SparseArray<>(
             DEFAULT_INITIAL_CAPACITY);
 
+    // List of all active recent tasks
+    private final ArrayList<TaskRecord> mTasks = new ArrayList<>();
+    private final ArrayList<Callbacks> mCallbacks = new ArrayList<>();
+
+    // These values are generally loaded from resources, but can be set dynamically in the tests
+    private boolean mHasVisibleRecentTasks;
+    private int mGlobalMaxNumTasks;
+    private int mMinNumVisibleTasks;
+    private int mMaxNumVisibleTasks;
+    private long mActiveTasksSessionDurationMs;
+
     // Mainly to avoid object recreation on multiple calls.
-    private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
+    private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<>();
     private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
     private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
-    private final ActivityInfo mTmpActivityInfo = new ActivityInfo();
-    private final ApplicationInfo mTmpAppInfo = new ApplicationInfo();
+    private final SparseBooleanArray mTmpQuietProfileUserIds = new SparseBooleanArray();
+    private final TaskActivitiesReport mTmpReport = new TaskActivitiesReport();
 
-    RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
-        File systemDir = Environment.getDataSystemDirectory();
+    @VisibleForTesting
+    RecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+            UserController userController) {
         mService = service;
-        mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
-        mStackSupervisor.setRecentTasks(this);
+        mUserController = userController;
+        mTaskPersister = taskPersister;
+        mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
+        mHasVisibleRecentTasks = true;
+    }
+
+    RecentTasks(ActivityManagerService service, ActivityStackSupervisor stackSupervisor) {
+        final File systemDir = Environment.getDataSystemDirectory();
+        final Resources res = service.mContext.getResources();
+        mService = service;
+        mUserController = service.mUserController;
+        mTaskPersister = new TaskPersister(systemDir, stackSupervisor, service, this);
+        mGlobalMaxNumTasks = ActivityManager.getMaxRecentTasksStatic();
+        mHasVisibleRecentTasks = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
+        loadParametersFromResources(res);
+    }
+
+    @VisibleForTesting
+    void setParameters(int minNumVisibleTasks, int maxNumVisibleTasks,
+            long activeSessionDurationMs) {
+        mMinNumVisibleTasks = minNumVisibleTasks;
+        mMaxNumVisibleTasks = maxNumVisibleTasks;
+        mActiveTasksSessionDurationMs = activeSessionDurationMs;
+    }
+
+    @VisibleForTesting
+    void setGlobalMaxNumTasks(int globalMaxNumTasks) {
+        mGlobalMaxNumTasks = globalMaxNumTasks;
+    }
+
+    /**
+     * Loads the parameters from the system resources.
+     */
+    @VisibleForTesting
+    void loadParametersFromResources(Resources res) {
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            mMinNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_lowRam);
+            mMaxNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_lowRam);
+        } else if (SystemProperties.getBoolean("ro.recents.grid", false)) {
+            mMinNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_minNumVisibleRecentTasks_grid);
+            mMaxNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks_grid);
+        } else {
+            mMinNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_minNumVisibleRecentTasks);
+            mMaxNumVisibleTasks = res.getInteger(
+                    com.android.internal.R.integer.config_maxNumVisibleRecentTasks);
+        }
+        final int sessionDurationHrs = res.getInteger(
+                com.android.internal.R.integer.config_activeTaskDurationHours);
+        mActiveTasksSessionDurationMs = (sessionDurationHrs > 0)
+                ? TimeUnit.HOURS.toMillis(sessionDurationHrs)
+                : -1;
+    }
+
+    /**
+     * Loads the static recents component.  This is called after the system is ready, but before
+     * any dependent services (like SystemUI) is started.
+     */
+    void loadRecentsComponent(Resources res) {
+        final String rawRecentsComponent = res.getString(
+                com.android.internal.R.string.config_recentsComponentName);
+        if (TextUtils.isEmpty(rawRecentsComponent)) {
+            return;
+        }
+
+        final ComponentName cn = ComponentName.unflattenFromString(rawRecentsComponent);
+        if (cn != null) {
+            try {
+                final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+                        .getApplicationInfo(cn.getPackageName(), 0, mService.mContext.getUserId());
+                if (appInfo != null) {
+                    mRecentsUid = appInfo.uid;
+                    mRecentsComponent = cn;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Could not load application info for recents component: " + cn);
+            }
+        }
+    }
+
+    /**
+     * @return whether the current caller has the same uid as the recents component.
+     */
+    boolean isCallerRecents(int callingUid) {
+        return UserHandle.isSameApp(callingUid, mRecentsUid);
+    }
+
+    /**
+     * @return whether the given component is the recents component and shares the same uid as the
+     *         recents component.
+     */
+    boolean isRecentsComponent(ComponentName cn, int uid) {
+        return cn.equals(mRecentsComponent) && UserHandle.isSameApp(uid, mRecentsUid);
+    }
+
+    /**
+     * @return the recents component.
+     */
+    ComponentName getRecentsComponent() {
+        return mRecentsComponent;
+    }
+
+    /**
+     * @return the uid for the recents component.
+     */
+    int getRecentsComponentUid() {
+        return mRecentsUid;
+    }
+
+    void registerCallback(Callbacks callback) {
+        mCallbacks.add(callback);
+    }
+
+    void unregisterCallback(Callbacks callback) {
+        mCallbacks.remove(callback);
+    }
+
+    private void notifyTaskAdded(TaskRecord task) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onRecentTaskAdded(task);
+        }
+    }
+
+    private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed);
+        }
     }
 
     /**
@@ -106,6 +306,7 @@
      */
     void loadUserRecentsLocked(int userId) {
         if (mUsersWithRecentsLoaded.get(userId)) {
+            // User already loaded, return early
             return;
         }
 
@@ -114,14 +315,14 @@
 
         // Check if any tasks are added before recents is loaded
         final SparseBooleanArray preaddedTasks = new SparseBooleanArray();
-        for (final TaskRecord task : this) {
+        for (final TaskRecord task : mTasks) {
             if (task.userId == userId && shouldPersistTaskLocked(task)) {
                 preaddedTasks.put(task.taskId, true);
             }
         }
 
         Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
-        addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks));
+        mTasks.addAll(mTaskPersister.restoreTasksForUserLocked(userId, preaddedTasks));
         cleanupLocked(userId);
         mUsersWithRecentsLoaded.put(userId, true);
 
@@ -140,11 +341,25 @@
         }
     }
 
-    boolean taskIdTakenForUserLocked(int taskId, int userId) {
+    /**
+     * @return whether the {@param taskId} is currently in use for the given user.
+     */
+    boolean containsTaskId(int taskId, int userId) {
         loadPersistedTaskIdsForUserLocked(userId);
         return mPersistedTaskIds.get(userId).get(taskId);
     }
 
+    /**
+     * @return all the task ids for the user with the given {@param userId}.
+     */
+    SparseBooleanArray getTaskIdsForUser(int userId) {
+        loadPersistedTaskIdsForUserLocked(userId);
+        return mPersistedTaskIds.get(userId);
+    }
+
+    /**
+     * Kicks off the task persister to write any pending tasks to disk.
+     */
     void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
         final ActivityStack stack = task != null ? task.getStack() : null;
         if (stack != null && stack.isHomeOrRecentsStack()) {
@@ -164,8 +379,8 @@
                 mPersistedTaskIds.valueAt(i).clear();
             }
         }
-        for (int i = size() - 1; i >= 0; i--) {
-            final TaskRecord task = get(i);
+        for (int i = mTasks.size() - 1; i >= 0; i--) {
+            final TaskRecord task = mTasks.get(i);
             if (shouldPersistTaskLocked(task)) {
                 // Set of persisted taskIds for task.userId should not be null here
                 // TODO Investigate why it can happen. For now initialize with an empty set
@@ -180,12 +395,13 @@
     }
 
     private static boolean shouldPersistTaskLocked(TaskRecord task) {
-        final ActivityStack<?> stack = task.getStack();
+        final ActivityStack stack = task.getStack();
         return task.isPersistable && (stack == null || !stack.isHomeOrRecentsStack());
     }
 
     void onSystemReadyLocked() {
-        clear();
+        loadRecentsComponent(mService.mContext.getResources());
+        mTasks.clear();
         mTaskPersister.startPersisting();
     }
 
@@ -225,14 +441,6 @@
         return usersWithRecentsLoaded;
     }
 
-    private void unloadUserRecentsLocked(int userId) {
-        if (mUsersWithRecentsLoaded.get(userId)) {
-            Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
-            mUsersWithRecentsLoaded.delete(userId);
-            removeTasksForUserLocked(userId);
-        }
-    }
-
     /**
      * Removes recent tasks and any other state kept in memory for the passed in user. Does not
      * touch the information present on persistent storage.
@@ -240,44 +448,36 @@
      * @param userId the id of the user
      */
     void unloadUserDataFromMemoryLocked(int userId) {
-        unloadUserRecentsLocked(userId);
+        if (mUsersWithRecentsLoaded.get(userId)) {
+            Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
+            mUsersWithRecentsLoaded.delete(userId);
+            removeTasksForUserLocked(userId);
+        }
         mPersistedTaskIds.delete(userId);
         mTaskPersister.unloadUserDataFromMemory(userId);
     }
 
-    TaskRecord taskForIdLocked(int id) {
-        final int recentsCount = size();
-        for (int i = 0; i < recentsCount; i++) {
-            TaskRecord tr = get(i);
-            if (tr.taskId == id) {
-                return tr;
-            }
-        }
-        return null;
-    }
-
     /** Remove recent tasks for a user. */
-    void removeTasksForUserLocked(int userId) {
+    private void removeTasksForUserLocked(int userId) {
         if(userId <= 0) {
             Slog.i(TAG, "Can't remove recent task on user " + userId);
             return;
         }
 
-        for (int i = size() - 1; i >= 0; --i) {
-            TaskRecord tr = get(i);
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            TaskRecord tr = mTasks.get(i);
             if (tr.userId == userId) {
                 if(DEBUG_TASKS) Slog.i(TAG_TASKS,
                         "remove RecentTask " + tr + " when finishing user" + userId);
-                remove(i);
-                tr.removedFromRecents();
+                remove(mTasks.get(i));
             }
         }
     }
 
     void onPackagesSuspendedChanged(String[] packages, boolean suspended, int userId) {
         final Set<String> packageNames = Sets.newHashSet(packages);
-        for (int i = size() - 1; i >= 0; --i) {
-            final TaskRecord tr = get(i);
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
             if (tr.realActivity != null
                     && packageNames.contains(tr.realActivity.getPackageName())
                     && tr.userId == userId
@@ -286,7 +486,36 @@
                notifyTaskPersisterLocked(tr, false);
             }
         }
+    }
 
+    void removeTasksByPackageName(String packageName, int userId) {
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            final String taskPackageName =
+                    tr.getBaseIntent().getComponent().getPackageName();
+            if (tr.userId != userId) return;
+            if (!taskPackageName.equals(packageName)) return;
+
+            mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+        }
+    }
+
+    void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
+            int userId) {
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            if (userId != UserHandle.USER_ALL && tr.userId != userId) {
+                continue;
+            }
+
+            ComponentName cn = tr.intent.getComponent();
+            final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
+                    && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
+            if (sameComponent) {
+                mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+                        REMOVE_FROM_RECENTS);
+            }
+        }
     }
 
     /**
@@ -295,24 +524,28 @@
      * of affiliations.
      */
     void cleanupLocked(int userId) {
-        int recentsCount = size();
+        int recentsCount = mTasks.size();
         if (recentsCount == 0) {
             // Happens when called from the packagemanager broadcast before boot,
             // or just any empty list.
             return;
         }
 
+        // Clear the temp lists
+        mTmpAvailActCache.clear();
+        mTmpAvailAppCache.clear();
+
         final IPackageManager pm = AppGlobals.getPackageManager();
         for (int i = recentsCount - 1; i >= 0; i--) {
-            final TaskRecord task = get(i);
+            final TaskRecord task = mTasks.get(i);
             if (userId != UserHandle.USER_ALL && task.userId != userId) {
                 // Only look at tasks for the user ID of interest.
                 continue;
             }
             if (task.autoRemoveRecents && task.getTopActivity() == null) {
                 // This situation is broken, and we should just get rid of it now.
-                remove(i);
-                task.removedFromRecents();
+                mTasks.remove(i);
+                notifyTaskRemoved(task, !TRIMMED);
                 Slog.w(TAG, "Removing auto-remove without activity: " + task);
                 continue;
             }
@@ -331,11 +564,11 @@
                         continue;
                     }
                     if (ai == null) {
-                        ai = mTmpActivityInfo;
+                        ai = NO_ACTIVITY_INFO_TOKEN;
                     }
                     mTmpAvailActCache.put(task.realActivity, ai);
                 }
-                if (ai == mTmpActivityInfo) {
+                if (ai == NO_ACTIVITY_INFO_TOKEN) {
                     // This could be either because the activity no longer exists, or the
                     // app is temporarily gone. For the former we want to remove the recents
                     // entry; for the latter we want to mark it as unavailable.
@@ -350,15 +583,15 @@
                             continue;
                         }
                         if (app == null) {
-                            app = mTmpAppInfo;
+                            app = NO_APPLICATION_INFO_TOKEN;
                         }
                         mTmpAvailAppCache.put(task.realActivity.getPackageName(), app);
                     }
-                    if (app == mTmpAppInfo
+                    if (app == NO_APPLICATION_INFO_TOKEN
                             || (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
                         // Doesn't exist any more! Good-bye.
-                        remove(i);
-                        task.removedFromRecents();
+                        mTasks.remove(i);
+                        notifyTaskRemoved(task, !TRIMMED);
                         Slog.w(TAG, "Removing no longer valid recent: " + task);
                         continue;
                     } else {
@@ -390,15 +623,670 @@
 
         // Verify the affiliate chain for each task.
         int i = 0;
-        recentsCount = size();
+        recentsCount = mTasks.size();
         while (i < recentsCount) {
             i = processNextAffiliateChainLocked(i);
         }
         // recent tasks are now in sorted, affiliated order.
     }
 
-    private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
-        int recentsCount = size();
+    /**
+     * @return whether the given {@param task} can be added to the list without causing another
+     * task to be trimmed as a result of that add.
+     */
+    private boolean canAddTaskWithoutTrim(TaskRecord task) {
+        return findTrimIndexForAddTask(task) == -1;
+    }
+
+    /**
+     * Returns the list of {@link ActivityManager.AppTask}s.
+     */
+    ArrayList<IBinder> getAppTasksList(int callingUid, String callingPackage) {
+        final ArrayList<IBinder> list = new ArrayList<>();
+        final int size = mTasks.size();
+        for (int i = 0; i < size; i++) {
+            final TaskRecord tr = mTasks.get(i);
+            // Skip tasks that do not match the caller.  We don't need to verify
+            // callingPackage, because we are also limiting to callingUid and know
+            // that will limit to the correct security sandbox.
+            if (tr.effectiveUid != callingUid) {
+                continue;
+            }
+            Intent intent = tr.getBaseIntent();
+            if (intent == null || !callingPackage.equals(intent.getComponent().getPackageName())) {
+                continue;
+            }
+            ActivityManager.RecentTaskInfo taskInfo = createRecentTaskInfo(tr);
+            AppTaskImpl taskImpl = new AppTaskImpl(mService, taskInfo.persistentId, callingUid);
+            list.add(taskImpl.asBinder());
+        }
+        return list;
+    }
+
+    /**
+     * @return the list of recent tasks for presentation.
+     */
+    ParceledListSlice<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+            boolean getTasksAllowed, boolean getDetailedTasks, int userId, int callingUid) {
+        final boolean withExcluded = (flags & RECENT_WITH_EXCLUDED) != 0;
+
+        if (!mService.isUserRunning(userId, FLAG_AND_UNLOCKED)) {
+            Slog.i(TAG, "user " + userId + " is still locked. Cannot load recents");
+            return ParceledListSlice.emptyList();
+        }
+        loadUserRecentsLocked(userId);
+
+        final Set<Integer> includedUsers = mUserController.getProfileIds(userId);
+        includedUsers.add(Integer.valueOf(userId));
+
+        final ArrayList<ActivityManager.RecentTaskInfo> res = new ArrayList<>();
+        final int size = mTasks.size();
+        int numVisibleTasks = 0;
+        for (int i = 0; i < size; i++) {
+            final TaskRecord tr = mTasks.get(i);
+
+            if (isVisibleRecentTask(tr)) {
+                numVisibleTasks++;
+                if (isInVisibleRange(tr, numVisibleTasks)) {
+                    // Fall through
+                } else {
+                    // Not in visible range
+                    continue;
+                }
+            } else {
+                // Not visible
+                continue;
+            }
+
+            // Skip remaining tasks once we reach the requested size
+            if (res.size() >= maxNum) {
+                continue;
+            }
+
+            // Only add calling user or related users recent tasks
+            if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
+                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not user: " + tr);
+                continue;
+            }
+
+            if (tr.realActivitySuspended) {
+                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, activity suspended: " + tr);
+                continue;
+            }
+
+            // Return the entry if desired by the caller.  We always return
+            // the first entry, because callers always expect this to be the
+            // foreground app.  We may filter others if the caller has
+            // not supplied RECENT_WITH_EXCLUDED and there is some reason
+            // we should exclude the entry.
+
+            if (i == 0
+                    || withExcluded
+                    || (tr.intent == null)
+                    || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                    == 0)) {
+                if (!getTasksAllowed) {
+                    // If the caller doesn't have the GET_TASKS permission, then only
+                    // allow them to see a small subset of tasks -- their own and home.
+                    if (!tr.isActivityTypeHome() && tr.effectiveUid != callingUid) {
+                        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "Skipping, not allowed: " + tr);
+                        continue;
+                    }
+                }
+                if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
+                    // Don't include auto remove tasks that are finished or finishing.
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "Skipping, auto-remove without activity: " + tr);
+                    continue;
+                }
+                if ((flags & RECENT_IGNORE_UNAVAILABLE) != 0 && !tr.isAvailable) {
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "Skipping, unavail real act: " + tr);
+                    continue;
+                }
+
+                if (!tr.mUserSetupComplete) {
+                    // Don't include task launched while user is not done setting-up.
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "Skipping, user setup not complete: " + tr);
+                    continue;
+                }
+
+                final ActivityManager.RecentTaskInfo rti = createRecentTaskInfo(tr);
+                if (!getDetailedTasks) {
+                    rti.baseIntent.replaceExtras((Bundle)null);
+                }
+
+                res.add(rti);
+            }
+        }
+        return new ParceledListSlice<>(res);
+    }
+
+    /**
+     * @return the list of persistable task ids.
+     */
+    void getPersistableTaskIds(ArraySet<Integer> persistentTaskIds) {
+        final int size = mTasks.size();
+        for (int i = 0; i < size; i++) {
+            final TaskRecord task = mTasks.get(i);
+            if (TaskPersister.DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task
+                    + " persistable=" + task.isPersistable);
+            final ActivityStack stack = task.getStack();
+            if ((task.isPersistable || task.inRecents)
+                    && (stack == null || !stack.isHomeOrRecentsStack())) {
+                if (TaskPersister.DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+                persistentTaskIds.add(task.taskId);
+            } else {
+                if (TaskPersister.DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task="
+                        + task);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    ArrayList<TaskRecord> getRawTasks() {
+        return mTasks;
+    }
+
+    /**
+     * @return the task in the task list with the given {@param id} if one exists.
+     */
+    TaskRecord getTask(int id) {
+        final int recentsCount = mTasks.size();
+        for (int i = 0; i < recentsCount; i++) {
+            TaskRecord tr = mTasks.get(i);
+            if (tr.taskId == id) {
+                return tr;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a new task to the recent tasks list.
+     */
+    void add(TaskRecord task) {
+        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task);
+
+        final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
+                || task.mNextAffiliateTaskId != INVALID_TASK_ID
+                || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
+
+        int recentsCount = mTasks.size();
+        // Quick case: never add voice sessions.
+        // TODO: VI what about if it's just an activity?
+        // Probably nothing to do here
+        if (task.voiceSession != null) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                    "addRecent: not adding voice interaction " + task);
+            return;
+        }
+        // Another quick case: check if the top-most recent task is the same.
+        if (!isAffiliated && recentsCount > 0 && mTasks.get(0) == task) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
+            return;
+        }
+        // Another quick case: check if this is part of a set of affiliated
+        // tasks that are at the top.
+        if (isAffiliated && recentsCount > 0 && task.inRecents
+                && task.mAffiliatedTaskId == mTasks.get(0).mAffiliatedTaskId) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + mTasks.get(0)
+                    + " at top when adding " + task);
+            return;
+        }
+
+        boolean needAffiliationFix = false;
+
+        // Slightly less quick case: the task is already in recents, so all we need
+        // to do is move it.
+        if (task.inRecents) {
+            int taskIndex = mTasks.indexOf(task);
+            if (taskIndex >= 0) {
+                if (!isAffiliated || !MOVE_AFFILIATED_TASKS_TO_FRONT) {
+                    // Simple case: this is not an affiliated task, so we just move it to the front.
+                    mTasks.remove(taskIndex);
+                    mTasks.add(0, task);
+                    notifyTaskPersisterLocked(task, false);
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
+                            + " from " + taskIndex);
+                    return;
+                } else {
+                    // More complicated: need to keep all affiliated tasks together.
+                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
+                        // All went well.
+                        return;
+                    }
+
+                    // Uh oh...  something bad in the affiliation chain, try to rebuild
+                    // everything and then go through our general path of adding a new task.
+                    needAffiliationFix = true;
+                }
+            } else {
+                Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
+                needAffiliationFix = true;
+            }
+        }
+
+        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
+        trimForAddTask(task);
+
+        task.inRecents = true;
+        if (!isAffiliated || needAffiliationFix) {
+            // If this is a simple non-affiliated task, or we had some failure trying to
+            // handle it as part of an affilated task, then just place it at the top.
+            mTasks.add(0, task);
+            notifyTaskAdded(task);
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
+        } else if (isAffiliated) {
+            // If this is a new affiliated task, then move all of the affiliated tasks
+            // to the front and insert this new one.
+            TaskRecord other = task.mNextAffiliate;
+            if (other == null) {
+                other = task.mPrevAffiliate;
+            }
+            if (other != null) {
+                int otherIndex = mTasks.indexOf(other);
+                if (otherIndex >= 0) {
+                    // Insert new task at appropriate location.
+                    int taskIndex;
+                    if (other == task.mNextAffiliate) {
+                        // We found the index of our next affiliation, which is who is
+                        // before us in the list, so add after that point.
+                        taskIndex = otherIndex+1;
+                    } else {
+                        // We found the index of our previous affiliation, which is who is
+                        // after us in the list, so add at their position.
+                        taskIndex = otherIndex;
+                    }
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "addRecent: new affiliated task added at " + taskIndex + ": " + task);
+                    mTasks.add(taskIndex, task);
+                    notifyTaskAdded(task);
+
+                    // Now move everything to the front.
+                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
+                        // All went well.
+                        return;
+                    }
+
+                    // Uh oh...  something bad in the affiliation chain, try to rebuild
+                    // everything and then go through our general path of adding a new task.
+                    needAffiliationFix = true;
+                } else {
+                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                            "addRecent: couldn't find other affiliation " + other);
+                    needAffiliationFix = true;
+                }
+            } else {
+                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
+                        "addRecent: adding affiliated task without next/prev:" + task);
+                needAffiliationFix = true;
+            }
+        }
+
+        if (needAffiliationFix) {
+            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
+            cleanupLocked(task.userId);
+        }
+
+        // Trim the set of tasks to the active set
+        trimInactiveRecentTasks();
+    }
+
+    /**
+     * Add the task to the bottom if possible.
+     */
+    boolean addToBottom(TaskRecord task) {
+        if (!canAddTaskWithoutTrim(task)) {
+            // Adding this task would cause the task to be removed (since it's appended at
+            // the bottom and would be trimmed) so just return now
+            return false;
+        }
+
+        add(task);
+        return true;
+    }
+
+    /**
+     * Remove a task from the recent tasks list.
+     */
+    void remove(TaskRecord task) {
+        mTasks.remove(task);
+        notifyTaskRemoved(task, !TRIMMED);
+    }
+
+    /**
+     * Trims the recents task list to the global max number of recents.
+     */
+    private void trimInactiveRecentTasks() {
+        int recentsCount = mTasks.size();
+
+        // Remove from the end of the list until we reach the max number of recents
+        while (recentsCount > mGlobalMaxNumTasks) {
+            final TaskRecord tr = mTasks.remove(recentsCount - 1);
+            notifyTaskRemoved(tr, TRIMMED);
+            recentsCount--;
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + tr
+                    + " max=" + mGlobalMaxNumTasks);
+        }
+
+        // Remove any tasks that belong to currently quiet profiles
+        final int[] profileUserIds = mUserController.getCurrentProfileIds();
+        mTmpQuietProfileUserIds.clear();
+        for (int userId : profileUserIds) {
+            final UserInfo userInfo = mUserController.getUserInfo(userId);
+            if (userInfo != null && userInfo.isManagedProfile() && userInfo.isQuietModeEnabled()) {
+                mTmpQuietProfileUserIds.put(userId, true);
+            }
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "User: " + userInfo
+                    + " quiet=" + mTmpQuietProfileUserIds.get(userId));
+        }
+
+        // Remove any inactive tasks, calculate the latest set of visible tasks
+        int numVisibleTasks = 0;
+        for (int i = 0; i < mTasks.size();) {
+            final TaskRecord task = mTasks.get(i);
+
+            if (isActiveRecentTask(task, mTmpQuietProfileUserIds)) {
+                if (!mHasVisibleRecentTasks) {
+                    // Keep all active tasks if visible recent tasks is not supported
+                    i++;
+                    continue;
+                }
+
+                if (!isVisibleRecentTask(task)) {
+                    // Keep all active-but-invisible tasks
+                    i++;
+                    continue;
+                } else {
+                    numVisibleTasks++;
+                    if (isInVisibleRange(task, numVisibleTasks)) {
+                        // Keep visible tasks in range
+                        i++;
+                        continue;
+                    } else {
+                        // Fall through to trim visible tasks that are no longer in range
+                        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
+                                "Trimming out-of-range visible task=" + task);
+                    }
+                }
+            } else {
+                // Fall through to trim inactive tasks
+                if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming inactive task=" + task);
+            }
+
+            // Task is no longer active, trim it from the list
+            mTasks.remove(task);
+            notifyTaskRemoved(task, TRIMMED);
+            notifyTaskPersisterLocked(task, false /* flush */);
+        }
+    }
+
+    /**
+     * @return whether the given task should be considered active.
+     */
+    private boolean isActiveRecentTask(TaskRecord task, SparseBooleanArray quietProfileUserIds) {
+        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isActiveRecentTask: task=" + task
+                + " globalMax=" + mGlobalMaxNumTasks);
+
+        if (quietProfileUserIds.get(task.userId)) {
+            // Quiet profile user's tasks are never active
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\tisQuietProfileTask=true");
+            return false;
+        }
+
+        if (task.mAffiliatedTaskId != INVALID_TASK_ID && task.mAffiliatedTaskId != task.taskId) {
+            // Keep the task active if its affiliated task is also active
+            final TaskRecord affiliatedTask = getTask(task.mAffiliatedTaskId);
+            if (affiliatedTask != null) {
+                if (!isActiveRecentTask(affiliatedTask, quietProfileUserIds)) {
+                    if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
+                            "\taffiliatedWithTask=" + affiliatedTask + " is not active");
+                    return false;
+                }
+            }
+        }
+
+        // All other tasks are considered active
+        return true;
+    }
+
+    /**
+     * @return whether the given active task should be presented to the user through SystemUI.
+     */
+    private boolean isVisibleRecentTask(TaskRecord task) {
+        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "isVisibleRecentTask: task=" + task
+                + " minVis=" + mMinNumVisibleTasks + " maxVis=" + mMaxNumVisibleTasks
+                + " sessionDuration=" + mActiveTasksSessionDurationMs
+                + " inactiveDuration=" + task.getInactiveDuration()
+                + " activityType=" + task.getActivityType()
+                + " windowingMode=" + task.getWindowingMode());
+
+        // Ignore certain activity types completely
+        switch (task.getActivityType()) {
+            case ACTIVITY_TYPE_HOME:
+            case ACTIVITY_TYPE_RECENTS:
+                return false;
+        }
+
+        // Ignore certain windowing modes
+        switch (task.getWindowingMode()) {
+            case WINDOWING_MODE_PINNED:
+                return false;
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\ttop=" + task.getStack().topTask());
+                final ActivityStack stack = task.getStack();
+                if (stack != null && stack.topTask() == task) {
+                    // Only the non-top task of the primary split screen mode is visible
+                    return false;
+                }
+        }
+
+        return true;
+    }
+
+    /**
+     * @return whether the given visible task is within the policy range.
+     */
+    private boolean isInVisibleRange(TaskRecord task, int numVisibleTasks) {
+        // Keep the last most task even if it is excluded from recents
+        final boolean isExcludeFromRecents =
+                (task.getBaseIntent().getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+                        == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+        if (isExcludeFromRecents) {
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "\texcludeFromRecents=true");
+            return numVisibleTasks == 1;
+        }
+
+        if (mMinNumVisibleTasks >= 0 && numVisibleTasks <= mMinNumVisibleTasks) {
+            // Always keep up to the min number of recent tasks, after that fall through to the
+            // checks below
+            return true;
+        }
+
+        if (mMaxNumVisibleTasks >= 0) {
+            // Always keep up to the max number of recent tasks, but return false afterwards
+            return numVisibleTasks <= mMaxNumVisibleTasks;
+        }
+
+        if (mActiveTasksSessionDurationMs > 0) {
+            // Keep the task if the inactive time is within the session window, this check must come
+            // after the checks for the min/max visible task range
+            if (task.getInactiveDuration() <= mActiveTasksSessionDurationMs) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * If needed, remove oldest existing entries in recents that are for the same kind
+     * of task as the given one.
+     */
+    private void trimForAddTask(TaskRecord task) {
+        final int removeIndex = findTrimIndexForAddTask(task);
+        if (removeIndex == -1) {
+            // Nothing to trim
+            return;
+        }
+
+        // There is a similar task that will be removed for the addition of {@param task}, but it
+        // can be the same task, and if so, the task will be re-added in add(), so skip the
+        // callbacks here.
+        final TaskRecord removedTask = mTasks.remove(removeIndex);
+        if (removedTask != task) {
+            notifyTaskRemoved(removedTask, TRIMMED);
+            if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask
+                    + " for addition of task=" + task);
+        }
+        notifyTaskPersisterLocked(removedTask, false /* flush */);
+    }
+
+    /**
+     * Find the task that would be removed if the given {@param task} is added to the recent tasks
+     * list (if any).
+     */
+    private int findTrimIndexForAddTask(TaskRecord task) {
+        int recentsCount = mTasks.size();
+        final Intent intent = task.intent;
+        final boolean document = intent != null && intent.isDocument();
+        int maxRecents = task.maxRecents - 1;
+        final ActivityStack stack = task.getStack();
+        for (int i = 0; i < recentsCount; i++) {
+            final TaskRecord tr = mTasks.get(i);
+            final ActivityStack trStack = tr.getStack();
+
+            if (task != tr) {
+                if (stack != null && trStack != null && stack != trStack) {
+                    continue;
+                }
+                if (task.userId != tr.userId) {
+                    continue;
+                }
+                final Intent trIntent = tr.intent;
+                final boolean sameAffinity =
+                        task.affinity != null && task.affinity.equals(tr.affinity);
+                final boolean sameIntent = intent != null && intent.filterEquals(trIntent);
+                boolean multiTasksAllowed = false;
+                final int flags = intent.getFlags();
+                if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
+                        && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+                    multiTasksAllowed = true;
+                }
+                final boolean trIsDocument = trIntent != null && trIntent.isDocument();
+                final boolean bothDocuments = document && trIsDocument;
+                if (!sameAffinity && !sameIntent && !bothDocuments) {
+                    continue;
+                }
+
+                if (bothDocuments) {
+                    // Do these documents belong to the same activity?
+                    final boolean sameActivity = task.realActivity != null
+                            && tr.realActivity != null
+                            && task.realActivity.equals(tr.realActivity);
+                    if (!sameActivity) {
+                        // If the document is open in another app or is not the same document, we
+                        // don't need to trim it.
+                        continue;
+                    } else if (maxRecents > 0) {
+                        // Otherwise only trim if we are over our max recents for this task
+                        --maxRecents;
+                        if (!sameIntent || multiTasksAllowed) {
+                            // We don't want to trim if we are not over the max allowed entries and
+                            // the tasks are not of the same intent filter, or multiple entries for
+                            // the task is allowed.
+                            continue;
+                        }
+                    }
+                    // Hit the maximum number of documents for this task. Fall through
+                    // and remove this document from recents.
+                } else if (document || trIsDocument) {
+                    // Only one of these is a document. Not the droid we're looking for.
+                    continue;
+                }
+            }
+            return i;
+        }
+        return -1;
+    }
+
+    // Extract the affiliates of the chain containing recent at index start.
+    private int processNextAffiliateChainLocked(int start) {
+        final TaskRecord startTask = mTasks.get(start);
+        final int affiliateId = startTask.mAffiliatedTaskId;
+
+        // Quick identification of isolated tasks. I.e. those not launched behind.
+        if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
+                startTask.mNextAffiliate == null) {
+            // There is still a slim chance that there are other tasks that point to this task
+            // and that the chain is so messed up that this task no longer points to them but
+            // the gain of this optimization outweighs the risk.
+            startTask.inRecents = true;
+            return start + 1;
+        }
+
+        // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
+        mTmpRecents.clear();
+        for (int i = mTasks.size() - 1; i >= start; --i) {
+            final TaskRecord task = mTasks.get(i);
+            if (task.mAffiliatedTaskId == affiliateId) {
+                mTasks.remove(i);
+                mTmpRecents.add(task);
+            }
+        }
+
+        // Sort them all by taskId. That is the order they were create in and that order will
+        // always be correct.
+        Collections.sort(mTmpRecents, TASK_ID_COMPARATOR);
+
+        // Go through and fix up the linked list.
+        // The first one is the end of the chain and has no next.
+        final TaskRecord first = mTmpRecents.get(0);
+        first.inRecents = true;
+        if (first.mNextAffiliate != null) {
+            Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
+            first.setNextAffiliate(null);
+            notifyTaskPersisterLocked(first, false);
+        }
+        // Everything in the middle is doubly linked from next to prev.
+        final int tmpSize = mTmpRecents.size();
+        for (int i = 0; i < tmpSize - 1; ++i) {
+            final TaskRecord next = mTmpRecents.get(i);
+            final TaskRecord prev = mTmpRecents.get(i + 1);
+            if (next.mPrevAffiliate != prev) {
+                Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
+                        " setting prev=" + prev);
+                next.setPrevAffiliate(prev);
+                notifyTaskPersisterLocked(next, false);
+            }
+            if (prev.mNextAffiliate != next) {
+                Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
+                        " setting next=" + next);
+                prev.setNextAffiliate(next);
+                notifyTaskPersisterLocked(prev, false);
+            }
+            prev.inRecents = true;
+        }
+        // The last one is the beginning of the list and has no prev.
+        final TaskRecord last = mTmpRecents.get(tmpSize - 1);
+        if (last.mPrevAffiliate != null) {
+            Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
+            last.setPrevAffiliate(null);
+            notifyTaskPersisterLocked(last, false);
+        }
+
+        // Insert the group back into mTmpTasks at start.
+        mTasks.addAll(start, mTmpRecents);
+        mTmpRecents.clear();
+
+        // Let the caller know where we left off.
+        return start + tmpSize;
+    }
+
+    private boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) {
+        int recentsCount = mTasks.size();
         TaskRecord top = task;
         int topIndex = taskIndex;
         while (top.mNextAffiliate != null && topIndex > 0) {
@@ -412,7 +1300,7 @@
         int endIndex = topIndex;
         TaskRecord prev = top;
         while (endIndex < recentsCount) {
-            TaskRecord cur = get(endIndex);
+            TaskRecord cur = mTasks.get(endIndex);
             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: looking at next chain @"
                     + endIndex + " " + cur);
             if (cur == top) {
@@ -487,8 +1375,8 @@
             for (int i=topIndex; i<=endIndex; i++) {
                 if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving affiliated " + task
                         + " from " + i + " to " + (i-topIndex));
-                TaskRecord cur = remove(i);
-                add(i - topIndex, cur);
+                TaskRecord cur = mTasks.remove(i);
+                mTasks.add(i - topIndex, cur);
             }
             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: done moving tasks  " +  topIndex
                     + " to " + endIndex);
@@ -499,301 +1387,75 @@
         return false;
     }
 
-    final void addLocked(TaskRecord task) {
-        final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
-                || task.mNextAffiliateTaskId != INVALID_TASK_ID
-                || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
-
-        int recentsCount = size();
-        // Quick case: never add voice sessions.
-        // TODO: VI what about if it's just an activity?
-        // Probably nothing to do here
-        if (task.voiceSession != null) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                    "addRecent: not adding voice interaction " + task);
-            return;
-        }
-        // Another quick case: check if the top-most recent task is the same.
-        if (!isAffiliated && recentsCount > 0 && get(0) == task) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: already at top: " + task);
-            return;
-        }
-        // Another quick case: check if this is part of a set of affiliated
-        // tasks that are at the top.
-        if (isAffiliated && recentsCount > 0 && task.inRecents
-                && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: affiliated " + get(0)
-                    + " at top when adding " + task);
+    void dump(PrintWriter pw, boolean dumpAll, String dumpPackage) {
+        pw.println("ACTIVITY MANAGER RECENT TASKS (dumpsys activity recents)");
+        pw.println("mRecentsUid=" + mRecentsUid);
+        pw.println("mRecentsComponent=" + mRecentsComponent);
+        if (mTasks.isEmpty()) {
             return;
         }
 
-        boolean needAffiliationFix = false;
+        boolean printedAnything = false;
+        boolean printedHeader = false;
+        final int size = mTasks.size();
+        for (int i = 0; i < size; i++) {
+            final TaskRecord tr = mTasks.get(i);
+            if (dumpPackage != null && (tr.realActivity == null ||
+                    !dumpPackage.equals(tr.realActivity.getPackageName()))) {
+                continue;
+            }
 
-        // Slightly less quick case: the task is already in recents, so all we need
-        // to do is move it.
-        if (task.inRecents) {
-            int taskIndex = indexOf(task);
-            if (taskIndex >= 0) {
-                if (!isAffiliated || MOVE_AFFILIATED_TASKS_TO_FRONT) {
-                    // Simple case: this is not an affiliated task, so we just move it to the front.
-                    remove(taskIndex);
-                    add(0, task);
-                    notifyTaskPersisterLocked(task, false);
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
-                            + " from " + taskIndex);
-                    return;
-                } else {
-                    // More complicated: need to keep all affiliated tasks together.
-                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
-                        // All went well.
-                        return;
-                    }
-
-                    // Uh oh...  something bad in the affiliation chain, try to rebuild
-                    // everything and then go through our general path of adding a new task.
-                    needAffiliationFix = true;
-                }
-            } else {
-                Slog.wtf(TAG, "Task with inRecent not in recents: " + task);
-                needAffiliationFix = true;
+            if (!printedHeader) {
+                pw.println("  Recent tasks:");
+                printedHeader = true;
+                printedAnything = true;
+            }
+            pw.print("  * Recent #"); pw.print(i); pw.print(": ");
+            pw.println(tr);
+            if (dumpAll) {
+                tr.dump(pw, "    ");
             }
         }
 
-        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
-        trimForTaskLocked(task, true);
-
-        recentsCount = size();
-        final int maxRecents = ActivityManager.getMaxRecentTasksStatic();
-        while (recentsCount >= maxRecents) {
-            final TaskRecord tr = remove(recentsCount - 1);
-            tr.removedFromRecents();
-            recentsCount--;
-        }
-        task.inRecents = true;
-        if (!isAffiliated || needAffiliationFix) {
-            // If this is a simple non-affiliated task, or we had some failure trying to
-            // handle it as part of an affilated task, then just place it at the top.
-            add(0, task);
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
-        } else if (isAffiliated) {
-            // If this is a new affiliated task, then move all of the affiliated tasks
-            // to the front and insert this new one.
-            TaskRecord other = task.mNextAffiliate;
-            if (other == null) {
-                other = task.mPrevAffiliate;
-            }
-            if (other != null) {
-                int otherIndex = indexOf(other);
-                if (otherIndex >= 0) {
-                    // Insert new task at appropriate location.
-                    int taskIndex;
-                    if (other == task.mNextAffiliate) {
-                        // We found the index of our next affiliation, which is who is
-                        // before us in the list, so add after that point.
-                        taskIndex = otherIndex+1;
-                    } else {
-                        // We found the index of our previous affiliation, which is who is
-                        // after us in the list, so add at their position.
-                        taskIndex = otherIndex;
-                    }
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                            "addRecent: new affiliated task added at " + taskIndex + ": " + task);
-                    add(taskIndex, task);
-
-                    // Now move everything to the front.
-                    if (moveAffiliatedTasksToFront(task, taskIndex)) {
-                        // All went well.
-                        return;
-                    }
-
-                    // Uh oh...  something bad in the affiliation chain, try to rebuild
-                    // everything and then go through our general path of adding a new task.
-                    needAffiliationFix = true;
-                } else {
-                    if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                            "addRecent: couldn't find other affiliation " + other);
-                    needAffiliationFix = true;
-                }
-            } else {
-                if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
-                        "addRecent: adding affiliated task without next/prev:" + task);
-                needAffiliationFix = true;
-            }
-        }
-
-        if (needAffiliationFix) {
-            if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: regrouping affiliations");
-            cleanupLocked(task.userId);
+        if (!printedAnything) {
+            pw.println("  (nothing)");
         }
     }
 
     /**
-     * If needed, remove oldest existing entries in recents that are for the same kind
-     * of task as the given one.
+     * Creates a new RecentTaskInfo from a TaskRecord.
      */
-    int trimForTaskLocked(TaskRecord task, boolean doTrim) {
-        int recentsCount = size();
-        final Intent intent = task.intent;
-        final boolean document = intent != null && intent.isDocument();
-        int maxRecents = task.maxRecents - 1;
-        final ActivityStack stack = task.getStack();
-        for (int i = 0; i < recentsCount; i++) {
-            final TaskRecord tr = get(i);
-            final ActivityStack trStack = tr.getStack();
-            if (task != tr) {
-                if (stack != null && trStack != null && stack != trStack) {
-                    continue;
-                }
-                if (task.userId != tr.userId) {
-                    continue;
-                }
-                final Intent trIntent = tr.intent;
-                final boolean sameAffinity =
-                        task.affinity != null && task.affinity.equals(tr.affinity);
-                final boolean sameIntentFilter = intent != null && intent.filterEquals(trIntent);
-                boolean multiTasksAllowed = false;
-                final int flags = intent.getFlags();
-                if ((flags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NEW_DOCUMENT)) != 0
-                        && (flags & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
-                    multiTasksAllowed = true;
-                }
-                final boolean trIsDocument = trIntent != null && trIntent.isDocument();
-                final boolean bothDocuments = document && trIsDocument;
-                if (!sameAffinity && !sameIntentFilter && !bothDocuments) {
-                    continue;
-                }
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
+        // Update the task description to reflect any changes in the task stack
+        tr.updateTaskDescription();
 
-                if (bothDocuments) {
-                    // Do these documents belong to the same activity?
-                    final boolean sameActivity = task.realActivity != null
-                            && tr.realActivity != null
-                            && task.realActivity.equals(tr.realActivity);
-                    // If the document is open in another app or is not the same
-                    // document, we don't need to trim it.
-                    if (!sameActivity) {
-                        continue;
-                    // Otherwise only trim if we are over our max recents for this task
-                    } else if (maxRecents > 0) {
-                        --maxRecents;
-                        if (!doTrim || !sameIntentFilter || multiTasksAllowed) {
-                            // We don't want to trim if we are not over the max allowed entries and
-                            // the caller doesn't want us to trim, the tasks are not of the same
-                            // intent filter, or multiple entries fot the task is allowed.
-                            continue;
-                        }
-                    }
-                    // Hit the maximum number of documents for this task. Fall through
-                    // and remove this document from recents.
-                } else if (document || trIsDocument) {
-                    // Only one of these is a document. Not the droid we're looking for.
-                    continue;
-                }
-            }
-
-            if (!doTrim) {
-                // If the caller is not actually asking for a trim, just tell them we reached
-                // a point where the trim would happen.
-                return i;
-            }
-
-            // Either task and tr are the same or, their affinities match or their intents match
-            // and neither of them is a document, or they are documents using the same activity
-            // and their maxRecents has been reached.
-            remove(i);
-            if (task != tr) {
-                tr.removedFromRecents();
-            }
-            i--;
-            recentsCount--;
-            if (task.intent == null) {
-                // If the new recent task we are adding is not fully
-                // specified, then replace it with the existing recent task.
-                task = tr;
-            }
-            notifyTaskPersisterLocked(tr, false);
+        // Compose the recent task info
+        ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
+        rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
+        rti.persistentId = tr.taskId;
+        rti.baseIntent = new Intent(tr.getBaseIntent());
+        rti.origActivity = tr.origActivity;
+        rti.realActivity = tr.realActivity;
+        rti.description = tr.lastDescription;
+        rti.stackId = tr.getStackId();
+        rti.userId = tr.userId;
+        rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
+        rti.lastActiveTime = tr.lastActiveTime;
+        rti.affiliatedTaskId = tr.mAffiliatedTaskId;
+        rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
+        rti.numActivities = 0;
+        if (tr.mBounds != null) {
+            rti.bounds = new Rect(tr.mBounds);
         }
+        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
+        rti.resizeMode = tr.mResizeMode;
+        rti.configuration.setTo(tr.getConfiguration());
 
-        return -1;
-    }
+        tr.getNumRunningActivities(mTmpReport);
+        rti.numActivities = mTmpReport.numActivities;
+        rti.baseActivity = (mTmpReport.base != null) ? mTmpReport.base.intent.getComponent() : null;
+        rti.topActivity = (mTmpReport.top != null) ? mTmpReport.top.intent.getComponent() : null;
 
-    // Sort by taskId
-    private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() {
-        @Override
-        public int compare(TaskRecord lhs, TaskRecord rhs) {
-            return rhs.taskId - lhs.taskId;
-        }
-    };
-
-    // Extract the affiliates of the chain containing recent at index start.
-    private int processNextAffiliateChainLocked(int start) {
-        final TaskRecord startTask = get(start);
-        final int affiliateId = startTask.mAffiliatedTaskId;
-
-        // Quick identification of isolated tasks. I.e. those not launched behind.
-        if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null &&
-                startTask.mNextAffiliate == null) {
-            // There is still a slim chance that there are other tasks that point to this task
-            // and that the chain is so messed up that this task no longer points to them but
-            // the gain of this optimization outweighs the risk.
-            startTask.inRecents = true;
-            return start + 1;
-        }
-
-        // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents.
-        mTmpRecents.clear();
-        for (int i = size() - 1; i >= start; --i) {
-            final TaskRecord task = get(i);
-            if (task.mAffiliatedTaskId == affiliateId) {
-                remove(i);
-                mTmpRecents.add(task);
-            }
-        }
-
-        // Sort them all by taskId. That is the order they were create in and that order will
-        // always be correct.
-        Collections.sort(mTmpRecents, sTaskRecordComparator);
-
-        // Go through and fix up the linked list.
-        // The first one is the end of the chain and has no next.
-        final TaskRecord first = mTmpRecents.get(0);
-        first.inRecents = true;
-        if (first.mNextAffiliate != null) {
-            Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
-            first.setNextAffiliate(null);
-            notifyTaskPersisterLocked(first, false);
-        }
-        // Everything in the middle is doubly linked from next to prev.
-        final int tmpSize = mTmpRecents.size();
-        for (int i = 0; i < tmpSize - 1; ++i) {
-            final TaskRecord next = mTmpRecents.get(i);
-            final TaskRecord prev = mTmpRecents.get(i + 1);
-            if (next.mPrevAffiliate != prev) {
-                Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
-                        " setting prev=" + prev);
-                next.setPrevAffiliate(prev);
-                notifyTaskPersisterLocked(next, false);
-            }
-            if (prev.mNextAffiliate != next) {
-                Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
-                        " setting next=" + next);
-                prev.setNextAffiliate(next);
-                notifyTaskPersisterLocked(prev, false);
-            }
-            prev.inRecents = true;
-        }
-        // The last one is the beginning of the list and has no prev.
-        final TaskRecord last = mTmpRecents.get(tmpSize - 1);
-        if (last.mPrevAffiliate != null) {
-            Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
-            last.setPrevAffiliate(null);
-            notifyTaskPersisterLocked(last, false);
-        }
-
-        // Insert the group back into mRecentTasks at start.
-        addAll(start, mTmpRecents);
-        mTmpRecents.clear();
-
-        // Let the caller know where we left off.
-        return start + tmpSize;
+        return rti;
     }
 }
diff --git a/services/core/java/com/android/server/am/RunningTasks.java b/services/core/java/com/android/server/am/RunningTasks.java
new file mode 100644
index 0000000..400b03a
--- /dev/null
+++ b/services/core/java/com/android/server/am/RunningTasks.java
@@ -0,0 +1,102 @@
+/*
+ * 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.server.am;
+
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.WindowConfiguration.ActivityType;
+import android.app.WindowConfiguration.WindowingMode;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Class for resolving the set of running tasks in the system.
+ */
+class RunningTasks {
+
+    // Comparator to sort by last active time (descending)
+    private static final Comparator<TaskRecord> LAST_ACTIVE_TIME_COMPARATOR =
+            (o1, o2) -> Long.signum(o2.lastActiveTime - o1.lastActiveTime);
+
+    private final TaskRecord.TaskActivitiesReport mTmpReport =
+            new TaskRecord.TaskActivitiesReport();
+    private final TreeSet<TaskRecord> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+    private final ArrayList<TaskRecord> mTmpStackTasks = new ArrayList<>();
+
+    void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
+            @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
+            int callingUid, boolean allowed) {
+        // For each stack on each display, add the tasks into the sorted set and then pull the first
+        // {@param maxNum} from the set
+
+        // Gather all of the tasks across all of the tasks, and add them to the sorted set
+        mTmpSortedSet.clear();
+        mTmpStackTasks.clear();
+        final int numDisplays = activityDisplays.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final ActivityDisplay display = activityDisplays.valueAt(displayNdx);
+            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = display.getChildAt(stackNdx);
+                stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
+                        callingUid, allowed);
+                for (int i = mTmpStackTasks.size() - 1; i >= 0; i--) {
+                    mTmpSortedSet.addAll(mTmpStackTasks);
+                }
+            }
+        }
+
+        // Take the first {@param maxNum} tasks and create running task infos for them
+        final Iterator<TaskRecord> iter = mTmpSortedSet.iterator();
+        while (iter.hasNext()) {
+            if (maxNum == 0) {
+                break;
+            }
+
+            final TaskRecord task = iter.next();
+            list.add(createRunningTaskInfo(task));
+            maxNum--;
+        }
+    }
+
+    /**
+     * Constructs a {@link RunningTaskInfo} from a given {@param task}.
+     */
+    private RunningTaskInfo createRunningTaskInfo(TaskRecord task) {
+        task.getNumRunningActivities(mTmpReport);
+
+        final RunningTaskInfo ci = new RunningTaskInfo();
+        ci.id = task.taskId;
+        ci.stackId = task.getStackId();
+        ci.baseActivity = mTmpReport.base.intent.getComponent();
+        ci.topActivity = mTmpReport.top.intent.getComponent();
+        ci.lastActiveTime = task.lastActiveTime;
+        ci.description = task.lastDescription;
+        ci.numActivities = mTmpReport.numActivities;
+        ci.numRunning = mTmpReport.numRunning;
+        ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
+        ci.resizeMode = task.mResizeMode;
+        ci.configuration.setTo(task.getConfiguration());
+        return ci;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 027dc08..16995e5 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -33,6 +33,7 @@
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -517,6 +518,22 @@
                             } catch (PackageManager.NameNotFoundException e) {
                             }
                         }
+                        if (nm.getNotificationChannel(localPackageName, appUid,
+                                localForegroundNoti.getChannelId()) == null) {
+                            int targetSdkVersion = Build.VERSION_CODES.O_MR1;
+                            try {
+                                final ApplicationInfo applicationInfo =
+                                        ams.mContext.getPackageManager().getApplicationInfoAsUser(
+                                                appInfo.packageName, 0, userId);
+                                targetSdkVersion = applicationInfo.targetSdkVersion;
+                            } catch (PackageManager.NameNotFoundException e) {
+                            }
+                            if (targetSdkVersion >= Build.VERSION_CODES.O_MR1) {
+                                throw new RuntimeException(
+                                        "invalid channel for service notification: "
+                                                + foregroundNoti);
+                            }
+                        }
                         if (localForegroundNoti.getSmallIcon() == null) {
                             // Notifications whose icon is 0 are defined to not show
                             // a notification, silently ignoring it.  We don't want to
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 6a986bb..5a7e7ce 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -95,7 +95,8 @@
     };
 
     private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
-        l.onActivityPinned((String) m.obj, m.arg1, m.arg2);
+        final ActivityRecord r = (ActivityRecord) m.obj;
+        l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
     };
 
     private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -278,10 +279,9 @@
     }
 
     /** Notifies all listeners when an Activity is pinned. */
-    void notifyActivityPinned(String packageName, int userId, int taskId) {
+    void notifyActivityPinned(ActivityRecord r) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
-        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
-                userId, taskId, packageName);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
     }
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index f6e20cd..2689d6a 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -54,9 +54,6 @@
 import java.util.Comparator;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
 
 public class TaskPersister {
@@ -570,7 +567,7 @@
         SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>();
         synchronized (mService) {
             for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) {
-                SparseBooleanArray taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId);
+                SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForUser(userId);
                 SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId);
                 if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) {
                     continue;
@@ -643,7 +640,7 @@
         @Override
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
+            ArraySet<Integer> persistentTaskIds = new ArraySet<>();
             while (true) {
                 // We can't lock mService while holding TaskPersister.this, but we don't want to
                 // call removeObsoleteFiles every time through the loop, only the last time before
@@ -657,20 +654,7 @@
                     persistentTaskIds.clear();
                     synchronized (mService) {
                         if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
-                        for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-                            final TaskRecord task = mRecentTasks.get(taskNdx);
-                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
-                                    " persistable=" + task.isPersistable);
-                            final ActivityStack stack = task.getStack();
-                            if ((task.isPersistable || task.inRecents)
-                                    && (stack == null || !stack.isHomeOrRecentsStack())) {
-                                if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
-                                persistentTaskIds.add(task.taskId);
-                            } else {
-                                if (DEBUG) Slog.d(TAG,
-                                        "omitting from persistentTaskIds task=" + task);
-                            }
-                        }
+                        mRecentTasks.getPersistableTaskIds(persistentTaskIds);
                         mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds,
                                 mRecentTasks.usersWithRecentsLoadedLocked());
                     }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index fb8b034..1a4f9d4 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -18,22 +18,21 @@
 
 import static android.app.ActivityManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -46,7 +45,6 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE_DEPRECATED;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -64,6 +62,7 @@
 import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.proto.TaskRecordProto.ACTIVITIES;
@@ -77,7 +76,6 @@
 import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
 import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
 import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
-import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
 import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
 import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
 
@@ -87,7 +85,6 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
@@ -103,6 +100,7 @@
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -114,6 +112,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.util.XmlUtils;
+import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.wm.AppWindowContainerController;
 import com.android.server.wm.ConfigurationContainer;
 import com.android.server.wm.StackWindowController;
@@ -156,8 +155,6 @@
     private static final String ATTR_EFFECTIVE_UID = "effective_uid";
     @Deprecated
     private static final String ATTR_TASKTYPE = "task_type";
-    private static final String ATTR_FIRSTACTIVETIME = "first_active_time";
-    private static final String ATTR_LASTACTIVETIME = "last_active_time";
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
     private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
@@ -169,7 +166,6 @@
     private static final String ATTR_CALLING_PACKAGE = "calling_package";
     private static final String ATTR_SUPPORTS_PICTURE_IN_PICTURE = "supports_picture_in_picture";
     private static final String ATTR_RESIZE_MODE = "resize_mode";
-    private static final String ATTR_PRIVILEGED = "privileged";
     private static final String ATTR_NON_FULLSCREEN_BOUNDS = "non_fullscreen_bounds";
     private static final String ATTR_MIN_WIDTH = "min_width";
     private static final String ATTR_MIN_HEIGHT = "min_height";
@@ -178,7 +174,6 @@
     // Current version of the task record we persist. Used to check if we need to run any upgrade
     // code.
     private static final int PERSIST_TASK_VERSION = 1;
-    private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     static final int INVALID_TASK_ID = -1;
     private static final int INVALID_MIN_SIZE = -1;
@@ -193,13 +188,13 @@
             REPARENT_KEEP_STACK_AT_FRONT,
             REPARENT_LEAVE_STACK_IN_PLACE
     })
-    public @interface ReparentMoveStackMode {}
+    @interface ReparentMoveStackMode {}
     // Moves the stack to the front if it was not at the front
-    public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+    static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
     // Only moves the stack to the front if it was focused or front most already
-    public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+    static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
     // Do not move the stack as a part of reparenting
-    public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+    static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
 
     final int taskId;       // Unique identifier for this task.
     String affinity;        // The affinity name for this task, or null; may change identity.
@@ -213,9 +208,10 @@
     ComponentName realActivity; // The actual activity component that started the task.
     boolean realActivitySuspended; // True if the actual activity component that started the
                                    // task is suspended.
-    long firstActiveTime;   // First time this task was active.
-    long lastActiveTime;    // Last time this task was active, including sleep.
     boolean inRecents;      // Actually in the recents list?
+    long lastActiveTime;    // Last time this task was active in the current device session,
+                            // including sleep. This time is initialized to the elapsed time when
+                            // restored from disk.
     boolean isAvailable;    // Is the activity available to be launched?
     boolean rootWasReset;   // True if the intent at the root of the task had
                             // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
@@ -236,13 +232,6 @@
     private boolean mSupportsPictureInPicture;  // Whether or not this task and its activities
             // support PiP. Based on the {@link ActivityInfo#FLAG_SUPPORTS_PICTURE_IN_PICTURE} flag
             // of the root activity.
-    boolean mTemporarilyUnresizable; // Separate flag from mResizeMode used to suppress resize
-                                     // changes on a temporary basis.
-    private int mLockTaskMode;  // Which tasklock mode to launch this task in. One of
-                                // ActivityManager.LOCK_TASK_LAUNCH_MODE_*
-    private boolean mPrivileged;    // The root activity application of this task holds
-                                    // privileged permissions.
-
     /** Can't be put in lockTask mode. */
     final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
     /** Can enter app pinning with user approval. Can never start over existing lockTask task. */
@@ -277,12 +266,6 @@
      * (positive) or back (negative). Absolute value indicates time. */
     long mLastTimeMoved = System.currentTimeMillis();
 
-    /** Indication of what to run next when task exits. */
-    // TODO: Shouldn't be needed if we have things in visual order. I.e. we stop using stacks or
-    // have a stack per standard application type...
-    /*@WindowConfiguration.ActivityType*/
-    private int mTaskToReturnTo = ACTIVITY_TYPE_STANDARD;
-
     /** If original intent did not allow relinquishing task identity, save that information */
     private boolean mNeverRelinquishIdentity = true;
 
@@ -290,7 +273,6 @@
     // do not want to delete the stack when the task goes empty.
     private boolean mReuseTask = false;
 
-    private final String mFilename;
     CharSequence lastDescription; // Last description captured for this item.
 
     int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
@@ -336,10 +318,9 @@
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
+        lastActiveTime = SystemClock.elapsedRealtime();
         mAffiliatedTaskId = _taskId;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
@@ -356,10 +337,9 @@
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             TaskDescription _taskDescription) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         userId = UserHandle.getUserId(info.applicationInfo.uid);
         taskId = _taskId;
+        lastActiveTime = SystemClock.elapsedRealtime();
         mAffiliatedTaskId = _taskId;
         voiceSession = null;
         voiceInteractor = null;
@@ -375,7 +355,6 @@
         maxRecents = Math.min(Math.max(info.maxRecents, 1),
                 ActivityManager.getMaxAppRecentsLimitStatic());
 
-        mTaskToReturnTo = ACTIVITY_TYPE_HOME;
         lastTaskDescription = _taskDescription;
         touchActiveTime();
         mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -386,15 +365,12 @@
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
             boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId,
             int _effectiveUid, String _lastDescription, ArrayList<ActivityRecord> activities,
-            long _firstActiveTime, long _lastActiveTime, long lastTimeMoved,
-            boolean neverRelinquishIdentity, TaskDescription _lastTaskDescription,
-            int taskAffiliation, int prevTaskId, int nextTaskId, int taskAffiliationColor,
-            int callingUid, String callingPackage, int resizeMode, boolean supportsPictureInPicture,
-            boolean privileged, boolean _realActivitySuspended, boolean userSetupComplete,
-            int minWidth, int minHeight) {
+            long lastTimeMoved, boolean neverRelinquishIdentity,
+            TaskDescription _lastTaskDescription, int taskAffiliation, int prevTaskId,
+            int nextTaskId, int taskAffiliationColor, int callingUid, String callingPackage,
+            int resizeMode, boolean supportsPictureInPicture, boolean _realActivitySuspended,
+            boolean userSetupComplete, int minWidth, int minHeight) {
         mService = service;
-        mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
-                TaskPersister.IMAGE_EXTENSION;
         taskId = _taskId;
         intent = _intent;
         affinityIntent = _affinityIntent;
@@ -409,12 +385,10 @@
         isAvailable = true;
         autoRemoveRecents = _autoRemoveRecents;
         askedCompatMode = _askedCompatMode;
-        mTaskToReturnTo = ACTIVITY_TYPE_HOME;
         userId = _userId;
         mUserSetupComplete = userSetupComplete;
         effectiveUid = _effectiveUid;
-        firstActiveTime = _firstActiveTime;
-        lastActiveTime = _lastActiveTime;
+        lastActiveTime = SystemClock.elapsedRealtime();
         lastDescription = _lastDescription;
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
@@ -428,7 +402,6 @@
         mCallingPackage = callingPackage;
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
-        mPrivileged = privileged;
         mMinWidth = minWidth;
         mMinHeight = minHeight;
         mService.mTaskChangeNotificationController.notifyTaskCreated(_taskId, realActivity);
@@ -465,7 +438,7 @@
     }
 
     void removeWindowContainer() {
-        mService.mLockTaskController.removeLockedTask(this);
+        mService.mLockTaskController.clearLockedTask(this);
         mWindowContainerController.removeContainer();
         if (!getWindowConfiguration().persistTaskBounds()) {
             // Reset current bounds for task whose bounds shouldn't be persisted so it uses
@@ -521,9 +494,9 @@
             // All we can do for now is update the bounds so it can be used when the task is
             // added to window manager.
             updateOverrideConfiguration(bounds);
-            if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+            if (!inFreeformWindowingMode()) {
                 // re-restore the task so it can have the proper stack association.
-                mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
+                mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
             }
             return true;
         }
@@ -573,36 +546,36 @@
     /**
      * Convenience method to reparent a task to the top or bottom position of the stack.
      */
-    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, String reason) {
-        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
-                deferResume, true /* schedulePictureInPictureModeChange */, reason);
+    boolean reparent(ActivityStack preferredStack, boolean toTop,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            String reason) {
+        return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate, deferResume,
+                true /* schedulePictureInPictureModeChange */, reason);
     }
 
     /**
      * Convenience method to reparent a task to the top or bottom position of the stack, with
      * an option to skip scheduling the picture-in-picture mode change.
      */
-    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
-            String reason) {
-        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+    boolean reparent(ActivityStack preferredStack, boolean toTop,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            boolean schedulePictureInPictureModeChange, String reason) {
+        return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate,
                 deferResume, schedulePictureInPictureModeChange, reason);
     }
 
-    /**
-     * Convenience method to reparent a task to a specific position of the stack.
-     */
-    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, String reason) {
-        return reparent(preferredStackId, position, moveStackMode, animate, deferResume,
+    /** Convenience method to reparent a task to a specific position of the stack. */
+    boolean reparent(ActivityStack preferredStack, int position,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            String reason) {
+        return reparent(preferredStack, position, moveStackMode, animate, deferResume,
                 true /* schedulePictureInPictureModeChange */, reason);
     }
 
     /**
      * Reparents the task into a preferred stack, creating it if necessary.
      *
-     * @param preferredStackId the stack id of the target stack to move this task
+     * @param preferredStack the target stack to move this task
      * @param position the position to place this task in the new stack
      * @param animate whether or not we should wait for the new window created as a part of the
      *            reparenting to be drawn and animated in
@@ -616,24 +589,26 @@
      * @param reason the caller of this reparenting
      * @return whether the task was reparented
      */
-    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
-            String reason) {
+    // TODO: Inspect all call sites and change to just changing windowing mode of the stack vs.
+    // re-parenting the task. Can only be done when we are no longer using static stack Ids.
+    boolean reparent(ActivityStack preferredStack, int position,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            boolean schedulePictureInPictureModeChange, String reason) {
         final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
         final WindowManagerService windowManager = mService.mWindowManager;
         final ActivityStack sourceStack = getStack();
-        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack,
                 position == MAX_VALUE);
         if (toStack == sourceStack) {
             return false;
         }
 
-        final int sourceStackId = getStackId();
-        final int stackId = toStack.getStackId();
+        final int toStackWindowingMode = toStack.getWindowingMode();
         final ActivityRecord topActivity = getTopActivity();
 
-        final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)
-                && topActivity != null;
+        final boolean mightReplaceWindow =
+                replaceWindowsOnTaskMove(getWindowingMode(), toStackWindowingMode)
+                        && topActivity != null;
         if (mightReplaceWindow) {
             // We are about to relaunch the activity because its configuration changed due to
             // being maximized, i.e. size change. The activity will first remove the old window
@@ -658,7 +633,7 @@
             // In some cases the focused stack isn't the front stack. E.g. pinned stack.
             // Whenever we are moving the top activity from the front stack we want to make sure to
             // move the stack to the front.
-            final boolean wasFront = r != null && supervisor.isFrontStackOnDisplay(sourceStack)
+            final boolean wasFront = r != null && sourceStack.isTopStackOnDisplay()
                     && (sourceStack.topRunningActivityLocked() == r);
 
             // Adjust the position for the new parent stack as needed.
@@ -705,19 +680,22 @@
             toStack.prepareFreezingTaskBounds();
 
             // Make sure the task has the appropriate bounds/size for the stack it is in.
-            if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
+            final boolean toStackSplitScreenPrimary =
+                    toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+            if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN
+                    || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
                     && !Objects.equals(mBounds, toStack.mBounds)) {
                 kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
                         deferResume);
-            } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+            } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
-                    toStack.layoutTaskInStack(this, null);
+                    mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
                     bounds = mBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
-            } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
-                if (stackId == DOCKED_STACK_ID && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
+            } else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
+                if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
                     // Move recents to front so it is not behind home stack when going into docked
                     // mode
                     mService.mStackSupervisor.moveRecentsStackToFront(reason);
@@ -744,17 +722,30 @@
         }
 
         // TODO: Handle incorrect request to move before the actual move, not after.
-        supervisor.handleNonResizableTaskIfNeeded(this, getWindowingModeForStackId(preferredStackId,
-                        supervisor.getStack(DOCKED_STACK_ID) != null), DEFAULT_DISPLAY, stackId);
+        final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenPrimaryStack();
+        supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
+                DEFAULT_DISPLAY, toStack);
 
-        boolean successful = (preferredStackId == stackId);
-        if (successful && stackId == DOCKED_STACK_ID) {
+        boolean successful = (preferredStack == toStack);
+        if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If task moved to docked stack - show recents if needed.
             mService.mWindowManager.showRecentApps(false /* fromHome */);
         }
         return successful;
     }
 
+    /**
+     * Returns true if the windows of tasks being moved to the target stack from the source
+     * stack should be replaced, meaning that window manager will keep the old window around
+     * until the new is ready.
+     * @hide
+     */
+    private static boolean replaceWindowsOnTaskMove(
+            int sourceWindowingMode, int targetWindowingMode) {
+        return sourceWindowingMode == WINDOWING_MODE_FREEFORM
+                || targetWindowingMode == WINDOWING_MODE_FREEFORM;
+    }
+
     void cancelWindowTransition() {
         mWindowContainerController.cancelWindowTransition();
     }
@@ -774,14 +765,11 @@
     }
 
     void touchActiveTime() {
-        lastActiveTime = System.currentTimeMillis();
-        if (firstActiveTime == 0) {
-            firstActiveTime = lastActiveTime;
-        }
+        lastActiveTime = SystemClock.elapsedRealtime();
     }
 
     long getInactiveDuration() {
-        return System.currentTimeMillis() - lastActiveTime;
+        return SystemClock.elapsedRealtime() - lastActiveTime;
     }
 
     /** Sets the original intent, and the calling uid and package. */
@@ -789,6 +777,7 @@
         mCallingUid = r.launchedFromUid;
         mCallingPackage = r.launchedFromPackage;
         setIntent(r.intent, r.info);
+        setLockTaskAuth(r);
     }
 
     /** Sets the original intent, _without_ updating the calling uid or package. */
@@ -872,14 +861,6 @@
         }
         mResizeMode = info.resizeMode;
         mSupportsPictureInPicture = info.supportsPictureInPicture();
-        mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
-        mLockTaskMode = info.lockTaskLaunchMode;
-        if (!mPrivileged && (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
-                || mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
-            // Non-priv apps are not allowed to use always or never, fall back to default
-            mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
-        }
-        setLockTaskAuth();
     }
 
     /** Sets the original minimal width and height. */
@@ -906,29 +887,9 @@
         return this.intent.filterEquals(intent);
     }
 
-    void setTaskToReturnTo(/*@WindowConfiguration.ActivityType*/ int taskToReturnTo) {
-        mTaskToReturnTo = taskToReturnTo == ACTIVITY_TYPE_RECENTS
-                ? ACTIVITY_TYPE_HOME : taskToReturnTo;
-    }
-
-    void setTaskToReturnTo(ActivityRecord source) {
-        if (source.isActivityTypeRecents()) {
-            setTaskToReturnTo(ACTIVITY_TYPE_RECENTS);
-        } else if (source.isActivityTypeAssistant()) {
-            setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
-        }
-    }
-
-    int getTaskToReturnTo() {
-        return mTaskToReturnTo;
-    }
-
-    boolean returnsToHomeTask() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
-    }
-
-    boolean returnsToStandardTask() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_STANDARD;
+    boolean returnsToHomeStack() {
+        final int returnHomeFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME;
+        return (intent.getFlags() & returnHomeFlags) == returnHomeFlags;
     }
 
     void setPrevAffiliate(TaskRecord prevAffiliate) {
@@ -941,8 +902,8 @@
         mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
     }
 
-    ActivityStack getStack() {
-        return mStack;
+    <T extends ActivityStack> T getStack() {
+        return (T) mStack;
     }
 
     /**
@@ -1100,6 +1061,36 @@
         return null;
     }
 
+    /**
+     * Return the number of running activities, and the number of non-finishing/initializing
+     * activities in the provided {@param reportOut} respectively.
+     */
+    void getNumRunningActivities(TaskActivitiesReport reportOut) {
+        reportOut.reset();
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.finishing) {
+                continue;
+            }
+
+            reportOut.base = r;
+
+            // Increment the total number of non-finishing activities
+            reportOut.numActivities++;
+
+            if (reportOut.top == null || (reportOut.top.state == ActivityState.INITIALIZING)) {
+                reportOut.top = r;
+                // Reset the number of running activities until we hit the first non-initializing
+                // activity
+                reportOut.numRunning = 0;
+            }
+            if (r.app != null && r.app.thread != null) {
+                // Increment the number of actually running activities
+                reportOut.numRunning++;
+            }
+        }
+    }
+
     boolean okToShowLocked() {
         // NOTE: If {@link TaskRecord#topRunningActivityLocked} return is not null then it is
         // okay to show the activity when locked.
@@ -1257,7 +1248,7 @@
             mService.notifyTaskPersisterLocked(this, false);
         }
 
-        if (getStackId() == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             // We normally notify listeners of task stack changes on pause, however pinned stack
             // activities are normally in the paused state so no notification will be sent there
             // before the activity is removed. We send it here so instead.
@@ -1416,8 +1407,17 @@
     }
 
     void setLockTaskAuth() {
+        setLockTaskAuth(getRootActivity());
+    }
+
+    private void setLockTaskAuth(@Nullable ActivityRecord r) {
+        if (r == null) {
+            mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
+            return;
+        }
+
         final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
-        switch (mLockTaskMode) {
+        switch (r.lockTaskLaunchMode) {
             case LOCK_TASK_LAUNCH_MODE_DEFAULT:
                 mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
                         ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
@@ -1440,27 +1440,21 @@
                 " mLockTaskAuth=" + lockTaskAuthToString());
     }
 
-    boolean isOverHomeStack() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
-    }
-
-    boolean isOverAssistantStack() {
-        return mTaskToReturnTo == ACTIVITY_TYPE_ASSISTANT;
-    }
-
     private boolean isResizeable(boolean checkSupportsPip) {
         return (mService.mForceResizableActivities || ActivityInfo.isResizeableMode(mResizeMode)
-                || (checkSupportsPip && mSupportsPictureInPicture)) && !mTemporarilyUnresizable;
+                || (checkSupportsPip && mSupportsPictureInPicture));
     }
 
     boolean isResizeable() {
         return isResizeable(true /* checkSupportsPip */);
     }
 
-    boolean supportsSplitScreen() {
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
         // A task can not be docked even if it is considered resizeable because it only supports
         // picture-in-picture mode but has a non-resizeable resizeMode
-        return mService.mSupportsSplitScreenMultiWindow
+        return super.supportsSplitScreenWindowingMode()
+                && mService.mSupportsSplitScreenMultiWindow
                 && (mService.mForceResizableActivities
                         || (isResizeable(false /* checkSupportsPip */)
                                 && !ActivityInfo.isPreserveOrientationMode(mResizeMode)));
@@ -1485,7 +1479,7 @@
      * @return True if the requested bounds are okay for a resizing request.
      */
     private boolean canResizeToBounds(Rect bounds) {
-        if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+        if (bounds == null || !inFreeformWindowingMode()) {
             // Note: If not on the freeform workspace, we ignore the bounds.
             return true;
         }
@@ -1551,6 +1545,7 @@
             // values in the TaskRecord.
             String label = null;
             String iconFilename = null;
+            int iconResource = -1;
             int colorPrimary = 0;
             int colorBackground = 0;
             int statusBarColor = 0;
@@ -1562,6 +1557,9 @@
                     if (label == null) {
                         label = r.taskDescription.getLabel();
                     }
+                    if (iconResource == -1) {
+                        iconResource = r.taskDescription.getIconResource();
+                    }
                     if (iconFilename == null) {
                         iconFilename = r.taskDescription.getIconFilename();
                     }
@@ -1576,8 +1574,8 @@
                 }
                 topActivity = false;
             }
-            lastTaskDescription = new TaskDescription(label, null, iconFilename, colorPrimary,
-                    colorBackground, statusBarColor, navigationBarColor);
+            lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename,
+                    colorPrimary, colorBackground, statusBarColor, navigationBarColor);
             if (mWindowContainerController != null) {
                 mWindowContainerController.setTaskDescription(lastTaskDescription);
             }
@@ -1639,8 +1637,6 @@
         out.attribute(null, ATTR_USERID, String.valueOf(userId));
         out.attribute(null, ATTR_USER_SETUP_COMPLETE, String.valueOf(mUserSetupComplete));
         out.attribute(null, ATTR_EFFECTIVE_UID, String.valueOf(effectiveUid));
-        out.attribute(null, ATTR_FIRSTACTIVETIME, String.valueOf(firstActiveTime));
-        out.attribute(null, ATTR_LASTACTIVETIME, String.valueOf(lastActiveTime));
         out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
         out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
         if (lastDescription != null) {
@@ -1658,7 +1654,6 @@
         out.attribute(null, ATTR_RESIZE_MODE, String.valueOf(mResizeMode));
         out.attribute(null, ATTR_SUPPORTS_PICTURE_IN_PICTURE,
                 String.valueOf(mSupportsPictureInPicture));
-        out.attribute(null, ATTR_PRIVILEGED, String.valueOf(mPrivileged));
         if (mLastNonFullscreenBounds != null) {
             out.attribute(
                     null, ATTR_NON_FULLSCREEN_BOUNDS, mLastNonFullscreenBounds.flattenToString());
@@ -1713,8 +1708,6 @@
         boolean userSetupComplete = true;
         int effectiveUid = -1;
         String lastDescription = null;
-        long firstActiveTime = -1;
-        long lastActiveTime = -1;
         long lastTimeOnTop = 0;
         boolean neverRelinquishIdentity = true;
         int taskId = INVALID_TASK_ID;
@@ -1728,7 +1721,6 @@
         String callingPackage = "";
         int resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
         boolean supportsPictureInPicture = false;
-        boolean privileged = false;
         Rect bounds = null;
         int minWidth = INVALID_MIN_SIZE;
         int minHeight = INVALID_MIN_SIZE;
@@ -1766,10 +1758,6 @@
                 effectiveUid = Integer.parseInt(attrValue);
             } else if (ATTR_TASKTYPE.equals(attrName)) {
                 taskType = Integer.parseInt(attrValue);
-            } else if (ATTR_FIRSTACTIVETIME.equals(attrName)) {
-                firstActiveTime = Long.parseLong(attrValue);
-            } else if (ATTR_LASTACTIVETIME.equals(attrName)) {
-                lastActiveTime = Long.parseLong(attrValue);
             } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
                 lastDescription = attrValue;
             } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
@@ -1794,8 +1782,6 @@
                 resizeMode = Integer.parseInt(attrValue);
             } else if (ATTR_SUPPORTS_PICTURE_IN_PICTURE.equals(attrName)) {
                 supportsPictureInPicture = Boolean.parseBoolean(attrValue);
-            } else if (ATTR_PRIVILEGED.equals(attrName)) {
-                privileged = Boolean.parseBoolean(attrValue);
             } else if (ATTR_NON_FULLSCREEN_BOUNDS.equals(attrName)) {
                 bounds = Rect.unflattenFromString(attrValue);
             } else if (ATTR_MIN_WIDTH.equals(attrName)) {
@@ -1880,10 +1866,10 @@
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, rootAffinity, realActivity, origActivity, rootHasReset,
                 autoRemoveRecents, askedCompatMode, userId, effectiveUid, lastDescription,
-                activities, firstActiveTime, lastActiveTime, lastTimeOnTop, neverRelinquishIdentity,
-                taskDescription, taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor,
-                callingUid, callingPackage, resizeMode, supportsPictureInPicture, privileged,
-                realActivitySuspended, userSetupComplete, minWidth, minHeight);
+                activities, lastTimeOnTop, neverRelinquishIdentity, taskDescription,
+                taskAffiliation, prevTaskId, nextTaskId, taskAffiliationColor, callingUid,
+                callingPackage, resizeMode, supportsPictureInPicture, realActivitySuspended,
+                userSetupComplete, minWidth, minHeight);
         task.updateOverrideConfiguration(bounds);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
@@ -1903,7 +1889,7 @@
         // If the task has no requested minimal size, we'd like to enforce a minimal size
         // so that the user can not render the task too small to manipulate. We don't need
         // to do this for the pinned stack as the bounds are controlled by the system.
-        if (getStackId() != PINNED_STACK_ID) {
+        if (!inPinnedWindowingMode()) {
             if (minWidth == INVALID_MIN_SIZE) {
                 minWidth = mService.mStackSupervisor.mDefaultMinSizeOfResizeableTask;
             }
@@ -2077,7 +2063,7 @@
             return;
         }
 
-        if (inStack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+        if (inStack.inFreeformWindowingMode()) {
             if (!isResizeable()) {
                 throw new IllegalArgumentException("Can not position non-resizeable task="
                         + this + " in stack=" + inStack);
@@ -2088,7 +2074,7 @@
             if (mLastNonFullscreenBounds != null) {
                 updateOverrideConfiguration(mLastNonFullscreenBounds);
             } else {
-                inStack.layoutTaskInStack(this, null);
+                mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
             }
         } else {
             updateOverrideConfiguration(inStack.mBounds);
@@ -2101,12 +2087,10 @@
             return null;
         }
 
-        final int stackId = mStack.mStackId;
-        if (stackId == HOME_STACK_ID
-                || stackId == RECENTS_STACK_ID
-                || stackId == ASSISTANT_STACK_ID
-                || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                || (stackId == DOCKED_STACK_ID && !isResizeable())) {
+        final int windowingMode = getWindowingMode();
+        if (!isActivityTypeStandardOrUndefined()
+                || windowingMode == WINDOWING_MODE_FULLSCREEN
+                || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
             return isResizeable() ? mStack.mBounds : null;
         } else if (!getWindowConfiguration().persistTaskBounds()) {
             return mStack.mBounds;
@@ -2165,13 +2149,11 @@
             pw.print(prefix); pw.print("realActivity=");
             pw.println(realActivity.flattenToShortString());
         }
-        if (autoRemoveRecents || isPersistable || !isActivityTypeStandard()
-                || mTaskToReturnTo != ACTIVITY_TYPE_STANDARD || numFullscreen != 0) {
+        if (autoRemoveRecents || isPersistable || !isActivityTypeStandard() || numFullscreen != 0) {
             pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents);
                     pw.print(" isPersistable="); pw.print(isPersistable);
                     pw.print(" numFullscreen="); pw.print(numFullscreen);
-                    pw.print(" activityType="); pw.print(getActivityType());
-                    pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+                    pw.print(" activityType="); pw.println(getActivityType());
         }
         if (rootWasReset || mNeverRelinquishIdentity || mReuseTask
                 || mLockTaskAuth != LOCK_TASK_AUTH_PINNABLE) {
@@ -2214,7 +2196,6 @@
                 pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
                 pw.print(" mSupportsPictureInPicture=" + mSupportsPictureInPicture);
                 pw.print(" isResizeable=" + isResizeable());
-                pw.print(" firstActiveTime=" + firstActiveTime);
                 pw.print(" lastActiveTime=" + lastActiveTime);
                 pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)");
     }
@@ -2255,7 +2236,7 @@
 
     public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
         proto.write(ID, taskId);
         for (int i = mActivities.size() - 1; i >= 0; i--) {
             ActivityRecord activity = mActivities.get(i);
@@ -2272,7 +2253,6 @@
             proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
         }
         proto.write(ACTIVITY_TYPE, getActivityType());
-        proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
         proto.write(RESIZE_MODE, mResizeMode);
         proto.write(FULLSCREEN, mFullscreen);
         if (mBounds != null) {
@@ -2282,4 +2262,19 @@
         proto.write(MIN_HEIGHT, mMinHeight);
         proto.end(token);
     }
+
+    /**
+     * See {@link #getNumRunningActivities(TaskActivitiesReport)}.
+     */
+    static class TaskActivitiesReport {
+        int numRunning;
+        int numActivities;
+        ActivityRecord top;
+        ActivityRecord base;
+
+        void reset() {
+            numRunning = numActivities = 0;
+            top = base = null;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index f2e2942..2df5dc9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -67,6 +67,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -79,6 +80,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
+import android.util.TimingsTraceLog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -103,13 +105,18 @@
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ *
+ * <p>This class use {@link #mLock} to synchronize access to internal state. Methods that require
+ * {@link #mLock} to be held should have "LU" suffix in the name.
+ *
+ * <p><strong>Important:</strong> Synchronized code, i.e. one executed inside a synchronized(mLock)
+ * block or inside LU method, should only access internal state of this class or make calls to
+ * other LU methods. Non-LU method calls or calls to external classes are discouraged as they
+ * may cause lock inversion.
  */
 class UserController implements Handler.Callback {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
 
-    // Maximum number of users we allow to be running at a time.
-    static final int MAX_RUNNING_USERS = 3;
-
     // Amount of time we wait for observers to handle a user switch before
     // giving up on them and unfreezing the screen.
     static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
@@ -136,7 +143,20 @@
     // when it never calls back.
     private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
 
-    private final Object mLock;
+    /**
+     * Maximum number of users we allow to be running at a time, including system user.
+     *
+     * <p>This parameter only affects how many background users will be stopped when switching to a
+     * new user. It has no impact on {@link #startUser(int, boolean)} behavior.
+     *
+     * <p>Note: Current and system user (and their related profiles) are never stopped when
+     * switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+     */
+    int mMaxRunningUsers;
+
+    // Lock for internal state.
+    private final Object mLock = new Object();
+
     private final Injector mInjector;
     private final Handler mHandler;
     private final Handler mUiHandler;
@@ -175,7 +195,8 @@
     /**
      * Mapping from each known user ID to the profile group ID it is associated with.
      */
-    private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+    @GuardedBy("mLock")
+    private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
 
     /**
      * Registered observers of the user switching mechanics.
@@ -206,42 +227,41 @@
     @VisibleForTesting
     UserController(Injector injector) {
         mInjector = injector;
-        mLock = injector.getLock();
         mHandler = mInjector.getHandler(this);
         mUiHandler = mInjector.getUiHandler(this);
         // User 0 is the first and only user that runs at boot.
         final UserState uss = new UserState(UserHandle.SYSTEM);
+        uss.mUnlockProgress.addListener(new UserProgressListener());
         mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
         mUserLru.add(UserHandle.USER_SYSTEM);
         mLockPatternUtils = mInjector.getLockPatternUtils();
-        updateStartedUserArrayLocked();
+        updateStartedUserArrayLU();
     }
 
     void finishUserSwitch(UserState uss) {
+        finishUserBoot(uss);
+        startProfiles();
         synchronized (mLock) {
-            finishUserBoot(uss);
-
-            startProfilesLocked();
-            stopRunningUsersLocked(MAX_RUNNING_USERS);
+            stopRunningUsersLU(mMaxRunningUsers);
         }
     }
 
-    void stopRunningUsersLocked(int maxRunningUsers) {
-        int num = mUserLru.size();
+    void stopRunningUsersLU(int maxRunningUsers) {
+        int currentlyRunning = mUserLru.size();
         int i = 0;
-        while (num > maxRunningUsers && i < mUserLru.size()) {
+        while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) {
             Integer oldUserId = mUserLru.get(i);
             UserState oldUss = mStartedUsers.get(oldUserId);
             if (oldUss == null) {
                 // Shouldn't happen, but be sane if it does.
                 mUserLru.remove(i);
-                num--;
+                currentlyRunning--;
                 continue;
             }
             if (oldUss.state == UserState.STATE_STOPPING
                     || oldUss.state == UserState.STATE_SHUTDOWN) {
                 // This user is already stopping, doesn't count.
-                num--;
+                currentlyRunning--;
                 i++;
                 continue;
             }
@@ -249,16 +269,15 @@
                 // Owner/System user and current user can't be stopped. We count it as running
                 // when it is not a pure system user.
                 if (UserInfo.isSystemOnly(oldUserId)) {
-                    num--;
+                    currentlyRunning--;
                 }
                 i++;
                 continue;
             }
             // This is a user to be stopped.
-            if (stopUsersLocked(oldUserId, false, null) != USER_OP_SUCCESS) {
-                num--;
+            if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) {
+                currentlyRunning--;
             }
-            num--;
             i++;
         }
     }
@@ -273,55 +292,57 @@
         Slog.d(TAG, "Finishing user boot " + userId);
         synchronized (mLock) {
             // Bail if we ended up with a stale user
-            if (mStartedUsers.get(userId) != uss) return;
+            if (mStartedUsers.get(userId) != uss) {
+                return;
+            }
+        }
 
-            // We always walk through all the user lifecycle states to send
-            // consistent developer events. We step into RUNNING_LOCKED here,
-            // but we might immediately step into RUNNING below if the user
-            // storage is already unlocked.
-            if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                // Do not report secondary users, runtime restarts or first boot/upgrade
-                if (userId == UserHandle.USER_SYSTEM
-                        && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                    int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
-                    MetricsLogger.histogram(mInjector.getContext(),
-                            "framework_locked_boot_completed", uptimeSeconds);
-                    final int MAX_UPTIME_SECONDS = 120;
-                    if (uptimeSeconds > MAX_UPTIME_SECONDS) {
-                        Slog.wtf("SystemServerTiming",
-                                "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
-                    }
+        // We always walk through all the user lifecycle states to send
+        // consistent developer events. We step into RUNNING_LOCKED here,
+        // but we might immediately step into RUNNING below if the user
+        // storage is already unlocked.
+        if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
+            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+            // Do not report secondary users, runtime restarts or first boot/upgrade
+            if (userId == UserHandle.USER_SYSTEM
+                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+                int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
+                MetricsLogger.histogram(mInjector.getContext(),
+                        "framework_locked_boot_completed", uptimeSeconds);
+                final int MAX_UPTIME_SECONDS = 120;
+                if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+                    Slog.wtf("SystemServerTiming",
+                            "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
                 }
-
-                mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
-                        userId, 0));
-                Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                mInjector.broadcastIntentLocked(intent, null, resultTo, 0, null, null,
-                        new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
-                        AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
             }
 
-            // We need to delay unlocking managed profiles until the parent user
-            // is also unlocked.
-            if (mInjector.getUserManager().isManagedProfile(userId)) {
-                final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-                if (parent != null
-                        && isUserRunningLocked(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
-                    Slog.d(TAG, "User " + userId + " (parent " + parent.id
-                            + "): attempting unlock because parent is unlocked");
-                    maybeUnlockUser(userId);
-                } else {
-                    String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
-                    Slog.d(TAG, "User " + userId + " (parent " + parentId
-                            + "): delaying unlock because parent is locked");
-                }
-            } else {
+            mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
+                    userId, 0));
+            Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
+                    new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+        }
+
+        // We need to delay unlocking managed profiles until the parent user
+        // is also unlocked.
+        if (mInjector.getUserManager().isManagedProfile(userId)) {
+            final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+            if (parent != null
+                    && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
+                Slog.d(TAG, "User " + userId + " (parent " + parent.id
+                        + "): attempting unlock because parent is unlocked");
                 maybeUnlockUser(userId);
+            } else {
+                String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
+                Slog.d(TAG, "User " + userId + " (parent " + parentId
+                        + "): delaying unlock because parent is locked");
             }
+        } else {
+            maybeUnlockUser(userId);
         }
     }
 
@@ -331,34 +352,30 @@
      */
     private void finishUserUnlocking(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
-        boolean proceedWithUnlock = false;
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
 
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            if (uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                proceedWithUnlock = true;
+            // Do not proceed if unexpected state
+            if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+                return;
             }
         }
+        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+        uss.mUnlockProgress.start();
 
-        if (proceedWithUnlock) {
-            uss.mUnlockProgress.start();
+        // Prepare app storage before we go any further
+        uss.mUnlockProgress.setProgress(5,
+                    mInjector.getContext().getString(R.string.android_start_title));
+        mInjector.getUserManager().onBeforeUnlockUser(userId);
+        uss.mUnlockProgress.setProgress(20);
 
-            // Prepare app storage before we go any further
-            uss.mUnlockProgress.setProgress(5,
-                        mInjector.getContext().getString(R.string.android_start_title));
-            mInjector.getUserManager().onBeforeUnlockUser(userId);
-            uss.mUnlockProgress.setProgress(20);
-
-            // Dispatch unlocked to system services; when fully dispatched,
-            // that calls through to the next "unlocked" phase
-            mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
-                    .sendToTarget();
-        }
+        // Dispatch unlocked to system services; when fully dispatched,
+        // that calls through to the next "unlocked" phase
+        mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+                .sendToTarget();
     }
 
     /**
@@ -367,64 +384,64 @@
      */
     void finishUserUnlocked(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
 
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            if (uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                uss.mUnlockProgress.finish();
-
-                // Dispatch unlocked to external apps
-                final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
-                unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                unlockedIntent.addFlags(
-                        Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-                mInjector.broadcastIntentLocked(unlockedIntent, null, null, 0, null,
-                        null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
-                        userId);
-
-                if (getUserInfo(userId).isManagedProfile()) {
-                    UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-                    if (parent != null) {
-                        final Intent profileUnlockedIntent = new Intent(
-                                Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-                        profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
-                        profileUnlockedIntent.addFlags(
-                                Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                                | Intent.FLAG_RECEIVER_FOREGROUND);
-                        mInjector.broadcastIntentLocked(profileUnlockedIntent,
-                                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, false, false, MY_PID, SYSTEM_UID,
-                                parent.id);
-                    }
-                }
-
-                // Send PRE_BOOT broadcasts if user fingerprint changed; we
-                // purposefully block sending BOOT_COMPLETED until after all
-                // PRE_BOOT receivers are finished to avoid ANR'ing apps
-                final UserInfo info = getUserInfo(userId);
-                if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
-                    // Suppress double notifications for managed profiles that
-                    // were unlocked automatically as part of their parent user
-                    // being unlocked.
-                    final boolean quiet;
-                    if (info.isManagedProfile()) {
-                        quiet = !uss.tokenProvided
-                                || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
-                    } else {
-                        quiet = false;
-                    }
-                    mInjector.sendPreBootBroadcast(userId, quiet,
-                            () -> finishUserUnlockedCompleted(uss));
-                } else {
-                    finishUserUnlockedCompleted(uss);
-                }
+            // Do not proceed if unexpected state
+            if (!uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
+                return;
             }
         }
+        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+        uss.mUnlockProgress.finish();
+        // Dispatch unlocked to external apps
+        final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        unlockedIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
+                null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                userId);
+
+        if (getUserInfo(userId).isManagedProfile()) {
+            UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+            if (parent != null) {
+                final Intent profileUnlockedIntent = new Intent(
+                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
+                profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+                profileUnlockedIntent.addFlags(
+                        Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                                | Intent.FLAG_RECEIVER_FOREGROUND);
+                mInjector.broadcastIntent(profileUnlockedIntent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, false, false, MY_PID, SYSTEM_UID,
+                        parent.id);
+            }
+        }
+
+        // Send PRE_BOOT broadcasts if user fingerprint changed; we
+        // purposefully block sending BOOT_COMPLETED until after all
+        // PRE_BOOT receivers are finished to avoid ANR'ing apps
+        final UserInfo info = getUserInfo(userId);
+        if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
+            // Suppress double notifications for managed profiles that
+            // were unlocked automatically as part of their parent user
+            // being unlocked.
+            final boolean quiet;
+            if (info.isManagedProfile()) {
+                quiet = !uss.tokenProvided
+                        || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+            } else {
+                quiet = false;
+            }
+            mInjector.sendPreBootBroadcast(userId, quiet,
+                    () -> finishUserUnlockedCompleted(uss));
+        } else {
+            finishUserUnlockedCompleted(uss);
+        }
     }
 
     private void finishUserUnlockedCompleted(UserState uss) {
@@ -432,60 +449,59 @@
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
-            final UserInfo userInfo = getUserInfo(userId);
-            if (userInfo == null) {
-                return;
-            }
-
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            // Remember that we logged in
-            mInjector.getUserManager().onUserLoggedIn(userId);
-
-            if (!userInfo.isInitialized()) {
-                if (userId != UserHandle.USER_SYSTEM) {
-                    Slog.d(TAG, "Initializing user #" + userId);
-                    Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
-                    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                            | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                    mInjector.broadcastIntentLocked(intent, null,
-                            new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered,
-                                        boolean sticky, int sendingUser) {
-                                    // Note: performReceive is called with mService lock held
-                                    mInjector.getUserManager().makeInitialized(userInfo.id);
-                                }
-                            }, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, SYSTEM_UID, userId);
-                }
-            }
-
-            Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
-            // Do not report secondary users, runtime restarts or first boot/upgrade
-            if (userId == UserHandle.USER_SYSTEM
-                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
-                MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
-                        uptimeSeconds);
-            }
-            final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-            bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-            bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            mInjector.broadcastIntentLocked(bootIntent, null, new IIntentReceiver.Stub() {
-                @Override
-                public void performReceive(Intent intent, int resultCode, String data,
-                        Bundle extras, boolean ordered, boolean sticky, int sendingUser)
-                        throws RemoteException {
-                    Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
-                }
-            }, 0, null, null,
-                    new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
-                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
         }
+        UserInfo userInfo = getUserInfo(userId);
+        if (userInfo == null) {
+            return;
+        }
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+
+        // Remember that we logged in
+        mInjector.getUserManager().onUserLoggedIn(userId);
+
+        if (!userInfo.isInitialized()) {
+            if (userId != UserHandle.USER_SYSTEM) {
+                Slog.d(TAG, "Initializing user #" + userId);
+                Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                mInjector.broadcastIntent(intent, null,
+                        new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky, int sendingUser) {
+                                // Note: performReceive is called with mService lock held
+                                mInjector.getUserManager().makeInitialized(userInfo.id);
+                            }
+                        }, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, true, false, MY_PID, SYSTEM_UID, userId);
+            }
+        }
+
+        Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
+        // Do not report secondary users, runtime restarts or first boot/upgrade
+        if (userId == UserHandle.USER_SYSTEM
+                && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+            int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
+            MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
+                    uptimeSeconds);
+        }
+        final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+        bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+                            throws RemoteException {
+                        Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+                    }
+                }, 0, null, null,
+                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
     }
 
     int restartUser(final int userId, final boolean foreground) {
@@ -516,33 +532,33 @@
         }
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
-            return stopUsersLocked(userId, force, callback);
+            return stopUsersLU(userId, force, callback);
         }
     }
 
     /**
      * Stops the user along with its related users. The method calls
-     * {@link #getUsersToStopLocked(int)} to determine the list of users that should be stopped.
+     * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
-    private int stopUsersLocked(final int userId, boolean force, final IStopUserCallback callback) {
+    private int stopUsersLU(final int userId, boolean force, final IStopUserCallback callback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
         }
-        if (isCurrentUserLocked(userId)) {
+        if (isCurrentUserLU(userId)) {
             return USER_OP_IS_CURRENT;
         }
-        int[] usersToStop = getUsersToStopLocked(userId);
+        int[] usersToStop = getUsersToStopLU(userId);
         // If one of related users is system or current, no related users should be stopped
         for (int i = 0; i < usersToStop.length; i++) {
             int relatedUserId = usersToStop[i];
-            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLocked(relatedUserId)) {
+            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
                 if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked cannot stop related user "
                         + relatedUserId);
                 // We still need to stop the requested user if it's a force stop.
                 if (force) {
                     Slog.i(TAG,
                             "Force stop user " + userId + ". Related users will not be stopped");
-                    stopSingleUserLocked(userId, callback);
+                    stopSingleUserLU(userId, callback);
                     return USER_OP_SUCCESS;
                 }
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -550,25 +566,22 @@
         }
         if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
-            stopSingleUserLocked(userIdToStop, userIdToStop == userId ? callback : null);
+            stopSingleUserLU(userIdToStop, userIdToStop == userId ? callback : null);
         }
         return USER_OP_SUCCESS;
     }
 
-    private void stopSingleUserLocked(final int userId, final IStopUserCallback callback) {
+    private void stopSingleUserLU(final int userId, final IStopUserCallback callback) {
         if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
         if (uss == null) {
             // User is not started, nothing to do...  but we do need to
             // callback if requested.
             if (callback != null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callback.userStopped(userId);
-                        } catch (RemoteException e) {
-                        }
+                mHandler.post(() -> {
+                    try {
+                        callback.userStopped(userId);
+                    } catch (RemoteException e) {
                     }
                 });
             }
@@ -583,10 +596,10 @@
                 && uss.state != UserState.STATE_SHUTDOWN) {
             uss.setState(UserState.STATE_STOPPING);
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-            updateStartedUserArrayLocked();
+            updateStartedUserArrayLU();
 
-            long ident = Binder.clearCallingIdentity();
-            try {
+            // Post to handler to obtain amLock
+            mHandler.post(() -> {
                 // We are going to broadcast ACTION_USER_STOPPING and then
                 // once that is done send a final ACTION_SHUTDOWN and then
                 // stop the user.
@@ -599,31 +612,24 @@
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                finishUserStopping(userId, uss);
-                            }
-                        });
+                        mHandler.post(() -> finishUserStopping(userId, uss));
                     }
                 };
+
                 // Clear broadcast queue for the user to avoid delivering stale broadcasts
-                mInjector.clearBroadcastQueueForUserLocked(userId);
+                mInjector.clearBroadcastQueueForUser(userId);
                 // Kick things off.
-                mInjector.broadcastIntentLocked(stoppingIntent,
+                mInjector.broadcastIntent(stoppingIntent,
                         null, stoppingReceiver, 0, null, null,
                         new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                         null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+            });
         }
     }
 
     void finishUserStopping(final int userId, final UserState uss) {
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
-        shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         // This is the result receiver for the final shutdown broadcast.
         final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
             @Override
@@ -652,18 +658,17 @@
                 Integer.toString(userId), userId);
         mInjector.getSystemServiceManager().stopUser(userId);
 
-        synchronized (mLock) {
-            mInjector.broadcastIntentLocked(shutdownIntent,
-                    null, shutdownReceiver, 0, null, null, null,
-                    AppOpsManager.OP_NONE,
-                    null, true, false, MY_PID, SYSTEM_UID, userId);
-        }
+        mInjector.broadcastIntent(shutdownIntent,
+                null, shutdownReceiver, 0, null, null, null,
+                AppOpsManager.OP_NONE,
+                null, true, false, MY_PID, SYSTEM_UID, userId);
     }
 
     void finishUserStopped(UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         boolean stopped;
         ArrayList<IStopUserCallback> callbacks;
+        boolean forceStopUser = false;
         synchronized (mLock) {
             callbacks = new ArrayList<>(uss.mStopCallbacks);
             if (mStartedUsers.get(userId) != uss) {
@@ -674,16 +679,18 @@
                 stopped = true;
                 // User can no longer run.
                 mStartedUsers.remove(userId);
-                mInjector.getUserManagerInternal().removeUserState(userId);
                 mUserLru.remove(Integer.valueOf(userId));
-                updateStartedUserArrayLocked();
-
-                mInjector.activityManagerOnUserStopped(userId);
-                // Clean up all state and processes associated with the user.
-                // Kill all the processes for the user.
-                forceStopUserLocked(userId, "finish user");
+                updateStartedUserArrayLU();
+                forceStopUser = true;
             }
         }
+        if (forceStopUser) {
+            mInjector.getUserManagerInternal().removeUserState(userId);
+            mInjector.activityManagerOnUserStopped(userId);
+            // Clean up all state and processes associated with the user.
+            // Kill all the processes for the user.
+            forceStopUser(userId, "finish user");
+        }
 
         for (int i = 0; i < callbacks.size(); i++) {
             try {
@@ -695,9 +702,7 @@
 
         if (stopped) {
             mInjector.systemServiceManagerCleanupUser(userId);
-            synchronized (mLock) {
-                mInjector.getActivityStackSupervisor().removeUserLocked(userId);
-            }
+            mInjector.stackSupervisorRemoveUser(userId);
             // Remove the user if it is ephemeral.
             if (getUserInfo(userId).isEphemeral()) {
                 mInjector.getUserManager().removeUser(userId);
@@ -715,39 +720,36 @@
      * Determines the list of users that should be stopped together with the specified
      * {@code userId}. The returned list includes {@code userId}.
      */
-    private @NonNull int[] getUsersToStopLocked(int userId) {
+    private @NonNull int[] getUsersToStopLU(int userId) {
         int startedUsersSize = mStartedUsers.size();
         IntArray userIds = new IntArray();
         userIds.add(userId);
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            int userGroupId = mUserProfileGroupIdsSelfLocked.get(userId,
+        int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+        for (int i = 0; i < startedUsersSize; i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            int startedUserId = uss.mHandle.getIdentifier();
+            // Skip unrelated users (profileGroupId mismatch)
+            int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
-            for (int i = 0; i < startedUsersSize; i++) {
-                UserState uss = mStartedUsers.valueAt(i);
-                int startedUserId = uss.mHandle.getIdentifier();
-                // Skip unrelated users (profileGroupId mismatch)
-                int startedUserGroupId = mUserProfileGroupIdsSelfLocked.get(startedUserId,
-                        UserInfo.NO_PROFILE_GROUP_ID);
-                boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
-                        && (userGroupId == startedUserGroupId);
-                // userId has already been added
-                boolean sameUserId = startedUserId == userId;
-                if (!sameGroup || sameUserId) {
-                    continue;
-                }
-                userIds.add(startedUserId);
+            boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+                    && (userGroupId == startedUserGroupId);
+            // userId has already been added
+            boolean sameUserId = startedUserId == userId;
+            if (!sameGroup || sameUserId) {
+                continue;
             }
+            userIds.add(startedUserId);
         }
         return userIds.toArray();
     }
 
-    private void forceStopUserLocked(int userId, String reason) {
-        mInjector.activityManagerForceStopPackageLocked(userId, reason);
+    private void forceStopUser(int userId, String reason) {
+        mInjector.activityManagerForceStopPackage(userId, reason);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_FOREGROUND);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        mInjector.broadcastIntentLocked(intent,
+        mInjector.broadcastIntent(intent,
                 null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                 null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
     }
@@ -756,6 +758,7 @@
      * Stops the guest or ephemeral user if it has gone to the background.
      */
     private void stopGuestOrEphemeralUserIfBackground() {
+        IntArray userIds = new IntArray();
         synchronized (mLock) {
             final int num = mUserLru.size();
             for (int i = 0; i < num; i++) {
@@ -766,41 +769,48 @@
                         || oldUss.state == UserState.STATE_SHUTDOWN) {
                     continue;
                 }
-                UserInfo userInfo = getUserInfo(oldUserId);
-                if (userInfo.isEphemeral()) {
-                    LocalServices.getService(UserManagerInternal.class)
-                            .onEphemeralUserStop(oldUserId);
+                userIds.add(oldUserId);
+            }
+        }
+        final int userIdsSize = userIds.size();
+        for (int i = 0; i < userIdsSize; i++) {
+            int oldUserId = userIds.get(i);
+            UserInfo userInfo = getUserInfo(oldUserId);
+            if (userInfo.isEphemeral()) {
+                LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
+            }
+            if (userInfo.isGuest() || userInfo.isEphemeral()) {
+                // This is a user to be stopped.
+                synchronized (mLock) {
+                    stopUsersLU(oldUserId, true, null);
                 }
-                if (userInfo.isGuest() || userInfo.isEphemeral()) {
-                    // This is a user to be stopped.
-                    stopUsersLocked(oldUserId, true, null);
-                    break;
-                }
+                break;
             }
         }
     }
 
-    void scheduleStartProfilesLocked() {
+    void scheduleStartProfiles() {
         if (!mHandler.hasMessages(START_PROFILES_MSG)) {
             mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
                     DateUtils.SECOND_IN_MILLIS);
         }
     }
 
-    void startProfilesLocked() {
+    void startProfiles() {
+        int currentUserId = getCurrentUserId();
         if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
         List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
+                currentUserId, false /* enabledOnly */);
         List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
         for (UserInfo user : profiles) {
             if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
-                    && user.id != mCurrentUserId && !user.isQuietModeEnabled()) {
+                    && user.id != currentUserId && !user.isQuietModeEnabled()) {
                 profilesToStart.add(user);
             }
         }
         final int profilesToStartSize = profilesToStart.size();
         int i = 0;
-        for (; i < profilesToStartSize && i < (MAX_RUNNING_USERS - 1); ++i) {
+        for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
             startUser(profilesToStart.get(i).id, /* foreground= */ false);
         }
         if (i < profilesToStartSize) {
@@ -856,143 +866,155 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
+            final int oldUserId = getCurrentUserId();
+            if (oldUserId == userId) {
+                return true;
+            }
+
+            if (foreground) {
+                mInjector.clearAllLockedTasks("startUser");
+            }
+
+            final UserInfo userInfo = getUserInfo(userId);
+            if (userInfo == null) {
+                Slog.w(TAG, "No user info for user #" + userId);
+                return false;
+            }
+            if (foreground && userInfo.isManagedProfile()) {
+                Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+                return false;
+            }
+
+            if (foreground && mUserSwitchUiEnabled) {
+                mInjector.getWindowManager().startFreezingScreen(
+                        R.anim.screen_user_exit, R.anim.screen_user_enter);
+            }
+
+            boolean needStart = false;
+            boolean updateUmState = false;
+            UserState uss;
+
+            // If the user we are switching to is not currently started, then
+            // we need to start it now.
             synchronized (mLock) {
-                final int oldUserId = mCurrentUserId;
-                if (oldUserId == userId) {
-                    return true;
-                }
-
-                if (foreground) {
-                    // TODO: I don't think this does what the caller think it does. Seems to only
-                    // remove one locked task and won't work if multiple locked tasks are present.
-                    mInjector.getLockTaskController().clearLockTaskMode("startUser");
-                }
-
-                final UserInfo userInfo = getUserInfo(userId);
-                if (userInfo == null) {
-                    Slog.w(TAG, "No user info for user #" + userId);
-                    return false;
-                }
-                if (foreground && userInfo.isManagedProfile()) {
-                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
-                    return false;
-                }
-
-                if (foreground && mUserSwitchUiEnabled) {
-                    mInjector.getWindowManager().startFreezingScreen(
-                            R.anim.screen_user_exit, R.anim.screen_user_enter);
-                }
-
-                boolean needStart = false;
-
-                // If the user we are switching to is not currently started, then
-                // we need to start it now.
-                if (mStartedUsers.get(userId) == null) {
-                    UserState userState = new UserState(UserHandle.of(userId));
-                    mStartedUsers.put(userId, userState);
-                    mInjector.getUserManagerInternal().setUserState(userId, userState.state);
-                    updateStartedUserArrayLocked();
+                uss = mStartedUsers.get(userId);
+                if (uss == null) {
+                    uss = new UserState(UserHandle.of(userId));
+                    uss.mUnlockProgress.addListener(new UserProgressListener());
+                    mStartedUsers.put(userId, uss);
+                    updateStartedUserArrayLU();
                     needStart = true;
+                    updateUmState = true;
                 }
-
-                final UserState uss = mStartedUsers.get(userId);
                 final Integer userIdInt = userId;
                 mUserLru.remove(userIdInt);
                 mUserLru.add(userIdInt);
-
-                if (foreground) {
+            }
+            if (updateUmState) {
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+            }
+            if (foreground) {
+                synchronized (mLock) {
                     mCurrentUserId = userId;
-                    mInjector.updateUserConfigurationLocked();
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
-                    updateCurrentProfileIdsLocked();
-                    mInjector.getWindowManager().setCurrentUser(userId, mCurrentProfileIds);
-                    // Once the internal notion of the active user has switched, we lock the device
-                    // with the option to show the user switcher on the keyguard.
-                    if (mUserSwitchUiEnabled) {
-                        mInjector.getWindowManager().setSwitchingUser(true);
-                        mInjector.getWindowManager().lockNow(null);
-                    }
-                } else {
-                    final Integer currentUserIdInt = mCurrentUserId;
-                    updateCurrentProfileIdsLocked();
-                    mInjector.getWindowManager().setCurrentProfileIds(mCurrentProfileIds);
+                }
+                mInjector.updateUserConfiguration();
+                updateCurrentProfileIds();
+                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+                // Once the internal notion of the active user has switched, we lock the device
+                // with the option to show the user switcher on the keyguard.
+                if (mUserSwitchUiEnabled) {
+                    mInjector.getWindowManager().setSwitchingUser(true);
+                    mInjector.getWindowManager().lockNow(null);
+                }
+            } else {
+                final Integer currentUserIdInt = mCurrentUserId;
+                updateCurrentProfileIds();
+                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
+                synchronized (mLock) {
                     mUserLru.remove(currentUserIdInt);
                     mUserLru.add(currentUserIdInt);
                 }
+            }
 
-                // Make sure user is in the started state.  If it is currently
-                // stopping, we need to knock that off.
-                if (uss.state == UserState.STATE_STOPPING) {
-                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
-                    // so we can just fairly silently bring the user back from
-                    // the almost-dead.
-                    uss.setState(uss.lastState);
-                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                } else if (uss.state == UserState.STATE_SHUTDOWN) {
-                    // This means ACTION_SHUTDOWN has been sent, so we will
-                    // need to treat this as a new boot of the user.
-                    uss.setState(UserState.STATE_BOOTING);
-                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                    updateStartedUserArrayLocked();
-                    needStart = true;
+            // Make sure user is in the started state.  If it is currently
+            // stopping, we need to knock that off.
+            if (uss.state == UserState.STATE_STOPPING) {
+                // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                // so we can just fairly silently bring the user back from
+                // the almost-dead.
+                uss.setState(uss.lastState);
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+                synchronized (mLock) {
+                    updateStartedUserArrayLU();
                 }
-
-                if (uss.state == UserState.STATE_BOOTING) {
-                    // Give user manager a chance to propagate user restrictions
-                    // to other services and prepare app storage
-                    mInjector.getUserManager().onBeforeStartUser(userId);
-
-                    // Booting up a new user, need to tell system services about it.
-                    // Note that this is on the same handler as scheduling of broadcasts,
-                    // which is important because it needs to go first.
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+                needStart = true;
+            } else if (uss.state == UserState.STATE_SHUTDOWN) {
+                // This means ACTION_SHUTDOWN has been sent, so we will
+                // need to treat this as a new boot of the user.
+                uss.setState(UserState.STATE_BOOTING);
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+                synchronized (mLock) {
+                    updateStartedUserArrayLU();
                 }
+                needStart = true;
+            }
 
-                if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
-                            oldUserId));
-                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
-                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
-                            oldUserId, userId, uss));
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
-                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
-                }
+            if (uss.state == UserState.STATE_BOOTING) {
+                // Give user manager a chance to propagate user restrictions
+                // to other services and prepare app storage
+                mInjector.getUserManager().onBeforeStartUser(userId);
 
-                if (needStart) {
-                    // Send USER_STARTED broadcast
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    mInjector.broadcastIntentLocked(intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, userId);
-                }
+                // Booting up a new user, need to tell system services about it.
+                // Note that this is on the same handler as scheduling of broadcasts,
+                // which is important because it needs to go first.
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+            }
 
-                if (foreground) {
-                    moveUserToForegroundLocked(uss, oldUserId, userId);
-                } else {
-                    finishUserBoot(uss);
-                }
+            if (foreground) {
+                mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                        oldUserId));
+                mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                        oldUserId, userId, uss));
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                        oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
+            }
 
-                if (needStart) {
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    mInjector.broadcastIntentLocked(intent,
-                            null, new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered, boolean sticky,
-                                        int sendingUser) throws RemoteException {
-                                }
-                            }, 0, null, null,
-                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
-                }
+            if (needStart) {
+                // Send USER_STARTED broadcast
+                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                mInjector.broadcastIntent(intent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, false, false, MY_PID, SYSTEM_UID, userId);
+            }
+
+            if (foreground) {
+                moveUserToForeground(uss, oldUserId, userId);
+            } else {
+                finishUserBoot(uss);
+            }
+
+            if (needStart) {
+                Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                mInjector.broadcastIntent(intent,
+                        null, new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky,
+                                    int sendingUser) throws RemoteException {
+                            }
+                        }, 0, null, null,
+                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1036,7 +1058,7 @@
      * when the the credential-encrypted storage isn't tied to a user-provided
      * PIN or pattern.
      */
-    boolean maybeUnlockUser(final int userId) {
+    private boolean maybeUnlockUser(final int userId) {
         // Try unlocking storage using empty token
         return unlockUserCleared(userId, null, null, null);
     }
@@ -1049,54 +1071,53 @@
         }
     }
 
-    boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+    private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
             IProgressListener listener) {
         UserState uss;
-        synchronized (mLock) {
-            // TODO Move this block outside of synchronized if it causes lock contention
-            if (!StorageManager.isUserKeyUnlocked(userId)) {
-                final UserInfo userInfo = getUserInfo(userId);
-                final IStorageManager storageManager = getStorageManager();
-                try {
-                    // We always want to unlock user storage, even user is not started yet
-                    storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
-                } catch (RemoteException | RuntimeException e) {
-                    Slog.w(TAG, "Failed to unlock: " + e.getMessage());
-                }
+        if (!StorageManager.isUserKeyUnlocked(userId)) {
+            final UserInfo userInfo = getUserInfo(userId);
+            final IStorageManager storageManager = getStorageManager();
+            try {
+                // We always want to unlock user storage, even user is not started yet
+                storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
+            } catch (RemoteException | RuntimeException e) {
+                Slog.w(TAG, "Failed to unlock: " + e.getMessage());
             }
-            // Bail if user isn't actually running, otherwise register the given
-            // listener to watch for unlock progress
+        }
+        synchronized (mLock) {
+            // Register the given listener to watch for unlock progress
             uss = mStartedUsers.get(userId);
-            if (uss == null) {
-                notifyFinished(userId, listener);
-                return false;
-            } else {
+            if (uss != null) {
                 uss.mUnlockProgress.addListener(listener);
                 uss.tokenProvided = (token != null);
             }
         }
+        // Bail if user isn't actually running
+        if (uss == null) {
+            notifyFinished(userId, listener);
+            return false;
+        }
 
         finishUserUnlocking(uss);
 
-        final ArraySet<Integer> childProfilesToUnlock = new ArraySet<>();
-        synchronized (mLock) {
+        // We just unlocked a user, so let's now attempt to unlock any
+        // managed profiles under that user.
 
-            // We just unlocked a user, so let's now attempt to unlock any
-            // managed profiles under that user.
-            for (int i = 0; i < mStartedUsers.size(); i++) {
-                final int testUserId = mStartedUsers.keyAt(i);
-                final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
-                if (parent != null && parent.id == userId && testUserId != userId) {
-                    Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
-                            + "): attempting unlock because parent was just unlocked");
-                    childProfilesToUnlock.add(testUserId);
-                }
+        // First, get list of userIds. Requires mLock, so we cannot make external calls, e.g. to UMS
+        int[] userIds;
+        synchronized (mLock) {
+            userIds = new int[mStartedUsers.size()];
+            for (int i = 0; i < userIds.length; i++) {
+                userIds[i] = mStartedUsers.keyAt(i);
             }
         }
-
-        final int size = childProfilesToUnlock.size();
-        for (int i = 0; i < size; i++) {
-            maybeUnlockUser(childProfilesToUnlock.valueAt(i));
+        for (int testUserId : userIds) {
+            final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
+            if (parent != null && parent.id == userId && testUserId != userId) {
+                Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
+                        + "): attempting unlock because parent was just unlocked");
+                maybeUnlockUser(testUserId);
+            }
         }
 
         return true;
@@ -1104,33 +1125,31 @@
 
     boolean switchUser(final int targetUserId) {
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
-        int currentUserId;
-        UserInfo targetUserInfo;
+        int currentUserId = getCurrentUserId();
+        UserInfo targetUserInfo = getUserInfo(targetUserId);
+        if (targetUserId == currentUserId) {
+            Slog.i(TAG, "user #" + targetUserId + " is already the current user");
+            return true;
+        }
+        if (targetUserInfo == null) {
+            Slog.w(TAG, "No user info for user #" + targetUserId);
+            return false;
+        }
+        if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mInjector.getContext())) {
+            Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
+                    + " when device is in demo mode");
+            return false;
+        }
+        if (!targetUserInfo.supportsSwitchTo()) {
+            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
+            return false;
+        }
+        if (targetUserInfo.isManagedProfile()) {
+            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
+            return false;
+        }
         synchronized (mLock) {
-            currentUserId = getCurrentUserIdLocked();
-            targetUserInfo = getUserInfo(targetUserId);
-            if (targetUserId == currentUserId) {
-                Slog.i(TAG, "user #" + targetUserId + " is already the current user");
-                return true;
-            }
-            if (targetUserInfo == null) {
-                Slog.w(TAG, "No user info for user #" + targetUserId);
-                return false;
-            }
-            if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mInjector.getContext())) {
-                Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
-                        + " when device is in demo mode");
-                return false;
-            }
-            if (!targetUserInfo.supportsSwitchTo()) {
-                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
-                return false;
-            }
-            if (targetUserInfo.isManagedProfile()) {
-                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
-                return false;
-            }
-            setTargetUserIdLocked(targetUserId);
+            mTargetUserId = targetUserId;
         }
         if (mUserSwitchUiEnabled) {
             UserInfo currentUserInfo = getUserInfo(currentUserId);
@@ -1146,12 +1165,12 @@
         return true;
     }
 
-    void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
+    private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second);
     }
 
-    void dispatchForegroundProfileChanged(int userId) {
+    private void dispatchForegroundProfileChanged(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1176,7 +1195,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void dispatchLockedBootComplete(int userId) {
+    private void dispatchLockedBootComplete(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1202,23 +1221,23 @@
         synchronized (mLock) {
             if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                     + " and related users");
-            stopUsersLocked(oldUserId, false, null);
+            stopUsersLU(oldUserId, false, null);
         }
     }
 
-    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         synchronized (mLock) {
             Slog.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
             mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
             mHandler.removeMessages(USER_SWITCH_CALLBACKS_TIMEOUT_MSG);
-            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             // Report observers that never called back (USER_SWITCH_CALLBACKS_TIMEOUT)
             mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
                     oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
         }
     }
 
-    void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
+    private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
         synchronized (mLock) {
             if (mTimeoutUserSwitchCallbacks != null && !mTimeoutUserSwitchCallbacks.isEmpty()) {
                 Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId
@@ -1261,7 +1280,7 @@
                                 if (waitingCallbacksCount.decrementAndGet() == 0
                                         && (curWaitingUserSwitchCallbacks
                                         == mCurWaitingUserSwitchCallbacks)) {
-                                    sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                                    sendContinueUserSwitchLU(uss, oldUserId, newUserId);
                                 }
                             }
                         }
@@ -1272,13 +1291,13 @@
             }
         } else {
             synchronized (mLock) {
-                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             }
         }
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+    void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
         mCurWaitingUserSwitchCallbacks = null;
         mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
         mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
@@ -1288,9 +1307,7 @@
     void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
         Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
         if (mUserSwitchUiEnabled) {
-            synchronized (mLock) {
-                mInjector.getWindowManager().stopFreezingScreen();
-            }
+            mInjector.getWindowManager().stopFreezingScreen();
         }
         uss.switching = false;
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -1300,19 +1317,18 @@
         stopBackgroundUsersIfEnforced(oldUserId);
     }
 
-    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
-        boolean homeInFront =
-                mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
+    private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
+        boolean homeInFront = mInjector.stackSupervisorSwitchUser(newUserId, uss);
         if (homeInFront) {
-            mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
+            mInjector.startHomeActivity(newUserId, "moveUserToForeground");
         } else {
-            mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
+            mInjector.stackSupervisorResumeFocusedStackTopActivity();
         }
         EventLogTags.writeAmSwitchUser(newUserId);
-        sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+        sendUserSwitchBroadcasts(oldUserId, newUserId);
     }
 
-    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
         long ident = Binder.clearCallingIdentity();
         try {
             Intent intent;
@@ -1326,7 +1342,7 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    mInjector.broadcastIntentLocked(intent,
+                    mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                 }
@@ -1341,7 +1357,7 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    mInjector.broadcastIntentLocked(intent,
+                    mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                 }
@@ -1349,7 +1365,7 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
                 intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
-                mInjector.broadcastIntentLocked(intent,
+                mInjector.broadcastIntent(intent,
                         null, null, 0, null, null,
                         new String[] {android.Manifest.permission.MANAGE_USERS},
                         AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
@@ -1374,7 +1390,7 @@
         // the value the caller will receive and someone else changing it.
         // We assume that USER_CURRENT_OR_SELF will use the current user; later
         // we will switch to the calling user if access to the current user fails.
-        int targetUserId = unsafeConvertIncomingUserLocked(userId);
+        int targetUserId = unsafeConvertIncomingUser(userId);
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
@@ -1442,9 +1458,9 @@
         return targetUserId;
     }
 
-    int unsafeConvertIncomingUserLocked(int userId) {
+    int unsafeConvertIncomingUser(int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
-                ? getCurrentUserIdLocked(): userId;
+                ? getCurrentUserId(): userId;
     }
 
     void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
@@ -1470,15 +1486,17 @@
         mUserSwitchObservers.unregister(observer);
     }
 
-    UserState getStartedUserStateLocked(int userId) {
-        return mStartedUsers.get(userId);
+    UserState getStartedUserState(int userId) {
+        synchronized (mLock) {
+            return mStartedUsers.get(userId);
+        }
     }
 
     boolean hasStartedUserState(int userId) {
         return mStartedUsers.get(userId) != null;
     }
 
-    private void updateStartedUserArrayLocked() {
+    private void updateStartedUserArrayLU() {
         int num = 0;
         for (int i = 0; i < mStartedUsers.size(); i++) {
             UserState uss = mStartedUsers.valueAt(i);
@@ -1499,15 +1517,20 @@
         }
     }
 
-    void sendBootCompletedLocked(IIntentReceiver resultTo) {
-        for (int i = 0; i < mStartedUsers.size(); i++) {
-            UserState uss = mStartedUsers.valueAt(i);
+    void sendBootCompleted(IIntentReceiver resultTo) {
+        // Get a copy of mStartedUsers to use outside of lock
+        SparseArray<UserState> startedUsers;
+        synchronized (mLock) {
+            startedUsers = mStartedUsers.clone();
+        }
+        for (int i = 0; i < startedUsers.size(); i++) {
+            UserState uss = startedUsers.valueAt(i);
             finishUserBoot(uss, resultTo);
         }
     }
 
     void onSystemReady() {
-        updateCurrentProfileIdsLocked();
+        updateCurrentProfileIds();
     }
 
     /**
@@ -1515,33 +1538,35 @@
      * user switch happens or when a new related user is started in the
      * background.
      */
-    private void updateCurrentProfileIdsLocked() {
-        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(mCurrentUserId,
+    private void updateCurrentProfileIds() {
+        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
                 false /* enabledOnly */);
         int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
         for (int i = 0; i < currentProfileIds.length; i++) {
             currentProfileIds[i] = profiles.get(i).id;
         }
-        mCurrentProfileIds = currentProfileIds;
+        final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+        synchronized (mLock) {
+            mCurrentProfileIds = currentProfileIds;
 
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            mUserProfileGroupIdsSelfLocked.clear();
-            final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+            mUserProfileGroupIds.clear();
             for (int i = 0; i < users.size(); i++) {
                 UserInfo user = users.get(i);
                 if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
-                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+                    mUserProfileGroupIds.put(user.id, user.profileGroupId);
                 }
             }
         }
     }
 
-    int[] getStartedUserArrayLocked() {
-        return mStartedUserArray;
+    int[] getStartedUserArray() {
+        synchronized (mLock) {
+            return mStartedUserArray;
+        }
     }
 
-    boolean isUserRunningLocked(int userId, int flags) {
-        UserState state = getStartedUserStateLocked(userId);
+    boolean isUserRunning(int userId, int flags) {
+        UserState state = getStartedUserState(userId);
         if (state == null) {
             return false;
         }
@@ -1604,29 +1629,38 @@
             return getUserInfo(mCurrentUserId);
         }
         synchronized (mLock) {
-            return getCurrentUserLocked();
+            return getCurrentUserLU();
         }
     }
 
-    UserInfo getCurrentUserLocked() {
+    UserInfo getCurrentUserLU() {
         int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
         return getUserInfo(userId);
     }
 
-    int getCurrentOrTargetUserIdLocked() {
+    int getCurrentOrTargetUserId() {
+        synchronized (mLock) {
+            return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        }
+    }
+
+    int getCurrentOrTargetUserIdLU() {
         return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
     }
 
-    int getCurrentUserIdLocked() {
+
+    int getCurrentUserIdLU() {
         return mCurrentUserId;
     }
 
-    private boolean isCurrentUserLocked(int userId) {
-        return userId == getCurrentOrTargetUserIdLocked();
+    int getCurrentUserId() {
+        synchronized (mLock) {
+            return mCurrentUserId;
+        }
     }
 
-    int setTargetUserIdLocked(int targetUserId) {
-        return mTargetUserId = targetUserId;
+    private boolean isCurrentUserLU(int userId) {
+        return userId == getCurrentOrTargetUserIdLU();
     }
 
     int[] getUsers() {
@@ -1673,22 +1707,26 @@
         if (callingUserId == targetUserId) {
             return true;
         }
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+        synchronized (mLock) {
+            int callingProfile = mUserProfileGroupIds.get(callingUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
-            int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+            int targetProfile = mUserProfileGroupIds.get(targetUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
             return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
                     && callingProfile == targetProfile;
         }
     }
 
-    boolean isCurrentProfileLocked(int userId) {
-        return ArrayUtils.contains(mCurrentProfileIds, userId);
+    boolean isCurrentProfile(int userId) {
+        synchronized (mLock) {
+            return ArrayUtils.contains(mCurrentProfileIds, userId);
+        }
     }
 
-    int[] getCurrentProfileIdsLocked() {
-        return mCurrentProfileIds;
+    int[] getCurrentProfileIds() {
+        synchronized (mLock) {
+            return mCurrentProfileIds;
+        }
     }
 
     /**
@@ -1713,35 +1751,40 @@
     }
 
     void dump(PrintWriter pw, boolean dumpAll) {
-        pw.println("  mStartedUsers:");
-        for (int i = 0; i < mStartedUsers.size(); i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
-            pw.print(": "); uss.dump("", pw);
-        }
-        pw.print("  mStartedUserArray: [");
-        for (int i = 0; i < mStartedUserArray.length; i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(mStartedUserArray[i]);
-        }
-        pw.println("]");
-        pw.print("  mUserLru: [");
-        for (int i = 0; i < mUserLru.size(); i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(mUserLru.get(i));
-        }
-        pw.println("]");
-        if (dumpAll) {
-            pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
-        }
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+        synchronized (mLock) {
+            pw.println("  mStartedUsers:");
+            for (int i = 0; i < mStartedUsers.size(); i++) {
+                UserState uss = mStartedUsers.valueAt(i);
+                pw.print("    User #");
+                pw.print(uss.mHandle.getIdentifier());
+                pw.print(": ");
+                uss.dump("", pw);
+            }
+            pw.print("  mStartedUserArray: [");
+            for (int i = 0; i < mStartedUserArray.length; i++) {
+                if (i > 0)
+                    pw.print(", ");
+                pw.print(mStartedUserArray[i]);
+            }
+            pw.println("]");
+            pw.print("  mUserLru: [");
+            for (int i = 0; i < mUserLru.size(); i++) {
+                if (i > 0)
+                    pw.print(", ");
+                pw.print(mUserLru.get(i));
+            }
+            pw.println("]");
+            if (dumpAll) {
+                pw.print("  mStartedUserArray: ");
+                pw.println(Arrays.toString(mStartedUserArray));
+            }
+            if (mUserProfileGroupIds.size() > 0) {
                 pw.println("  mUserProfileGroupIds:");
-                for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+                for (int i=0; i< mUserProfileGroupIds.size(); i++) {
                     pw.print("    User #");
-                    pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+                    pw.print(mUserProfileGroupIds.keyAt(i));
                     pw.print(" -> profile #");
-                    pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+                    pw.println(mUserProfileGroupIds.valueAt(i));
                 }
             }
         }
@@ -1765,9 +1808,7 @@
                 timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
                 break;
             case START_PROFILES_MSG:
-                synchronized (mLock) {
-                    startProfilesLocked();
-                }
+                startProfiles();
                 break;
             case SYSTEM_USER_START_MSG:
                 mInjector.batteryStatsServiceNoteEvent(
@@ -1778,9 +1819,7 @@
             case SYSTEM_USER_UNLOCK_MSG:
                 final int userId = msg.arg1;
                 mInjector.getSystemServiceManager().unlockUser(userId);
-                synchronized (mLock) {
-                    mInjector.loadUserRecentsLocked(userId);
-                }
+                mInjector.loadUserRecents(userId);
                 if (userId == UserHandle.USER_SYSTEM) {
                     mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
                 }
@@ -1813,6 +1852,33 @@
         return false;
     }
 
+    private static class UserProgressListener extends IProgressListener.Stub {
+        private volatile long mUnlockStarted;
+        @Override
+        public void onStarted(int id, Bundle extras) throws RemoteException {
+            Slog.d(TAG, "Started unlocking user " + id);
+            mUnlockStarted = SystemClock.uptimeMillis();
+        }
+
+        @Override
+        public void onProgress(int id, int progress, Bundle extras) throws RemoteException {
+            Slog.d(TAG, "Unlocking user " + id + " progress " + progress);
+        }
+
+        @Override
+        public void onFinished(int id, Bundle extras) throws RemoteException {
+            long unlockTime = SystemClock.uptimeMillis() - mUnlockStarted;
+
+            // Report system user unlock time to perf dashboard
+            if (id == UserHandle.USER_SYSTEM) {
+                new TimingsTraceLog("SystemServerTiming", Trace.TRACE_TAG_SYSTEM_SERVER)
+                        .logDuration("SystemUserUnlock", unlockTime);
+            } else {
+                Slog.d(TAG, "Unlocking user " + id + " took " + unlockTime + " ms");
+            }
+        }
+    };
+
     @VisibleForTesting
     static class Injector {
         private final ActivityManagerService mService;
@@ -1823,10 +1889,6 @@
             mService = service;
         }
 
-        protected Object getLock() {
-            return mService;
-        }
-
         protected Handler getHandler(Handler.Callback callback) {
             return new Handler(mService.mHandlerThread.getLooper(), callback);
         }
@@ -1843,13 +1905,16 @@
             return new LockPatternUtils(getContext());
         }
 
-        protected int broadcastIntentLocked(Intent intent, String resolvedType,
+        protected int broadcastIntent(Intent intent, String resolvedType,
                 IIntentReceiver resultTo, int resultCode, String resultData,
                 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                 boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
-            return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
-                    resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
-                    ordered, sticky, callingPid, callingUid, userId);
+            // TODO b/64165549 Verify that mLock is not held before calling AMS methods
+            synchronized (mService) {
+                return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
+                        resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
+                        ordered, sticky, callingPid, callingUid, userId);
+            }
         }
 
         int checkCallingPermission(String permission) {
@@ -1860,7 +1925,9 @@
             return mService.mWindowManager;
         }
         void activityManagerOnUserStopped(int userId) {
-            mService.onUserStoppedLocked(userId);
+            synchronized (mService) {
+                mService.onUserStoppedLocked(userId);
+            }
         }
 
         void systemServiceManagerCleanupUser(int userId) {
@@ -1916,9 +1983,11 @@
             }.sendNext();
         }
 
-        void activityManagerForceStopPackageLocked(int userId, String reason) {
-            mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
-                    userId, reason);
+        void activityManagerForceStopPackage(int userId, String reason) {
+            synchronized (mService) {
+                mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+                        userId, reason);
+            }
         };
 
         int checkComponentPermission(String permission, int pid, int uid, int owningUid,
@@ -1926,20 +1995,28 @@
             return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
         }
 
-        void startHomeActivityLocked(int userId, String reason) {
-            mService.startHomeActivityLocked(userId, reason);
+        protected void startHomeActivity(int userId, String reason) {
+            synchronized (mService) {
+                mService.startHomeActivityLocked(userId, reason);
+            }
         }
 
-        void updateUserConfigurationLocked() {
-            mService.updateUserConfigurationLocked();
+        void updateUserConfiguration() {
+            synchronized (mService) {
+                mService.updateUserConfigurationLocked();
+            }
         }
 
-        void clearBroadcastQueueForUserLocked(int userId) {
-            mService.clearBroadcastQueueForUserLocked(userId);
+        void clearBroadcastQueueForUser(int userId) {
+            synchronized (mService) {
+                mService.clearBroadcastQueueForUserLocked(userId);
+            }
         }
 
-        void loadUserRecentsLocked(int userId) {
-            mService.mRecentTasks.loadUserRecentsLocked(userId);
+        void loadUserRecents(int userId) {
+            synchronized (mService) {
+                mService.getRecentTasks().loadUserRecentsLocked(userId);
+            }
         }
 
         void startPersistentApps(int matchFlags) {
@@ -1956,12 +2033,28 @@
             d.show();
         }
 
-        ActivityStackSupervisor getActivityStackSupervisor() {
-            return mService.mStackSupervisor;
+        void stackSupervisorRemoveUser(int userId) {
+            synchronized (mService) {
+                mService.mStackSupervisor.removeUserLocked(userId);
+            }
         }
 
-        LockTaskController getLockTaskController() {
-            return mService.mLockTaskController;
+        protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
+            synchronized (mService) {
+                return mService.mStackSupervisor.switchUserLocked(userId, uss);
+            }
+        }
+
+        protected void stackSupervisorResumeFocusedStackTopActivity() {
+            synchronized (mService) {
+                mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+            }
+        }
+
+        protected void clearAllLockedTasks(String reason) {
+            synchronized (mService) {
+                mService.mLockTaskController.clearLockedTasks(reason);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 2e27387..d36d9cb 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -59,8 +59,10 @@
     /**
      * The last time that a provider was reported to usage stats as being brought to important
      * foreground procstate.
+     * <p><strong>Important: </strong>Only access this field when holding ActivityManagerService
+     * lock.
      */
-    public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
+    final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
 
     public UserState(UserHandle handle) {
         mHandle = handle;
diff --git a/services/core/java/com/android/server/audio/AudioEventLogger.java b/services/core/java/com/android/server/audio/AudioEventLogger.java
index c96138f..9ebd75b 100644
--- a/services/core/java/com/android/server/audio/AudioEventLogger.java
+++ b/services/core/java/com/android/server/audio/AudioEventLogger.java
@@ -16,6 +16,8 @@
 
 package com.android.server.audio;
 
+import android.util.Log;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -47,6 +49,22 @@
         }
 
         /**
+         * Causes the string message for the event to appear in the logcat.
+         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
+         * (an instance of AudioEventLogger) while also making it show in the logcat:
+         * <pre>
+         *     myLogger.log(
+         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
+         * </pre>
+         * @param tag the tag for the android.util.Log.v
+         * @return the same instance of the event
+         */
+        public Event printLog(String tag) {
+            Log.i(tag, eventToString());
+            return this;
+        }
+
+        /**
          * Convert event to String.
          * This method is only called when the logger history is about to the dumped,
          * so this method is where expensive String conversions should be made, not when the Event
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 11d0470..5eb2a8d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -461,6 +461,8 @@
 
     // Forced device usage for communications
     private int mForcedUseForComm;
+    private int mForcedUseForCommExt; // External state returned by getters: always consistent
+                                      // with requests by setters
 
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
@@ -749,6 +751,9 @@
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
 
+        mPlaybackMonitor =
+                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
         mRecordMonitor = new RecordingActivityMonitor(mContext);
@@ -2544,13 +2549,15 @@
             }
         }
         int status = AudioSystem.AUDIO_STATUS_OK;
+        int actualMode;
         do {
+            actualMode = mode;
             if (mode == AudioSystem.MODE_NORMAL) {
                 // get new mode from client at top the list if any
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     hdlr = mSetModeDeathHandlers.get(0);
                     cb = hdlr.getBinder();
-                    mode = hdlr.getMode();
+                    actualMode = hdlr.getMode();
                     if (DEBUG_MODE) {
                         Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid="
                                 + hdlr.mPid);
@@ -2574,12 +2581,11 @@
                 hdlr.setMode(mode);
             }
 
-            if (mode != mMode) {
-                status = AudioSystem.setPhoneState(mode);
+            if (actualMode != mMode) {
+                status = AudioSystem.setPhoneState(actualMode);
                 if (status == AudioSystem.AUDIO_STATUS_OK) {
-                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); }
-                    mMode = mode;
-                    mModeLogger.log(new PhoneStateEvent(caller, pid, mode));
+                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
+                    mMode = actualMode;
                 } else {
                     if (hdlr != null) {
                         mSetModeDeathHandlers.remove(hdlr);
@@ -2595,13 +2601,16 @@
         } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());
 
         if (status == AudioSystem.AUDIO_STATUS_OK) {
-            if (mode != AudioSystem.MODE_NORMAL) {
+            if (actualMode != AudioSystem.MODE_NORMAL) {
                 if (mSetModeDeathHandlers.isEmpty()) {
                     Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
                 } else {
                     newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
                 }
             }
+            // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
+            mModeLogger.log(
+                    new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode));
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
             int device = getDeviceForStream(streamType);
             int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
@@ -2890,13 +2899,14 @@
             mForcedUseForComm = AudioSystem.FORCE_NONE;
         }
 
+        mForcedUseForCommExt = mForcedUseForComm;
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
                 AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
     }
 
     /** @see AudioManager#isSpeakerphoneOn() */
     public boolean isSpeakerphoneOn() {
-        return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
+        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
     }
 
     /** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -2904,6 +2914,13 @@
         if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
             return;
         }
+
+        // Only enable calls from system components
+        if (Binder.getCallingUid() >= FIRST_APPLICATION_UID) {
+            mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+            return;
+        }
+
         // for logging only
         final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
@@ -2913,11 +2930,21 @@
 
     public void setBluetoothScoOnInt(boolean on, String eventSource) {
         if (on) {
+            // do not accept SCO ON if SCO audio is not connected
+            synchronized(mScoClients) {
+                if ((mBluetoothHeadset != null) &&
+                    (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                             != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                    return;
+                }
+            }
             mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
         } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
             mForcedUseForComm = AudioSystem.FORCE_NONE;
         }
-
+        mForcedUseForCommExt = mForcedUseForComm;
+        AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
                 AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
@@ -2926,7 +2953,7 @@
 
     /** @see AudioManager#isBluetoothScoOn() */
     public boolean isBluetoothScoOn() {
-        return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
+        return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
     }
 
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
@@ -4134,7 +4161,8 @@
                     newDevice, AudioSystem.getOutputDeviceName(newDevice)));
         }
         synchronized (mConnectedDevices) {
-            if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+            if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                    && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
                     && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
                     && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
                     && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
@@ -6952,7 +6980,7 @@
     //======================
     // Audio playback notification
     //======================
-    private final PlaybackActivityMonitor mPlaybackMonitor = new PlaybackActivityMonitor();
+    private final PlaybackActivityMonitor mPlaybackMonitor;
 
     public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
         final boolean isPrivileged =
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 634c8c2..9d9e35b 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -26,20 +26,27 @@
 
     final static class PhoneStateEvent extends AudioEventLogger.Event {
         final String mPackage;
-        final int mPid;
-        final int mMode;
+        final int mOwnerPid;
+        final int mRequesterPid;
+        final int mRequestedMode;
+        final int mActualMode;
 
-        PhoneStateEvent(String callingPackage, int pid, int mode) {
+        PhoneStateEvent(String callingPackage, int requesterPid, int requestedMode,
+                        int ownerPid, int actualMode) {
             mPackage = callingPackage;
-            mPid = pid;
-            mMode = mode;
+            mRequesterPid = requesterPid;
+            mRequestedMode = requestedMode;
+            mOwnerPid = ownerPid;
+            mActualMode = actualMode;
         }
 
         @Override
         public String eventToString() {
-            return new StringBuilder("setMode(").append(AudioSystem.modeToString(mMode))
+            return new StringBuilder("setMode(").append(AudioSystem.modeToString(mRequestedMode))
                     .append(") from package=").append(mPackage)
-                    .append(" pid=").append(mPid).toString();
+                    .append(" pid=").append(mRequesterPid)
+                    .append(" selected mode=").append(AudioSystem.modeToString(mActualMode))
+                    .append(" by pid=").append(mOwnerPid).toString();
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 7d742ff..c5f563c7 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -89,6 +89,9 @@
         pw.println("\nMediaFocusControl dump time: "
                 + DateFormat.getTimeInstance().format(new Date()));
         dumpFocusStack(pw);
+        pw.println("\n");
+        // log
+        mEventLogger.dump(pw);
     }
 
     //=================================================================
@@ -120,6 +123,14 @@
     private final static Object mAudioFocusLock = new Object();
 
     /**
+     * Arbitrary maximum size of audio focus stack to prevent apps OOM'ing this process.
+     */
+    private static final int MAX_STACK_SIZE = 100;
+
+    private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
+            "focus commands as seen by MediaFocusControl");
+
+    /**
      * Discard the current audio focus owner.
      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
      * focus), remove it from the stack, and clear the remote control display.
@@ -643,11 +654,14 @@
     protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
             int sdk) {
-        Log.i(TAG, " AudioFocus  requestAudioFocus() from uid/pid " + Binder.getCallingUid()
-                + "/" + Binder.getCallingPid()
-                + " clientId=" + clientId
-                + " req=" + focusChangeHint
-                + " flags=0x" + Integer.toHexString(flags));
+        mEventLogger.log((new AudioEventLogger.StringEvent(
+                "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
+                    + "/" + Binder.getCallingPid()
+                    + " clientId=" + clientId + " callingPack=" + callingPackageName
+                    + " req=" + focusChangeHint
+                    + " flags=0x" + Integer.toHexString(flags)
+                    + " sdk=" + sdk))
+                .printLog(TAG));
         // we need a valid binder callback for clients
         if (!cb.pingBinder()) {
             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -660,6 +674,11 @@
         }
 
         synchronized(mAudioFocusLock) {
+            if (mFocusStack.size() > MAX_STACK_SIZE) {
+                Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
+                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+            }
+
             boolean enteringRingOrCall = !mRingOrCallActive
                     & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
             if (enteringRingOrCall) { mRingOrCallActive = true; }
@@ -770,10 +789,12 @@
      * */
     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
             String callingPackageName) {
-        // AudioAttributes are currently ignored, to be used for zones
-        Log.i(TAG, " AudioFocus  abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
-                + "/" + Binder.getCallingPid()
-                + " clientId=" + clientId);
+        // AudioAttributes are currently ignored, to be used for zones / a11y
+        mEventLogger.log((new AudioEventLogger.StringEvent(
+                "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+                    + "/" + Binder.getCallingPid()
+                    + " clientId=" + clientId))
+                .printLog(TAG));
         try {
             // this will take care of notifying the new focus owner if needed
             synchronized(mAudioFocusLock) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index d1a37af..4943173 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,6 +17,8 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
@@ -90,7 +92,14 @@
     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
             new HashMap<Integer, AudioPlaybackConfiguration>();
 
-    PlaybackActivityMonitor() {
+    private final Context mContext;
+    private int mSavedAlarmVolume = -1;
+    private final int mMaxAlarmVolume;
+    private int mPrivilegedAlarmActiveCount = 0;
+
+    PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+        mContext = context;
+        mMaxAlarmVolume = maxAlarmVolume;
         PlayMonitorClient.sListenerDeathMonitor = this;
         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
     }
@@ -105,7 +114,7 @@
             if (index >= 0) {
                 if (!disable) {
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        mEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
+                        sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
                     }
                     mBannedUids.remove(index);
                     // nothing else to do, future playback requests from this uid are ok
@@ -116,7 +125,7 @@
                         checkBanPlayer(apc, uid);
                     }
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        mEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
+                        sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
                     }
                     mBannedUids.add(new Integer(uid));
                 } // no else to handle, uid already not in list, so enabling again is no-op
@@ -151,7 +160,7 @@
                 new AudioPlaybackConfiguration(pic, newPiid,
                         Binder.getCallingUid(), Binder.getCallingPid());
         apc.init();
-        mEventLogger.log(new NewPlayerEvent(apc));
+        sEventLogger.log(new NewPlayerEvent(apc));
         synchronized(mPlayerLock) {
             mPlayers.put(newPiid, apc);
         }
@@ -163,7 +172,7 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                mEventLogger.log(new AudioAttrEvent(piid, attr));
+                sEventLogger.log(new AudioAttrEvent(piid, attr));
                 change = apc.handleAudioAttributesEvent(attr);
             } else {
                 Log.e(TAG, "Error updating audio attributes");
@@ -175,6 +184,42 @@
         }
     }
 
+    private static final int FLAGS_FOR_SILENCE_OVERRIDE =
+            AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
+            AudioAttributes.FLAG_BYPASS_MUTE;
+
+    private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
+        if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
+                apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+            if ((apc.getAudioAttributes().getAllFlags() & FLAGS_FOR_SILENCE_OVERRIDE)
+                        == FLAGS_FOR_SILENCE_OVERRIDE  &&
+                    apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
+                    mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
+                            apc.getClientPid(), apc.getClientUid()) ==
+                            PackageManager.PERMISSION_GRANTED) {
+                if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+                        apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    if (mPrivilegedAlarmActiveCount++ == 0) {
+                        mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
+                                AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+                        AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                                mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                    }
+                } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+                        apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    if (--mPrivilegedAlarmActiveCount == 0) {
+                        if (AudioSystem.getStreamVolumeIndex(
+                                AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
+                                mMaxAlarmVolume) {
+                            AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                                    mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public void playerEvent(int piid, int event, int binderUid) {
         if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
         final boolean change;
@@ -183,12 +228,12 @@
             if (apc == null) {
                 return;
             }
-            mEventLogger.log(new PlayerEvent(piid, event));
+            sEventLogger.log(new PlayerEvent(piid, event));
             if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                 for (Integer uidInteger: mBannedUids) {
                     if (checkBanPlayer(apc, uidInteger.intValue())) {
                         // player was banned, do not update its state
-                        mEventLogger.log(new AudioEventLogger.StringEvent(
+                        sEventLogger.log(new AudioEventLogger.StringEvent(
                                 "not starting piid:" + piid + " ,is banned"));
                         return;
                     }
@@ -200,6 +245,7 @@
             }
             if (checkConfigurationCaller(piid, apc, binderUid)) {
                 //TODO add generation counter to only update to the latest state
+                checkVolumeForPrivilegedAlarm(apc, event);
                 change = apc.handleStateEvent(event);
             } else {
                 Log.e(TAG, "Error handling event " + event);
@@ -216,7 +262,7 @@
 
     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
         // no check on UID yet because this is only for logging at the moment
-        mEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+        sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
     }
 
     public void releasePlayer(int piid, int binderUid) {
@@ -224,10 +270,11 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                mEventLogger.log(new AudioEventLogger.StringEvent(
+                sEventLogger.log(new AudioEventLogger.StringEvent(
                         "releasing player piid:" + piid));
                 mPlayers.remove(new Integer(piid));
                 mDuckingManager.removeReleased(apc);
+                checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
                 apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
             }
         }
@@ -278,7 +325,7 @@
             }
             pw.println("\n");
             // log
-            mEventLogger.dump(pw);
+            sEventLogger.dump(pw);
         }
     }
 
@@ -456,7 +503,8 @@
                 }
                 if (mute) {
                     try {
-                        Log.v(TAG, "call: muting player" + piid + " uid:" + apc.getClientUid());
+                        sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+                                + piid + " uid:" + apc.getClientUid())).printLog(TAG));
                         apc.getPlayerProxy().setVolume(0.0f);
                         mMutedPlayers.add(new Integer(piid));
                     } catch (Exception e) {
@@ -480,7 +528,8 @@
                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
                 if (apc != null) {
                     try {
-                        Log.v(TAG, "call: unmuting player" + piid + " uid:" + apc.getClientUid());
+                        sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
+                                + piid).printLog(TAG));
                         apc.getPlayerProxy().setVolume(1.0f);
                     } catch (Exception e) {
                         Log.e(TAG, "call: error unmuting player " + piid + " uid:"
@@ -669,8 +718,7 @@
                     return;
                 }
                 try {
-                    Log.v(TAG, "ducking (skipRamp=" + skipRamp + ") player piid:"
-                            + apc.getPlayerInterfaceId() + " uid:" + mUid);
+                    sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
                     apc.getPlayerProxy().applyVolumeShaper(
                             DUCK_VSHAPE,
                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -685,7 +733,8 @@
                     final AudioPlaybackConfiguration apc = players.get(piid);
                     if (apc != null) {
                         try {
-                            Log.v(TAG, "unducking player " + piid + " uid:" + mUid);
+                            sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+                                    + piid)).printLog(TAG));
                             apc.getPlayerProxy().applyVolumeShaper(
                                     DUCK_ID,
                                     VolumeShaper.Operation.REVERSE);
@@ -772,7 +821,28 @@
         }
     }
 
-    private final static class AudioAttrEvent extends AudioEventLogger.Event {
+    private static final class DuckEvent extends AudioEventLogger.Event {
+        private final int mPlayerIId;
+        private final boolean mSkipRamp;
+        private final int mClientUid;
+        private final int mClientPid;
+
+        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+            mPlayerIId = apc.getPlayerInterfaceId();
+            mSkipRamp = skipRamp;
+            mClientUid = apc.getClientUid();
+            mClientPid = apc.getClientPid();
+        }
+
+        @Override
+        public String eventToString() {
+            return new StringBuilder("ducking player piid:").append(mPlayerIId)
+                    .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
+                    .append(" skip ramp:").append(mSkipRamp).toString();
+        }
+    }
+
+    private static final class AudioAttrEvent extends AudioEventLogger.Event {
         private final int mPlayerIId;
         private final AudioAttributes mPlayerAttr;
 
@@ -787,6 +857,6 @@
         }
     }
 
-    private final AudioEventLogger mEventLogger = new AudioEventLogger(100,
+    private static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
             "playback activity as reported through PlayerBase");
 }
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index e6228d4..0c9d70a 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -435,11 +435,12 @@
     }
 
     private boolean isDeviceLocked() {
+        int callingUserId = UserHandle.getCallingUserId();
         final long token = Binder.clearCallingIdentity();
         try {
             final KeyguardManager keyguardManager = getContext().getSystemService(
                     KeyguardManager.class);
-            return keyguardManager != null && keyguardManager.isDeviceLocked();
+            return keyguardManager != null && keyguardManager.isDeviceLocked(callingUserId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
new file mode 100644
index 0000000..8981db1
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.connectivity;
+
+import android.net.LinkProperties;
+import android.net.metrics.DefaultNetworkEvent;
+import android.net.metrics.IpConnectivityLog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tracks events related to the default network for the purpose of default network metrics.
+ * {@hide}
+ */
+public class DefaultNetworkMetrics {
+
+    private static final int ROLLING_LOG_SIZE = 64;
+
+    // Event buffer used for metrics upload. The buffer is cleared when events are collected.
+    @GuardedBy("this")
+    private final List<DefaultNetworkEvent> mEvents = new ArrayList<>();
+
+    public synchronized void listEvents(PrintWriter pw) {
+        long localTimeMs = System.currentTimeMillis();
+        for (DefaultNetworkEvent ev : mEvents) {
+            pw.println(ev);
+        }
+    }
+
+    public synchronized void listEventsAsProto(PrintWriter pw) {
+        for (DefaultNetworkEvent ev : mEvents) {
+            pw.print(IpConnectivityEventBuilder.toProto(ev));
+        }
+    }
+
+    public synchronized void flushEvents(List<IpConnectivityEvent> out) {
+        for (DefaultNetworkEvent ev : mEvents) {
+            out.add(IpConnectivityEventBuilder.toProto(ev));
+        }
+        mEvents.clear();
+    }
+
+    public synchronized void logDefaultNetworkEvent(
+            NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
+        DefaultNetworkEvent ev = new DefaultNetworkEvent();
+        if (newNai != null) {
+            ev.netId = newNai.network().netId;
+            ev.transportTypes = newNai.networkCapabilities.getTransportTypes();
+        }
+        if (prevNai != null) {
+            ev.prevNetId = prevNai.network().netId;
+            final LinkProperties lp = prevNai.linkProperties;
+            ev.prevIPv4 = lp.hasIPv4Address() && lp.hasIPv4DefaultRoute();
+            ev.prevIPv6 = lp.hasGlobalIPv6Address() && lp.hasIPv6DefaultRoute();
+        }
+
+        mEvents.add(ev);
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 67e7216..3d71ecb 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -132,6 +132,18 @@
         return out;
     }
 
+    public static IpConnectivityEvent toProto(DefaultNetworkEvent in) {
+        IpConnectivityLogClass.DefaultNetworkEvent ev =
+                new IpConnectivityLogClass.DefaultNetworkEvent();
+        ev.networkId = netIdOf(in.netId);
+        ev.previousNetworkId = netIdOf(in.prevNetId);
+        ev.transportTypes = in.transportTypes;
+        ev.previousNetworkIpSupport = ipSupportOf(in);
+        final IpConnectivityEvent out = buildEvent(in.netId, 0, null);
+        out.setDefaultNetworkEvent(ev);
+        return out;
+    }
+
     private static IpConnectivityEvent buildEvent(int netId, long transports, String ifname) {
         final IpConnectivityEvent ev = new IpConnectivityEvent();
         ev.networkId = netId;
@@ -164,11 +176,6 @@
             return true;
         }
 
-        if (in instanceof DefaultNetworkEvent) {
-            setDefaultNetworkEvent(out, (DefaultNetworkEvent) in);
-            return true;
-        }
-
         if (in instanceof NetworkEvent) {
             setNetworkEvent(out, (NetworkEvent) in);
             return true;
@@ -225,16 +232,6 @@
         out.setIpReachabilityEvent(ipReachabilityEvent);
     }
 
-    private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) {
-        IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent =
-                new IpConnectivityLogClass.DefaultNetworkEvent();
-        defaultNetworkEvent.networkId = netIdOf(in.netId);
-        defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId);
-        defaultNetworkEvent.transportTypes = in.transportTypes;
-        defaultNetworkEvent.previousNetworkIpSupport = ipSupportOf(in);
-        out.setDefaultNetworkEvent(defaultNetworkEvent);
-    }
-
     private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) {
         IpConnectivityLogClass.NetworkEvent networkEvent =
                 new IpConnectivityLogClass.NetworkEvent();
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 475d786..24217e6 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -32,11 +32,15 @@
 import android.util.ArrayMap;
 import android.util.Base64;
 import android.util.Log;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -44,7 +48,11 @@
 import java.util.List;
 import java.util.function.ToIntFunction;
 
-/** {@hide} */
+/**
+ * Event buffering service for core networking and connectivity metrics.
+ *
+ * {@hide}
+ */
 final public class IpConnectivityMetrics extends SystemService {
     private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
     private static final boolean DBG = false;
@@ -58,7 +66,10 @@
 
     private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
 
-    // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
+    // Default size of the event rolling log for bug report dumps.
+    private static final int DEFAULT_LOG_SIZE = 500;
+    // Default size of the event buffer for metrics reporting.
+    // Once the buffer is full, incoming events are dropped.
     private static final int DEFAULT_BUFFER_SIZE = 2000;
     // Maximum size of the event buffer.
     private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
@@ -67,29 +78,46 @@
 
     private static final int ERROR_RATE_LIMITED = -1;
 
-    // Lock ensuring that concurrent manipulations of the event buffer are correct.
+    // Lock ensuring that concurrent manipulations of the event buffers are correct.
     // There are three concurrent operations to synchronize:
     //  - appending events to the buffer.
     //  - iterating throught the buffer.
     //  - flushing the buffer content and replacing it by a new buffer.
     private final Object mLock = new Object();
 
+    // Implementation instance of IIpConnectivityMetrics.aidl.
     @VisibleForTesting
     public final Impl impl = new Impl();
+    // Subservice listening to Netd events via INetdEventListener.aidl.
     @VisibleForTesting
     NetdEventListenerService mNetdListener;
 
+    // Rolling log of the most recent events. This log is used for dumping
+    // connectivity events in bug reports.
+    @GuardedBy("mLock")
+    private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
+            new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
+    // Buffer of connectivity events used for metrics reporting. This buffer
+    // does not rotate automatically and instead saturates when it becomes full.
+    // It is flushed at metrics reporting.
     @GuardedBy("mLock")
     private ArrayList<ConnectivityMetricsEvent> mBuffer;
+    // Total number of events dropped from mBuffer since last metrics reporting.
     @GuardedBy("mLock")
     private int mDropped;
+    // Capacity of mBuffer
     @GuardedBy("mLock")
     private int mCapacity;
+    // A list of rate limiting counters keyed by connectivity event types for
+    // metrics reporting mBuffer.
     @GuardedBy("mLock")
     private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
 
     private final ToIntFunction<Context> mCapacityGetter;
 
+    @VisibleForTesting
+    final DefaultNetworkMetrics mDefaultNetworkMetrics = new DefaultNetworkMetrics();
+
     public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) {
         super(ctx);
         mCapacityGetter = capacityGetter;
@@ -113,6 +141,8 @@
 
             publishBinderService(SERVICE_NAME, impl);
             publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener);
+
+            LocalServices.addService(Logger.class, new LoggerImpl());
         }
     }
 
@@ -132,6 +162,7 @@
     private int append(ConnectivityMetricsEvent event) {
         if (DBG) Log.d(TAG, "logEvent: " + event);
         synchronized (mLock) {
+            mEventLog.append(event);
             final int left = mCapacity - mBuffer.size();
             if (event == null) {
                 return left;
@@ -165,6 +196,8 @@
 
         final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events);
 
+        mDefaultNetworkMetrics.flushEvents(protoEvents);
+
         if (mNetdListener != null) {
             mNetdListener.flushStatistics(protoEvents);
         }
@@ -205,6 +238,7 @@
             if (mNetdListener != null) {
                 mNetdListener.listAsProtos(pw);
             }
+            mDefaultNetworkMetrics.listEventsAsProto(pw);
             return;
         }
 
@@ -214,6 +248,25 @@
         if (mNetdListener != null) {
             mNetdListener.list(pw);
         }
+        mDefaultNetworkMetrics.listEvents(pw);
+    }
+
+    /**
+     * Prints for bug reports the content of the rolling event log and the
+     * content of Netd event listener.
+     */
+    private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final ConnectivityMetricsEvent[] events;
+        synchronized (mLock) {
+            events = mEventLog.toArray();
+        }
+        for (ConnectivityMetricsEvent ev : events) {
+            pw.println(ev.toString());
+        }
+        if (mNetdListener != null) {
+            mNetdListener.list(pw);
+        }
+        mDefaultNetworkMetrics.listEvents(pw);
     }
 
     private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -258,7 +311,8 @@
                     cmdFlush(fd, pw, args);
                     return;
                 case CMD_DUMPSYS:
-                    // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
+                    cmdDumpsys(fd, pw, args);
+                    return;
                 case CMD_LIST:
                     cmdList(fd, pw, args);
                     return;
@@ -325,4 +379,15 @@
         map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50));
         return map;
     }
+
+    /** Direct non-Binder interface for event producer clients within the system servers. */
+    public interface Logger {
+        DefaultNetworkMetrics defaultNetworkMetrics();
+    }
+
+    private class LoggerImpl implements Logger {
+        public DefaultNetworkMetrics defaultNetworkMetrics() {
+            return mDefaultNetworkMetrics;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index fbbdf00..fceacba 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -152,7 +152,6 @@
      * turn ND offload off if on WiFi.
      */
     private void enterRunningState() {
-        maybeSetIpv6NdOffload(mBaseIface, false);
         mState = State.RUNNING;
     }
 
@@ -160,10 +159,6 @@
      * Stop clatd, and turn ND offload on if it had been turned off.
      */
     private void enterStoppingState() {
-        if (isRunning()) {
-            maybeSetIpv6NdOffload(mBaseIface, true);
-        }
-
         try {
             mNMService.stopClatd(mBaseIface);
         } catch(RemoteException|IllegalStateException e) {
@@ -279,19 +274,6 @@
         }
     }
 
-    private void maybeSetIpv6NdOffload(String iface, boolean on) {
-        // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
-        if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
-            return;
-        }
-        try {
-            Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
-            mNMService.setInterfaceIpv6NdOffload(iface, on);
-        } catch(RemoteException|IllegalStateException e) {
-            Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
-        }
-    }
-
     /**
      * Adds stacked link on base link and transitions to RUNNING state.
      */
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 25dba35..05c6e69 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -27,6 +27,7 @@
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.NetworkMetrics;
 import android.net.metrics.WakeupEvent;
 import android.net.metrics.WakeupStats;
 import android.os.RemoteException;
@@ -34,16 +35,19 @@
 import android.util.Log;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
+
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Function;
-import java.util.function.IntFunction;
+import java.util.StringJoiner;
 
 /**
  * Implementation of the INetdEventListener interface.
@@ -56,13 +60,13 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false;
 
-    private static final int INITIAL_DNS_BATCH_SIZE = 100;
-
     // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum
     // bursts of 5000 measurements.
     private static final int CONNECT_LATENCY_BURST_LIMIT  = 5000;
     private static final int CONNECT_LATENCY_FILL_RATE    = 15 * (int) DateUtils.SECOND_IN_MILLIS;
-    private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000;
+
+    private static final long METRICS_SNAPSHOT_SPAN_MS = 5 * DateUtils.MINUTE_IN_MILLIS;
+    private static final int METRICS_SNAPSHOT_BUFFER_SIZE = 48; // 4 hours
 
     @VisibleForTesting
     static final int WAKEUP_EVENT_BUFFER_LENGTH = 1024;
@@ -71,20 +75,23 @@
     @VisibleForTesting
     static final String WAKEUP_EVENT_IFACE_PREFIX = "iface:";
 
-    // Sparse arrays of DNS and connect events, grouped by net id.
+    // Array of aggregated DNS and connect events sent by netd, grouped by net id.
     @GuardedBy("this")
-    private final SparseArray<DnsEvent> mDnsEvents = new SparseArray<>();
+    private final SparseArray<NetworkMetrics> mNetworkMetrics = new SparseArray<>();
+
     @GuardedBy("this")
-    private final SparseArray<ConnectStats> mConnectEvents = new SparseArray<>();
+    private final RingBuffer<NetworkMetricsSnapshot> mNetworkMetricsSnapshots =
+            new RingBuffer<>(NetworkMetricsSnapshot.class, METRICS_SNAPSHOT_BUFFER_SIZE);
+    @GuardedBy("this")
+    private long mLastSnapshot = 0;
 
     // Array of aggregated wakeup event stats, grouped by interface name.
     @GuardedBy("this")
     private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
     // Ring buffer array for storing packet wake up events sent by Netd.
     @GuardedBy("this")
-    private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH];
-    @GuardedBy("this")
-    private long mWakeupEventCursor = 0;
+    private final RingBuffer<WakeupEvent> mWakeupEvents =
+            new RingBuffer<>(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
 
     private final ConnectivityManager mCm;
 
@@ -116,6 +123,41 @@
         mCm = cm;
     }
 
+    private static long projectSnapshotTime(long timeMs) {
+        return (timeMs / METRICS_SNAPSHOT_SPAN_MS) * METRICS_SNAPSHOT_SPAN_MS;
+    }
+
+    private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
+        collectPendingMetricsSnapshot(timeMs);
+        NetworkMetrics metrics = mNetworkMetrics.get(netId);
+        if (metrics == null) {
+            // TODO: allow to change transport for a given netid.
+            metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
+            mNetworkMetrics.put(netId, metrics);
+        }
+        return metrics;
+    }
+
+    private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
+        collectPendingMetricsSnapshot(System.currentTimeMillis());
+        return mNetworkMetricsSnapshots.toArray();
+    }
+
+    private void collectPendingMetricsSnapshot(long timeMs) {
+        // Detects time differences larger than the snapshot collection period.
+        // This is robust against clock jumps and long inactivity periods.
+        if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
+            return;
+        }
+        mLastSnapshot = projectSnapshotTime(timeMs);
+        NetworkMetricsSnapshot snapshot =
+                NetworkMetricsSnapshot.collect(mLastSnapshot, mNetworkMetrics);
+        if (snapshot.stats.isEmpty()) {
+            return;
+        }
+        mNetworkMetricsSnapshots.append(snapshot);
+    }
+
     @Override
     // Called concurrently by multiple binder threads.
     // This method must not block or perform long-running operations.
@@ -124,15 +166,10 @@
             throws RemoteException {
         maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs);
 
-        DnsEvent dnsEvent = mDnsEvents.get(netId);
-        if (dnsEvent == null) {
-            dnsEvent = makeDnsEvent(netId);
-            mDnsEvents.put(netId, dnsEvent);
-        }
-        dnsEvent.addResult((byte) eventType, (byte) returnCode, latencyMs);
+        long timestamp = System.currentTimeMillis();
+        getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
 
         if (mNetdEventCallback != null) {
-            long timestamp = System.currentTimeMillis();
             mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
         }
     }
@@ -144,15 +181,11 @@
             int port, int uid) throws RemoteException {
         maybeVerboseLog("onConnectEvent(%d, %d, %dms)", netId, error, latencyMs);
 
-        ConnectStats connectStats = mConnectEvents.get(netId);
-        if (connectStats == null) {
-            connectStats = makeConnectStats(netId);
-            mConnectEvents.put(netId, connectStats);
-        }
-        connectStats.addEvent(error, latencyMs, ipAddr);
+        long timestamp = System.currentTimeMillis();
+        getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
 
         if (mNetdEventCallback != null) {
-            mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
+            mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
         }
     }
 
@@ -175,13 +208,11 @@
 
     @GuardedBy("this")
     private void addWakeupEvent(String iface, long timestampMs, int uid) {
-        int index = wakeupEventIndex(mWakeupEventCursor);
-        mWakeupEventCursor++;
         WakeupEvent event = new WakeupEvent();
         event.iface = iface;
         event.timestampMs = timestampMs;
         event.uid = uid;
-        mWakeupEvents[index] = event;
+        mWakeupEvents.append(event);
         WakeupStats stats = mWakeupStats.get(iface);
         if (stats == null) {
             stats = new WakeupStats(iface);
@@ -190,29 +221,25 @@
         stats.countEvent(event);
     }
 
-    @GuardedBy("this")
-    private WakeupEvent[] getWakeupEvents() {
-        int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length);
-        WakeupEvent[] out = new WakeupEvent[length];
-        // Reverse iteration from youngest event to oldest event.
-        long inCursor = mWakeupEventCursor - 1;
-        int outIdx = out.length - 1;
-        while (outIdx >= 0) {
-            out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)];
-        }
-        return out;
-    }
-
-    private static int wakeupEventIndex(long cursor) {
-        return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH);
-    }
-
     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
-        flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
-        flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            ConnectStats stats = mNetworkMetrics.valueAt(i).connectMetrics;
+            if (stats.eventCount == 0) {
+                continue;
+            }
+            events.add(IpConnectivityEventBuilder.toProto(stats));
+        }
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            DnsEvent ev = mNetworkMetrics.valueAt(i).dnsMetrics;
+            if (ev.eventCount == 0) {
+                continue;
+            }
+            events.add(IpConnectivityEventBuilder.toProto(ev));
+        }
         for (int i = 0; i < mWakeupStats.size(); i++) {
             events.add(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
         }
+        mNetworkMetrics.clear();
         mWakeupStats.clear();
     }
 
@@ -225,52 +252,35 @@
     }
 
     public synchronized void list(PrintWriter pw) {
-        listEvents(pw, mConnectEvents, (x) -> x, "\n");
-        listEvents(pw, mDnsEvents, (x) -> x, "\n");
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.println(mNetworkMetrics.valueAt(i).connectMetrics);
+        }
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.println(mNetworkMetrics.valueAt(i).dnsMetrics);
+        }
+        for (NetworkMetricsSnapshot s : getNetworkMetricsSnapshots()) {
+            pw.println(s);
+        }
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
-        for (WakeupEvent wakeup : getWakeupEvents()) {
+        for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
             pw.println(wakeup);
         }
     }
 
     public synchronized void listAsProtos(PrintWriter pw) {
-        listEvents(pw, mConnectEvents, IpConnectivityEventBuilder::toProto, "");
-        listEvents(pw, mDnsEvents, IpConnectivityEventBuilder::toProto, "");
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).connectMetrics));
+        }
+        for (int i = 0; i < mNetworkMetrics.size(); i++) {
+            pw.print(IpConnectivityEventBuilder.toProto(mNetworkMetrics.valueAt(i).dnsMetrics));
+        }
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.print(IpConnectivityEventBuilder.toProto(mWakeupStats.valueAt(i)));
         }
     }
 
-    private static <T> void flushProtos(List<IpConnectivityEvent> out, SparseArray<T> in,
-            Function<T, IpConnectivityEvent> mapper) {
-        for (int i = 0; i < in.size(); i++) {
-            out.add(mapper.apply(in.valueAt(i)));
-        }
-        in.clear();
-    }
-
-    private static <T> void listEvents(
-            PrintWriter pw, SparseArray<T> events, Function<T, Object> mapper, String separator) {
-        // Proto derived Classes have toString method that adds a \n at the end.
-        // Let the caller control that by passing in the line separator explicitly.
-        for (int i = 0; i < events.size(); i++) {
-            pw.print(mapper.apply(events.valueAt(i)));
-            pw.print(separator);
-        }
-    }
-
-    private ConnectStats makeConnectStats(int netId) {
-        long transports = getTransports(netId);
-        return new ConnectStats(netId, transports, mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS);
-    }
-
-    private DnsEvent makeDnsEvent(int netId) {
-        long transports = getTransports(netId);
-        return new DnsEvent(netId, transports, INITIAL_DNS_BATCH_SIZE);
-    }
-
     private long getTransports(int netId) {
         // TODO: directly query ConnectivityService instead of going through Binder interface.
         NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
@@ -287,4 +297,32 @@
     private static void maybeVerboseLog(String s, Object... args) {
         if (VDBG) Log.d(TAG, String.format(s, args));
     }
+
+    /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
+    static class NetworkMetricsSnapshot {
+
+        public long timeMs;
+        public List<NetworkMetrics.Summary> stats = new ArrayList<>();
+
+        static NetworkMetricsSnapshot collect(long timeMs, SparseArray<NetworkMetrics> networkMetrics) {
+            NetworkMetricsSnapshot snapshot = new NetworkMetricsSnapshot();
+            snapshot.timeMs = timeMs;
+            for (int i = 0; i < networkMetrics.size(); i++) {
+                NetworkMetrics.Summary s = networkMetrics.valueAt(i).getPendingStats();
+                if (s != null) {
+                    snapshot.stats.add(s);
+                }
+            }
+            return snapshot;
+        }
+
+        @Override
+        public String toString() {
+            StringJoiner j = new StringJoiner(", ");
+            for (NetworkMetrics.Summary s : stats) {
+                j.add(s.toString());
+            }
+            return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 5583e86..59870cb 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -28,6 +28,7 @@
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
 import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED;
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
 import static com.android.server.ConnectivityService.SHORT_ARG;
 
 import android.app.Notification;
@@ -60,6 +61,7 @@
 import android.net.RouteInfo;
 import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
+import android.net.util.VersionedBroadcastListener;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -68,6 +70,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
@@ -184,6 +187,8 @@
     // TODO: Figure out how to merge this and other downstream-tracking objects
     // into a single coherent structure.
     private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;
+    private final VersionedBroadcastListener mCarrierConfigChange;
+    // TODO: Delete SimChangeListener; it's obsolete.
     private final SimChangeListener mSimChange;
 
     private volatile TetheringConfiguration mConfig;
@@ -224,11 +229,26 @@
         mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                 mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
+        mCarrierConfigChange = new VersionedBroadcastListener(
+                "CarrierConfigChangeListener", mContext, smHandler, filter,
+                (Intent ignored) -> {
+                    mLog.log("OBSERVED carrier config change");
+                    reevaluateSimCardProvisioning();
+                });
+        // TODO: Remove SimChangeListener altogether. For now, we retain it
+        // for logging purposes in case we need to debug something that might
+        // be related to changing signals from ACTION_SIM_STATE_CHANGED to
+        // ACTION_CARRIER_CONFIG_CHANGED.
         mSimChange = new SimChangeListener(
-                mContext, smHandler, () -> reevaluateSimCardProvisioning());
+                mContext, smHandler, () -> {
+                    mLog.log("OBSERVED SIM card change");
+                });
 
         mStateReceiver = new StateReceiver();
-        IntentFilter filter = new IntentFilter();
+        filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
@@ -364,18 +384,30 @@
             return false;
         }
 
+        if (carrierConfigAffirmsEntitlementCheckNotRequired()) {
+            return false;
+        }
+        return (provisionApp.length == 2);
+    }
+
+    // The logic here is aimed solely at confirming that a CarrierConfig exists
+    // and affirms that entitlement checks are not required.
+    //
+    // TODO: find a better way to express this, or alter the checking process
+    // entirely so that this is more intuitive.
+    private boolean carrierConfigAffirmsEntitlementCheckNotRequired() {
         // Check carrier config for entitlement checks
         final CarrierConfigManager configManager = (CarrierConfigManager) mContext
              .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configManager != null && configManager.getConfig() != null) {
-            // we do have a CarrierConfigManager and it has a config.
-            boolean isEntitlementCheckRequired = configManager.getConfig().getBoolean(
-                    CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
-            if (!isEntitlementCheckRequired) {
-                return false;
-            }
-        }
-        return (provisionApp.length == 2);
+        if (configManager == null) return false;
+
+        final PersistableBundle carrierConfig = configManager.getConfig();
+        if (carrierConfig == null) return false;
+
+        // A CarrierConfigManager was found and it has a config.
+        final boolean isEntitlementCheckRequired = carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL);
+        return !isEntitlementCheckRequired;
     }
 
     // Used by the SIM card change observation code.
@@ -818,6 +850,7 @@
             } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
                 handleWifiApAction(intent);
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                mLog.log("OBSERVED configuration changed");
                 updateConfiguration();
             }
         }
@@ -1192,6 +1225,7 @@
 
     private void reevaluateSimCardProvisioning() {
         if (!hasMobileHotspotProvisionApp()) return;
+        if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
 
         ArrayList<Integer> tethered = new ArrayList<>();
         synchronized (mPublicSync) {
@@ -1371,6 +1405,7 @@
                     sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
                 }
             }
+            mUpstreamNetworkMonitor.setCurrentUpstream((ns != null) ? ns.network : null);
             setUpstreamNetwork(ns);
         }
 
@@ -1558,6 +1593,7 @@
                     return;
                 }
 
+                mCarrierConfigChange.startListening();
                 mSimChange.startListening();
                 mUpstreamNetworkMonitor.start();
 
@@ -1575,6 +1611,7 @@
                 mOffload.stop();
                 mUpstreamNetworkMonitor.stop();
                 mSimChange.stopListening();
+                mCarrierConfigChange.stopListening();
                 notifyDownstreamsOfNewUpstreamIface(null);
                 handleNewUpstreamNetworkState(null);
             }
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index 057704a..cff216c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -596,9 +596,10 @@
         }
 
         mNatUpdateCallbacksReceived++;
+        final String natDescription = String.format("%s (%s, %s) -> (%s, %s)",
+                protoName, srcAddr, srcPort, dstAddr, dstPort);
         if (DBG) {
-            mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)",
-                     protoName, srcAddr, srcPort, dstAddr, dstPort));
+            mLog.log("NAT timeout update: " + natDescription);
         }
 
         final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
@@ -609,7 +610,7 @@
             NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
         } catch (ErrnoException e) {
             mNatUpdateNetlinkErrors++;
-            mLog.e("Error updating NAT conntrack entry: " + e
+            mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
                     + ", msg: " + NetlinkConstants.hexify(msg));
             mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
             mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
diff --git a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
index 3e60f9f..33c9355 100644
--- a/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
+++ b/services/core/java/com/android/server/connectivity/tethering/SimChangeListener.java
@@ -23,12 +23,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.util.VersionedBroadcastListener;
+import android.net.util.VersionedBroadcastListener.IntentCallback;
 import android.os.Handler;
 import android.util.Log;
 
 import com.android.internal.telephony.TelephonyIntents;
 
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 
 /**
@@ -37,88 +40,40 @@
  *
  * @hide
  */
-public class SimChangeListener {
+public class SimChangeListener extends VersionedBroadcastListener {
     private static final String TAG = SimChangeListener.class.getSimpleName();
     private static final boolean DBG = false;
 
-    private final Context mContext;
-    private final Handler mTarget;
-    private final AtomicInteger mSimBcastGenerationNumber;
-    private final Runnable mCallback;
-    private BroadcastReceiver mBroadcastReceiver;
-
     public SimChangeListener(Context ctx, Handler handler, Runnable onSimCardLoadedCallback) {
-        mContext = ctx;
-        mTarget = handler;
-        mCallback = onSimCardLoadedCallback;
-        mSimBcastGenerationNumber = new AtomicInteger(0);
+        super(TAG, ctx, handler, makeIntentFilter(), makeCallback(onSimCardLoadedCallback));
     }
 
-    public int generationNumber() {
-        return mSimBcastGenerationNumber.get();
-    }
-
-    public void startListening() {
-        if (DBG) Log.d(TAG, "startListening for SIM changes");
-
-        if (mBroadcastReceiver != null) return;
-
-        mBroadcastReceiver = new SimChangeBroadcastReceiver(
-                mSimBcastGenerationNumber.incrementAndGet());
+    private static IntentFilter makeIntentFilter() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
-
-        mContext.registerReceiver(mBroadcastReceiver, filter, null, mTarget);
+        return filter;
     }
 
-    public void stopListening() {
-        if (DBG) Log.d(TAG, "stopListening for SIM changes");
+    private static Consumer<Intent> makeCallback(Runnable onSimCardLoadedCallback) {
+        return new Consumer<Intent>() {
+            private boolean mSimNotLoadedSeen = false;
 
-        if (mBroadcastReceiver == null) return;
+            @Override
+            public void accept(Intent intent) {
+                final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
+                Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
+                        mSimNotLoadedSeen);
 
-        mSimBcastGenerationNumber.incrementAndGet();
-        mContext.unregisterReceiver(mBroadcastReceiver);
-        mBroadcastReceiver = null;
-    }
+                if (!INTENT_VALUE_ICC_LOADED.equals(state)) {
+                    mSimNotLoadedSeen = true;
+                    return;
+                }
 
-    private boolean isSimCardLoaded(String state) {
-        return INTENT_VALUE_ICC_LOADED.equals(state);
-    }
-
-    private class SimChangeBroadcastReceiver extends BroadcastReceiver {
-        // used to verify this receiver is still current
-        final private int mGenerationNumber;
-
-        // used to check the sim state transition from non-loaded to loaded
-        private boolean mSimNotLoadedSeen = false;
-
-        public SimChangeBroadcastReceiver(int generationNumber) {
-            mGenerationNumber = generationNumber;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final int currentGenerationNumber = mSimBcastGenerationNumber.get();
-
-            if (DBG) {
-                Log.d(TAG, "simchange mGenerationNumber=" + mGenerationNumber +
-                        ", current generationNumber=" + currentGenerationNumber);
+                if (mSimNotLoadedSeen) {
+                    mSimNotLoadedSeen = false;
+                    onSimCardLoadedCallback.run();
+                }
             }
-            if (mGenerationNumber != currentGenerationNumber) return;
-
-            final String state = intent.getStringExtra(INTENT_KEY_ICC_STATE);
-            Log.d(TAG, "got Sim changed to state " + state + ", mSimNotLoadedSeen=" +
-                    mSimNotLoadedSeen);
-
-            if (!isSimCardLoaded(state)) {
-                mSimNotLoadedSeen = true;
-                return;
-            }
-
-            if (mSimNotLoadedSeen) {
-                mSimNotLoadedSeen = false;
-                mCallback.run();
-            }
-        }
+        };
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index c5f7528..b35ed75 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -95,7 +95,10 @@
     private NetworkCallback mDefaultNetworkCallback;
     private NetworkCallback mMobileNetworkCallback;
     private boolean mDunRequired;
-    private Network mCurrentDefault;
+    // The current system default network (not really used yet).
+    private Network mDefaultInternetNetwork;
+    // The current upstream network used for tethering.
+    private Network mTetheringUpstreamNetwork;
 
     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
         mContext = ctx;
@@ -130,10 +133,12 @@
 
         releaseCallback(mDefaultNetworkCallback);
         mDefaultNetworkCallback = null;
+        mDefaultInternetNetwork = null;
 
         releaseCallback(mListenAllCallback);
         mListenAllCallback = null;
 
+        mTetheringUpstreamNetwork = null;
         mNetworkMap.clear();
     }
 
@@ -207,7 +212,7 @@
                 break;
             default:
                 /* If we've found an active upstream connection that's not DUN/HIPRI
-                 * we should stop any outstanding DUN/HIPRI start requests.
+                 * we should stop any outstanding DUN/HIPRI requests.
                  *
                  * If we found NONE we don't want to do this as we want any previous
                  * requests to keep trying to bring up something we can use.
@@ -219,6 +224,10 @@
         return typeStatePair.ns;
     }
 
+    public void setCurrentUpstream(Network upstream) {
+        mTetheringUpstreamNetwork = upstream;
+    }
+
     public Set<IpPrefix> getLocalPrefixes() {
         return (Set<IpPrefix>) mLocalPrefixes.clone();
     }
@@ -250,7 +259,7 @@
                     // These request*() calls can be deleted post oag/339444.
                     return;
                 }
-                mCurrentDefault = network;
+                mDefaultInternetNetwork = network;
                 break;
 
             case CALLBACK_MOBILE_REQUEST:
@@ -302,6 +311,13 @@
                     network, newNc));
         }
 
+        // Log changes in upstream network signal strength, if available.
+        if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
+            final int newSignal = newNc.getSignalStrength();
+            final String prevSignal = getSignalStrength(prev.networkCapabilities);
+            mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
+        }
+
         mNetworkMap.put(network, new NetworkState(
                 null, prev.linkProperties, newNc, network, null, null));
         // TODO: If sufficient information is available to select a more
@@ -330,9 +346,21 @@
         notifyTarget(EVENT_ON_LINKPROPERTIES, network);
     }
 
+    private void handleSuspended(int callbackType, Network network) {
+        if (callbackType != CALLBACK_LISTEN_ALL) return;
+        if (!network.equals(mTetheringUpstreamNetwork)) return;
+        mLog.log("SUSPENDED current upstream: " + network);
+    }
+
+    private void handleResumed(int callbackType, Network network) {
+        if (callbackType != CALLBACK_LISTEN_ALL) return;
+        if (!network.equals(mTetheringUpstreamNetwork)) return;
+        mLog.log("RESUMED current upstream: " + network);
+    }
+
     private void handleLost(int callbackType, Network network) {
         if (callbackType == CALLBACK_TRACK_DEFAULT) {
-            mCurrentDefault = null;
+            mDefaultInternetNetwork = null;
             // Receiving onLost() for a default network does not necessarily
             // mean the network is gone.  We wait for a separate notification
             // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
@@ -401,8 +429,15 @@
             recomputeLocalPrefixes();
         }
 
-        // TODO: Handle onNetworkSuspended();
-        // TODO: Handle onNetworkResumed();
+        @Override
+        public void onNetworkSuspended(Network network) {
+            handleSuspended(mCallbackType, network);
+        }
+
+        @Override
+        public void onNetworkResumed(Network network) {
+            handleResumed(mCallbackType, network);
+        }
 
         @Override
         public void onLost(Network network) {
@@ -467,4 +502,9 @@
 
         return prefixSet;
     }
+
+    private static String getSignalStrength(NetworkCapabilities nc) {
+        if (nc == null || !nc.hasSignalStrength()) return "unknown";
+        return Integer.toString(nc.getSignalStrength());
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 205e828..9cd52d7 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -46,8 +46,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.content.pm.RegisteredServicesCache;
 import android.content.pm.RegisteredServicesCacheListener;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 9aabdab..9a6e609 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -29,6 +29,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.text.format.DateUtils;
 import android.util.EventLog;
 import android.util.MathUtils;
@@ -304,6 +305,7 @@
     }
 
     private void handleLightSensorEvent(long time, float lux) {
+        Trace.traceCounter(Trace.TRACE_TAG_POWER, "ALS", (int) lux);
         mHandler.removeMessages(MSG_UPDATE_AMBIENT_LUX);
 
         if (mAmbientLightRingBuffer.size() == 0) {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 2541050..c2167eb 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -585,9 +585,11 @@
                     } else {
                         flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN;
                     }
-                    mSurfaceControl = new SurfaceControl(mSurfaceSession,
-                            "ColorFade", mDisplayWidth, mDisplayHeight,
-                            PixelFormat.OPAQUE, flags);
+                    mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+                            .setName("ColorFade")
+                            .setSize(mDisplayWidth, mDisplayHeight)
+                            .setFlags(flags)
+                            .build();
                 } catch (OutOfResourcesException ex) {
                     Slog.e(TAG, "Unable to create surface.", ex);
                     return false;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index ef6de4c..fddb81b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -98,6 +98,12 @@
     public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 9;
 
     /**
+     * Flag: This display will destroy its content on removal.
+     * @hide
+     */
+    public static final int FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 10;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f8e5836..f930b52 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -683,8 +683,8 @@
         // Configure auto-brightness.
         boolean autoBrightnessEnabled = false;
         if (mAutomaticBrightnessController != null) {
-            final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig
-                    && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND);
+            final boolean autoBrightnessEnabledInDoze =
+                    mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
             autoBrightnessEnabled = mPowerRequest.useAutoBrightness
                     && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
                     && brightness < 0;
@@ -726,8 +726,7 @@
         }
 
         // Use default brightness when dozing unless overridden.
-        if (brightness < 0 && (state == Display.STATE_DOZE
-                || state == Display.STATE_DOZE_SUSPEND)) {
+        if (brightness < 0 && Display.isDozeState(state)) {
             brightness = mScreenBrightnessDozeConfig;
         }
 
@@ -777,7 +776,6 @@
         // Skip the animation when the screen is off or suspended or transition to/from VR.
         if (!mPendingScreenOff) {
             if (mSkipScreenOnBrightnessRamp) {
-
                 if (state == Display.STATE_ON) {
                     if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
                         mInitialAutoBrightness = brightness;
@@ -794,15 +792,25 @@
                 }
             }
 
-            boolean wasOrWillBeInVr = (state == Display.STATE_VR || oldState == Display.STATE_VR);
-            if ((state == Display.STATE_ON
-                    && mSkipRampState == RAMP_STATE_SKIP_NONE
-                    || state == Display.STATE_DOZE && !mBrightnessBucketsInDozeConfig)
-                    && !wasOrWillBeInVr) {
+            final boolean wasOrWillBeInVr =
+                    (state == Display.STATE_VR || oldState == Display.STATE_VR);
+            final boolean initialRampSkip =
+                    state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE;
+            // While dozing, sometimes the brightness is split into buckets. Rather than animating
+            // through the buckets, which is unlikely to be smooth in the first place, just jump
+            // right to the suggested brightness.
+            final boolean hasBrightnessBuckets =
+                    Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
+            // If the color fade is totally covering the screen then we can change the backlight
+            // level without it being a noticeable jump since any actual content isn't yet visible.
+            final boolean isDisplayContentVisible =
+                    mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
+            if (initialRampSkip || hasBrightnessBuckets
+                    || wasOrWillBeInVr || !isDisplayContentVisible) {
+                animateScreenBrightness(brightness, 0);
+            } else {
                 animateScreenBrightness(brightness,
                         slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast);
-            } else {
-                animateScreenBrightness(brightness, 0);
             }
         }
 
@@ -925,6 +933,7 @@
             }
 
             if (!reportOnly) {
+                Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
                 mPowerState.setScreenState(state);
                 // Tell battery stats about the transition.
                 try {
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index dbbb318..bef6898 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -16,15 +16,20 @@
 
 package com.android.server.display;
 
+import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.opengl.Matrix;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import com.android.internal.annotations.GuardedBy;
 
+import com.android.internal.app.NightDisplayController;
 import java.util.Arrays;
 
 /**
@@ -34,6 +39,8 @@
 
     private static final String TAG = "DisplayTransformManager";
 
+    private static final String SURFACE_FLINGER = "SurfaceFlinger";
+
     /**
      * Color transform level used by Night display to tint the display red.
      */
@@ -50,6 +57,15 @@
     private static final int SURFACE_FLINGER_TRANSACTION_COLOR_MATRIX = 1015;
     private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
 
+    private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
+    private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+
+    private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
+    private static final int SURFACE_FLINGER_TRANSACTION_NATIVE_MODE = 1023;
+
+    private static final float COLOR_SATURATION_NATURAL = 1.0f;
+    private static final float COLOR_SATURATION_BOOSTED = 1.1f;
+
     /**
      * Map of level -> color transformation matrix.
      */
@@ -161,7 +177,7 @@
      * Propagates the provided color transformation matrix to the SurfaceFlinger.
      */
     private static void applyColorMatrix(float[] m) {
-        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
         if (flinger != null) {
             final Parcel data = Parcel.obtain();
             data.writeInterfaceToken("android.ui.ISurfaceComposer");
@@ -187,7 +203,7 @@
      * Propagates the provided Daltonization mode to the SurfaceFlinger.
      */
     private static void applyDaltonizerMode(int mode) {
-        final IBinder flinger = ServiceManager.getService("SurfaceFlinger");
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
         if (flinger != null) {
             final Parcel data = Parcel.obtain();
             data.writeInterfaceToken("android.ui.ISurfaceComposer");
@@ -201,4 +217,73 @@
             }
         }
     }
+
+    public static boolean isNativeModeEnabled() {
+        return SystemProperties.getBoolean(PERSISTENT_PROPERTY_NATIVE_MODE, false);
+    }
+
+    public boolean setColorMode(int colorMode) {
+        if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+            applySaturation(COLOR_SATURATION_NATURAL);
+            setNativeMode(false);
+        } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+            applySaturation(COLOR_SATURATION_BOOSTED);
+            setNativeMode(false);
+        } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+            applySaturation(COLOR_SATURATION_NATURAL);
+            setNativeMode(true);
+        }
+
+        updateConfiguration();
+
+        return true;
+    }
+
+    /**
+     * Propagates the provided saturation to the SurfaceFlinger.
+     */
+    private void applySaturation(float saturation) {
+        SystemProperties.set(PERSISTENT_PROPERTY_SATURATION, Float.toString(saturation));
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
+        if (flinger != null) {
+            final Parcel data = Parcel.obtain();
+            data.writeInterfaceToken("android.ui.ISurfaceComposer");
+            data.writeFloat(saturation);
+            try {
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_SATURATION, data, null, 0);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to set saturation", ex);
+            } finally {
+                data.recycle();
+            }
+        }
+    }
+
+    /**
+     * Toggles native mode on/off in SurfaceFlinger.
+     */
+    private void setNativeMode(boolean enabled) {
+        SystemProperties.set(PERSISTENT_PROPERTY_NATIVE_MODE, enabled ? "1" : "0");
+        final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
+        if (flinger != null) {
+            final Parcel data = Parcel.obtain();
+            data.writeInterfaceToken("android.ui.ISurfaceComposer");
+            data.writeInt(enabled ? 1 : 0);
+            try {
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_NATIVE_MODE, data, null, 0);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to set native mode", ex);
+            } finally {
+                data.recycle();
+            }
+        }
+    }
+
+    private void updateConfiguration() {
+        try {
+            ActivityManager.getService().updateConfiguration(null);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not update configuration", e);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 8756484..d61a418 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -473,17 +473,18 @@
                         }
 
                         // If the state change was from or to VR, then we need to tell the light
-                        // so that it can apply appropriate VR brightness settings. This should
-                        // happen prior to changing the brightness but also if there is no
-                        // brightness change at all.
+                        // so that it can apply appropriate VR brightness settings. Also, update the
+                        // brightness so the state is propogated to light.
+                        boolean vrModeChange = false;
                         if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
                                 currentState != state) {
                             setVrMode(state == Display.STATE_VR);
+                            vrModeChange = true;
                         }
 
 
                         // Apply brightness changes given that we are in a non-suspended state.
-                        if (brightnessChanged) {
+                        if (brightnessChanged || vrModeChange) {
                             setDisplayBrightness(brightness);
                         }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index addad0b..78a5407 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -238,6 +238,9 @@
                 // For private displays by default content is destroyed on removal.
                 mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+                mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
+            }
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRESENTATION) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION;
             }
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
index 9cf1367..a7c3ff9 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -52,7 +52,8 @@
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.TimeZone;
+
+import com.android.internal.R;
 
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
 
@@ -126,12 +127,6 @@
     public NightDisplayService(Context context) {
         super(context);
         mHandler = new Handler(Looper.getMainLooper());
-
-        final String[] coefficients = context.getResources().getStringArray(
-                com.android.internal.R.array.config_nightDisplayColorTemperatureCoefficients);
-        for (int i = 0; i < 9 && i < coefficients.length; i++) {
-            mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
-        }
     }
 
     @Override
@@ -236,6 +231,8 @@
         mController = new NightDisplayController(getContext(), mCurrentUser);
         mController.setListener(this);
 
+        setCoefficientMatrix(getContext());
+
         // Prepare color transformation matrix.
         setMatrix(mController.getColorTemperature(), mMatrixNight);
 
@@ -331,6 +328,26 @@
         applyTint(true);
     }
 
+    @Override
+    public void onDisplayColorModeChanged(int colorMode) {
+        final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+        dtm.setColorMode(colorMode);
+
+        setCoefficientMatrix(getContext());
+        setMatrix(mController.getColorTemperature(), mMatrixNight);
+        applyTint(true);
+    }
+
+    private void setCoefficientMatrix(Context context) {
+        final boolean isNative = DisplayTransformManager.isNativeModeEnabled();
+        final String[] coefficients = context.getResources().getStringArray(isNative
+                ? R.array.config_nightDisplayColorTemperatureCoefficientsNative
+                : R.array.config_nightDisplayColorTemperatureCoefficients);
+        for (int i = 0; i < 9 && i < coefficients.length; i++) {
+            mColorTempCoefficients[i] = Float.parseFloat(coefficients[i]);
+        }
+    }
+
     /**
      * Applies current color temperature matrix, or removes it if deactivated.
      *
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d6ab888..f86d576 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -24,6 +24,8 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+import static android.hardware.display.DisplayManager
+        .VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 
 import android.content.Context;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -363,6 +365,9 @@
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+                  mInfo.flags |= DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL;
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index b1c165e..d0d951b 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -59,10 +59,9 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.security.KeyStore;
-import android.service.fingerprint.FingerprintActionStatsProto;
-import android.service.fingerprint.FingerprintServiceDumpProto;
-import android.service.fingerprint.FingerprintUserStatsProto;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,7 +80,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -89,18 +87,19 @@
 /**
  * A service to manage multiple clients that want to access the fingerprint HAL API.
  * The service is responsible for maintaining a list of clients and dispatching all
- * fingerprint -related events.
+ * fingerprint-related events.
  *
  * @hide
  */
 public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient {
     static final String TAG = "FingerprintService";
     static final boolean DEBUG = true;
-    private static final boolean CLEANUP_UNUSED_FP = false;
+    private static final boolean CLEANUP_UNUSED_FP = true;
     private static final String FP_DATA_DIR = "fpdata";
     private static final int MSG_USER_SWITCHING = 10;
     private static final String ACTION_LOCKOUT_RESET =
             "com.android.server.fingerprint.ACTION_LOCKOUT_RESET";
+    private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
 
     private class PerformanceStats {
         int accept; // number of accepted fingerprints
@@ -128,8 +127,8 @@
     private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
     private Context mContext;
     private long mHalDeviceId;
-    private boolean mTimedLockoutCleared;
-    private int mFailedAttempts;
+    private SparseBooleanArray mTimedLockoutCleared;
+    private SparseIntArray mFailedAttempts;
     @GuardedBy("this")
     private IBiometricsFingerprint mDaemon;
     private final PowerManager mPowerManager;
@@ -139,10 +138,8 @@
     private ClientMonitor mPendingClient;
     private PerformanceStats mPerformanceStats;
 
-
     private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
-    private LinkedList<Integer> mEnumeratingUserIds = new LinkedList<>();
-    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw finterprints
+    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
 
     private class UserFingerprint {
         Fingerprint f;
@@ -177,15 +174,17 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
-                resetFailedAttempts(false /* clearAttemptCounter */);
+                final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
+                resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
             }
         }
     };
 
-    private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
+    private final Runnable mResetFailedAttemptsForCurrentUserRunnable = new Runnable() {
         @Override
         public void run() {
-            resetFailedAttempts(true /* clearAttemptCounter */);
+            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    ActivityManager.getCurrentUser());
         }
     };
 
@@ -221,6 +220,8 @@
         mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
                 RESET_FINGERPRINT_LOCKOUT, null /* handler */);
         mUserManager = UserManager.get(mContext);
+        mTimedLockoutCleared = new SparseBooleanArray();
+        mFailedAttempts = new SparseIntArray();
     }
 
     @Override
@@ -233,7 +234,7 @@
 
     public synchronized IBiometricsFingerprint getFingerprintDaemon() {
         if (mDaemon == null) {
-            Slog.v(TAG, "mDeamon was null, reconnect to fingerprint");
+            Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
             try {
                 mDaemon = IBiometricsFingerprint.getService();
             } catch (java.util.NoSuchElementException e) {
@@ -259,7 +260,7 @@
             if (mHalDeviceId != 0) {
                 loadAuthenticatorIds();
                 updateActiveGroup(ActivityManager.getCurrentUser(), null);
-                doFingerprintCleanup(ActivityManager.getCurrentUser());
+                doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
             } else {
                 Slog.w(TAG, "Failed to open Fingerprint HAL!");
                 MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
@@ -288,52 +289,41 @@
         }
     }
 
-    private void doFingerprintCleanup(int userId) {
+    /**
+     * This method should be called upon connection to the daemon, and when user switches.
+     * @param userId
+     */
+    private void doFingerprintCleanupForUser(int userId) {
         if (CLEANUP_UNUSED_FP) {
-            resetEnumerateState();
-            mEnumeratingUserIds.push(userId);
-            enumerateNextUser();
+            enumerateUser(userId);
         }
     }
 
-    private void resetEnumerateState() {
-        if (DEBUG) Slog.v(TAG, "Enumerate cleaning up");
-        mEnumeratingUserIds.clear();
+    private void clearEnumerateState() {
+        if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
         mUnknownFingerprints.clear();
     }
 
-    private void enumerateNextUser() {
-        int nextUser = mEnumeratingUserIds.getFirst();
-        updateActiveGroup(nextUser, null);
+    private void enumerateUser(int userId) {
+        if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
         boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-
-        if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of "
-                + mEnumeratingUserIds.size() + " remaining users");
-
-        startEnumerate(mToken, nextUser, null, restricted, true /* internal */);
+        startEnumerate(mToken, userId, null, restricted, true /* internal */);
     }
 
     // Remove unknown fingerprints from hardware
     private void cleanupUnknownFingerprints() {
         if (!mUnknownFingerprints.isEmpty()) {
-            Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size());
             UserFingerprint uf = mUnknownFingerprints.get(0);
             mUnknownFingerprints.remove(uf);
             boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-            updateActiveGroup(uf.userId, null);
             startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
                     restricted, true /* internal */);
         } else {
-            resetEnumerateState();
+            clearEnumerateState();
         }
     }
 
     protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId
-                + ", gid=" + groupId
-                + ", dev=" + deviceId
-                + ", rem=" + remaining);
-
         ClientMonitor client = mCurrentClient;
 
         if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
@@ -343,24 +333,21 @@
 
         // All fingerprints in hardware for this user were enumerated
         if (remaining == 0) {
-            mEnumeratingUserIds.poll();
-
             if (client instanceof InternalEnumerateClient) {
-                List<Fingerprint> enrolled = ((InternalEnumerateClient) client).getEnumeratedList();
-                Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion");
-                for (Fingerprint f : enrolled) {
+                List<Fingerprint> unknownFingerprints =
+                        ((InternalEnumerateClient) client).getUnknownFingerprints();
+
+                if (!unknownFingerprints.isEmpty()) {
+                    Slog.w(TAG, "Adding " + unknownFingerprints.size() +
+                            " fingerprints for deletion");
+                }
+                for (Fingerprint f : unknownFingerprints) {
                     mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
                 }
-            }
-
-            removeClient(client);
-
-            if (!mEnumeratingUserIds.isEmpty()) {
-                enumerateNextUser();
-            } else if (client instanceof InternalEnumerateClient) {
-                if (DEBUG) Slog.v(TAG, "Finished enumerating all users");
-                // This will start a chain of InternalRemovalClients
+                removeClient(client);
                 cleanupUnknownFingerprints();
+            } else {
+                removeClient(client);
             }
         }
     }
@@ -368,7 +355,7 @@
     protected void handleError(long deviceId, int error, int vendorCode) {
         ClientMonitor client = mCurrentClient;
         if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) {
-            resetEnumerateState();
+            clearEnumerateState();
         }
         if (client != null && client.onError(error, vendorCode)) {
             removeClient(client);
@@ -412,7 +399,7 @@
         if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
             cleanupUnknownFingerprints();
         } else if (client instanceof InternalRemovalClient){
-            resetEnumerateState();
+            clearEnumerateState();
         }
     }
 
@@ -466,8 +453,14 @@
     }
 
     void handleUserSwitching(int userId) {
+        if (mCurrentClient instanceof InternalRemovalClient
+                || mCurrentClient instanceof InternalEnumerateClient) {
+            Slog.w(TAG, "User switched while performing cleanup");
+            removeClient(mCurrentClient);
+            clearEnumerateState();
+        }
         updateActiveGroup(userId, null);
-        doFingerprintCleanup(userId);
+        doFingerprintCleanupForUser(userId);
     }
 
     private void removeClient(ClientMonitor client) {
@@ -488,27 +481,32 @@
     }
 
     private int getLockoutMode() {
-        if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+        final int currentUser = ActivityManager.getCurrentUser();
+        final int failedAttempts = mFailedAttempts.get(currentUser, 0);
+        if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
             return AuthenticationClient.LOCKOUT_PERMANENT;
-        } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
-                (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+        } else if (failedAttempts > 0 &&
+                mTimedLockoutCleared.get(currentUser, false) == false
+                && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
             return AuthenticationClient.LOCKOUT_TIMED;
         }
         return AuthenticationClient.LOCKOUT_NONE;
     }
 
-    private void scheduleLockoutReset() {
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent());
+    private void scheduleLockoutResetForUser(int userId) {
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+                getLockoutResetIntentForUser(userId));
     }
 
-    private void cancelLockoutReset() {
-        mAlarmManager.cancel(getLockoutResetIntent());
+    private void cancelLockoutResetForUser(int userId) {
+        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
     }
 
-    private PendingIntent getLockoutResetIntent() {
-        return PendingIntent.getBroadcast(mContext, 0,
-                new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT);
+    private PendingIntent getLockoutResetIntentForUser(int userId) {
+        return PendingIntent.getBroadcast(mContext, userId,
+                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     public long startPreEnroll(IBinder token) {
@@ -555,6 +553,12 @@
                 // This condition means we're currently running internal diagnostics to
                 // remove extra fingerprints in the hardware and/or the software
                 // TODO: design an escape hatch in case client never finishes
+                if (newClient != null) {
+                    Slog.w(TAG, "Internal cleanup in progress but trying to start client "
+                            + newClient.getClass().getSuperclass().getSimpleName()
+                            + "(" + newClient.getOwnerString() + ")"
+                            + ", initiatedByClient = " + initiatedByClient);
+                }
             }
             else {
                 currentClient.stop(initiatedByClient);
@@ -567,7 +571,7 @@
             if (DEBUG) Slog.v(TAG, "starting client "
                     + newClient.getClass().getSuperclass().getSimpleName()
                     + "(" + newClient.getOwnerString() + ")"
-                    + ", initiatedByClient = " + initiatedByClient + ")");
+                    + ", initiatedByClient = " + initiatedByClient);
             notifyClientActiveCallbacks(true);
 
             newClient.start();
@@ -813,8 +817,9 @@
                 receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
             @Override
             public int handleFailedAttempt() {
-                mFailedAttempts++;
-                mTimedLockoutCleared = false;
+                final int currentUser = ActivityManager.getCurrentUser();
+                mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
+                mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
                 final int lockoutMode = getLockoutMode();
                 if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
                     mPerformanceStats.permanentLockout++;
@@ -824,7 +829,7 @@
 
                 // Failing multiple times will continue to push out the lockout time
                 if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
-                    scheduleLockoutReset();
+                    scheduleLockoutResetForUser(currentUser);
                     return lockoutMode;
                 }
                 return AuthenticationClient.LOCKOUT_NONE;
@@ -832,7 +837,8 @@
 
             @Override
             public void resetFailedAttempts() {
-                FingerprintService.this.resetFailedAttempts(true /* clearAttemptCounter */);
+                FingerprintService.this.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                        ActivityManager.getCurrentUser());
             }
 
             @Override
@@ -886,17 +892,17 @@
 
     // attempt counter should only be cleared when Keyguard goes away or when
     // a fingerprint is successfully authenticated
-    protected void resetFailedAttempts(boolean clearAttemptCounter) {
+    protected void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
         if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
             Slog.v(TAG, "Reset fingerprint lockout, clearAttemptCounter=" + clearAttemptCounter);
         }
         if (clearAttemptCounter) {
-            mFailedAttempts = 0;
+            mFailedAttempts.put(userId, 0);
         }
-        mTimedLockoutCleared = true;
+        mTimedLockoutCleared.put(userId, true);
         // If we're asked to reset failed attempts externally (i.e. from Keyguard),
         // the alarm might still be pending; remove it.
-        cancelLockoutReset();
+        cancelLockoutResetForUser(userId);
         notifyLockoutResetMonitors();
     }
 
@@ -1277,7 +1283,7 @@
         public void resetTimeout(byte [] token) {
             checkPermission(RESET_FINGERPRINT_LOCKOUT);
             // TODO: confirm security token when we move timeout management into the HAL layer.
-            mHandler.post(mResetFailedAttemptsRunnable);
+            mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
         }
 
         @Override
@@ -1338,6 +1344,8 @@
                 set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
                 set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
                 set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+                set.put("permanentLockoutCrypto",
+                    (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
                 sets.put(set);
             }
 
@@ -1363,11 +1371,11 @@
             final PerformanceStats normal = mPerformanceMap.get(userId);
             if (normal != null) {
                 final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
-                proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept);
-                proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
-                proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
-                proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
+                proto.write(PerformanceStatsProto.ACCEPT, normal.accept);
+                proto.write(PerformanceStatsProto.REJECT, normal.reject);
+                proto.write(PerformanceStatsProto.ACQUIRE, normal.acquire);
+                proto.write(PerformanceStatsProto.LOCKOUT, normal.lockout);
+                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, normal.permanentLockout);
                 proto.end(countsToken);
             }
 
@@ -1376,11 +1384,11 @@
             final PerformanceStats crypto = mCryptoPerformanceMap.get(userId);
             if (crypto != null) {
                 final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
-                proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept);
-                proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
-                proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
-                proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
+                proto.write(PerformanceStatsProto.ACCEPT, crypto.accept);
+                proto.write(PerformanceStatsProto.REJECT, crypto.reject);
+                proto.write(PerformanceStatsProto.ACQUIRE, crypto.acquire);
+                proto.write(PerformanceStatsProto.LOCKOUT, crypto.lockout);
+                proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, crypto.permanentLockout);
                 proto.end(countsToken);
             }
 
diff --git a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
index 88d9ef4..434db98 100644
--- a/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
+++ b/services/core/java/com/android/server/fingerprint/InternalEnumerateClient.java
@@ -30,7 +30,7 @@
 public abstract class InternalEnumerateClient extends EnumerateClient {
 
     private List<Fingerprint> mEnrolledList;
-    private List<Fingerprint> mEnumeratedList = new ArrayList<>(); // list of fp to delete
+    private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
 
     public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
             IFingerprintServiceReceiver receiver, int groupId, int userId,
@@ -47,7 +47,6 @@
             if (mEnrolledList.get(i).getFingerId() == fingerId) {
                 mEnrolledList.remove(i);
                 matched = true;
-                Slog.e(TAG, "Matched fingerprint fid=" + fingerId);
                 break;
             }
         }
@@ -55,7 +54,7 @@
         // fingerId 0 means no fingerprints are in hardware
         if (!matched && fingerId != 0) {
             Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
-            mEnumeratedList.add(fingerprint);
+            mUnknownFingerprints.add(fingerprint);
         }
     }
 
@@ -76,8 +75,8 @@
         mEnrolledList.clear();
     }
 
-    public List<Fingerprint> getEnumeratedList() {
-        return mEnumeratedList;
+    public List<Fingerprint> getUnknownFingerprints() {
+        return mUnknownFingerprints;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index ac80794..78aa2f9 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -795,18 +795,22 @@
      * @param uid Uid to check against for removal of a job.
      *
      */
-    public void cancelJobsForUid(int uid, String reason) {
+    public boolean cancelJobsForUid(int uid, String reason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
-            return;
+            return false;
         }
+
+        boolean jobsCanceled = false;
         synchronized (mLock) {
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
             for (int i=0; i<jobsForUid.size(); i++) {
                 JobStatus toRemove = jobsForUid.get(i);
                 cancelJobImplLocked(toRemove, null, reason);
+                jobsCanceled = true;
             }
         }
+        return jobsCanceled;
     }
 
     /**
@@ -816,13 +820,14 @@
      * @param uid Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public void cancelJob(int uid, int jobId) {
+    public boolean cancelJob(int uid, int jobId) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
                 cancelJobImplLocked(toCancel, null, "cancel() called by app");
             }
+            return (toCancel != null);
         }
     }
 
@@ -2147,6 +2152,39 @@
         return 0;
     }
 
+    // Shell command infrastructure: cancel a scheduled job
+    int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+            boolean hasJobId, int jobId) {
+        if (DEBUG) {
+            Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
+        }
+
+        int pkgUid = -1;
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            pkgUid = pm.getPackageUid(pkgName, 0, userId);
+        } catch (RemoteException e) { /* can't happen */ }
+
+        if (pkgUid < 0) {
+            pw.println("Package " + pkgName + " not found.");
+            return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+        }
+
+        if (!hasJobId) {
+            pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
+            if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+                pw.println("No matching jobs found.");
+            }
+        } else {
+            pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
+            if (!cancelJob(pkgUid, jobId)) {
+                pw.println("No matching job found.");
+            }
+        }
+
+        return 0;
+    }
+
     void setMonitorBattery(boolean enabled) {
         synchronized (mLock) {
             if (mBatteryController != null) {
diff --git a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
index a53c088..d630aab 100644
--- a/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/services/core/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -48,6 +48,8 @@
                     return runJob(pw);
                 case "timeout":
                     return timeout(pw);
+                case "cancel":
+                    return cancelJob(pw);
                 case "monitor-battery":
                     return monitorBattery(pw);
                 case "get-battery-seq":
@@ -205,6 +207,42 @@
         }
     }
 
+    private int cancelJob(PrintWriter pw) throws Exception {
+        checkPermission("cancel jobs");
+
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId < 0) {
+            pw.println("Error: must specify a concrete user ID");
+            return -1;
+        }
+
+        final String pkgName = getNextArg();
+        final String jobIdStr = getNextArg();
+        final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int monitorBattery(PrintWriter pw) throws Exception {
         checkPermission("change battery monitoring");
         String opt = getNextArgRequired();
@@ -315,6 +353,12 @@
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         all users");
+        pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+        pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
+        pw.println("    by that package will be canceled.  USE WITH CAUTION.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
         pw.println("  monitor-battery [on|off]");
         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
         pw.println("    on makes get-battery-seq useful.");
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index d3fd3a9..a7e674b 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -65,7 +65,7 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
     private static final String TAG = "JobServiceContext";
     /** Amount of time a job is allowed to execute for before being considered timed-out. */
-    private static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
+    public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
     /** Amount of time the JobScheduler waits for the initial service launch+bind. */
     private static final long OP_BIND_TIMEOUT_MILLIS = 18 * 1000;
     /** Amount of time the JobScheduler will wait for a response from an app for a message. */
@@ -216,7 +216,7 @@
             final JobInfo ji = job.getJob();
             mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
-                    isDeadlineExpired, triggeredUris, triggeredAuthorities);
+                    isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
             mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
 
             // Once we'e begun executing a job, we by definition no longer care whether
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index 0539c02..78b4160 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -167,6 +167,7 @@
 
     @Override
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
+        pw.println("BackgroundJobsController");
         pw.print("Foreground uids: [");
         for (int i = 0; i < mForegroundUids.size(); i++) {
             if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index 78367fe..ddee345 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -25,13 +25,16 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkPolicyManager;
+import android.net.TrafficStats;
 import android.os.Process;
 import android.os.UserHandle;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.job.JobServiceContext;
 import com.android.server.job.StateChangedListener;
 
 import java.io.PrintWriter;
@@ -99,18 +102,66 @@
         }
     }
 
+    /**
+     * Test to see if running the given job on the given network is sane.
+     * <p>
+     * For example, if a job is trying to send 10MB over a 128Kbps EDGE
+     * connection, it would take 10.4 minutes, and has no chance of succeeding
+     * before the job times out, so we'd be insane to try running it.
+     */
+    private boolean isSane(JobStatus jobStatus, NetworkCapabilities capabilities) {
+        final long estimatedBytes = jobStatus.getEstimatedNetworkBytes();
+        if (estimatedBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+            // We don't know how large the job is; cross our fingers!
+            return true;
+        }
+        if (capabilities == null) {
+            // We don't know what the network is like; cross our fingers!
+            return true;
+        }
+
+        // We don't ask developers to differentiate between upstream/downstream
+        // in their size estimates, so test against the slowest link direction.
+        final long downstream = capabilities.getLinkDownstreamBandwidthKbps();
+        final long upstream = capabilities.getLinkUpstreamBandwidthKbps();
+        final long slowest;
+        if (downstream > 0 && upstream > 0) {
+            slowest = Math.min(downstream, upstream);
+        } else if (downstream > 0) {
+            slowest = downstream;
+        } else if (upstream > 0) {
+            slowest = upstream;
+        } else {
+            // We don't know what the network is like; cross our fingers!
+            return true;
+        }
+
+        final long estimatedMillis = ((estimatedBytes * DateUtils.SECOND_IN_MILLIS)
+                / (slowest * TrafficStats.KB_IN_BYTES / 8));
+        if (estimatedMillis > JobServiceContext.EXECUTING_TIMESLICE_MILLIS) {
+            // If we'd never finish before the timeout, we'd be insane!
+            Slog.w(TAG, "Estimated " + estimatedBytes + " bytes over " + slowest
+                    + " kbps network would take " + estimatedMillis + "ms; that's insane!");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
     private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
         final int jobUid = jobStatus.getSourceUid();
         final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
-        final NetworkInfo info = mConnManager.getActiveNetworkInfoForUid(jobUid, ignoreBlocked);
         final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
+        final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
+
         final NetworkCapabilities capabilities = (network != null)
                 ? mConnManager.getNetworkCapabilities(network) : null;
 
+        final boolean connected = (info != null) && info.isConnected();
         final boolean validated = (capabilities != null)
                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
-        final boolean connected = (info != null) && info.isConnected();
-        final boolean connectionUsable = connected && validated;
+        final boolean sane = isSane(jobStatus, capabilities);
+        final boolean connectionUsable = connected && validated && sane;
 
         final boolean metered = connected && (capabilities != null)
                 && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
@@ -125,6 +176,11 @@
         changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
         changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
 
+        // Pass along the evaluated network for job to use; prevents race
+        // conditions as default routes change over time, and opens the door to
+        // using non-default routes.
+        jobStatus.network = network;
+
         // Track system-uid connected/validated as a general reportable proxy for the
         // overall state of connectivity constraint satisfiability.
         if (jobUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 85993b9..374ab43 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -22,6 +22,7 @@
 import android.content.IntentFilter;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
@@ -55,6 +56,9 @@
      */
     private boolean mDeviceIdleMode;
     private int[] mDeviceIdleWhitelistAppIds;
+    private int[] mPowerSaveTempWhitelistAppIds;
+    // These jobs were added when the app was in temp whitelist, these should be exempted from doze
+    private final ArraySet<JobStatus> mTempWhitelistedJobs;
 
     final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
         @Override public void process(JobStatus jobStatus) {
@@ -79,15 +83,39 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
-                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
-                updateIdleMode(mPowerManager != null
-                        ? (mPowerManager.isDeviceIdleMode()
-                                || mPowerManager.isLightDeviceIdleMode())
-                        : false);
-            } else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) {
-                updateWhitelist();
+            switch (intent.getAction()) {
+                case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
+                case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+                    updateIdleMode(mPowerManager != null && (mPowerManager.isDeviceIdleMode()
+                            || mPowerManager.isLightDeviceIdleMode()));
+                    break;
+                case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
+                    synchronized (mLock) {
+                        mDeviceIdleWhitelistAppIds =
+                                mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
+                        if (LOG_DEBUG) {
+                            Slog.d(LOG_TAG, "Got whitelist "
+                                    + Arrays.toString(mDeviceIdleWhitelistAppIds));
+                        }
+                    }
+                    break;
+                case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
+                    synchronized (mLock) {
+                        mPowerSaveTempWhitelistAppIds =
+                                mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
+                        if (LOG_DEBUG) {
+                            Slog.d(LOG_TAG, "Got temp whitelist "
+                                    + Arrays.toString(mPowerSaveTempWhitelistAppIds));
+                        }
+                        boolean changed = false;
+                        for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
+                            changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+                        }
+                        if (changed) {
+                            mStateChangedListener.onControllerStateChanged();
+                        }
+                    }
+                    break;
             }
         }
     };
@@ -101,20 +129,21 @@
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mLocalDeviceIdleController =
                 LocalServices.getService(DeviceIdleController.LocalService.class);
+        mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
+        mPowerSaveTempWhitelistAppIds =
+                mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
+        mTempWhitelistedJobs = new ArraySet<>();
         final IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
         mContext.registerReceiverAsUser(
                 mBroadcastReceiver, UserHandle.ALL, filter, null, null);
     }
 
     void updateIdleMode(boolean enabled) {
         boolean changed = false;
-        // Need the whitelist to be ready when going into idle
-        if (mDeviceIdleWhitelistAppIds == null) {
-            updateWhitelist();
-        }
         synchronized (mLock) {
             if (mDeviceIdleMode != enabled) {
                 changed = true;
@@ -130,46 +159,42 @@
     }
 
     /**
-     * Fetches the latest whitelist from the device idle controller.
-     */
-    void updateWhitelist() {
-        synchronized (mLock) {
-            if (mLocalDeviceIdleController != null) {
-                mDeviceIdleWhitelistAppIds =
-                        mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
-                if (LOG_DEBUG) {
-                    Slog.d(LOG_TAG, "Got whitelist " + Arrays.toString(mDeviceIdleWhitelistAppIds));
-                }
-            }
-        }
-    }
-
-    /**
      * Checks if the given job's scheduling app id exists in the device idle user whitelist.
      */
     boolean isWhitelistedLocked(JobStatus job) {
-        if (mDeviceIdleWhitelistAppIds != null
-                && ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
-                        UserHandle.getAppId(job.getSourceUid()))) {
-            return true;
-        }
-        return false;
+        return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
+                UserHandle.getAppId(job.getSourceUid()));
     }
 
-    private void updateTaskStateLocked(JobStatus task) {
-        final boolean whitelisted = isWhitelistedLocked(task);
+    /**
+     * Checks if the given job's scheduling app id exists in the device idle temp whitelist.
+     */
+    boolean isTempWhitelistedLocked(JobStatus job) {
+        return ArrayUtils.contains(mPowerSaveTempWhitelistAppIds,
+                UserHandle.getAppId(job.getSourceUid()));
+    }
+
+    private boolean updateTaskStateLocked(JobStatus task) {
+        final boolean whitelisted = isWhitelistedLocked(task)
+                || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
         final boolean enableTask = !mDeviceIdleMode || whitelisted;
-        task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
+        return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
     }
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        updateTaskStateLocked(jobStatus);
+        if (isTempWhitelistedLocked(jobStatus)) {
+            mTempWhitelistedJobs.add(jobStatus);
+            jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
+        } else {
+            updateTaskStateLocked(jobStatus);
+        }
     }
 
     @Override
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
+        mTempWhitelistedJobs.remove(jobStatus);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 23caa8c..46ed84e 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -22,6 +22,7 @@
 import android.app.job.JobWorkItem;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.net.Network;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -167,6 +168,7 @@
     // These are filled in by controllers when preparing for execution.
     public ArraySet<Uri> changedUris;
     public ArraySet<String> changedAuthorities;
+    public Network network;
 
     public int lastEvaluatedPriority;
 
@@ -217,6 +219,8 @@
      */
     ContentObserverController.JobInstance contentObserverJobInstance;
 
+    private long totalNetworkBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+
     /** Provide a handle to the service that this job will be run on. */
     public int getServiceToken() {
         return callingUid;
@@ -294,6 +298,8 @@
 
         mLastSuccessfulRunTime = lastSuccessfulRunTime;
         mLastFailedRunTime = lastFailedRunTime;
+
+        updateEstimatedNetworkBytesLocked();
     }
 
     /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -387,6 +393,7 @@
                     sourcePackageName, sourceUserId, toShortString()));
         }
         pendingWork.add(work);
+        updateEstimatedNetworkBytesLocked();
     }
 
     public JobWorkItem dequeueWorkLocked() {
@@ -399,6 +406,7 @@
                 executingWork.add(work);
                 work.bumpDeliveryCount();
             }
+            updateEstimatedNetworkBytesLocked();
             return work;
         }
         return null;
@@ -457,6 +465,7 @@
             pendingWork = null;
             executingWork = null;
             incomingJob.nextPendingWorkId = nextPendingWorkId;
+            incomingJob.updateEstimatedNetworkBytesLocked();
         } else {
             // We are completely stopping the job...  need to clean up work.
             ungrantWorkList(am, pendingWork);
@@ -464,6 +473,7 @@
             ungrantWorkList(am, executingWork);
             executingWork = null;
         }
+        updateEstimatedNetworkBytesLocked();
     }
 
     public void prepareLocked(IActivityManager am) {
@@ -566,6 +576,38 @@
         return job.getFlags();
     }
 
+    private void updateEstimatedNetworkBytesLocked() {
+        totalNetworkBytes = computeEstimatedNetworkBytesLocked();
+    }
+
+    private long computeEstimatedNetworkBytesLocked() {
+        // If any component of the job has unknown usage, we don't have a
+        // complete picture of what data will be used, and we have to treat the
+        // entire job as unknown.
+        long totalNetworkBytes = 0;
+        long networkBytes = job.getEstimatedNetworkBytes();
+        if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+            return JobInfo.NETWORK_BYTES_UNKNOWN;
+        } else {
+            totalNetworkBytes += networkBytes;
+        }
+        if (pendingWork != null) {
+            for (int i = 0; i < pendingWork.size(); i++) {
+                networkBytes = pendingWork.get(i).getEstimatedNetworkBytes();
+                if (networkBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    return JobInfo.NETWORK_BYTES_UNKNOWN;
+                } else {
+                    totalNetworkBytes += networkBytes;
+                }
+            }
+        }
+        return totalNetworkBytes;
+    }
+
+    public long getEstimatedNetworkBytes() {
+        return totalNetworkBytes;
+    }
+
     /** Does this job have any sort of networking constraint? */
     public boolean hasConnectivityConstraint() {
         return (requiredConstraints&CONNECTIVITY_MASK) != 0;
@@ -1045,6 +1087,9 @@
             if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
                 pw.print(prefix); pw.print("  Network type: "); pw.println(job.getNetworkType());
             }
+            if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                pw.print(prefix); pw.print("  Network bytes: "); pw.println(totalNetworkBytes);
+            }
             if (job.getMinLatencyMillis() != 0) {
                 pw.print(prefix); pw.print("  Minimum latency: ");
                 TimeUtils.formatDuration(job.getMinLatencyMillis(), pw);
@@ -1101,6 +1146,9 @@
                 }
             }
         }
+        if (network != null) {
+            pw.print(prefix); pw.print("Network: "); pw.println(network);
+        }
         if (pendingWork != null && pendingWork.size() > 0) {
             pw.print(prefix); pw.println("Pending work:");
             for (int i = 0; i < pendingWork.size(); i++) {
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index d90699a..ee4c606 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -110,7 +110,7 @@
             maybeUpdateAlarmsLocked(
                     job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
                     job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
-                    job.getSourceUid());
+                    new WorkSource(job.getSourceUid(), job.getSourcePackageName()));
         }
     }
 
@@ -156,6 +156,7 @@
         synchronized (mLock) {
             long nextExpiryTime = Long.MAX_VALUE;
             int nextExpiryUid = 0;
+            String nextExpiryPackageName = null;
             final long nowElapsedMillis = SystemClock.elapsedRealtime();
 
             Iterator<JobStatus> it = mTrackedJobs.iterator();
@@ -171,10 +172,13 @@
                 } else {  // Sorted by expiry time, so take the next one and stop.
                     nextExpiryTime = job.getLatestRunTimeElapsed();
                     nextExpiryUid = job.getSourceUid();
+                    nextExpiryPackageName = job.getSourcePackageName();
                     break;
                 }
             }
-            setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryUid);
+            setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryPackageName != null
+                    ? new WorkSource(nextExpiryUid, nextExpiryPackageName)
+                    : new WorkSource(nextExpiryUid));
         }
     }
 
@@ -200,6 +204,7 @@
             final long nowElapsedMillis = SystemClock.elapsedRealtime();
             long nextDelayTime = Long.MAX_VALUE;
             int nextDelayUid = 0;
+            String nextDelayPackageName = null;
             boolean ready = false;
             Iterator<JobStatus> it = mTrackedJobs.iterator();
             while (it.hasNext()) {
@@ -221,13 +226,16 @@
                     if (nextDelayTime > jobDelayTime) {
                         nextDelayTime = jobDelayTime;
                         nextDelayUid = job.getSourceUid();
+                        nextDelayPackageName = job.getSourcePackageName();
                     }
                 }
             }
             if (ready) {
                 mStateChangedListener.onControllerStateChanged();
             }
-            setDelayExpiredAlarmLocked(nextDelayTime, nextDelayUid);
+            setDelayExpiredAlarmLocked(nextDelayTime, nextDelayPackageName != null
+                    ? new WorkSource(nextDelayUid, nextDelayPackageName)
+                    : new WorkSource(nextDelayUid));
         }
     }
 
@@ -241,12 +249,12 @@
     }
 
     private void maybeUpdateAlarmsLocked(long delayExpiredElapsed, long deadlineExpiredElapsed,
-            int uid) {
+            WorkSource ws) {
         if (delayExpiredElapsed < mNextDelayExpiredElapsedMillis) {
-            setDelayExpiredAlarmLocked(delayExpiredElapsed, uid);
+            setDelayExpiredAlarmLocked(delayExpiredElapsed, ws);
         }
         if (deadlineExpiredElapsed < mNextJobExpiredElapsedMillis) {
-            setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, uid);
+            setDeadlineExpiredAlarmLocked(deadlineExpiredElapsed, ws);
         }
     }
 
@@ -255,11 +263,11 @@
      * delay will expire.
      * This alarm <b>will</b> wake up the phone.
      */
-    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
+    private void setDelayExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
         mNextDelayExpiredElapsedMillis = alarmTimeElapsedMillis;
         updateAlarmWithListenerLocked(DELAY_TAG, mNextDelayExpiredListener,
-                mNextDelayExpiredElapsedMillis, uid);
+                mNextDelayExpiredElapsedMillis, ws);
     }
 
     /**
@@ -267,11 +275,11 @@
      * deadline will expire.
      * This alarm <b>will</b> wake up the phone.
      */
-    private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, int uid) {
+    private void setDeadlineExpiredAlarmLocked(long alarmTimeElapsedMillis, WorkSource ws) {
         alarmTimeElapsedMillis = maybeAdjustAlarmTime(alarmTimeElapsedMillis);
         mNextJobExpiredElapsedMillis = alarmTimeElapsedMillis;
         updateAlarmWithListenerLocked(DEADLINE_TAG, mDeadlineExpiredListener,
-                mNextJobExpiredElapsedMillis, uid);
+                mNextJobExpiredElapsedMillis, ws);
     }
 
     private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
@@ -283,7 +291,7 @@
     }
 
     private void updateAlarmWithListenerLocked(String tag, OnAlarmListener listener,
-            long alarmTimeElapsed, int uid) {
+            long alarmTimeElapsed, WorkSource ws) {
         ensureAlarmServiceLocked();
         if (alarmTimeElapsed == Long.MAX_VALUE) {
             mAlarmService.cancel(listener);
@@ -292,7 +300,7 @@
                 Slog.d(TAG, "Setting " + tag + " for: " + alarmTimeElapsed);
             }
             mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTimeElapsed,
-                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, new WorkSource(uid));
+                    AlarmManager.WINDOW_HEURISTIC, 0, tag, listener, null, ws);
         }
     }
 
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 0aa6a90..4cf35bc 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -417,7 +417,11 @@
     // stops output right at 600m/s, depriving this of the information of a device that reaches
     // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases.
     private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F;
-    private boolean mItarSpeedLimitExceeded = false;
+
+    // TODO: improve comment
+    // Volatile to ensure that potentially near-concurrent outputs from HAL
+    // react to this value change promptly
+    private volatile boolean mItarSpeedLimitExceeded = false;
 
     // GNSS Metrics
     private GnssMetrics mGnssMetrics;
@@ -665,9 +669,11 @@
         for (String item : configValues) {
             if (DEBUG) Log.d(TAG, "GpsParamsResource: " + item);
             // We need to support "KEY =", but not "=VALUE".
-            String[] split = item.split("=");
-            if (split.length == 2) {
-                properties.setProperty(split[0].trim().toUpperCase(), split[1]);
+            int index = item.indexOf("=");
+            if (index > 0 && index + 1 < item.length()) {
+                String key = item.substring(0, index);
+                String value = item.substring(index + 1);
+                properties.setProperty(key.trim().toUpperCase(), value);
             } else {
                 Log.w(TAG, "malformed contents: " + item);
             }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 14d9afb..a1a0106 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -376,7 +376,7 @@
         }
 
         public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
-            return new SyntheticPasswordManager(storage, getUserManager());
+            return new SyntheticPasswordManager(getContext(), storage, getUserManager());
         }
 
         public int binderGetCallingUid() {
@@ -763,7 +763,8 @@
     private void migrateOldDataAfterSystemReady() {
         try {
             // Migrate the FRP credential to the persistent data block
-            if (LockPatternUtils.frpCredentialEnabled() && !getBoolean("migrated_frp", false, 0)) {
+            if (LockPatternUtils.frpCredentialEnabled(mContext)
+                    && !getBoolean("migrated_frp", false, 0)) {
                 migrateFrpCredential();
                 setBoolean("migrated_frp", true, 0);
                 Slog.i(TAG, "Migrated migrated_frp.");
@@ -784,7 +785,7 @@
             return;
         }
         for (UserInfo userInfo : mUserManager.getUsers()) {
-            if (userOwnsFrpCredential(userInfo) && isUserSecure(userInfo.id)) {
+            if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
                 synchronized (mSpManager) {
                     if (isSyntheticPasswordBasedCredentialLocked(userInfo.id)) {
                         int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
@@ -2504,7 +2505,7 @@
         }
 
         public void onSystemReady() {
-            if (frpCredentialEnabled()) {
+            if (frpCredentialEnabled(mContext)) {
                 updateRegistration();
             } else {
                 // If we don't intend to use frpCredentials and we're not provisioned yet, send
@@ -2533,7 +2534,7 @@
         private void clearFrpCredentialIfOwnerNotSecure() {
             List<UserInfo> users = mUserManager.getUsers();
             for (UserInfo user : users) {
-                if (userOwnsFrpCredential(user)) {
+                if (userOwnsFrpCredential(mContext, user)) {
                     if (!isUserSecure(user.id)) {
                         mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
                                 0, null);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index 542b929..c9c9329 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -27,6 +27,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.DeadObjectException;
@@ -74,7 +75,10 @@
     }
 
     public void systemReady() {
-        mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        }
     }
 
     private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 33a9a99..9440f17 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.weaver.V1_0.IWeaver;
 import android.hardware.weaver.V1_0.WeaverConfig;
@@ -255,13 +256,16 @@
         byte[] aggregatedSecret;
     }
 
+    private final Context mContext;
     private LockSettingsStorage mStorage;
     private IWeaver mWeaver;
     private WeaverConfig mWeaverConfig;
 
     private final UserManager mUserManager;
 
-    public SyntheticPasswordManager(LockSettingsStorage storage, UserManager userManager) {
+    public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
+            UserManager userManager) {
+        mContext = context;
         mStorage = storage;
         mUserManager = userManager;
     }
@@ -645,7 +649,7 @@
 
     public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(userInfo)) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
             PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
                     userInfo.id));
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -662,7 +666,8 @@
     private void synchronizeFrpPassword(PasswordData pwd,
             int requestedQuality, int userId) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext,
+                mUserManager.getUserInfo(userId))) {
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                         pwd.toBytes());
@@ -675,7 +680,8 @@
     private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
             int weaverSlot) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext,
+                mUserManager.getUserInfo(userId))) {
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                         requestedQuality, pwd.toBytes());
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3795b7f..1cfd5f0 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -271,14 +271,6 @@
 
     // Binder call
     @Override
-    public boolean isGlobalBluetoothA2doOn() {
-        synchronized (mLock) {
-            return mGlobalBluetoothA2dpOn;
-        }
-    }
-
-    // Binder call
-    @Override
     public void setDiscoveryRequest(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         if (client == null) {
@@ -383,7 +375,7 @@
             synchronized (mLock) {
                 a2dpOn = mGlobalBluetoothA2dpOn;
             }
-            Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")");
+            Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
             mAudioService.setBluetoothA2dpOn(a2dpOn);
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 0b11479..664d2f9 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -784,6 +784,14 @@
                 mService.enforcePhoneStatePermission(pid, uid);
             }
             mFlags = flags;
+            if ((flags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mService.setGlobalPrioritySession(MediaSessionRecord.this);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
             mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE);
         }
 
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index b9a2d18..aa65244 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -178,17 +178,6 @@
                 return;
             }
             if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
-                if (mGlobalPrioritySession != record) {
-                    Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
-                            + " to " + record);
-                    mGlobalPrioritySession = record;
-                    if (user != null && user.mPriorityStack.contains(record)) {
-                        // Handle the global priority session separately.
-                        // Otherwise, it will be the media button session even after it becomes
-                        // inactive because it has been the lastly played media app.
-                        user.mPriorityStack.removeSession(record);
-                    }
-                }
                 if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
                 }
@@ -204,10 +193,27 @@
         }
     }
 
+    public void setGlobalPrioritySession(MediaSessionRecord record) {
+        synchronized (mLock) {
+            FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+            if (mGlobalPrioritySession != record) {
+                Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
+                        + " to " + record);
+                mGlobalPrioritySession = record;
+                if (user != null && user.mPriorityStack.contains(record)) {
+                    // Handle the global priority session separately.
+                    // Otherwise, it can be the media button session regardless of the active state
+                    // because it or other system components might have been the lastly played media
+                    // app.
+                    user.mPriorityStack.removeSession(record);
+                }
+            }
+        }
+    }
+
     private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
-        List<MediaSessionRecord> records;
+        List<MediaSessionRecord> records = new ArrayList<>();
         if (userId == UserHandle.USER_ALL) {
-            records = new ArrayList<>();
             int size = mUserRecords.size();
             for (int i = 0; i < size; i++) {
                 records.addAll(mUserRecords.valueAt(i).mPriorityStack.getActiveSessions(userId));
@@ -216,9 +222,9 @@
             FullUserRecord user = getFullUserRecordLocked(userId);
             if (user == null) {
                 Log.w(TAG, "getSessions failed. Unknown user " + userId);
-                return new ArrayList<>();
+                return records;
             }
-            records = user.mPriorityStack.getActiveSessions(userId);
+            records.addAll(user.mPriorityStack.getActiveSessions(userId));
         }
 
         // Return global priority session at the first whenever it's asked.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 90dab2c..b4056b3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -331,7 +331,6 @@
     private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
     private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
     private static final int MSG_POLICIES_CHANGED = 13;
-    private static final int MSG_SET_FIREWALL_RULES = 14;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
@@ -3138,9 +3137,9 @@
                     uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
                 }
             }
-            setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+            setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
         } else {
-            setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
+            setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
         }
     }
 
@@ -3207,7 +3206,7 @@
                 }
             }
 
-            setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+            setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -3906,18 +3905,6 @@
                     removeInterfaceQuota((String) msg.obj);
                     return true;
                 }
-                case MSG_SET_FIREWALL_RULES: {
-                    final int chain = msg.arg1;
-                    final int toggle = msg.arg2;
-                    final SparseIntArray uidRules = (SparseIntArray) msg.obj;
-                    if (uidRules != null) {
-                        setUidFirewallRules(chain, uidRules);
-                    }
-                    if (toggle != CHAIN_TOGGLE_NONE) {
-                        enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
-                    }
-                    return true;
-                }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
                     resetUidFirewallRules(msg.arg1);
                     return true;
@@ -4063,15 +4050,20 @@
 
     /**
      * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
-     * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+     * {@link #enableFirewallChainUL(int, boolean)} synchronously.
      *
      * @param chain firewall chain.
      * @param uidRules new UID rules; if {@code null}, only toggles chain state.
      * @param toggle whether the chain should be enabled, disabled, or not changed.
      */
-    private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+    private void setUidFirewallRulesUL(int chain, @Nullable SparseIntArray uidRules,
             @ChainToggleType int toggle) {
-        mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+        if (uidRules != null) {
+            setUidFirewallRulesUL(chain, uidRules);
+        }
+        if (toggle != CHAIN_TOGGLE_NONE) {
+            enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+        }
     }
 
     /**
@@ -4079,7 +4071,7 @@
      * here to netd.  It will clean up dead rules and make sure the target chain only contains rules
      * specified here.
      */
-    private void setUidFirewallRules(int chain, SparseIntArray uidRules) {
+    private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
         try {
             int size = uidRules.size();
             int[] uids = new int[size];
diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS
index 061fd8d..6b77d83 100644
--- a/services/core/java/com/android/server/net/OWNERS
+++ b/services/core/java/com/android/server/net/OWNERS
@@ -2,7 +2,7 @@
 
 ek@google.com
 hugobenichi@google.com
-jsharkey@google.com
+jsharkey@android.com
 lorenzo@google.com
 satk@google.com
 silberst@google.com
diff --git a/services/core/java/com/android/server/notification/ImportanceExtractor.java b/services/core/java/com/android/server/notification/ImportanceExtractor.java
index 46ec92b..452121c 100644
--- a/services/core/java/com/android/server/notification/ImportanceExtractor.java
+++ b/services/core/java/com/android/server/notification/ImportanceExtractor.java
@@ -22,7 +22,7 @@
  * Determines the importance of the given notification.
  */
 public class ImportanceExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantTopicExtractor";
+    private static final String TAG = "ImportanceExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index b7b91a7..019c7c2 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -45,12 +45,16 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.notification.ManagedServiceInfoProto;
+import android.service.notification.ManagedServicesProto;
+import android.service.notification.ManagedServicesProto.ServiceProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
@@ -214,6 +218,53 @@
         }
     }
 
+    public void dump(ProtoOutputStream proto, DumpFilter filter) {
+        proto.write(ManagedServicesProto.CAPTION, getCaption());
+        final int N = mApproved.size();
+        for (int i = 0 ; i < N; i++) {
+            final int userId = mApproved.keyAt(i);
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+            if (approvedByType != null) {
+                final int M = approvedByType.size();
+                for (int j = 0; j < M; j++) {
+                    final boolean isPrimary = approvedByType.keyAt(j);
+                    final ArraySet<String> approved = approvedByType.valueAt(j);
+                    if (approvedByType != null && approvedByType.size() > 0) {
+                        final long sToken = proto.start(ManagedServicesProto.APPROVED);
+                        for (String s : approved) {
+                            proto.write(ServiceProto.NAME, s);
+                        }
+                        proto.write(ServiceProto.USER_ID, userId);
+                        proto.write(ServiceProto.IS_PRIMARY, isPrimary);
+                        proto.end(sToken);
+                    }
+                }
+            }
+        }
+
+        for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+            if (filter != null && !filter.matches(cmpt)) continue;
+
+            final long cToken = proto.start(ManagedServicesProto.ENABLED);
+            cmpt.toProto(proto);
+            proto.end(cToken);
+        }
+
+        for (ManagedServiceInfo info : mServices) {
+            if (filter != null && !filter.matches(info.component)) continue;
+
+            final long lToken = proto.start(ManagedServicesProto.LIVE_SERVICES);
+            info.toProto(proto, this);
+            proto.end(lToken);
+        }
+
+        for (ComponentName name : mSnoozingForCurrentProfiles) {
+            final long cToken = proto.start(ManagedServicesProto.SNOOZED);
+            name.toProto(proto);
+            proto.end(cToken);
+        }
+    }
+
     protected void onSettingRestored(String element, String value, int backupSdkInt, int userId) {
         if (!mUseXml) {
             Slog.d(TAG, "Restored managed service setting: " + element);
@@ -495,6 +546,17 @@
         return null;
     }
 
+    protected boolean isServiceTokenValidLocked(IInterface service) {
+        if (service == null) {
+            return false;
+        }
+        ManagedServiceInfo info = getServiceFromTokenLocked(service);
+        if (info != null) {
+            return true;
+        }
+        return false;
+    }
+
     protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
         checkNotNull(service);
         ManagedServiceInfo info = getServiceFromTokenLocked(service);
@@ -778,7 +840,12 @@
                     ServiceInfo info = mPm.getServiceInfo(component,
                             PackageManager.MATCH_DIRECT_BOOT_AWARE
                                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
-                    if (info == null || !mConfig.bindPermission.equals(info.permission)) {
+                    if (info == null) {
+                        Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+                                + ": service not found");
+                        continue;
+                    }
+                    if (!mConfig.bindPermission.equals(info.permission)) {
                         Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": it does not require the permission " + mConfig.bindPermission);
                         continue;
@@ -1036,6 +1103,16 @@
                     .append(']').toString();
         }
 
+        public void toProto(ProtoOutputStream proto, ManagedServices host) {
+            final long cToken = proto.start(ManagedServiceInfoProto.COMPONENT);
+            component.toProto(proto);
+            proto.end(cToken);
+            proto.write(ManagedServiceInfoProto.USER_ID, userid);
+            proto.write(ManagedServiceInfoProto.SERVICE, service.getClass().getName());
+            proto.write(ManagedServiceInfoProto.IS_SYSTEM, isSystem);
+            proto.write(ManagedServiceInfoProto.IS_GUEST, isGuest(host));
+        }
+
         public boolean enabledAndUserMatches(int nid) {
             if (!isEnabledForCurrentProfiles()) {
                 return false;
diff --git a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
index 7c82845..3bfd93f 100644
--- a/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -22,7 +22,7 @@
  * Applies adjustments from the group helper and notification assistant
  */
 public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "BadgeExtractor";
+    private static final String TAG = "AdjustmentExtractor";
     private static final boolean DBG = false;
 
 
@@ -35,7 +35,6 @@
             if (DBG) Slog.d(TAG, "skipping empty notification");
             return null;
         }
-
         record.applyAdjustments();
 
         return null;
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index 46ab556..11c7ab7 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -22,7 +22,7 @@
  * Stores the latest notification channel information for this notification
  */
 public class NotificationChannelExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "BadgeExtractor";
+    private static final String TAG = "ChannelExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 6a1401c..36bc096 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -16,6 +16,8 @@
 
 package com.android.server.notification;
 
+import android.service.notification.NotificationStats;
+
 import com.android.internal.statusbar.NotificationVisibility;
 
 public interface NotificationDelegate {
@@ -24,7 +26,8 @@
     void onNotificationClick(int callingUid, int callingPid, String key);
     void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex);
     void onNotificationClear(int callingUid, int callingPid,
-            String pkg, String tag, int id, int userId);
+            String pkg, String tag, int id, int userId, String key,
+            @NotificationStats.DismissalSurface int dismissalSurface);
     void onNotificationError(int callingUid, int callingPid,
             String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId);
@@ -35,4 +38,6 @@
             NotificationVisibility[] newlyVisibleKeys,
             NotificationVisibility[] noLongerVisibleKeys);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+    void onNotificationDirectReplied(String key);
+    void onNotificationSettingsViewed(String key);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 4923b06..f1476b3 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -17,8 +17,10 @@
 package com.android.server.notification;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 
 public interface NotificationManagerInternal {
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
     void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, Notification notification, int userId);
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fe39fcc..98b8af1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -16,8 +16,10 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -125,12 +127,13 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
+import android.service.notification.ListenersDisablingEffectsProto;
 import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.NotificationRecordProto;
 import android.service.notification.NotificationServiceDumpProto;
-import android.service.notification.NotificationServiceProto;
+import android.service.notification.NotificationStats;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
@@ -278,6 +281,7 @@
     private WindowManagerInternal mWindowManagerInternal;
     private AlarmManager mAlarmManager;
     private ICompanionDeviceManager mCompanionManager;
+    private AccessibilityManager mAccessibilityManager;
 
     final IBinder mForegroundToken = new Binder();
     private WorkerHandler mHandler;
@@ -675,7 +679,14 @@
 
         @Override
         public void onNotificationClear(int callingUid, int callingPid,
-                String pkg, String tag, int id, int userId) {
+                String pkg, String tag, int id, int userId, String key,
+                @NotificationStats.DismissalSurface int dismissalSurface) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordDismissalSurface(dismissalSurface);
+                }
+            }
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
                     Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
                     true, userId, REASON_CANCEL, null);
@@ -761,12 +772,35 @@
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
                                         : MetricsEvent.TYPE_COLLAPSE));
                     }
+                    if (expanded) {
+                        r.recordExpanded();
+                    }
                     EventLogTags.writeNotificationExpansion(key,
                             userAction ? 1 : 0, expanded ? 1 : 0,
                             r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
                 }
             }
         }
+
+        @Override
+        public void onNotificationDirectReplied(String key) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordDirectReplied();
+                }
+            }
+        }
+
+        @Override
+        public void onNotificationSettingsViewed(String key) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordViewedSettings();
+                }
+            }
+        }
     };
 
     @GuardedBy("mNotificationLock")
@@ -1142,6 +1176,12 @@
     }
 
     @VisibleForTesting
+    NotificationRecord getNotificationRecord(String key) {
+        return mNotificationsByKey.get(key);
+    }
+
+
+    @VisibleForTesting
     void setSystemReady(boolean systemReady) {
         mSystemReady = systemReady;
     }
@@ -1181,6 +1221,11 @@
         mUsageStats = us;
     }
 
+    @VisibleForTesting
+    void setAccessibilityManager(AccessibilityManager am) {
+        mAccessibilityManager = am;
+    }
+
     // TODO: All tests should use this init instead of the one-off setters above.
     @VisibleForTesting
     void init(Looper looper, IPackageManager packageManager,
@@ -1195,6 +1240,8 @@
                 Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                 DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE);
 
+        mAccessibilityManager =
+                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
         mAm = ActivityManager.getService();
         mPackageManager = packageManager;
         mPackageManagerClient = packageManagerClient;
@@ -1216,7 +1263,7 @@
         mUsageStats = usageStats;
         mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
         mRankingHelper = new RankingHelper(getContext(),
-                getContext().getPackageManager(),
+                mPackageManagerClient,
                 mRankingHandler,
                 mUsageStats,
                 extractorNames);
@@ -1269,13 +1316,11 @@
                 R.array.config_notificationFallbackVibePattern,
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
-
         mInCallNotificationUri = Uri.parse("file://" +
                 resources.getString(R.string.config_inCallNotificationSound));
         mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
-                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                 .build();
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
@@ -1476,7 +1521,7 @@
                 }
             }
         }
-        mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+        mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
 
         if (!fromListener) {
             final NotificationChannel modifiedChannel =
@@ -2772,19 +2817,25 @@
         @Override
         public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
                 throws RemoteException {
+            setNotificationPolicyAccessGrantedForUser(
+                    pkg, getCallingUserHandle().getIdentifier(), granted);
+        }
+
+        @Override
+        public void setNotificationPolicyAccessGrantedForUser(
+                String pkg, int userId, boolean granted) {
             checkCallerIsSystemOrShell();
             final long identity = Binder.clearCallingIdentity();
             try {
                 if (!mActivityManager.isLowRamDevice()) {
                     mConditionProviders.setPackageOrComponentEnabled(
-                            pkg, getCallingUserHandle().getIdentifier(), true, granted);
+                            pkg, userId, true, granted);
 
                     getContext().sendBroadcastAsUser(new Intent(
                             NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                     .setPackage(pkg)
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
-                            getCallingUserHandle(), null);
-
+                            UserHandle.of(userId), null);
                     savePolicyFile();
                 }
             } finally {
@@ -2879,11 +2930,10 @@
                             userId, true, granted);
 
                     getContext().sendBroadcastAsUser(new Intent(
-                                    NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
-
+                            NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                     .setPackage(listener.getPackageName())
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
-                            getCallingUserHandle(), null);
+                            UserHandle.of(userId), null);
 
                     savePolicyFile();
                 }
@@ -2909,7 +2959,7 @@
                             NotificationManager.ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
                                     .setPackage(assistant.getPackageName())
                                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
-                            getCallingUserHandle(), null);
+                            UserHandle.of(userId), null);
 
                     savePolicyFile();
                 }
@@ -3216,7 +3266,7 @@
                     final NotificationRecord nr = mNotificationList.get(i);
                     if (filter.filtered && !filter.matches(nr.sbn)) continue;
                     nr.dump(proto, filter.redact);
-                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.POSTED);
+                    proto.write(NotificationRecordProto.STATE, NotificationRecordProto.POSTED);
                 }
             }
             N = mEnqueuedNotifications.size();
@@ -3225,7 +3275,7 @@
                     final NotificationRecord nr = mEnqueuedNotifications.get(i);
                     if (filter.filtered && !filter.matches(nr.sbn)) continue;
                     nr.dump(proto, filter.redact);
-                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.ENQUEUED);
+                    proto.write(NotificationRecordProto.STATE, NotificationRecordProto.ENQUEUED);
                 }
             }
             List<NotificationRecord> snoozed = mSnoozeHelper.getSnoozed();
@@ -3235,18 +3285,55 @@
                     final NotificationRecord nr = snoozed.get(i);
                     if (filter.filtered && !filter.matches(nr.sbn)) continue;
                     nr.dump(proto, filter.redact);
-                    proto.write(NotificationRecordProto.STATE, NotificationServiceProto.SNOOZED);
+                    proto.write(NotificationRecordProto.STATE, NotificationRecordProto.SNOOZED);
                 }
             }
             proto.end(records);
-        }
 
-        long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
-        mZenModeHelper.dump(proto);
-        for (ComponentName suppressor : mEffectsSuppressors) {
-            proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+            long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
+            mZenModeHelper.dump(proto);
+            for (ComponentName suppressor : mEffectsSuppressors) {
+                proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+            }
+            proto.end(zenLog);
+
+            long listenersToken = proto.start(NotificationServiceDumpProto.NOTIFICATION_LISTENERS);
+            mListeners.dump(proto, filter);
+            proto.end(listenersToken);
+
+            proto.write(NotificationServiceDumpProto.LISTENER_HINTS, mListenerHints);
+
+            for (int i = 0; i < mListenersDisablingEffects.size(); ++i) {
+                long effectsToken = proto.start(
+                    NotificationServiceDumpProto.LISTENERS_DISABLING_EFFECTS);
+
+                proto.write(
+                    ListenersDisablingEffectsProto.HINT, mListenersDisablingEffects.keyAt(i));
+                final ArraySet<ManagedServiceInfo> listeners =
+                    mListenersDisablingEffects.valueAt(i);
+                for (int j = 0; j < listeners.size(); j++) {
+                    final ManagedServiceInfo listener = listeners.valueAt(i);
+                    listenersToken = proto.start(ListenersDisablingEffectsProto.LISTENERS);
+                    listener.toProto(proto, null);
+                    proto.end(listenersToken);
+                }
+
+                proto.end(effectsToken);
+            }
+
+            long assistantsToken = proto.start(
+                NotificationServiceDumpProto.NOTIFICATION_ASSISTANTS);
+            mAssistants.dump(proto, filter);
+            proto.end(assistantsToken);
+
+            long conditionsToken = proto.start(NotificationServiceDumpProto.CONDITION_PROVIDERS);
+            mConditionProviders.dump(proto, filter);
+            proto.end(conditionsToken);
+
+            long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
+            mRankingHelper.dump(proto, filter);
+            proto.end(rankingToken);
         }
-        proto.end(zenLog);
 
         proto.flush();
     }
@@ -3401,6 +3488,12 @@
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
         @Override
+        public NotificationChannel getNotificationChannel(String pkg, int uid, String
+                channelId) {
+            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+        }
+
+        @Override
         public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
                 String tag, int id, Notification notification, int userId) {
             enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
@@ -3519,6 +3612,21 @@
                 user, null, System.currentTimeMillis());
         final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
 
+        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
+                && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+                && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
+            // Increase the importance of foreground service notifications unless the user had an
+            // opinion otherwise
+            if (TextUtils.isEmpty(channelId)
+                    || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+                r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
+            } else {
+                channel.setImportance(IMPORTANCE_LOW);
+                mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+                r.updateNotificationChannel(channel);
+            }
+        }
+
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.sbn.getOverrideGroupKey() != null)) {
             return;
@@ -3752,6 +3860,8 @@
             MetricsLogger.action(r.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
                     .setType(MetricsEvent.TYPE_CLOSE)
+                    .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+                            mDuration)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
             boolean wasPosted = removeFromNotificationListsLocked(r);
@@ -3763,6 +3873,7 @@
             } else {
                 mSnoozeHelper.snooze(r, mDuration);
             }
+            r.recordSnoozed();
             savePolicyFile();
         }
     }
@@ -3902,7 +4013,7 @@
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
                         if (old != null && !old.isCanceled) {
                             mListeners.notifyRemovedLocked(n,
-                                    NotificationListenerService.REASON_ERROR);
+                                    NotificationListenerService.REASON_ERROR, null);
                             mHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
@@ -4018,30 +4129,37 @@
         // These are set inside the conditional if the notification is allowed to make noise.
         boolean hasValidVibrate = false;
         boolean hasValidSound = false;
+        boolean sentAccessibilityEvent = false;
+        // If the notification will appear in the status bar, it should send an accessibility
+        // event
+        if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
+            sendAccessibilityEvent(notification, record.sbn.getPackageName());
+            sentAccessibilityEvent = true;
+        }
 
         if (aboveThreshold && isNotificationForCurrentUser(record)) {
-            // If the notification will appear in the status bar, it should send an accessibility
-            // event
-            if (!record.isUpdate && record.getImportance() > IMPORTANCE_MIN) {
-                sendAccessibilityEvent(notification, record.sbn.getPackageName());
-            }
+
             if (mSystemReady && mAudioManager != null) {
                 Uri soundUri = record.getSound();
                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
-
                 long[] vibration = record.getVibration();
                 // Demote sound to vibration if vibration missing & phone in vibration mode.
                 if (vibration == null
                         && hasValidSound
                         && (mAudioManager.getRingerModeInternal()
-                        == AudioManager.RINGER_MODE_VIBRATE)) {
+                        == AudioManager.RINGER_MODE_VIBRATE)
+                        && mAudioManager.getStreamVolume(
+                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
                     vibration = mFallbackVibrationPattern;
                 }
                 hasValidVibrate = vibration != null;
 
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
-
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
+                    if (!sentAccessibilityEvent) {
+                        sendAccessibilityEvent(notification, record.sbn.getPackageName());
+                        sentAccessibilityEvent = true;
+                    }
                     if (DBG) Slog.v(TAG, "Interrupting!");
                     if (hasValidSound) {
                         mSoundNotificationKey = key;
@@ -4137,8 +4255,9 @@
         boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
         // do not play notifications if there is a user of exclusive audio focus
         // or the device is in vibrate mode
-        if (!mAudioManager.isAudioFocusExclusive() && mAudioManager.getRingerModeInternal()
-                != AudioManager.RINGER_MODE_VIBRATE) {
+        if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal()
+                != AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume(
+                AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
             final long identity = Binder.clearCallingIdentity();
             try {
                 final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -4394,6 +4513,7 @@
             ArrayList<String> groupKeyBefore = new ArrayList<>(N);
             ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
             ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
+            ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
                 orderBefore.add(r.getKey());
@@ -4403,6 +4523,7 @@
                 groupKeyBefore.add(r.getGroupKey());
                 overridePeopleBefore.add(r.getPeopleOverride());
                 snoozeCriteriaBefore.add(r.getSnoozeCriteria());
+                userSentimentBefore.add(r.getUserSentiment());
                 mRankingHelper.extractSignals(r);
             }
             mRankingHelper.sort(mNotificationList);
@@ -4414,7 +4535,8 @@
                         || !Objects.equals(channelBefore.get(i), r.getChannel())
                         || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
                         || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
-                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())) {
+                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
+                        || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())) {
                     mHandler.scheduleSendRankingUpdate();
                     return;
                 }
@@ -4558,8 +4680,7 @@
     }
 
     void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
-        AccessibilityManager manager = AccessibilityManager.getInstance(getContext());
-        if (!manager.isEnabled()) {
+        if (!mAccessibilityManager.isEnabled()) {
             return;
         }
 
@@ -4573,7 +4694,7 @@
             event.getText().add(tickerText);
         }
 
-        manager.sendAccessibilityEvent(event);
+        mAccessibilityManager.sendAccessibilityEvent(event);
     }
 
     /**
@@ -4607,6 +4728,10 @@
         // Record caller.
         recordCallerLocked(r);
 
+        if (r.getStats().getDismissalSurface() == NotificationStats.DISMISSAL_NOT_DISMISSED) {
+            r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
+        }
+
         // tell the app
         if (sendDelete) {
             if (r.getNotification().deleteIntent != null) {
@@ -4627,7 +4752,7 @@
                 if (reason != REASON_SNOOZED) {
                     r.isCanceled = true;
                 }
-                mListeners.notifyRemovedLocked(r.sbn, reason);
+                mListeners.notifyRemovedLocked(r.sbn, reason, r.getStats());
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
@@ -5221,6 +5346,7 @@
         Bundle overridePeople = new Bundle();
         Bundle snoozeCriteria = new Bundle();
         Bundle showBadge = new Bundle();
+        Bundle userSentiment = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -5246,6 +5372,7 @@
             overridePeople.putStringArrayList(key, record.getPeopleOverride());
             snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
             showBadge.putBoolean(key, record.canShowBadge());
+            userSentiment.putInt(key, record.getUserSentiment());
         }
         final int M = keys.size();
         String[] keysAr = keys.toArray(new String[M]);
@@ -5256,7 +5383,7 @@
         }
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge);
+                channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -5345,7 +5472,7 @@
         @Override
         protected Config getConfig() {
             Config c = new Config();
-            c.caption = "notification assistant service";
+            c.caption = "notification assistant";
             c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
             c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
             c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
@@ -5388,8 +5515,6 @@
                     continue;
                 }
 
-                final int importance = r.getImportance();
-                final boolean fromUser = r.isImportanceFromUser();
                 final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
@@ -5420,6 +5545,10 @@
                 final String snoozeCriterionId) {
             TrimCache trimCache = new TrimCache(sbn);
             for (final ManagedServiceInfo info : getServices()) {
+                boolean sbnVisible = isVisibleToListener(sbn, info);
+                if (!sbnVisible) {
+                    continue;
+                }
                 final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
@@ -5541,7 +5670,8 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
+                            notifyRemoved(
+                                    info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
                         }
                     });
                     continue;
@@ -5561,7 +5691,8 @@
          * asynchronously notify all listeners about a removed notification
          */
         @GuardedBy("mNotificationLock")
-        public void notifyRemovedLocked(StatusBarNotification sbn, int reason) {
+        public void notifyRemovedLocked(StatusBarNotification sbn, int reason,
+                NotificationStats notificationStats) {
             // make a copy in case changes are made to the underlying Notification object
             // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
             // notification
@@ -5570,11 +5701,14 @@
                 if (!isVisibleToListener(sbn, info)) {
                     continue;
                 }
+                // Only assistants can get stats
+                final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
+                        ? notificationStats : null;
                 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyRemoved(info, sbnLight, update, reason);
+                        notifyRemoved(info, sbnLight, update, stats, reason);
                     }
                 });
             }
@@ -5679,14 +5813,14 @@
         }
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
-                NotificationRankingUpdate rankingUpdate, int reason) {
+                NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
             if (!info.enabledAndUserMatches(sbn.getUserId())) {
                 return;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                listener.onNotificationRemoved(sbnHolder, rankingUpdate, reason);
+                listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
             } catch (RemoteException ex) {
                 Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
             }
@@ -5772,10 +5906,9 @@
             final DumpFilter filter = new DumpFilter();
             for (int ai = 0; ai < args.length; ai++) {
                 final String a = args[ai];
-                if ("--proto".equals(args[0])) {
+                if ("--proto".equals(a)) {
                     filter.proto = true;
-                }
-                if ("--noredact".equals(a) || "--reveal".equals(a)) {
+                } else if ("--noredact".equals(a) || "--reveal".equals(a)) {
                     filter.redact = false;
                 } else if ("p".equals(a) || "pkg".equals(a) || "--package".equals(a)) {
                     if (ai < args.length-1) {
@@ -5848,8 +5981,8 @@
 
     private class ShellCmd extends ShellCommand {
         public static final String USAGE = "help\n"
-                + "allow_listener COMPONENT\n"
-                + "disallow_listener COMPONENT\n"
+                + "allow_listener COMPONENT [user_id]\n"
+                + "disallow_listener COMPONENT [user_id]\n"
                 + "set_assistant COMPONENT\n"
                 + "remove_assistant COMPONENT\n"
                 + "allow_dnd PACKAGE\n"
@@ -5880,7 +6013,13 @@
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), true);
+                        }
                     }
                     break;
                     case "disallow_listener": {
@@ -5889,7 +6028,13 @@
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), false);
+                        }
                     }
                     break;
                     case "allow_assistant": {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 77bf9e3..faa300f2 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -20,6 +20,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEUTRAL;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -41,6 +43,7 @@
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationStats;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -84,8 +87,6 @@
 
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
-    /** Whether the notification was seen by the user via one of the notification listeners. */
-    boolean mIsSeen;
 
     // These members are used by NotificationSignalExtractors
     // to communicate with the ranking module.
@@ -136,6 +137,8 @@
     private String mChannelIdLogTag;
 
     private final List<Adjustment> mAdjustments;
+    private final NotificationStats mStats;
+    private int mUserSentiment;
 
     @VisibleForTesting
     public NotificationRecord(Context context, StatusBarNotification sbn,
@@ -156,6 +159,7 @@
         mImportance = calculateImportance();
         mLight = calculateLights();
         mAdjustments = new ArrayList<>();
+        mStats = new NotificationStats();
     }
 
     private boolean isPreChannelsNotification() {
@@ -395,7 +399,7 @@
         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
         pw.println(prefix + "pri=" + notification.priority);
         pw.println(prefix + "key=" + sbn.getKey());
-        pw.println(prefix + "seen=" + mIsSeen);
+        pw.println(prefix + "seen=" + mStats.hasSeen());
         pw.println(prefix + "groupKey=" + getGroupKey());
         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
         pw.println(prefix + "contentIntent=" + notification.contentIntent);
@@ -572,6 +576,10 @@
                             adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
                     setOverrideGroupKey(groupOverrideKey);
                 }
+                if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
+                    setUserSentiment(adjustment.getSignals().getInt(
+                            Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+                }
             }
         }
     }
@@ -740,6 +748,7 @@
                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
         if (visible) {
+            setSeen();
             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
         }
         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
@@ -777,12 +786,12 @@
 
     /** Check if any of the listeners have marked this notification as seen by the user. */
     public boolean isSeen() {
-        return mIsSeen;
+        return mStats.hasSeen();
     }
 
     /** Mark the notification as seen by the user. */
     public void setSeen() {
-        mIsSeen = true;
+        mStats.setSeen();
     }
 
     public void setAuthoritativeRank(int authoritativeRank) {
@@ -883,6 +892,38 @@
         mSnoozeCriteria = snoozeCriteria;
     }
 
+    private void setUserSentiment(int userSentiment) {
+        mUserSentiment = userSentiment;
+    }
+
+    public int getUserSentiment() {
+        return mUserSentiment;
+    }
+
+    public NotificationStats getStats() {
+        return mStats;
+    }
+
+    public void recordExpanded() {
+        mStats.setExpanded();
+    }
+
+    public void recordDirectReplied() {
+        mStats.setDirectReplied();
+    }
+
+    public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
+        mStats.setDismissalSurface(surface);
+    }
+
+    public void recordSnoozed() {
+        mStats.setSnoozed();
+    }
+
+    public void recordViewedSettings() {
+        mStats.setViewedSettings();
+    }
+
     public LogMaker getLogMaker(long now) {
         if (mLogMaker == null) {
             // initialize fields that only change on update (so a new record)
diff --git a/services/core/java/com/android/server/notification/PriorityExtractor.java b/services/core/java/com/android/server/notification/PriorityExtractor.java
index 5d5d39d..7a287db 100644
--- a/services/core/java/com/android/server/notification/PriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/PriorityExtractor.java
@@ -23,7 +23,7 @@
  * Determines if the given notification can bypass Do Not Disturb.
  */
 public class PriorityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantTopicExtractor";
+    private static final String TAG = "PriorityExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index b5ef1c6..b9c0d90 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,7 @@
             int uid, boolean includeDeleted);
     void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index fc24581..d7e9cf3 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -36,10 +36,13 @@
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -228,7 +231,11 @@
                                 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
                                     NotificationChannel channel = new NotificationChannel(id,
                                             channelName, channelImportance);
-                                    channel.populateFromXml(parser);
+                                    if (forRestore) {
+                                        channel.populateFromXmlForRestore(parser, mContext);
+                                    } else {
+                                        channel.populateFromXml(parser);
+                                    }
                                     r.channels.put(id, channel);
                                 }
                             }
@@ -391,7 +398,11 @@
                     }
 
                     for (NotificationChannel channel : r.channels.values()) {
-                        if (!forBackup || (forBackup && !channel.isDeleted())) {
+                        if (forBackup) {
+                            if (!channel.isDeleted()) {
+                                channel.writeXmlForBackup(out, mContext);
+                            }
+                        } else {
                             channel.writeXml(out);
                         }
                     }
@@ -613,7 +624,8 @@
     }
 
     @Override
-    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
+    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+            boolean fromUser) {
         Preconditions.checkNotNull(updatedChannel);
         Preconditions.checkNotNull(updatedChannel.getId());
         Record r = getOrCreateRecord(pkg, uid);
@@ -627,7 +639,11 @@
         if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
             updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
         }
-        lockFieldsForUpdate(channel, updatedChannel);
+        updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+        updatedChannel.lockFields(channel.getUserLockedFields());
+        if (fromUser) {
+            lockFieldsForUpdate(channel, updatedChannel);
+        }
         r.channels.put(updatedChannel.getId(), updatedChannel);
 
         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
@@ -874,8 +890,6 @@
 
     @VisibleForTesting
     void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
-        update.unlockFields(update.getUserLockedFields());
-        update.lockFields(original.getUserLockedFields());
         if (original.canBypassDnd() != update.canBypassDnd()) {
             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
         }
@@ -912,8 +926,7 @@
                 pw.print("  ");
                 pw.println(mSignalExtractors[i]);
             }
-        }
-        if (filter == null) {
+
             pw.print(prefix);
             pw.println("per-package config:");
         }
@@ -925,6 +938,52 @@
         dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
     }
 
+    public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
+                mSignalExtractors[i].getClass().getSimpleName());
+        }
+        synchronized (mRecords) {
+            dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
+        }
+        dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+            mRestoredWithoutUids);
+    }
+
+    private static void dumpRecords(ProtoOutputStream proto, long fieldId,
+            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+        final int N = records.size();
+        long fToken;
+        for (int i = 0; i < N; i++) {
+            final Record r = records.valueAt(i);
+            if (filter == null || filter.matches(r.pkg)) {
+                fToken = proto.start(fieldId);
+
+                proto.write(RecordProto.PACKAGE, r.pkg);
+                proto.write(RecordProto.UID, r.uid);
+                proto.write(RecordProto.IMPORTANCE, r.importance);
+                proto.write(RecordProto.PRIORITY, r.priority);
+                proto.write(RecordProto.VISIBILITY, r.visibility);
+                proto.write(RecordProto.SHOW_BADGE, r.showBadge);
+
+                long token;
+                for (NotificationChannel channel : r.channels.values()) {
+                    token = proto.start(RecordProto.CHANNELS);
+                    channel.toProto(proto);
+                    proto.end(token);
+                }
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    token = proto.start(RecordProto.CHANNEL_GROUPS);
+                    group.toProto(proto);
+                    proto.end(token);
+                }
+
+                proto.end(fToken);
+            }
+        }
+    }
+
     private static void dumpRecords(PrintWriter pw, String prefix,
             NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
         final int N = records.size();
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index a7a2743..abf2900 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -117,15 +117,19 @@
                 ZenLog.traceIntercepted(record, "alarmsOnly");
                 return true;
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                if (isAlarm(record)) {
-                    // Alarms are always priority
-                    return false;
-                }
                 // allow user-prioritized packages through in priority mode
                 if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
                     ZenLog.traceNotIntercepted(record, "priorityApp");
                     return false;
                 }
+
+                if (isAlarm(record)) {
+                    if (!config.allowAlarms) {
+                        ZenLog.traceIntercepted(record, "!allowAlarms");
+                        return true;
+                    }
+                    return false;
+                }
                 if (isCall(record)) {
                     if (config.allowRepeatCallers
                             && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
@@ -159,6 +163,15 @@
                     }
                     return false;
                 }
+                AudioAttributes aa = record.getAudioAttributes();
+                if (aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
+                        AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) {
+                    if (!config.allowMediaSystemOther) {
+                        ZenLog.traceIntercepted(record, "!allowMediaSystemOther");
+                        return true;
+                    }
+                    return false;
+                }
                 ZenLog.traceIntercepted(record, "!priority");
                 return true;
             default:
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ffdafc5..f61cec9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -59,6 +59,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.LocalServices;
 
@@ -87,7 +88,7 @@
     private final Context mContext;
     private final H mHandler;
     private final SettingsObserver mSettingsObserver;
-    private final AppOpsManager mAppOps;
+    @VisibleForTesting protected final AppOpsManager mAppOps;
     protected ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     private final ZenModeFiltering mFiltering;
@@ -102,9 +103,9 @@
     private final String SCHEDULED_DEFAULT_RULE_1 = "SCHEDULED_DEFAULT_RULE_1";
     private final String SCHEDULED_DEFAULT_RULE_2 = "SCHEDULED_DEFAULT_RULE_2";
 
-    private int mZenMode;
+    @VisibleForTesting protected int mZenMode;
     private int mUser = UserHandle.USER_SYSTEM;
-    protected ZenModeConfig mConfig;
+    @VisibleForTesting protected ZenModeConfig mConfig;
     private AudioManagerInternal mAudioManager;
     protected PackageManager mPm;
     private long mSuppressedEffects;
@@ -125,12 +126,6 @@
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
 
         mDefaultConfig = new ZenModeConfig();
-        mDefaultRuleWeeknightsName = mContext.getResources()
-                .getString(R.string.zen_mode_default_weeknights_name);
-        mDefaultRuleWeekendsName = mContext.getResources()
-                .getString(R.string.zen_mode_default_weekends_name);
-        mDefaultRuleEventsName = mContext.getResources()
-                .getString(R.string.zen_mode_default_events_name);
         setDefaultZenRules(mContext);
         mConfig = mDefaultConfig;
         mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
@@ -435,6 +430,7 @@
     }
 
     private void appendDefaultRules (ZenModeConfig config) {
+        getDefaultRuleNames();
         appendDefaultScheduleRules(config);
         appendDefaultEventRules(config);
     }
@@ -567,7 +563,7 @@
                     proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString());
                 }
             }
-            proto.write(ZenModeProto.POLICY, mConfig.toNotificationPolicy().toString());
+            mConfig.toNotificationPolicy().toProto(proto, ZenModeProto.POLICY);
             proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects);
         }
     }
@@ -595,8 +591,9 @@
             pw.println(config);
             return;
         }
-        pw.printf("allow(calls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s,"
+        pw.printf("allow(alarms=%b,media=%bcalls=%b,callsFrom=%s,repeatCallers=%b,messages=%b,messagesFrom=%s,"
                 + "events=%b,reminders=%b,whenScreenOff=%b,whenScreenOn=%b)\n",
+                config.allowAlarms, config.allowMediaSystemOther,
                 config.allowCalls, ZenModeConfig.sourceToString(config.allowCallsFrom),
                 config.allowRepeatCallers, config.allowMessages,
                 ZenModeConfig.sourceToString(config.allowMessagesFrom),
@@ -813,7 +810,18 @@
         }
     }
 
-    private void applyRestrictions() {
+    private void getDefaultRuleNames() {
+        // on locale-change, these values differ
+        mDefaultRuleWeeknightsName = mContext.getResources()
+                .getString(R.string.zen_mode_default_weeknights_name);
+        mDefaultRuleWeekendsName = mContext.getResources()
+                .getString(R.string.zen_mode_default_weekends_name);
+        mDefaultRuleEventsName = mContext.getResources()
+                .getString(R.string.zen_mode_default_events_name);
+    }
+
+    @VisibleForTesting
+    protected void applyRestrictions() {
         final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
 
         // notification restrictions
@@ -822,6 +830,10 @@
         // call restrictions
         final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
                 || (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
+        // alarm restrictions
+        final boolean muteAlarms = zen && !mConfig.allowAlarms;
+        // alarm restrictions
+        final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
         // total silence restrictions
         final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
 
@@ -833,13 +845,18 @@
                 applyRestrictions(muteNotifications || muteEverything, usage);
             } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_CALL) {
                 applyRestrictions(muteCalls || muteEverything, usage);
+            } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_ALARM) {
+                applyRestrictions(muteAlarms || muteEverything, usage);
+            } else if (suppressionBehavior == AudioAttributes.SUPPRESSIBLE_MEDIA_SYSTEM_OTHER) {
+                applyRestrictions(muteMediaAndSystemSounds || muteEverything, usage);
             } else {
                 applyRestrictions(muteEverything, usage);
             }
         }
     }
 
-    private void applyRestrictions(boolean mute, int usage) {
+    @VisibleForTesting
+    protected void applyRestrictions(boolean mute, int usage) {
         final String[] exceptionPackages = null; // none (for now)
         mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
@@ -916,6 +933,7 @@
         weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
         weeknights.startHour = 22;
         weeknights.endHour = 7;
+        weeknights.exitAtAlarm = true;
         final ZenRule rule1 = new ZenRule();
         rule1.enabled = false;
         rule1.name = mDefaultRuleWeeknightsName;
@@ -931,6 +949,7 @@
         weekends.startHour = 23;
         weekends.startMinute = 30;
         weekends.endHour = 10;
+        weekends.exitAtAlarm = true;
         final ZenRule rule2 = new ZenRule();
         rule2.enabled = false;
         rule2.name = mDefaultRuleWeekendsName;
diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java
index 40c6639..5b3d1ec 100644
--- a/services/core/java/com/android/server/oemlock/OemLockService.java
+++ b/services/core/java/com/android/server/oemlock/OemLockService.java
@@ -31,6 +31,7 @@
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.service.oemlock.IOemLockService;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Slog;
 
 import com.android.server.LocalServices;
@@ -98,6 +99,7 @@
                         !newRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET);
                 if (!unlockAllowedByAdmin) {
                     mOemLock.setOemUnlockAllowedByDevice(false);
+                    setPersistentDataBlockOemUnlockAllowedBit(false);
                 }
             }
         }
@@ -158,6 +160,7 @@
                 }
 
                 mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
+                setPersistentDataBlockOemUnlockAllowedBit(allowedByUser);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -202,6 +205,20 @@
         }
     };
 
+    /**
+     * Always synchronize the OemUnlockAllowed bit to the FRP partition, which
+     * is used to erase FRP information on a unlockable device.
+     */
+    private void setPersistentDataBlockOemUnlockAllowedBit(boolean allowed) {
+        final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+                mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+        // if mOemLock is PersistentDataBlockLock, then the bit should have already been set
+        if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)) {
+            Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
+            pdbm.setOemUnlockEnabled(allowed);
+        }
+    }
+
     private boolean isOemUnlockAllowedByAdmin() {
         return !UserManager.get(mContext)
                 .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM);
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 6d8cac0..679250c 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -340,7 +340,8 @@
             int dexoptFlags =
                     DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES |
                     DexoptOptions.DEXOPT_BOOT_COMPLETE |
-                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
+                    (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0) |
+                    DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
             if (is_for_primary_dex) {
                 int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
                         dexoptFlags));
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
deleted file mode 100644
index 30fda1e..0000000
--- a/services/core/java/com/android/server/pm/BasePermission.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2006 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.server.pm;
-
-import android.content.pm.PackageParser;
-import android.content.pm.PermissionInfo;
-import android.os.UserHandle;
-
-final class BasePermission {
-    final static int TYPE_NORMAL = 0;
-
-    final static int TYPE_BUILTIN = 1;
-
-    final static int TYPE_DYNAMIC = 2;
-
-    final String name;
-
-    String sourcePackage;
-
-    PackageSettingBase packageSetting;
-
-    final int type;
-
-    int protectionLevel;
-
-    PackageParser.Permission perm;
-
-    PermissionInfo pendingInfo;
-
-    /** UID that owns the definition of this permission */
-    int uid;
-
-    /** Additional GIDs given to apps granted this permission */
-    private int[] gids;
-
-    /**
-     * Flag indicating that {@link #gids} should be adjusted based on the
-     * {@link UserHandle} the granted app is running as.
-     */
-    private boolean perUser;
-
-    BasePermission(String _name, String _sourcePackage, int _type) {
-        name = _name;
-        sourcePackage = _sourcePackage;
-        type = _type;
-        // Default to most conservative protection level.
-        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
-    }
-
-    @Override
-    public String toString() {
-        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
-                + "}";
-    }
-
-    public void setGids(int[] gids, boolean perUser) {
-        this.gids = gids;
-        this.perUser = perUser;
-    }
-
-    public int[] computeGids(int userId) {
-        if (perUser) {
-            final int[] userGids = new int[gids.length];
-            for (int i = 0; i < gids.length; i++) {
-                userGids[i] = UserHandle.getUid(userId, gids[i]);
-            }
-            return userGids;
-        } else {
-            return gids;
-        }
-    }
-
-    public boolean isRuntime() {
-        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                == PermissionInfo.PROTECTION_DANGEROUS;
-    }
-
-    public boolean isDevelopment() {
-        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                == PermissionInfo.PROTECTION_SIGNATURE
-                && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
-    }
-
-    public boolean isInstant() {
-        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
-    }
-
-    public boolean isRuntimeOnly() {
-        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
-    }
-}
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
deleted file mode 100644
index a3811ba..0000000
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ /dev/null
@@ -1,1244 +0,0 @@
-/*
- * Copyright (C) 2015 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.server.pm;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.DownloadManager;
-import android.app.admin.DevicePolicyManager;
-import android.companion.CompanionDeviceManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal.PackagesProvider;
-import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
-import android.content.pm.PackageParser;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.storage.StorageManager;
-import android.print.PrintManager;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
-import android.provider.Telephony.Sms.Intents;
-import android.telephony.TelephonyManager;
-import android.security.Credentials;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static android.os.Process.FIRST_APPLICATION_UID;
-
-/**
- * This class is the policy for granting runtime permissions to
- * platform components and default handlers in the system such
- * that the device is usable out-of-the-box. For example, the
- * shell UID is a part of the system and the Phone app should
- * have phone related permission by default.
- */
-final class DefaultPermissionGrantPolicy {
-    private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
-    private static final boolean DEBUG = false;
-
-    private static final int DEFAULT_FLAGS =
-            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
-    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
-
-    private static final String TAG_EXCEPTIONS = "exceptions";
-    private static final String TAG_EXCEPTION = "exception";
-    private static final String TAG_PERMISSION = "permission";
-    private static final String ATTR_PACKAGE = "package";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_FIXED = "fixed";
-
-    private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
-    static {
-        PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
-        PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
-        PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
-        PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
-        PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
-        PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
-        PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
-    }
-
-    private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
-    static {
-        CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
-        CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
-        CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
-    }
-
-    private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
-    static {
-        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
-        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
-    }
-
-    private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
-    static {
-        CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
-        CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
-    }
-
-    private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
-    static {
-        SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
-        SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
-    }
-
-    private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
-    static {
-        MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
-    }
-
-    private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
-    static {
-        CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
-    }
-
-    private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
-    static {
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
-    }
-
-    private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
-    static {
-        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
-        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-    }
-
-    private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
-
-    private static final String ACTION_TRACK = "com.android.fitness.TRACK";
-
-    private final PackageManagerService mService;
-    private final Handler mHandler;
-
-    private PackagesProvider mLocationPackagesProvider;
-    private PackagesProvider mVoiceInteractionPackagesProvider;
-    private PackagesProvider mSmsAppPackagesProvider;
-    private PackagesProvider mDialerAppPackagesProvider;
-    private PackagesProvider mSimCallManagerPackagesProvider;
-    private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
-
-    private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
-
-    public DefaultPermissionGrantPolicy(PackageManagerService service) {
-        mService = service;
-        mHandler = new Handler(mService.mHandlerThread.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
-                    synchronized (mService.mPackages) {
-                        if (mGrantExceptions == null) {
-                            mGrantExceptions = readDefaultPermissionExceptionsLPw();
-                        }
-                    }
-                }
-            }
-        };
-    }
-
-    public void setLocationPackagesProviderLPw(PackagesProvider provider) {
-        mLocationPackagesProvider = provider;
-    }
-
-    public void setVoiceInteractionPackagesProviderLPw(PackagesProvider provider) {
-        mVoiceInteractionPackagesProvider = provider;
-    }
-
-    public void setSmsAppPackagesProviderLPw(PackagesProvider provider) {
-        mSmsAppPackagesProvider = provider;
-    }
-
-    public void setDialerAppPackagesProviderLPw(PackagesProvider provider) {
-        mDialerAppPackagesProvider = provider;
-    }
-
-    public void setSimCallManagerPackagesProviderLPw(PackagesProvider provider) {
-        mSimCallManagerPackagesProvider = provider;
-    }
-
-    public void setSyncAdapterPackagesProviderLPw(SyncAdapterPackagesProvider provider) {
-        mSyncAdapterPackagesProvider = provider;
-    }
-
-    public void grantDefaultPermissions(int userId) {
-        if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
-            grantAllRuntimePermissions(userId);
-        } else {
-            grantPermissionsToSysComponentsAndPrivApps(userId);
-            grantDefaultSystemHandlerPermissions(userId);
-            grantDefaultPermissionExceptions(userId);
-        }
-    }
-
-    private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) {
-        Set<String> permissions = new ArraySet<>();
-        for (String permission :  pkg.requestedPermissions) {
-            BasePermission bp = mService.mSettings.mPermissions.get(permission);
-            if (bp != null && bp.isRuntime()) {
-                permissions.add(permission);
-            }
-        }
-        if (!permissions.isEmpty()) {
-            grantRuntimePermissionsLPw(pkg, permissions, true, userId);
-        }
-    }
-
-    private void grantAllRuntimePermissions(int userId) {
-        Log.i(TAG, "Granting all runtime permissions for user " + userId);
-        synchronized (mService.mPackages) {
-            for (PackageParser.Package pkg : mService.mPackages.values()) {
-                grantRuntimePermissionsForPackageLocked(userId, pkg);
-            }
-        }
-    }
-
-    public void scheduleReadDefaultPermissionExceptions() {
-        mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-    }
-
-    private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
-        Log.i(TAG, "Granting permissions to platform components for user " + userId);
-
-        synchronized (mService.mPackages) {
-            for (PackageParser.Package pkg : mService.mPackages.values()) {
-                if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
-                        || !doesPackageSupportRuntimePermissions(pkg)
-                        || pkg.requestedPermissions.isEmpty()) {
-                    continue;
-                }
-                grantRuntimePermissionsForPackageLocked(userId, pkg);
-            }
-        }
-    }
-
-    private void grantDefaultSystemHandlerPermissions(int userId) {
-        Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
-
-        final PackagesProvider locationPackagesProvider;
-        final PackagesProvider voiceInteractionPackagesProvider;
-        final PackagesProvider smsAppPackagesProvider;
-        final PackagesProvider dialerAppPackagesProvider;
-        final PackagesProvider simCallManagerPackagesProvider;
-        final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
-
-        synchronized (mService.mPackages) {
-            locationPackagesProvider = mLocationPackagesProvider;
-            voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
-            smsAppPackagesProvider = mSmsAppPackagesProvider;
-            dialerAppPackagesProvider = mDialerAppPackagesProvider;
-            simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
-            syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
-        }
-
-        String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
-                ? voiceInteractionPackagesProvider.getPackages(userId) : null;
-        String[] locationPackageNames = (locationPackagesProvider != null)
-                ? locationPackagesProvider.getPackages(userId) : null;
-        String[] smsAppPackageNames = (smsAppPackagesProvider != null)
-                ? smsAppPackagesProvider.getPackages(userId) : null;
-        String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
-                ? dialerAppPackagesProvider.getPackages(userId) : null;
-        String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
-                ? simCallManagerPackagesProvider.getPackages(userId) : null;
-        String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
-                syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
-        String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
-                syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
-
-        synchronized (mService.mPackages) {
-            // Installer
-            PackageParser.Package installerPackage = getSystemPackageLPr(
-                    mService.mRequiredInstallerPackage);
-            if (installerPackage != null
-                    && doesPackageSupportRuntimePermissions(installerPackage)) {
-                grantRuntimePermissionsLPw(installerPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Verifier
-            PackageParser.Package verifierPackage = getSystemPackageLPr(
-                    mService.mRequiredVerifierPackage);
-            if (verifierPackage != null
-                    && doesPackageSupportRuntimePermissions(verifierPackage)) {
-                grantRuntimePermissionsLPw(verifierPackage, STORAGE_PERMISSIONS, true, userId);
-                grantRuntimePermissionsLPw(verifierPackage, PHONE_PERMISSIONS, false, userId);
-                grantRuntimePermissionsLPw(verifierPackage, SMS_PERMISSIONS, false, userId);
-            }
-
-            // SetupWizard
-            PackageParser.Package setupPackage = getSystemPackageLPr(
-                    mService.mSetupWizardPackage);
-            if (setupPackage != null
-                    && doesPackageSupportRuntimePermissions(setupPackage)) {
-                grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, CAMERA_PERMISSIONS, userId);
-            }
-
-            // Camera
-            Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-            PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    cameraIntent, userId);
-            if (cameraPackage != null
-                    && doesPackageSupportRuntimePermissions(cameraPackage)) {
-                grantRuntimePermissionsLPw(cameraPackage, CAMERA_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(cameraPackage, MICROPHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(cameraPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Media provider
-            PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackageLPr(
-                    MediaStore.AUTHORITY, userId);
-            if (mediaStorePackage != null) {
-                grantRuntimePermissionsLPw(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Downloads provider
-            PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackageLPr(
-                    "downloads", userId);
-            if (downloadsPackage != null) {
-                grantRuntimePermissionsLPw(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Downloads UI
-            Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
-            PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    downloadsUiIntent, userId);
-            if (downloadsUiPackage != null
-                    && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
-                grantRuntimePermissionsLPw(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Storage provider
-            PackageParser.Package storagePackage = getDefaultProviderAuthorityPackageLPr(
-                    "com.android.externalstorage.documents", userId);
-            if (storagePackage != null) {
-                grantRuntimePermissionsLPw(storagePackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // CertInstaller
-            Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
-            PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    certInstallerIntent, userId);
-            if (certInstallerPackage != null
-                    && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
-                grantRuntimePermissionsLPw(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Dialer
-            if (dialerAppPackageNames == null) {
-                Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
-                PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        dialerIntent, userId);
-                if (dialerPackage != null) {
-                    grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
-                }
-            } else {
-                for (String dialerAppPackageName : dialerAppPackageNames) {
-                    PackageParser.Package dialerPackage = getSystemPackageLPr(dialerAppPackageName);
-                    if (dialerPackage != null) {
-                        grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
-                    }
-                }
-            }
-
-            // Sim call manager
-            if (simCallManagerPackageNames != null) {
-                for (String simCallManagerPackageName : simCallManagerPackageNames) {
-                    PackageParser.Package simCallManagerPackage =
-                            getSystemPackageLPr(simCallManagerPackageName);
-                    if (simCallManagerPackage != null) {
-                        grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage,
-                                userId);
-                    }
-                }
-            }
-
-            // SMS
-            if (smsAppPackageNames == null) {
-                Intent smsIntent = new Intent(Intent.ACTION_MAIN);
-                smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
-                PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        smsIntent, userId);
-                if (smsPackage != null) {
-                   grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
-                }
-            } else {
-                for (String smsPackageName : smsAppPackageNames) {
-                    PackageParser.Package smsPackage = getSystemPackageLPr(smsPackageName);
-                    if (smsPackage != null) {
-                        grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
-                    }
-                }
-            }
-
-            // Cell Broadcast Receiver
-            Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
-            PackageParser.Package cbrPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(cbrIntent, userId);
-            if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
-                grantRuntimePermissionsLPw(cbrPackage, SMS_PERMISSIONS, userId);
-            }
-
-            // Carrier Provisioning Service
-            Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
-            PackageParser.Package carrierProvPackage =
-                    getDefaultSystemHandlerServicePackageLPr(carrierProvIntent, userId);
-            if (carrierProvPackage != null && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
-                grantRuntimePermissionsLPw(carrierProvPackage, SMS_PERMISSIONS, false, userId);
-            }
-
-            // Calendar
-            Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
-            calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
-            PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    calendarIntent, userId);
-            if (calendarPackage != null
-                    && doesPackageSupportRuntimePermissions(calendarPackage)) {
-                grantRuntimePermissionsLPw(calendarPackage, CALENDAR_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(calendarPackage, CONTACTS_PERMISSIONS, userId);
-            }
-
-            // Calendar provider
-            PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackageLPr(
-                    CalendarContract.AUTHORITY, userId);
-            if (calendarProviderPackage != null) {
-                grantRuntimePermissionsLPw(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(calendarProviderPackage, CALENDAR_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Calendar provider sync adapters
-            List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
-                    calendarSyncAdapterPackages, userId);
-            final int calendarSyncAdapterCount = calendarSyncAdapters.size();
-            for (int i = 0; i < calendarSyncAdapterCount; i++) {
-                PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
-                if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
-                    grantRuntimePermissionsLPw(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
-                }
-            }
-
-            // Contacts
-            Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
-            contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
-            PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    contactsIntent, userId);
-            if (contactsPackage != null
-                    && doesPackageSupportRuntimePermissions(contactsPackage)) {
-                grantRuntimePermissionsLPw(contactsPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(contactsPackage, PHONE_PERMISSIONS, userId);
-            }
-
-            // Contacts provider sync adapters
-            List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
-                    contactsSyncAdapterPackages, userId);
-            final int contactsSyncAdapterCount = contactsSyncAdapters.size();
-            for (int i = 0; i < contactsSyncAdapterCount; i++) {
-                PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
-                if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
-                    grantRuntimePermissionsLPw(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
-                }
-            }
-
-            // Contacts provider
-            PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackageLPr(
-                    ContactsContract.AUTHORITY, userId);
-            if (contactsProviderPackage != null) {
-                grantRuntimePermissionsLPw(contactsProviderPackage, CONTACTS_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(contactsProviderPackage, PHONE_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Device provisioning
-            Intent deviceProvisionIntent = new Intent(
-                    DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
-            PackageParser.Package deviceProvisionPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(deviceProvisionIntent, userId);
-            if (deviceProvisionPackage != null
-                    && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
-                grantRuntimePermissionsLPw(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
-            }
-
-            // Maps
-            Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
-            mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
-            PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    mapsIntent, userId);
-            if (mapsPackage != null
-                    && doesPackageSupportRuntimePermissions(mapsPackage)) {
-                grantRuntimePermissionsLPw(mapsPackage, LOCATION_PERMISSIONS, userId);
-            }
-
-            // Gallery
-            Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
-            galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
-            PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    galleryIntent, userId);
-            if (galleryPackage != null
-                    && doesPackageSupportRuntimePermissions(galleryPackage)) {
-                grantRuntimePermissionsLPw(galleryPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Email
-            Intent emailIntent = new Intent(Intent.ACTION_MAIN);
-            emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
-            PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    emailIntent, userId);
-            if (emailPackage != null
-                    && doesPackageSupportRuntimePermissions(emailPackage)) {
-                grantRuntimePermissionsLPw(emailPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(emailPackage, CALENDAR_PERMISSIONS, userId);
-            }
-
-            // Browser
-            PackageParser.Package browserPackage = null;
-            String defaultBrowserPackage = mService.getDefaultBrowserPackageName(userId);
-            if (defaultBrowserPackage != null) {
-                browserPackage = getPackageLPr(defaultBrowserPackage);
-            }
-            if (browserPackage == null) {
-                Intent browserIntent = new Intent(Intent.ACTION_MAIN);
-                browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
-                browserPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        browserIntent, userId);
-            }
-            if (browserPackage != null
-                    && doesPackageSupportRuntimePermissions(browserPackage)) {
-                grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, userId);
-            }
-
-            // Voice interaction
-            if (voiceInteractPackageNames != null) {
-                for (String voiceInteractPackageName : voiceInteractPackageNames) {
-                    PackageParser.Package voiceInteractPackage = getSystemPackageLPr(
-                            voiceInteractPackageName);
-                    if (voiceInteractPackage != null
-                            && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                CONTACTS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                CALENDAR_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                MICROPHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                PHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                SMS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                LOCATION_PERMISSIONS, userId);
-                    }
-                }
-            }
-
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                // Allow voice search on low-ram devices
-                Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
-                PackageParser.Package globalSearchPickerPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(globalSearchIntent, userId);
-
-                if (globalSearchPickerPackage != null
-                        && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
-                    grantRuntimePermissionsLPw(globalSearchPickerPackage,
-                        MICROPHONE_PERMISSIONS, true, userId);
-                    grantRuntimePermissionsLPw(globalSearchPickerPackage,
-                        LOCATION_PERMISSIONS, true, userId);
-                }
-            }
-
-            // Voice recognition
-            Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
-            voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackageLPr(
-                    voiceRecoIntent, userId);
-            if (voiceRecoPackage != null
-                    && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
-                grantRuntimePermissionsLPw(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
-            }
-
-            // Location
-            if (locationPackageNames != null) {
-                for (String packageName : locationPackageNames) {
-                    PackageParser.Package locationPackage = getSystemPackageLPr(packageName);
-                    if (locationPackage != null
-                            && doesPackageSupportRuntimePermissions(locationPackage)) {
-                        grantRuntimePermissionsLPw(locationPackage, CONTACTS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, CALENDAR_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, MICROPHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, PHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, SMS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, LOCATION_PERMISSIONS,
-                                true, userId);
-                        grantRuntimePermissionsLPw(locationPackage, CAMERA_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, SENSORS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, STORAGE_PERMISSIONS, userId);
-                    }
-                }
-            }
-
-            // Music
-            Intent musicIntent = new Intent(Intent.ACTION_VIEW);
-            musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
-                    AUDIO_MIME_TYPE);
-            PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    musicIntent, userId);
-            if (musicPackage != null
-                    && doesPackageSupportRuntimePermissions(musicPackage)) {
-                grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Home
-            Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-            homeIntent.addCategory(Intent.CATEGORY_HOME);
-            homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
-            PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackageLPr(
-                    homeIntent, userId);
-            if (homePackage != null
-                    && doesPackageSupportRuntimePermissions(homePackage)) {
-                grantRuntimePermissionsLPw(homePackage, LOCATION_PERMISSIONS, false, userId);
-            }
-
-            // Watches
-            if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
-                // Home application on watches
-                Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
-                wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
-
-                PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackageLPr(
-                        wearHomeIntent, userId);
-
-                if (wearHomePackage != null
-                        && doesPackageSupportRuntimePermissions(wearHomePackage)) {
-                    grantRuntimePermissionsLPw(wearHomePackage, CONTACTS_PERMISSIONS, false,
-                            userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
-                            userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
-                            userId);
-                }
-
-                // Fitness tracking on watches
-                Intent trackIntent = new Intent(ACTION_TRACK);
-                PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        trackIntent, userId);
-                if (trackPackage != null
-                        && doesPackageSupportRuntimePermissions(trackPackage)) {
-                    grantRuntimePermissionsLPw(trackPackage, SENSORS_PERMISSIONS, false, userId);
-                    grantRuntimePermissionsLPw(trackPackage, LOCATION_PERMISSIONS, false, userId);
-                }
-            }
-
-            // Print Spooler
-            PackageParser.Package printSpoolerPackage = getSystemPackageLPr(
-                    PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
-            if (printSpoolerPackage != null
-                    && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
-                grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
-            }
-
-            // EmergencyInfo
-            Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
-            PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackageLPr(
-                    emergencyInfoIntent, userId);
-            if (emergencyInfoPckg != null
-                    && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
-                grantRuntimePermissionsLPw(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
-                grantRuntimePermissionsLPw(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
-            }
-
-            // NFC Tag viewer
-            Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
-            nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
-            PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackageLPr(
-                    nfcTagIntent, userId);
-            if (nfcTagPkg != null
-                    && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
-                grantRuntimePermissionsLPw(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
-                grantRuntimePermissionsLPw(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
-            }
-
-            // Storage Manager
-            Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
-            PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackageLPr(
-                    storageManagerIntent, userId);
-            if (storageManagerPckg != null
-                    && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
-                grantRuntimePermissionsLPw(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Companion devices
-            PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackageLPr(
-                    CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
-            if (companionDeviceDiscoveryPackage != null
-                    && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
-                grantRuntimePermissionsLPw(companionDeviceDiscoveryPackage,
-                        LOCATION_PERMISSIONS, true, userId);
-            }
-
-            // Ringtone Picker
-            Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
-            PackageParser.Package ringtonePickerPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(ringtonePickerIntent, userId);
-            if (ringtonePickerPackage != null
-                    && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
-                grantRuntimePermissionsLPw(ringtonePickerPackage,
-                        STORAGE_PERMISSIONS, true, userId);
-            }
-
-            mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
-            PackageParser.Package dialerPackage, int userId) {
-        if (doesPackageSupportRuntimePermissions(dialerPackage)) {
-            boolean isPhonePermFixed =
-                    mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
-            grantRuntimePermissionsLPw(
-                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSystemSmsAppLPr(
-            PackageParser.Package smsPackage, int userId) {
-        if (doesPackageSupportRuntimePermissions(smsPackage)) {
-            grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultSmsAppLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package smsPackage = getPackageLPr(packageName);
-        if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
-            grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultDialerAppLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package dialerPackage = getPackageLPr(packageName);
-        if (dialerPackage != null
-                && doesPackageSupportRuntimePermissions(dialerPackage)) {
-            grantRuntimePermissionsLPw(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSimCallManagerLPr(
-            PackageParser.Package simCallManagerPackage, int userId) {
-        Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
-        if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
-            grantRuntimePermissionsLPw(simCallManagerPackage, PHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultSimCallManagerLPr(String packageName, int userId) {
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package simCallManagerPackage = getPackageLPr(packageName);
-        if (simCallManagerPackage != null) {
-            grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToEnabledCarrierAppsLPr(String[] packageNames, int userId) {
-        Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
-        if (packageNames == null) {
-            return;
-        }
-        for (String packageName : packageNames) {
-            PackageParser.Package carrierPackage = getSystemPackageLPr(packageName);
-            if (carrierPackage != null
-                    && doesPackageSupportRuntimePermissions(carrierPackage)) {
-                grantRuntimePermissionsLPw(carrierPackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(carrierPackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(carrierPackage, SMS_PERMISSIONS, userId);
-            }
-        }
-    }
-
-    public void grantDefaultPermissionsToEnabledImsServicesLPr(String[] packageNames, int userId) {
-        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
-        if (packageNames == null) {
-            return;
-        }
-        for (String packageName : packageNames) {
-            PackageParser.Package imsServicePackage = getSystemPackageLPr(packageName);
-            if (imsServicePackage != null
-                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
-                grantRuntimePermissionsLPw(imsServicePackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, CAMERA_PERMISSIONS, userId);
-            }
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultBrowserLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default browser for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package browserPackage = getSystemPackageLPr(packageName);
-        if (browserPackage != null
-                && doesPackageSupportRuntimePermissions(browserPackage)) {
-            grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
-        }
-    }
-
-    private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
-            Intent intent, int userId) {
-        ResolveInfo handler = mService.resolveIntent(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
-        if (handler == null || handler.activityInfo == null) {
-            return null;
-        }
-        ActivityInfo activityInfo = handler.activityInfo;
-        if (activityInfo.packageName.equals(mService.mResolveActivity.packageName)
-                && activityInfo.name.equals(mService.mResolveActivity.name)) {
-            return null;
-        }
-        return getSystemPackageLPr(handler.activityInfo.packageName);
-    }
-
-    private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
-            Intent intent, int userId) {
-        List<ResolveInfo> handlers = mService.queryIntentServices(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId)
-                .getList();
-        if (handlers == null) {
-            return null;
-        }
-        final int handlerCount = handlers.size();
-        for (int i = 0; i < handlerCount; i++) {
-            ResolveInfo handler = handlers.get(i);
-            PackageParser.Package handlerPackage = getSystemPackageLPr(
-                    handler.serviceInfo.packageName);
-            if (handlerPackage != null) {
-                return handlerPackage;
-            }
-        }
-        return null;
-    }
-
-    private List<PackageParser.Package> getHeadlessSyncAdapterPackagesLPr(
-            String[] syncAdapterPackageNames, int userId) {
-        List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
-
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
-        for (String syncAdapterPackageName : syncAdapterPackageNames) {
-            homeIntent.setPackage(syncAdapterPackageName);
-
-            ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
-                    homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
-                    userId);
-            if (homeActivity != null) {
-                continue;
-            }
-
-            PackageParser.Package syncAdapterPackage = getSystemPackageLPr(syncAdapterPackageName);
-            if (syncAdapterPackage != null) {
-                syncAdapterPackages.add(syncAdapterPackage);
-            }
-        }
-
-        return syncAdapterPackages;
-    }
-
-    private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
-            String authority, int userId) {
-        ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
-        if (provider != null) {
-            return getSystemPackageLPr(provider.packageName);
-        }
-        return null;
-    }
-
-    private PackageParser.Package getPackageLPr(String packageName) {
-        return mService.mPackages.get(packageName);
-    }
-
-    private PackageParser.Package getSystemPackageLPr(String packageName) {
-        PackageParser.Package pkg = getPackageLPr(packageName);
-        if (pkg != null && pkg.isSystemApp()) {
-            return !isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg) ? pkg : null;
-        }
-        return null;
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            int userId) {
-        grantRuntimePermissionsLPw(pkg, permissions, false, false, userId);
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, int userId) {
-        grantRuntimePermissionsLPw(pkg, permissions, systemFixed, false, userId);
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
-        if (pkg.requestedPermissions.isEmpty()) {
-            return;
-        }
-
-        List<String> requestedPermissions = pkg.requestedPermissions;
-        Set<String> grantablePermissions = null;
-
-        // If this is the default Phone or SMS app we grant permissions regardless
-        // whether the version on the system image declares the permission as used since
-        // selecting the app as the default Phone or SMS the user makes a deliberate
-        // choice to grant this app the permissions needed to function. For all other
-        // apps, (default grants on first boot and user creation) we don't grant default
-        // permissions if the version on the system image does not declare them.
-        if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
-            PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-            if (sysPs != null && sysPs.pkg != null) {
-                if (sysPs.pkg.requestedPermissions.isEmpty()) {
-                    return;
-                }
-                if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) {
-                    grantablePermissions = new ArraySet<>(requestedPermissions);
-                    requestedPermissions = sysPs.pkg.requestedPermissions;
-                }
-            }
-        }
-
-        final int grantablePermissionCount = requestedPermissions.size();
-        for (int i = 0; i < grantablePermissionCount; i++) {
-            String permission = requestedPermissions.get(i);
-
-            // If there is a disabled system app it may request a permission the updated
-            // version ot the data partition doesn't, In this case skip the permission.
-            if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
-                continue;
-            }
-
-            if (permissions.contains(permission)) {
-                final int flags = mService.getPermissionFlags(permission, pkg.packageName, userId);
-
-                // If any flags are set to the permission, then it is either set in
-                // its current state by the system or device/profile owner or the user.
-                // In all these cases we do not want to clobber the current state.
-                // Unless the caller wants to override user choices. The override is
-                // to make sure we can grant the needed permission to the default
-                // sms and phone apps after the user chooses this in the UI.
-                if (flags == 0 || isDefaultPhoneOrSms) {
-                    // Never clobber policy or system.
-                    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-                    if ((flags & fixedFlags) != 0) {
-                        continue;
-                    }
-
-                    mService.grantRuntimePermission(pkg.packageName, permission, userId);
-                    if (DEBUG) {
-                        Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
-                                + permission + " to default handler " + pkg.packageName);
-                    }
-
-                    int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-                    if (systemFixed) {
-                        newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-                    }
-
-                    mService.updatePermissionFlags(permission, pkg.packageName,
-                            newFlags, newFlags, userId);
-                }
-
-                // If a component gets a permission for being the default handler A
-                // and also default handler B, we grant the weaker grant form.
-                if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
-                        && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                        && !systemFixed) {
-                    if (DEBUG) {
-                        Log.i(TAG, "Granted not fixed " + permission + " to default handler "
-                                + pkg.packageName);
-                    }
-                    mService.updatePermissionFlags(permission, pkg.packageName,
-                            PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
-                }
-            }
-        }
-    }
-
-    private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
-        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
-            return true;
-        }
-        if (!pkg.isPrivilegedApp()) {
-            return false;
-        }
-        PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-        if (sysPkg != null && sysPkg.pkg != null) {
-            if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
-                return false;
-            }
-        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
-            return false;
-        }
-        return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
-                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
-    }
-
-    private void grantDefaultPermissionExceptions(int userId) {
-        synchronized (mService.mPackages) {
-            mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-
-            if (mGrantExceptions == null) {
-                mGrantExceptions = readDefaultPermissionExceptionsLPw();
-            }
-
-            // mGrantExceptions is null only before the first read and then
-            // it serves as a cache of the default grants that should be
-            // performed for every user. If there is an entry then the app
-            // is on the system image and supports runtime permissions.
-            Set<String> permissions = null;
-            final int exceptionCount = mGrantExceptions.size();
-            for (int i = 0; i < exceptionCount; i++) {
-                String packageName = mGrantExceptions.keyAt(i);
-                PackageParser.Package pkg = getSystemPackageLPr(packageName);
-                List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
-                final int permissionGrantCount = permissionGrants.size();
-                for (int j = 0; j < permissionGrantCount; j++) {
-                    DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
-                    if (permissions == null) {
-                        permissions = new ArraySet<>();
-                    } else {
-                        permissions.clear();
-                    }
-                    permissions.add(permissionGrant.name);
-                    grantRuntimePermissionsLPw(pkg, permissions,
-                            permissionGrant.fixed, userId);
-                }
-            }
-        }
-    }
-
-    private File[] getDefaultPermissionFiles() {
-        ArrayList<File> ret = new ArrayList<File>();
-        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
-        if (dir.isDirectory() && dir.canRead()) {
-            Collections.addAll(ret, dir.listFiles());
-        }
-        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
-        if (dir.isDirectory() && dir.canRead()) {
-            Collections.addAll(ret, dir.listFiles());
-        }
-        return ret.isEmpty() ? null : ret.toArray(new File[0]);
-    }
-
-    private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
-            readDefaultPermissionExceptionsLPw() {
-        File[] files = getDefaultPermissionFiles();
-        if (files == null) {
-            return new ArrayMap<>(0);
-        }
-
-        ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
-
-        // Iterate over the files in the directory and scan .xml files
-        for (File file : files) {
-            if (!file.getPath().endsWith(".xml")) {
-                Slog.i(TAG, "Non-xml file " + file + " in " + file.getParent() + " directory, ignoring");
-                continue;
-            }
-            if (!file.canRead()) {
-                Slog.w(TAG, "Default permissions file " + file + " cannot be read");
-                continue;
-            }
-            try (
-                InputStream str = new BufferedInputStream(new FileInputStream(file))
-            ) {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(str, null);
-                parse(parser, grantExceptions);
-            } catch (XmlPullParserException | IOException e) {
-                Slog.w(TAG, "Error reading default permissions file " + file, e);
-            }
-        }
-
-        return grantExceptions;
-    }
-
-    private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
-            outGrantExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_EXCEPTIONS.equals(parser.getName())) {
-                parseExceptions(parser, outGrantExceptions);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName());
-            }
-        }
-    }
-
-    private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
-            outGrantExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_EXCEPTION.equals(parser.getName())) {
-                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-
-                List<DefaultPermissionGrant> packageExceptions =
-                        outGrantExceptions.get(packageName);
-                if (packageExceptions == null) {
-                    // The package must be on the system image
-                    PackageParser.Package pkg = getSystemPackageLPr(packageName);
-                    if (pkg == null) {
-                        Log.w(TAG, "Unknown package:" + packageName);
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-
-                    // The package must support runtime permissions
-                    if (!doesPackageSupportRuntimePermissions(pkg)) {
-                        Log.w(TAG, "Skipping non supporting runtime permissions package:"
-                                + packageName);
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    packageExceptions = new ArrayList<>();
-                    outGrantExceptions.put(packageName, packageExceptions);
-                }
-
-                parsePermission(parser, packageExceptions);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
-            }
-        }
-    }
-
-    private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
-            outPackageExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            if (TAG_PERMISSION.contains(parser.getName())) {
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                if (name == null) {
-                    Log.w(TAG, "Mandatory name attribute missing for permission tag");
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-                }
-
-                final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
-
-                DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
-                outPackageExceptions.add(exception);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
-            }
-        }
-    }
-
-    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
-        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
-    }
-
-    private static final class DefaultPermissionGrant {
-        final String name;
-        final boolean fixed;
-
-        public DefaultPermissionGrant(String name, boolean fixed) {
-            this.name = name;
-            this.fixed = fixed;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
new file mode 100644
index 0000000..7ebef83
--- /dev/null
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -0,0 +1,95 @@
+/*
+ * 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.server.pm;
+
+public final class DumpState {
+    public static final int DUMP_LIBS = 1 << 0;
+    public static final int DUMP_FEATURES = 1 << 1;
+    public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+    public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+    public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+    public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+    public static final int DUMP_PERMISSIONS = 1 << 6;
+    public static final int DUMP_PACKAGES = 1 << 7;
+    public static final int DUMP_SHARED_USERS = 1 << 8;
+    public static final int DUMP_MESSAGES = 1 << 9;
+    public static final int DUMP_PROVIDERS = 1 << 10;
+    public static final int DUMP_VERIFIERS = 1 << 11;
+    public static final int DUMP_PREFERRED = 1 << 12;
+    public static final int DUMP_PREFERRED_XML = 1 << 13;
+    public static final int DUMP_KEYSETS = 1 << 14;
+    public static final int DUMP_VERSION = 1 << 15;
+    public static final int DUMP_INSTALLS = 1 << 16;
+    public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+    public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
+    public static final int DUMP_FROZEN = 1 << 19;
+    public static final int DUMP_DEXOPT = 1 << 20;
+    public static final int DUMP_COMPILER_STATS = 1 << 21;
+    public static final int DUMP_CHANGES = 1 << 22;
+    public static final int DUMP_VOLUMES = 1 << 23;
+
+    public static final int OPTION_SHOW_FILTERS = 1 << 0;
+
+    private int mTypes;
+
+    private int mOptions;
+
+    private boolean mTitlePrinted;
+
+    private SharedUserSetting mSharedUser;
+
+    public boolean isDumping(int type) {
+        if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
+            return true;
+        }
+
+        return (mTypes & type) != 0;
+    }
+
+    public void setDump(int type) {
+        mTypes |= type;
+    }
+
+    public boolean isOptionEnabled(int option) {
+        return (mOptions & option) != 0;
+    }
+
+    public void setOptionEnabled(int option) {
+        mOptions |= option;
+    }
+
+    public boolean onTitlePrinted() {
+        final boolean printed = mTitlePrinted;
+        mTitlePrinted = true;
+        return printed;
+    }
+
+    public boolean getTitlePrinted() {
+        return mTitlePrinted;
+    }
+
+    public void setTitlePrinted(boolean enabled) {
+        mTitlePrinted = enabled;
+    }
+
+    public SharedUserSetting getSharedUser() {
+        return mSharedUser;
+    }
+
+    public void setSharedUser(SharedUserSetting user) {
+        mSharedUser = user;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 371b3ef..210eb13 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -56,6 +56,8 @@
     public static final int DEXOPT_STORAGE_CE     = 1 << 7;
     /** Indicates that the dex file passed to dexopt in on DE storage. */
     public static final int DEXOPT_STORAGE_DE     = 1 << 8;
+    /** Indicates that dexopt is invoked from the background service. */
+    public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
 
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index e1e5b35..c964f91 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -49,6 +49,8 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.permission.BasePermission;
+
 import libcore.io.IoUtils;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -878,8 +880,9 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             for (String grantedPermission : appInfo.getGrantedPermissions()) {
-                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
-                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
+                final boolean propagatePermission =
+                        mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
+                if (propagatePermission) {
                     mService.grantRuntimePermission(packageName, grantedPermission, userId);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 49d3c8b..3574466 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -565,7 +565,7 @@
     }
 
     public void dumpLPr(PrintWriter pw, String packageName,
-                        PackageManagerService.DumpState dumpState) {
+                        DumpState dumpState) {
         boolean printedHeader = false;
         for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) {
             String keySetPackage = e.getKey();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 4a5ce12..b06b583 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -30,12 +30,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
 import android.content.pm.IOnAppsChangedListener;
-import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -64,7 +62,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -89,10 +86,14 @@
     static class BroadcastCookie {
         public final UserHandle user;
         public final String packageName;
+        public final int callingUid;
+        public final int callingPid;
 
-        BroadcastCookie(UserHandle userHandle, String packageName) {
+        BroadcastCookie(UserHandle userHandle, String packageName, int callingPid, int callingUid) {
             this.user = userHandle;
             this.packageName = packageName;
+            this.callingUid = callingUid;
+            this.callingPid = callingPid;
         }
     }
 
@@ -127,6 +128,11 @@
             return getCallingUid();
         }
 
+        @VisibleForTesting
+        int injectBinderCallingPid() {
+            return getCallingPid();
+        }
+
         final int injectCallingUserId() {
             return UserHandle.getUserId(injectBinderCallingUid());
         }
@@ -166,7 +172,7 @@
                 }
                 mListeners.unregister(listener);
                 mListeners.register(listener, new BroadcastCookie(UserHandle.of(getCallingUserId()),
-                        callingPackage));
+                        callingPackage, injectBinderCallingPid(), injectBinderCallingUid()));
             }
         }
 
@@ -438,7 +444,7 @@
         private void ensureShortcutPermission(@NonNull String callingPackage) {
             verifyCallingPackage(callingPackage);
             if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
-                    callingPackage)) {
+                    callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) {
                 throw new SecurityException("Caller can't access shortcut information");
             }
         }
@@ -461,7 +467,8 @@
             return new ParceledListSlice<>((List<ShortcutInfo>)
                     mShortcutServiceInternal.getShortcuts(getCallingUserId(),
                             callingPackage, changedSince, packageName, shortcutIds,
-                            componentName, flags, targetUser.getIdentifier()));
+                            componentName, flags, targetUser.getIdentifier(),
+                            injectBinderCallingPid(), injectBinderCallingUid()));
         }
 
         @Override
@@ -514,7 +521,7 @@
         public boolean hasShortcutHostPermission(String callingPackage) {
             verifyCallingPackage(callingPackage);
             return mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
-                    callingPackage);
+                    callingPackage, injectBinderCallingPid(), injectBinderCallingUid());
         }
 
         @Override
@@ -536,7 +543,8 @@
             }
 
             final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
-                    getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId);
+                    getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId,
+                    injectBinderCallingPid(), injectBinderCallingUid());
             if (intents == null || intents.length == 0) {
                 return false;
             }
@@ -901,7 +909,8 @@
 
                         // Make sure the caller has the permission.
                         if (!mShortcutServiceInternal.hasShortcutHostPermission(
-                                launcherUserId, cookie.packageName)) {
+                                launcherUserId, cookie.packageName,
+                                cookie.callingPid, cookie.callingUid)) {
                             continue;
                         }
                         // Each launcher has a different set of pinned shortcuts, so we need to do a
@@ -914,8 +923,8 @@
                                         /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
                                         /* component= */ null,
                                         ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
-                                        | ShortcutQuery.FLAG_GET_ALL_KINDS
-                                        , userId);
+                                        | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED
+                                        , userId, cookie.callingPid, cookie.callingUid);
                         try {
                             listener.onShortcutChanged(user, packageName,
                                     new ParceledListSlice<>(list));
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 6253857..03f662a 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -310,7 +310,7 @@
                 collectingInstaller, mPackageManagerService.mInstallLock, mContext);
 
         String[] libraryDependencies = pkg.usesLibraryFiles;
-        if (pkg.isSystemApp()) {
+        if (pkg.isSystem()) {
             // For system apps, we want to avoid classpaths checks.
             libraryDependencies = NO_LIBRARIES;
         }
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 4fafe34..86a1c03 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -23,6 +23,7 @@
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Log;
@@ -53,6 +54,7 @@
 import static com.android.server.pm.Installer.DEXOPT_FORCE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
 import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
+import static com.android.server.pm.Installer.DEXOPT_IDLE_BACKGROUND_JOB;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
 
@@ -103,7 +105,17 @@
     }
 
     static boolean canOptimizePackage(PackageParser.Package pkg) {
-        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        // We do not dexopt a package with no code.
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+            return false;
+        }
+
+        // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+        if (pkg.isPrivileged()) {
+            return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+        }
+
+        return true;
     }
 
     /**
@@ -225,9 +237,10 @@
      */
     @GuardedBy("mInstallLock")
     private int dexOptPath(PackageParser.Package pkg, String path, String isa,
-            String compilerFilter, boolean profileUpdated, String sharedLibrariesPath,
+            String compilerFilter, boolean profileUpdated, String classLoaderContext,
             int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
-        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated, downgrade);
+        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
+                profileUpdated, downgrade);
         if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
             return DEX_OPT_SKIPPED;
         }
@@ -240,8 +253,8 @@
         Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path
                 + " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa
                 + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
-                + " target-filter=" + compilerFilter + " oatDir=" + oatDir
-                + " sharedLibraries=" + sharedLibrariesPath);
+                + " targetFilter=" + compilerFilter + " oatDir=" + oatDir
+                + " classLoaderContext=" + classLoaderContext);
 
         try {
             long startTime = System.currentTimeMillis();
@@ -250,7 +263,7 @@
             // installd only uses downgrade flag for secondary dex files and ignores it for
             // primary dex files.
             mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
-                    compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo,
+                    compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
                     false /* downgrade*/);
 
             if (packageStats != null) {
@@ -354,18 +367,13 @@
                 + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
                 + " target-filter=" + compilerFilter);
 
-        String classLoaderContext;
-        if (dexUseInfo.isUnknownClassLoaderContext() ||
-                dexUseInfo.isUnsupportedClassLoaderContext() ||
-                dexUseInfo.isVariableClassLoaderContext()) {
-            // If we have an unknown (not yet set), unsupported (custom class loaders), or a
-            // variable class loader chain, compile without a context and mark the oat file with
-            // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
-            // TODO(calin): We should just extract in this case.
-            classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
-        } else {
-            classLoaderContext = dexUseInfo.getClassLoaderContext();
-        }
+        // TODO(calin): b/64530081 b/66984396. Use SKIP_SHARED_LIBRARY_CHECK for the context
+        // (instead of dexUseInfo.getClassLoaderContext()) in order to compile secondary dex files
+        // in isolation (and avoid to extract/verify the main apk if it's in the class path).
+        // Note this trades correctness for performance since the resulting slow down is
+        // unacceptable in some cases until b/64530081 is fixed.
+        String classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+
         try {
             for (String isa : dexUseInfo.getLoaderIsas()) {
                 // Reuse the same dexopt path as for the primary apks. We don't need all the
@@ -425,7 +433,7 @@
             }
 
             if (useInfo.isUsedByOtherApps(path)) {
-                pw.println("used be other apps: " + useInfo.getLoadingPackages(path));
+                pw.println("used by other apps: " + useInfo.getLoadingPackages(path));
             }
 
             Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap();
@@ -441,7 +449,7 @@
                     // TODO(calin): get the status of the oat file (needs installd call)
                     pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
                     if (dexUseInfo.isUsedByOtherApps()) {
-                        pw.println("used be other apps: " + dexUseInfo.getLoadingPackages());
+                        pw.println("used by other apps: " + dexUseInfo.getLoadingPackages());
                     }
                     pw.decreaseIndent();
                 }
@@ -502,11 +510,11 @@
      * configuration (isa, compiler filter, profile).
      */
     private int getDexoptNeeded(String path, String isa, String compilerFilter,
-            boolean newProfile, boolean downgrade) {
+            String classLoaderContext, boolean newProfile, boolean downgrade) {
         int dexoptNeeded;
         try {
-            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile,
-                    downgrade);
+            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext,
+                    newProfile, downgrade);
         } catch (IOException ioe) {
             Slog.w(TAG, "IOException reading apk: " + path, ioe);
             return DEX_OPT_FAILED;
@@ -605,6 +613,9 @@
         if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
             flagsList.add("storage_de");
         }
+        if ((flags & DEXOPT_IDLE_BACKGROUND_JOB) == DEXOPT_IDLE_BACKGROUND_JOB) {
+            flagsList.add("idle_background_job");
+        }
 
         return String.join(",", flagsList);
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1fa37b9..09f9cb8 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -80,6 +80,8 @@
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -122,6 +124,7 @@
 
     private final Context mContext;
     private final PackageManagerService mPm;
+    private final PermissionManagerInternal mPermissionManager;
 
     private AppOpsManager mAppOps;
 
@@ -177,6 +180,7 @@
     public PackageInstallerService(Context context, PackageManagerService pm) {
         mContext = context;
         mPm = pm;
+        mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
 
         mInstallThread = new HandlerThread(TAG);
         mInstallThread.start();
@@ -243,35 +247,6 @@
         }
     }
 
-    public void onSecureContainersAvailable() {
-        synchronized (mSessions) {
-            final ArraySet<String> unclaimed = new ArraySet<>();
-            for (String cid : PackageHelper.getSecureContainerList()) {
-                if (isStageName(cid)) {
-                    unclaimed.add(cid);
-                }
-            }
-
-            // Ignore stages claimed by active sessions
-            for (int i = 0; i < mSessions.size(); i++) {
-                final PackageInstallerSession session = mSessions.valueAt(i);
-                final String cid = session.stageCid;
-
-                if (unclaimed.remove(cid)) {
-                    // Claimed by active session, mount it
-                    PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
-                            Process.SYSTEM_UID);
-                }
-            }
-
-            // Clean up orphaned staging containers
-            for (String cid : unclaimed) {
-                Slog.w(TAG, "Deleting orphan container " + cid);
-                PackageHelper.destroySdDir(cid);
-            }
-        }
-    }
-
     public static boolean isStageName(String name) {
         final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
         final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
@@ -426,7 +401,8 @@
     private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
             throws IOException {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
+        mPermissionManager.enforceCrossUserPermission(
+                callingUid, userId, true, true, "createSession");
 
         if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
             throw new SecurityException("User restriction prevents installing");
@@ -671,13 +647,6 @@
         return "smdl" + sessionId + ".tmp";
     }
 
-    static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException {
-        if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(),
-                Process.SYSTEM_UID, true) == null) {
-            throw new IOException("Failed to create session cid: " + stageCid);
-        }
-    }
-
     @Override
     public SessionInfo getSessionInfo(int sessionId) {
         synchronized (mSessions) {
@@ -688,7 +657,8 @@
 
     @Override
     public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "getAllSessions");
 
         final List<SessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
@@ -704,7 +674,8 @@
 
     @Override
     public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "getMySessions");
         mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
 
         final List<SessionInfo> result = new ArrayList<>();
@@ -726,7 +697,7 @@
     public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
                 IntentSender statusReceiver, int userId) throws RemoteException {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
         if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
             mAppOps.checkPackage(callingUid, callerPackageName);
         }
@@ -775,7 +746,8 @@
 
     @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "registerCallback");
         mCallbacks.register(callback, userId);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ff6e5b3..0e11cc7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -36,7 +36,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.internal.util.XmlUtils.writeUriAttribute;
-import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 
 import android.Manifest;
@@ -96,7 +95,6 @@
 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
 
 import libcore.io.IoUtils;
-import libcore.io.Libcore;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -481,12 +479,7 @@
             if (stageDir != null) {
                 mResolvedStageDir = stageDir;
             } else {
-                final String path = PackageHelper.getSdDir(stageCid);
-                if (path != null) {
-                    mResolvedStageDir = new File(path);
-                } else {
-                    throw new IOException("Failed to resolve path to container " + stageCid);
-                }
+                throw new IOException("Missing stageDir");
             }
         }
         return mResolvedStageDir;
@@ -619,7 +612,7 @@
 
             // TODO: this should delegate to DCS so the system process avoids
             // holding open FDs into containers.
-            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(),
+            final FileDescriptor targetFd = Os.open(target.getAbsolutePath(),
                     O_CREAT | O_WRONLY, 0644);
             Os.chmod(target.getAbsolutePath(), 0644);
 
@@ -631,7 +624,7 @@
             }
 
             if (offsetBytes > 0) {
-                Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
+                Os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET);
             }
 
             if (PackageInstaller.ENABLE_REVOCABLE_FD) {
@@ -667,7 +660,7 @@
                 throw new IllegalArgumentException("Invalid name: " + name);
             }
             final File target = new File(resolveStageDirLocked(), name);
-            final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
+            final FileDescriptor targetFd = Os.open(target.getAbsolutePath(), O_RDONLY, 0);
             return new ParcelFileDescriptor(targetFd);
         } catch (ErrnoException e) {
             throw e.rethrowAsIOException();
@@ -880,14 +873,6 @@
             return;
         }
 
-        if (stageCid != null) {
-            // Figure out the final installed size and resize the container once
-            // and for all. Internally the parser handles straddling between two
-            // locations when inheriting.
-            final long finalSize = calculateInstalledSize();
-            resizeContainer(stageCid, finalSize);
-        }
-
         // Inherit any packages and native libraries from existing install that
         // haven't been overridden.
         if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
@@ -924,11 +909,6 @@
         // Unpack native libraries
         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
 
-        // Container is ready to go, let's seal it up!
-        if (stageCid != null) {
-            finalizeAndFixContainer(stageCid);
-        }
-
         // We've reached point of no return; call into PMS to install the stage.
         // Regardless of success or failure we always destroy session.
         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@@ -953,7 +933,7 @@
         }
 
         mRelinquished = true;
-        mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
+        mPm.installStage(mPackageName, stageDir, localObserver, params,
                 mInstallerPackageName, mInstallerUid, user, mCertificates);
     }
 
@@ -1146,15 +1126,8 @@
 
                         mResolvedInstructionSets.add(archSubDir.getName());
                         List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
-
-                        // Only add compiled files associated with the base.
-                        // Once b/62269291 is resolved, we can add all compiled files again.
-                        for (File oatFile : oatFiles) {
-                            if (oatFile.getName().equals("base.art")
-                                    || oatFile.getName().equals("base.odex")
-                                    || oatFile.getName().equals("base.vdex")) {
-                                mResolvedInheritedFiles.add(oatFile);
-                            }
+                        if (!oatFiles.isEmpty()) {
+                            mResolvedInheritedFiles.addAll(oatFiles);
                         }
                     }
                 }
@@ -1212,11 +1185,9 @@
         // straddled between the inherited and staged APKs.
         final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
                 splitPaths.toArray(new String[splitPaths.size()]), null);
-        final boolean isForwardLocked =
-                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
         try {
-            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
+            return PackageHelper.calculateInstalledSize(pkg, params.abiOverride);
         } catch (IOException e) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Failed to calculate install size", e);
@@ -1345,52 +1316,6 @@
         }
     }
 
-    private static void resizeContainer(String cid, long targetSize)
-            throws PackageManagerException {
-        String path = PackageHelper.getSdDir(cid);
-        if (path == null) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to find mounted " + cid);
-        }
-
-        final long currentSize = new File(path).getTotalSpace();
-        if (currentSize > targetSize) {
-            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
-                    + targetSize + "; skipping resize");
-            return;
-        }
-
-        if (!PackageHelper.unMountSdDir(cid)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to unmount " + cid + " before resize");
-        }
-
-        if (!PackageHelper.resizeSdDir(targetSize, cid,
-                PackageManagerService.getEncryptKey())) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to resize " + cid + " to " + targetSize + " bytes");
-        }
-
-        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
-                Process.SYSTEM_UID, false);
-        if (path == null) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to mount " + cid + " after resize");
-        }
-    }
-
-    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
-        if (!PackageHelper.finalizeSdDir(cid)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to finalize container " + cid);
-        }
-
-        if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to fix permissions on container " + cid);
-        }
-    }
-
     void setPermissionsResult(boolean accepted) {
         if (!mSealed) {
             throw new SecurityException("Must be sealed to accept permissions");
@@ -1419,20 +1344,8 @@
             if (!mPrepared) {
                 if (stageDir != null) {
                     prepareStageDir(stageDir);
-                } else if (stageCid != null) {
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        prepareExternalStageCid(stageCid, params.sizeBytes);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-
-                    // TODO: deliver more granular progress for ASEC allocation
-                    mInternalProgress = 0.25f;
-                    computeProgressLocked(true);
                 } else {
-                    throw new IllegalArgumentException(
-                            "Exactly one of stageDir or stageCid stage must be set");
+                    throw new IllegalArgumentException("stageDir must be set");
                 }
 
                 mPrepared = true;
@@ -1534,9 +1447,6 @@
             } catch (InstallerException ignored) {
             }
         }
-        if (stageCid != null) {
-            PackageHelper.destroySdDir(stageCid);
-        }
     }
 
     void dump(IndentingPrintWriter pw) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 050d9f0..7be0cde 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -55,7 +55,6 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
 import static android.content.pm.PackageManager.INSTALL_INTERNAL;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
@@ -103,10 +102,9 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
 import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
 
 import android.Manifest;
@@ -160,10 +158,11 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageStats;
@@ -230,7 +229,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Base64;
-import android.util.TimingsTraceLog;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
@@ -244,6 +242,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.TimingsTraceLog;
 import android.util.Xml;
 import android.util.jar.StrictJarFile;
 import android.util.proto.ProtoOutputStream;
@@ -283,12 +282,19 @@
 import com.android.server.Watchdog;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PermissionsState.PermissionState;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.PermissionManagerInternal;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
@@ -338,6 +344,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -385,21 +392,21 @@
 public class PackageManagerService extends IPackageManager.Stub
         implements PackageSender {
     static final String TAG = "PackageManager";
-    static final boolean DEBUG_SETTINGS = false;
+    public static final boolean DEBUG_SETTINGS = false;
     static final boolean DEBUG_PREFERRED = false;
     static final boolean DEBUG_UPGRADE = false;
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
     private static final boolean DEBUG_BACKUP = false;
-    private static final boolean DEBUG_INSTALL = false;
-    private static final boolean DEBUG_REMOVE = false;
+    public static final boolean DEBUG_INSTALL = false;
+    public static final boolean DEBUG_REMOVE = false;
     private static final boolean DEBUG_BROADCASTS = false;
     private static final boolean DEBUG_SHOW_INFO = false;
     private static final boolean DEBUG_PACKAGE_INFO = false;
     private static final boolean DEBUG_INTENT_MATCHING = false;
-    private static final boolean DEBUG_PACKAGE_SCANNING = false;
+    public static final boolean DEBUG_PACKAGE_SCANNING = false;
     private static final boolean DEBUG_VERIFY = false;
     private static final boolean DEBUG_FILTERS = false;
-    private static final boolean DEBUG_PERMISSIONS = false;
+    public static final boolean DEBUG_PERMISSIONS = false;
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
     private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
 
@@ -427,9 +434,6 @@
     private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
     private static final int SHELL_UID = Process.SHELL_UID;
 
-    // Cap the size of permission trees that 3rd party apps can define
-    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
-
     // Suffix used during package installation when copying/moving
     // package apks to install directory.
     private static final String INSTALL_PACKAGE_SUFFIX = "-";
@@ -518,7 +522,7 @@
      */
     private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW;
 
-    static final String PLATFORM_PACKAGE_NAME = "android";
+    public static final String PLATFORM_PACKAGE_NAME = "android";
 
     static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
 
@@ -538,18 +542,6 @@
 
     private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
 
-    /** Permission grant: not grant the permission. */
-    private static final int GRANT_DENIED = 1;
-
-    /** Permission grant: grant the permission as an install permission. */
-    private static final int GRANT_INSTALL = 2;
-
-    /** Permission grant: grant the permission as a runtime one. */
-    private static final int GRANT_RUNTIME = 3;
-
-    /** Permission grant: grant as runtime a permission that was granted as an install time one. */
-    private static final int GRANT_UPGRADE = 4;
-
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -655,9 +647,6 @@
     @GuardedBy("mPackages")
     private boolean mDexOptDialogShown;
 
-    /** The location for ASEC container files on internal storage. */
-    final String mAsecInternalPath;
-
     // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
     // LOCK HELD.  Can be called with mInstallLock held.
     @GuardedBy("mInstallLock")
@@ -752,9 +741,6 @@
 
     PackageManagerInternal.ExternalSourcesPolicy mExternalSourcesPolicy;
 
-    // System configuration read by SystemConfig.
-    final int[] mGlobalGids;
-    final SparseArray<ArraySet<String>> mSystemPermissions;
     @GuardedBy("mAvailableFeatures")
     final ArrayMap<String, FeatureInfo> mAvailableFeatures;
 
@@ -863,7 +849,7 @@
                 String targetPath) {
             return getStaticOverlayPaths(targetPackageName, targetPath);
         }
-    };
+    }
 
     class ParallelPackageParserCallback extends PackageParserCallback {
         List<PackageParser.Package> mOverlayPackages = null;
@@ -937,10 +923,6 @@
     final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation =
             new ArrayMap<ComponentName, PackageParser.Instrumentation>();
 
-    // Mapping from permission names to info about them.
-    final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
-            new ArrayMap<String, PackageParser.PermissionGroup>();
-
     // Packages whose data we have transfered into another package, thus
     // should no longer exist.
     final ArraySet<String> mTransferedPackages = new ArraySet<String>();
@@ -953,9 +935,6 @@
     final SparseArray<PackageVerificationState> mPendingVerification
             = new SparseArray<PackageVerificationState>();
 
-    /** Set of packages associated with each app op permission. */
-    final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>();
-
     final PackageInstallerService mInstallerService;
 
     private final PackageDexOptimizer mPackageDexOptimizer;
@@ -1005,7 +984,9 @@
     final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
             = new SparseArray<IntentFilterVerificationState>();
 
+    // TODO remove this and go through mPermissonManager directly
     final DefaultPermissionGrantPolicy mDefaultPermissionPolicy;
+    private final PermissionManagerInternal mPermissionManager;
 
     // List of packages names to keep cached, even if they are uninstalled for all users
     private List<String> mKeepUninstalledPackages;
@@ -1016,8 +997,6 @@
 
     private File mCacheDir;
 
-    private ArraySet<String> mPrivappPermissionsViolations;
-
     private Future<?> mPrepareAppDataFuture;
 
     private static class IFVerificationParams {
@@ -1316,7 +1295,6 @@
     static final int POST_INSTALL = 9;
     static final int MCS_RECONNECT = 10;
     static final int MCS_GIVE_UP = 11;
-    static final int UPDATED_MEDIA_STATUS = 12;
     static final int WRITE_SETTINGS = 13;
     static final int WRITE_PACKAGE_RESTRICTIONS = 14;
     static final int PACKAGE_VERIFIED = 15;
@@ -1406,8 +1384,6 @@
     final @NonNull String mServicesSystemSharedLibraryPackageName;
     final @NonNull String mSharedSystemSharedLibraryPackageName;
 
-    final boolean mPermissionReviewRequired;
-
     private final PackageUsage mPackageUsage = new PackageUsage();
     private final CompilerStats mCompilerStats = new CompilerStats();
 
@@ -1715,32 +1691,6 @@
 
                     Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
                 } break;
-                case UPDATED_MEDIA_STATUS: {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "Got message UPDATED_MEDIA_STATUS");
-                    boolean reportStatus = msg.arg1 == 1;
-                    boolean doGc = msg.arg2 == 1;
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "reportStatus=" + reportStatus + ", doGc = " + doGc);
-                    if (doGc) {
-                        // Force a gc to clear up stale containers.
-                        Runtime.getRuntime().gc();
-                    }
-                    if (msg.obj != null) {
-                        @SuppressWarnings("unchecked")
-                        Set<AsecInstallArgs> args = (Set<AsecInstallArgs>) msg.obj;
-                        if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers");
-                        // Unload containers
-                        unloadAllContainers(args);
-                    }
-                    if (reportStatus) {
-                        try {
-                            if (DEBUG_SD_INSTALL) Log.i(TAG,
-                                    "Invoking StorageManagerService call back");
-                            PackageHelper.getStorageManager().finishMediaUpdate();
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "StorageManagerService not running?");
-                        }
-                    }
-                } break;
                 case WRITE_SETTINGS: {
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
@@ -1911,6 +1861,71 @@
         }
     }
 
+    private PermissionCallback mPermissionCallback = new PermissionCallback() {
+        @Override
+        public void onGidsChanged(int appId, int userId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
+                }
+            });
+        }
+        @Override
+        public void onPermissionGranted(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Not critical; if this is lost, the application has to request again.
+            synchronized (mPackages) {
+                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            }
+        }
+        @Override
+        public void onInstallPermissionGranted() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionRevoked(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            synchronized (mPackages) {
+                // Critical; after this call the application should never have the permission
+                mSettings.writeRuntimePermissionsForUserLPr(userId, true);
+            }
+
+            final int appId = UserHandle.getAppId(uid);
+            killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+        @Override
+        public void onInstallPermissionRevoked() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
+            synchronized (mPackages) {
+                for (int userId : updatedUserIds) {
+                    mSettings.writeRuntimePermissionsForUserLPr(userId, sync);
+                }
+            }
+        }
+        @Override
+        public void onInstallPermissionUpdated() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionRemoved() {
+            synchronized (mPackages) {
+                mSettings.writeLPr();
+            }
+        }
+    };
+
     private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
             boolean killApp, boolean virtualPreload, String[] grantedPermissions,
             boolean launchedForRestore, String installerPackage,
@@ -1927,7 +1942,10 @@
             // review flag which is used to emulate runtime permissions for
             // legacy apps.
             if (grantPermissions) {
-                grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);
+                final int callingUid = Binder.getCallingUid();
+                mPermissionManager.grantRequestedRuntimePermissions(
+                        res.pkg, res.newUsers, grantedPermissions, callingUid,
+                        mPermissionCallback);
             }
 
             final boolean update = res.removedInfo != null
@@ -1943,9 +1961,9 @@
             // app that had no children, we grant requested runtime permissions to the new
             // children if the parent on the system image had them already granted.
             if (res.pkg.parentPackage != null) {
-                synchronized (mPackages) {
-                    grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg);
-                }
+                final int callingUid = Binder.getCallingUid();
+                mPermissionManager.grantRuntimePermissionsGrantedToDisabledPackage(
+                        res.pkg, callingUid, mPermissionCallback);
             }
 
             synchronized (mPackages) {
@@ -2110,39 +2128,6 @@
         }
     }
 
-    private void grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(
-            PackageParser.Package pkg) {
-        if (pkg.parentPackage == null) {
-            return;
-        }
-        if (pkg.requestedPermissions == null) {
-            return;
-        }
-        final PackageSetting disabledSysParentPs = mSettings
-                .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
-        if (disabledSysParentPs == null || disabledSysParentPs.pkg == null
-                || !disabledSysParentPs.isPrivileged()
-                || (disabledSysParentPs.childPackageNames != null
-                        && !disabledSysParentPs.childPackageNames.isEmpty())) {
-            return;
-        }
-        final int[] allUserIds = sUserManager.getUserIds();
-        final int permCount = pkg.requestedPermissions.size();
-        for (int i = 0; i < permCount; i++) {
-            String permission = pkg.requestedPermissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(permission);
-            if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
-                continue;
-            }
-            for (int userId : allUserIds) {
-                if (disabledSysParentPs.getPermissionsState().hasRuntimePermission(
-                        permission, userId)) {
-                    grantRuntimePermission(pkg.packageName, permission, userId);
-                }
-            }
-        }
-    }
-
     private StorageEventListener mStorageListener = new StorageEventListener() {
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -2165,14 +2150,6 @@
                     unloadPrivatePackages(vol);
                 }
             }
-
-            if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) {
-                if (vol.state == VolumeInfo.STATE_MOUNTED) {
-                    updateExternalMediaStatus(true, false);
-                } else if (vol.state == VolumeInfo.STATE_EJECTING) {
-                    updateExternalMediaStatus(false, false);
-                }
-            }
         }
 
         @Override
@@ -2203,58 +2180,6 @@
         }
     };
 
-    private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
-            String[] grantedPermissions) {
-        for (int userId : userIds) {
-            grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions);
-        }
-    }
-
-    private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
-            String[] grantedPermissions) {
-        PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-
-        PermissionsState permissionsState = ps.getPermissionsState();
-
-        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-
-        final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
-                >= Build.VERSION_CODES.M;
-
-        final boolean instantApp = isInstantApp(pkg.packageName, userId);
-
-        for (String permission : pkg.requestedPermissions) {
-            final BasePermission bp;
-            synchronized (mPackages) {
-                bp = mSettings.mPermissions.get(permission);
-            }
-            if (bp != null && (bp.isRuntime() || bp.isDevelopment())
-                    && (!instantApp || bp.isInstant())
-                    && (supportsRuntimePermissions || !bp.isRuntimeOnly())
-                    && (grantedPermissions == null
-                           || ArrayUtils.contains(grantedPermissions, permission))) {
-                final int flags = permissionsState.getPermissionFlags(permission, userId);
-                if (supportsRuntimePermissions) {
-                    // Installer cannot change immutable permissions.
-                    if ((flags & immutableFlags) == 0) {
-                        grantRuntimePermission(pkg.packageName, permission, userId);
-                    }
-                } else if (mPermissionReviewRequired) {
-                    // In permission review mode we clear the review flag when we
-                    // are asked to install the app with all permissions granted.
-                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                        updatePermissionFlags(permission, pkg.packageName,
-                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
-                    }
-                }
-            }
-        }
-    }
-
     Bundle extrasForInstallResult(PackageInstalledInfo res) {
         Bundle extras = null;
         switch (res.returnCode) {
@@ -2417,13 +2342,32 @@
 
         mContext = context;
 
-        mPermissionReviewRequired = context.getResources().getBoolean(
-                R.bool.config_permissionReviewRequired);
-
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
-        mSettings = new Settings(mPackages);
+        mInstaller = installer;
+
+        // Create sub-components that provide services / data. Order here is important.
+        synchronized (mInstallLock) {
+        synchronized (mPackages) {
+            // Expose private service for system components to use.
+            LocalServices.addService(
+                    PackageManagerInternal.class, new PackageManagerInternalImpl());
+            sUserManager = new UserManagerService(context, this,
+                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
+            mPermissionManager = PermissionManagerService.create(context,
+                    new DefaultPermissionGrantedCallback() {
+                        @Override
+                        public void onDefaultRuntimePermissionsGranted(int userId) {
+                            synchronized(mPackages) {
+                                mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
+                            }
+                        }
+                    }, mPackages /*externalLock*/);
+            mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
+            mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);
+        }
+        }
         mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
         mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
@@ -2454,7 +2398,6 @@
             mSeparateProcesses = null;
         }
 
-        mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
         mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
@@ -2467,8 +2410,6 @@
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "get system config");
         SystemConfig systemConfig = SystemConfig.getInstance();
-        mGlobalGids = systemConfig.getGlobalGids();
-        mSystemPermissions = systemConfig.getSystemPermissions();
         mAvailableFeatures = systemConfig.getAvailableFeatures();
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
@@ -2483,32 +2424,12 @@
             mHandler = new PackageHandler(mHandlerThread.getLooper());
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-
-            mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
             mInstantAppRegistry = new InstantAppRegistry(this);
 
             File dataDir = Environment.getDataDirectory();
             mAppInstallDir = new File(dataDir, "app");
             mAppLib32InstallDir = new File(dataDir, "app-lib");
-            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
-            sUserManager = new UserManagerService(context, this,
-                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
-
-            // Propagate permission configuration in to package manager.
-            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
-                    = systemConfig.getPermissions();
-            for (int i=0; i<permConfig.size(); i++) {
-                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
-                BasePermission bp = mSettings.mPermissions.get(perm.name);
-                if (bp == null) {
-                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
-                    mSettings.mPermissions.put(perm.name, bp);
-                }
-                if (perm.gids != null) {
-                    bp.setGids(perm.gids, perm.perUser);
-                }
-            }
 
             ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
             final int builtInLibCount = libConfig.size();
@@ -2925,13 +2846,14 @@
             // cases get permissions that the user didn't initially explicitly
             // allow...  it would be nice to have some better way to handle
             // this situation.
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
+            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+            if (sdkUpdated) {
                 Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for internal storage");
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL, updateFlags);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, sdkUpdated, mPackages.values(),
+                    mPermissionCallback);
             ver.sdkVersion = mSdkVersion;
 
             // If this is the first boot or an update from pre-M, and it is a normal
@@ -3111,8 +3033,6 @@
         // once we have a booted system.
         mInstaller.setWarnIfHeld(mPackages);
 
-        // Expose private service for system components to use.
-        LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
@@ -3315,6 +3235,7 @@
             removeCodePathLI(dstCodePath);
             return null;
         }
+
         return dstCodePath;
     }
 
@@ -3402,7 +3323,9 @@
     @Override
     public boolean isUpgrade() {
         // allow instant applications
-        return mIsUpgrade;
+        // The system property allows testing ota flow when upgraded to the same image.
+        return mIsUpgrade || SystemProperties.getBoolean(
+                "persist.pm.mock-upgrade", false /* default */);
     }
 
     private @Nullable String getRequiredButNotReallyRequiredVerifierLPr() {
@@ -3649,7 +3572,7 @@
         for (String packageName : packages) {
             PackageParser.Package pkg = mPackages.get(packageName);
             if (pkg != null) {
-                if (!pkg.isSystemApp()) {
+                if (!pkg.isSystem()) {
                     Slog.w(TAG, "Non-system app '" + packageName + "' in sysconfig <app-link>");
                     continue;
                 }
@@ -3796,19 +3719,16 @@
      * <p>
      * Currently, there are three cases in which this can occur:
      * <ol>
-     * <li>The calling application is a "special" process. The special
-     *     processes are {@link Process#SYSTEM_UID}, {@link Process#SHELL_UID}
-     *     and {@code 0}</li>
+     * <li>The calling application is a "special" process. Special processes
+     *     are those with a UID < {@link Process#FIRST_APPLICATION_UID}.</li>
      * <li>The calling application has the permission
-     *     {@link android.Manifest.permission#ACCESS_INSTANT_APPS}</li>
+     *     {@link android.Manifest.permission#ACCESS_INSTANT_APPS}.</li>
      * <li>The calling application is the default launcher on the
      *     system partition.</li>
      * </ol>
      */
     private boolean canViewInstantApps(int callingUid, int userId) {
-        if (callingUid == Process.SYSTEM_UID
-                || callingUid == Process.SHELL_UID
-                || callingUid == Process.ROOT_UID) {
+        if (callingUid < Process.FIRST_APPLICATION_UID) {
             return true;
         }
         if (mContext.checkCallingOrSelfPermission(
@@ -3910,7 +3830,7 @@
     public boolean isPackageAvailable(String packageName, int userId) {
         if (!sUserManager.exists(userId)) return false;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "is package available");
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(packageName);
@@ -3953,7 +3873,7 @@
             int flags, int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get package info");
 
         // reader
@@ -4215,7 +4135,7 @@
         if (!sUserManager.exists(userId)) return -1;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getPackageUid");
 
         // reader
@@ -4245,7 +4165,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getPackageGids");
 
         // reader
@@ -4272,145 +4192,30 @@
         return null;
     }
 
-    static PermissionInfo generatePermissionInfo(BasePermission bp, int flags) {
-        if (bp.perm != null) {
-            return PackageParser.generatePermissionInfo(bp.perm, flags);
-        }
-        PermissionInfo pi = new PermissionInfo();
-        pi.name = bp.name;
-        pi.packageName = bp.sourcePackage;
-        pi.nonLocalizedLabel = bp.name;
-        pi.protectionLevel = bp.protectionLevel;
-        return pi;
-    }
-
     @Override
     public PermissionInfo getPermissionInfo(String name, String packageName, int flags) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return null;
-        }
-        // reader
-        synchronized (mPackages) {
-            final BasePermission p = mSettings.mPermissions.get(name);
-            if (p == null) {
-                return null;
-            }
-            // If the caller is an app that targets pre 26 SDK drop protection flags.
-            PermissionInfo permissionInfo = generatePermissionInfo(p, flags);
-            if (permissionInfo != null) {
-                final int protectionLevel = adjustPermissionProtectionFlagsLPr(
-                        permissionInfo.protectionLevel, packageName, callingUid);
-                if (permissionInfo.protectionLevel != protectionLevel) {
-                    // If we return different protection level, don't use the cached info
-                    if (p.perm != null && p.perm.info == permissionInfo) {
-                        permissionInfo = new PermissionInfo(permissionInfo);
-                    }
-                    permissionInfo.protectionLevel = protectionLevel;
-                }
-            }
-            return permissionInfo;
-        }
-    }
-
-    private int adjustPermissionProtectionFlagsLPr(int protectionLevel,
-            String packageName, int uid) {
-        // Signature permission flags area always reported
-        final int protectionLevelMasked = protectionLevel
-                & (PermissionInfo.PROTECTION_NORMAL
-                | PermissionInfo.PROTECTION_DANGEROUS
-                | PermissionInfo.PROTECTION_SIGNATURE);
-        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
-            return protectionLevel;
-        }
-
-        // System sees all flags.
-        final int appId = UserHandle.getAppId(uid);
-        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
-                || appId == Process.SHELL_UID) {
-            return protectionLevel;
-        }
-
-        // Normalize package name to handle renamed packages and static libs
-        packageName = resolveInternalPackageNameLPr(packageName,
-                PackageManager.VERSION_CODE_HIGHEST);
-
-        // Apps that target O see flags for all protection levels.
-        final PackageSetting ps = mSettings.mPackages.get(packageName);
-        if (ps == null) {
-            return protectionLevel;
-        }
-        if (ps.appId != appId) {
-            return protectionLevel;
-        }
-
-        final PackageParser.Package pkg = mPackages.get(packageName);
-        if (pkg == null) {
-            return protectionLevel;
-        }
-        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
-            return protectionLevelMasked;
-        }
-
-        return protectionLevel;
+        return mPermissionManager.getPermissionInfo(name, packageName, flags, getCallingUid());
     }
 
     @Override
-    public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String group,
+    public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
             int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        // reader
-        synchronized (mPackages) {
-            if (group != null && !mPermissionGroups.containsKey(group)) {
-                // This is thrown as NameNotFoundException
-                return null;
-            }
-
-            ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
-            for (BasePermission p : mSettings.mPermissions.values()) {
-                if (group == null) {
-                    if (p.perm == null || p.perm.info.group == null) {
-                        out.add(generatePermissionInfo(p, flags));
-                    }
-                } else {
-                    if (p.perm != null && group.equals(p.perm.info.group)) {
-                        out.add(PackageParser.generatePermissionInfo(p.perm, flags));
-                    }
-                }
-            }
-            return new ParceledListSlice<>(out);
-        }
+        final List<PermissionInfo> permissionList =
+                mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid());
+        return (permissionList == null) ? null : new ParceledListSlice<>(permissionList);
     }
 
     @Override
-    public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        // reader
-        synchronized (mPackages) {
-            return PackageParser.generatePermissionGroupInfo(
-                    mPermissionGroups.get(name), flags);
-        }
+    public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+        return mPermissionManager.getPermissionGroupInfo(groupName, flags, getCallingUid());
     }
 
     @Override
     public @NonNull ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return ParceledListSlice.emptyList();
-        }
-        // reader
-        synchronized (mPackages) {
-            final int N = mPermissionGroups.size();
-            ArrayList<PermissionGroupInfo> out
-                    = new ArrayList<PermissionGroupInfo>(N);
-            for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) {
-                out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
-            }
-            return new ParceledListSlice<>(out);
-        }
+        final List<PermissionGroupInfo> permissionList =
+                mPermissionManager.getAllPermissionGroups(flags, getCallingUid());
+        return (permissionList == null)
+                ? ParceledListSlice.emptyList() : new ParceledListSlice<>(permissionList);
     }
 
     private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
@@ -4456,7 +4261,7 @@
             int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForApplication(flags, userId, packageName);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get application info");
 
         // writer
@@ -4765,7 +4570,8 @@
             triaged = false;
         }
         if ((flags & PackageManager.MATCH_ANY_USER) != 0) {
-            enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+            mPermissionManager.enforceCrossUserPermission(
+                    Binder.getCallingUid(), userId, false, false,
                     "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission at "
                     + Debug.getCallers(5));
         } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 && isCallerSystemUser
@@ -4894,7 +4700,7 @@
             int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get activity info");
         synchronized (mPackages) {
             PackageParser.Activity a = mActivities.mActivities.get(component);
@@ -4953,7 +4759,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get receiver info");
         synchronized (mPackages) {
             PackageParser.Activity a = mReceivers.mActivities.get(component);
@@ -5090,7 +4896,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get service info");
         synchronized (mPackages) {
             PackageParser.Service s = mServices.mServices.get(component);
@@ -5114,7 +4920,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get provider info");
         synchronized (mPackages) {
             PackageParser.Provider p = mProviders.mProviders.get(component);
@@ -5277,97 +5083,12 @@
 
     @Override
     public int checkPermission(String permName, String pkgName, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        synchronized (mPackages) {
-            final PackageParser.Package p = mPackages.get(pkgName);
-            if (p != null && p.mExtras != null) {
-                final PackageSetting ps = (PackageSetting) p.mExtras;
-                if (filterAppAccessLPr(ps, callingUid, userId)) {
-                    return PackageManager.PERMISSION_DENIED;
-                }
-                final boolean instantApp = ps.getInstantApp(userId);
-                final PermissionsState permissionsState = ps.getPermissionsState();
-                if (permissionsState.hasPermission(permName, userId)) {
-                    if (instantApp) {
-                        BasePermission bp = mSettings.mPermissions.get(permName);
-                        if (bp != null && bp.isInstant()) {
-                            return PackageManager.PERMISSION_GRANTED;
-                        }
-                    } else {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
-                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
-                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
-                    return PackageManager.PERMISSION_GRANTED;
-                }
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
+        return mPermissionManager.checkPermission(permName, pkgName, getCallingUid(), userId);
     }
 
     @Override
     public int checkUidPermission(String permName, int uid) {
-        final int callingUid = Binder.getCallingUid();
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
-        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
-        final int userId = UserHandle.getUserId(uid);
-        if (!sUserManager.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
-
-        synchronized (mPackages) {
-            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
-            if (obj != null) {
-                if (obj instanceof SharedUserSetting) {
-                    if (isCallerInstantApp) {
-                        return PackageManager.PERMISSION_DENIED;
-                    }
-                } else if (obj instanceof PackageSetting) {
-                    final PackageSetting ps = (PackageSetting) obj;
-                    if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
-                        return PackageManager.PERMISSION_DENIED;
-                    }
-                }
-                final SettingBase settingBase = (SettingBase) obj;
-                final PermissionsState permissionsState = settingBase.getPermissionsState();
-                if (permissionsState.hasPermission(permName, userId)) {
-                    if (isUidInstantApp) {
-                        BasePermission bp = mSettings.mPermissions.get(permName);
-                        if (bp != null && bp.isInstant()) {
-                            return PackageManager.PERMISSION_GRANTED;
-                        }
-                    } else {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
-                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
-                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
-                    return PackageManager.PERMISSION_GRANTED;
-                }
-            } else {
-                ArraySet<String> perms = mSystemPermissions.get(uid);
-                if (perms != null) {
-                    if (perms.contains(permName)) {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
-                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
+        return mPermissionManager.checkUidPermission(permName, uid, getCallingUid());
     }
 
     @Override
@@ -5410,448 +5131,49 @@
         }
     }
 
-    /**
-     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
-     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
-     * @param checkShell whether to prevent shell from access if there's a debugging restriction
-     * @param message the message to log on security exception
-     */
-    void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
-            boolean checkShell, String message) {
-        if (userId < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userId);
-        }
-        if (checkShell) {
-            enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-        }
-        if (userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
-        }
-    }
-
-    void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
-        if (callingUid == Process.SHELL_UID) {
-            if (userHandle >= 0
-                    && sUserManager.hasUserRestriction(restriction, userHandle)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
-            } else if (userHandle < 0) {
-                Slog.e(TAG, "Unable to check shell permission for user " + userHandle + "\n\t"
-                        + Debug.getCallers(3));
-            }
-        }
-    }
-
-    private BasePermission findPermissionTreeLP(String permName) {
-        for(BasePermission bp : mSettings.mPermissionTrees.values()) {
-            if (permName.startsWith(bp.name) &&
-                    permName.length() > bp.name.length() &&
-                    permName.charAt(bp.name.length()) == '.') {
-                return bp;
-            }
-        }
-        return null;
-    }
-
-    private BasePermission checkPermissionTreeLP(String permName) {
-        if (permName != null) {
-            BasePermission bp = findPermissionTreeLP(permName);
-            if (bp != null) {
-                if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) {
-                    return bp;
-                }
-                throw new SecurityException("Calling uid "
-                        + Binder.getCallingUid()
-                        + " is not allowed to add to permission tree "
-                        + bp.name + " owned by uid " + bp.uid);
-            }
-        }
-        throw new SecurityException("No permission tree found for " + permName);
-    }
-
-    static boolean compareStrings(CharSequence s1, CharSequence s2) {
-        if (s1 == null) {
-            return s2 == null;
-        }
-        if (s2 == null) {
-            return false;
-        }
-        if (s1.getClass() != s2.getClass()) {
-            return false;
-        }
-        return s1.equals(s2);
-    }
-
-    static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
-        if (pi1.icon != pi2.icon) return false;
-        if (pi1.logo != pi2.logo) return false;
-        if (pi1.protectionLevel != pi2.protectionLevel) return false;
-        if (!compareStrings(pi1.name, pi2.name)) return false;
-        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
-        // We'll take care of setting this one.
-        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
-        // These are not currently stored in settings.
-        //if (!compareStrings(pi1.group, pi2.group)) return false;
-        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
-        //if (pi1.labelRes != pi2.labelRes) return false;
-        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
-        return true;
-    }
-
-    int permissionInfoFootprint(PermissionInfo info) {
-        int size = info.name.length();
-        if (info.nonLocalizedLabel != null) size += info.nonLocalizedLabel.length();
-        if (info.nonLocalizedDescription != null) size += info.nonLocalizedDescription.length();
-        return size;
-    }
-
-    int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
-        int size = 0;
-        for (BasePermission perm : mSettings.mPermissions.values()) {
-            if (perm.uid == tree.uid) {
-                size += perm.name.length() + permissionInfoFootprint(perm.perm.info);
-            }
-        }
-        return size;
-    }
-
-    void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
-        // We calculate the max size of permissions defined by this uid and throw
-        // if that plus the size of 'info' would exceed our stated maximum.
-        if (tree.uid != Process.SYSTEM_UID) {
-            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
-            if (curTreeSize + permissionInfoFootprint(info) > MAX_PERMISSION_TREE_FOOTPRINT) {
-                throw new SecurityException("Permission tree size cap exceeded");
-            }
-        }
-    }
-
-    boolean addPermissionLocked(PermissionInfo info, boolean async) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant apps can't add permissions");
-        }
-        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
-            throw new SecurityException("Label must be specified in permission");
-        }
-        BasePermission tree = checkPermissionTreeLP(info.name);
-        BasePermission bp = mSettings.mPermissions.get(info.name);
-        boolean added = bp == null;
-        boolean changed = true;
-        int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
-        if (added) {
-            enforcePermissionCapLocked(info, tree);
-            bp = new BasePermission(info.name, tree.sourcePackage,
-                    BasePermission.TYPE_DYNAMIC);
-        } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
-            throw new SecurityException(
-                    "Not allowed to modify non-dynamic permission "
-                    + info.name);
-        } else {
-            if (bp.protectionLevel == fixedLevel
-                    && bp.perm.owner.equals(tree.perm.owner)
-                    && bp.uid == tree.uid
-                    && comparePermissionInfos(bp.perm.info, info)) {
-                changed = false;
-            }
-        }
-        bp.protectionLevel = fixedLevel;
-        info = new PermissionInfo(info);
-        info.protectionLevel = fixedLevel;
-        bp.perm = new PackageParser.Permission(tree.perm.owner, info);
-        bp.perm.info.packageName = tree.perm.info.packageName;
-        bp.uid = tree.uid;
-        if (added) {
-            mSettings.mPermissions.put(info.name, bp);
-        }
-        if (changed) {
-            if (!async) {
-                mSettings.writeLPr();
-            } else {
-                scheduleWriteSettingsLocked();
-            }
-        }
-        return added;
+    private boolean addDynamicPermission(PermissionInfo info, final boolean async) {
+        return mPermissionManager.addDynamicPermission(
+                info, async, getCallingUid(), new PermissionCallback() {
+                    @Override
+                    public void onPermissionChanged() {
+                        if (!async) {
+                            mSettings.writeLPr();
+                        } else {
+                            scheduleWriteSettingsLocked();
+                        }
+                    }
+                });
     }
 
     @Override
     public boolean addPermission(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermissionLocked(info, false);
+            return addDynamicPermission(info, false);
         }
     }
 
     @Override
     public boolean addPermissionAsync(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermissionLocked(info, true);
+            return addDynamicPermission(info, true);
         }
     }
 
     @Override
-    public void removePermission(String name) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant applications don't have access to this method");
-        }
-        synchronized (mPackages) {
-            checkPermissionTreeLP(name);
-            BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp != null) {
-                if (bp.type != BasePermission.TYPE_DYNAMIC) {
-                    throw new SecurityException(
-                            "Not allowed to modify non-dynamic permission "
-                            + name);
-                }
-                mSettings.mPermissions.remove(name);
-                mSettings.writeLPr();
-            }
-        }
-    }
-
-    private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(
-            PackageParser.Package pkg, BasePermission bp) {
-        int index = pkg.requestedPermissions.indexOf(bp.name);
-        if (index == -1) {
-            throw new SecurityException("Package " + pkg.packageName
-                    + " has not requested permission " + bp.name);
-        }
-        if (!bp.isRuntime() && !bp.isDevelopment()) {
-            throw new SecurityException("Permission " + bp.name
-                    + " is not a changeable permission type");
-        }
+    public void removePermission(String permName) {
+        mPermissionManager.removeDynamicPermission(permName, getCallingUid(), mPermissionCallback);
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String name, final int userId) {
-        grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
-    }
-
-    private void grantRuntimePermission(String packageName, String name, final int userId,
-            boolean overridePolicy) {
-        if (!sUserManager.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
-                "grantRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "grantRuntimePermission");
-
-        final int uid;
-        final PackageSetting ps;
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-            ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (mPermissionReviewRequired
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                    && bp.isRuntime()) {
-                return;
-            }
-
-            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
-
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
-            final int flags = permissionsState.getPermissionFlags(name, userId);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                throw new SecurityException("Cannot grant system fixed permission "
-                        + name + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot grant policy fixed permission "
-                        + name + " for package " + packageName);
-            }
-
-            if (bp.isDevelopment()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                if (permissionsState.grantInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    scheduleWriteSettingsLocked();
-                }
-                return;
-            }
-
-            if (ps.getInstantApp(userId) && !bp.isInstant()) {
-                throw new SecurityException("Cannot grant non-ephemeral permission"
-                        + name + " for package " + packageName);
-            }
-
-            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
-                return;
-            }
-
-            final int result = permissionsState.grantRuntimePermission(bp, userId);
-            switch (result) {
-                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
-                    return;
-                }
-
-                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
-                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
-                        }
-                    });
-                }
-                break;
-            }
-
-            if (bp.isRuntime()) {
-                logPermissionGranted(mContext, name, packageName);
-            }
-
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            // Not critical if that is lost - app has to request again.
-            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-        }
-
-        // Only need to do this if user is initialized. Otherwise it's a new user
-        // and there are no processes running as the user yet and there's no need
-        // to make an expensive call to remount processes for the changed permissions.
-        if (READ_EXTERNAL_STORAGE.equals(name)
-                || WRITE_EXTERNAL_STORAGE.equals(name)) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (sUserManager.isInitialized(userId)) {
-                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
-                            StorageManagerInternal.class);
-                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
+    public void grantRuntimePermission(String packageName, String permName, final int userId) {
+        mPermissionManager.grantRuntimePermission(permName, packageName, false /*overridePolicy*/,
+                getCallingUid(), userId, mPermissionCallback);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String name, int userId) {
-        revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
-    }
-
-    private void revokeRuntimePermission(String packageName, String name, int userId,
-            boolean overridePolicy) {
-        if (!sUserManager.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
-                "revokeRuntimePermission");
-
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "revokeRuntimePermission");
-
-        final int appId;
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-
-            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (mPermissionReviewRequired
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                    && bp.isRuntime()) {
-                return;
-            }
-
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
-            final int flags = permissionsState.getPermissionFlags(name, userId);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke system fixed permission "
-                        + name + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke policy fixed permission "
-                        + name + " for package " + packageName);
-            }
-
-            if (bp.isDevelopment()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                if (permissionsState.revokeInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    scheduleWriteSettingsLocked();
-                }
-                return;
-            }
-
-            if (permissionsState.revokeRuntimePermission(bp, userId) ==
-                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                return;
-            }
-
-            if (bp.isRuntime()) {
-                logPermissionRevoked(mContext, name, packageName);
-            }
-
-            mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
-
-            // Critical, after this call app should never have the permission.
-            mSettings.writeRuntimePermissionsForUserLPr(userId, true);
-
-            appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-        }
-
-        killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+    public void revokeRuntimePermission(String packageName, String permName, int userId) {
+        mPermissionManager.revokeRuntimePermission(permName, packageName, false /*overridePolicy*/,
+                getCallingUid(), userId, mPermissionCallback);
     }
 
     /**
@@ -5928,7 +5250,9 @@
         }
 
         synchronized (mPackages) {
-            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+                    mPermissionCallback);
             for (int userId : UserManagerService.getInstance().getUserIds()) {
                 final int packageCount = mPackages.size();
                 for (int i = 0; i < packageCount; i++) {
@@ -5944,91 +5268,17 @@
     }
 
     @Override
-    public int getPermissionFlags(String name, String packageName, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return 0;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, false /* checkShell */,
-                "getPermissionFlags");
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                return 0;
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                return 0;
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                return 0;
-            }
-            PermissionsState permissionsState = ps.getPermissionsState();
-            return permissionsState.getPermissionFlags(name, userId);
-        }
+    public int getPermissionFlags(String permName, String packageName, int userId) {
+        return mPermissionManager.getPermissionFlags(
+                permName, packageName, getCallingUid(), userId);
     }
 
     @Override
-    public void updatePermissionFlags(String name, String packageName, int flagMask,
+    public void updatePermissionFlags(String permName, String packageName, int flagMask,
             int flagValues, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "updatePermissionFlags");
-
-        // Only the system can change these flags and nothing else.
-        if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-        }
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-
-            PermissionsState permissionsState = ps.getPermissionsState();
-
-            boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
-
-            if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) {
-                // Install and runtime permissions are stored in different places,
-                // so figure out what permission changed and persist the change.
-                if (permissionsState.getInstallPermissionState(name) != null) {
-                    scheduleWriteSettingsLocked();
-                } else if (permissionsState.getRuntimePermissionState(name, userId) != null
-                        || hadState) {
-                    mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-                }
-            }
-        }
+        mPermissionManager.updatePermissionFlags(
+                permName, packageName, flagMask, flagValues, getCallingUid(), userId,
+                mPermissionCallback);
     }
 
     /**
@@ -6037,52 +5287,16 @@
      */
     @Override
     public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlagsForAllApps");
-
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "updatePermissionFlagsForAllApps");
-
-        // Only the system can change system fixed flags.
-        if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-        }
-
         synchronized (mPackages) {
-            boolean changed = false;
-            final int packageCount = mPackages.size();
-            for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
-                final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
-                final PackageSetting ps = (PackageSetting) pkg.mExtras;
-                if (ps == null) {
-                    continue;
-                }
-                PermissionsState permissionsState = ps.getPermissionsState();
-                changed |= permissionsState.updatePermissionFlagsForAllPermissions(
-                        userId, flagMask, flagValues);
-            }
+            final boolean changed = mPermissionManager.updatePermissionFlagsForAllApps(
+                    flagMask, flagValues, getCallingUid(), userId, mPackages.values(),
+                    mPermissionCallback);
             if (changed) {
                 mSettings.writeRuntimePermissionsForUserLPr(userId, false);
             }
         }
     }
 
-    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED
-            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(message + " requires "
-                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
-                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
-        }
-    }
-
     @Override
     public boolean shouldShowRequestPermissionRationale(String permissionName,
             String packageName, int userId) {
@@ -6274,7 +5488,7 @@
      * <br />
      * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
      */
-    static int compareSignatures(Signature[] s1, Signature[] s2) {
+    public static int compareSignatures(Signature[] s1, Signature[] s2) {
         if (s1 == null) {
             return s2 == null
                     ? PackageManager.SIGNATURE_NEITHER_SIGNED
@@ -6611,26 +5825,22 @@
     }
 
     @Override
-    public String[] getAppOpPermissionPackages(String permissionName) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        synchronized (mPackages) {
-            ArraySet<String> pkgs = mAppOpPermissionPackages.get(permissionName);
-            if (pkgs == null) {
-                return null;
-            }
-            return pkgs.toArray(new String[pkgs.size()]);
-        }
+    public String[] getAppOpPermissionPackages(String permName) {
+        return mPermissionManager.getAppOpPermissionPackages(permName);
     }
 
     @Override
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
             int flags, int userId) {
         return resolveIntentInternal(
-                intent, resolvedType, flags, userId, false /*includeInstantApps*/);
+                intent, resolvedType, flags, userId, false /*resolveForStart*/);
     }
 
+    /**
+     * Normally instant apps can only be resolved when they're visible to the caller.
+     * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+     * since we need to allow the system to start any installed application.
+     */
     private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
             int flags, int userId, boolean resolveForStart) {
         try {
@@ -6639,7 +5849,7 @@
             if (!sUserManager.exists(userId)) return null;
             final int callingUid = Binder.getCallingUid();
             flags = updateFlagsForResolve(flags, userId, intent, callingUid, resolveForStart);
-            enforceCrossUserPermission(callingUid, userId,
+            mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                     false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
 
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
@@ -7220,7 +6430,7 @@
             boolean resolveForStart, boolean allowDynamicSplits) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
         final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */,
                 "query intent activities");
         final String pkgName = intent.getPackage();
@@ -7591,6 +6801,13 @@
                     && info.activityInfo.splitName != null
                     && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
                             info.activityInfo.splitName)) {
+                if (mInstantAppInstallerInfo == null) {
+                    if (DEBUG_INSTALL) {
+                        Slog.v(TAG, "No installer - not adding it to the ResolveInfo list");
+                    }
+                    resolveInfos.remove(i);
+                    continue;
+                }
                 // requested activity is defined in a split that hasn't been installed yet.
                 // add the installer to the resolve list
                 if (DEBUG_INSTALL) {
@@ -7604,14 +6821,22 @@
                         installFailureActivity,
                         info.activityInfo.applicationInfo.versionCode,
                         null /*failureIntent*/);
-                // make sure this resolver is the default
-                installerInfo.isDefault = true;
                 installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
                         | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
                 // add a non-generic filter
                 installerInfo.filter = new IntentFilter();
-                // load resources from the correct package
+
+                // This resolve info may appear in the chooser UI, so let us make it
+                // look as the one it replaces as far as the user is concerned which
+                // requires loading the correct label and icon for the resolve info.
                 installerInfo.resolvePackageName = info.getComponentInfo().packageName;
+                installerInfo.labelRes = info.resolveLabelResId();
+                installerInfo.icon = info.resolveIconResId();
+
+                // propagate priority/preferred order/default
+                installerInfo.priority = info.priority;
+                installerInfo.preferredOrder = info.preferredOrder;
+                installerInfo.isDefault = info.isDefault;
                 resolveInfos.set(i, installerInfo);
                 continue;
             }
@@ -7975,7 +7200,7 @@
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForResolve(flags, userId, intent, callingUid,
                 false /*includeInstantApps*/);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent activity options");
         final String resultsAction = intent.getAction();
@@ -8156,7 +7381,7 @@
             String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8268,7 +7493,7 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8508,7 +7733,7 @@
         if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId, null);
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get installed packages");
 
@@ -8595,7 +7820,7 @@
             String[] permissions, int flags, int userId) {
         if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId, permissions);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get packages holding permissions");
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
@@ -8700,7 +7925,7 @@
             mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                     "getEphemeralApplications");
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getEphemeralApplications");
         synchronized (mPackages) {
@@ -8715,7 +7940,7 @@
 
     @Override
     public boolean isInstantApp(String packageName, int userId) {
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isInstantApp");
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
@@ -8748,7 +7973,7 @@
             return null;
         }
 
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppCookie");
         if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8766,7 +7991,7 @@
             return true;
         }
 
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setInstantAppCookie");
         if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8788,7 +8013,7 @@
             mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                     "getInstantAppIcon");
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppIcon");
 
@@ -8848,6 +8073,10 @@
 
     @Override
     public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+        return resolveContentProviderInternal(name, flags, userId);
+    }
+
+    private ProviderInfo resolveContentProviderInternal(String name, int flags, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId, name);
         final String instantAppPkgName = getInstantAppPackageName(Binder.getCallingUid());
@@ -9106,7 +8335,7 @@
         return fname;
     }
 
-    static void reportSettingsProblem(int priority, String msg) {
+    public static void reportSettingsProblem(int priority, String msg) {
         logCriticalInfo(priority, msg);
     }
 
@@ -9739,7 +8968,7 @@
      * and {@code numberOfPackagesFailed}.
      */
     private int[] performDexOptUpgrade(List<PackageParser.Package> pkgs, boolean showDialog,
-            String compilerFilter, boolean bootComplete) {
+            final String compilerFilter, boolean bootComplete) {
 
         int numberOfPackagesVisited = 0;
         int numberOfPackagesOptimized = 0;
@@ -9750,6 +8979,8 @@
         for (PackageParser.Package pkg : pkgs) {
             numberOfPackagesVisited++;
 
+            boolean useProfileForDexopt = false;
+
             if ((isFirstBoot() || isUpgrade()) && isSystemApp(pkg)) {
                 // Copy over initial preopt profiles since we won't get any JIT samples for methods
                 // that are already compiled.
@@ -9763,11 +8994,50 @@
                         if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
                                 pkg.applicationInfo.uid, pkg.packageName)) {
                             Log.e(TAG, "Installer failed to copy system profile!");
+                        } else {
+                            // Disabled as this causes speed-profile compilation during first boot
+                            // even if things are already compiled.
+                            // useProfileForDexopt = true;
                         }
                     } catch (Exception e) {
                         Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
                                 e);
                     }
+                } else {
+                    PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
+                    // Handle compressed APKs in this path. Only do this for stubs with profiles to
+                    // minimize the number off apps being speed-profile compiled during first boot.
+                    // The other paths will not change the filter.
+                    if (disabledPs != null && disabledPs.pkg.isStub) {
+                        // The package is the stub one, remove the stub suffix to get the normal
+                        // package and APK names.
+                        String systemProfilePath =
+                                getPrebuildProfilePath(disabledPs.pkg).replace(STUB_SUFFIX, "");
+                        profileFile = new File(systemProfilePath);
+                        // If we have a profile for a compressed APK, copy it to the reference
+                        // location.
+                        // Note that copying the profile here will cause it to override the
+                        // reference profile every OTA even though the existing reference profile
+                        // may have more data. We can't copy during decompression since the
+                        // directories are not set up at that point.
+                        if (profileFile.exists()) {
+                            try {
+                                // We could also do this lazily before calling dexopt in
+                                // PackageDexOptimizer to prevent this happening on first boot. The
+                                // issue is that we don't have a good way to say "do this only
+                                // once".
+                                if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+                                        pkg.applicationInfo.uid, pkg.packageName)) {
+                                    Log.e(TAG, "Failed to copy system profile for stub package!");
+                                } else {
+                                    useProfileForDexopt = true;
+                                }
+                            } catch (Exception e) {
+                                Log.e(TAG, "Failed to copy profile " +
+                                        profileFile.getAbsolutePath() + " ", e);
+                            }
+                        }
+                    }
                 }
             }
 
@@ -9796,17 +9066,13 @@
                 }
             }
 
-            // If the OTA updates a system app which was previously preopted to a non-preopted state
-            // the app might end up being verified at runtime. That's because by default the apps
-            // are verify-profile but for preopted apps there's no profile.
-            // Do a hacky check to ensure that if we have no profiles (a reasonable indication
-            // that before the OTA the app was preopted) the app gets compiled with a non-profile
-            // filter (by default 'quicken').
-            // Note that at this stage unused apps are already filtered.
-            if (isSystemApp(pkg) &&
-                    DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
-                    !Environment.getReferenceProfile(pkg.packageName).exists()) {
-                compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
+            String pkgCompilerFilter = compilerFilter;
+            if (useProfileForDexopt) {
+                // Use background dexopt mode to try and use the profile. Note that this does not
+                // guarantee usage of the profile.
+                pkgCompilerFilter =
+                        PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                                PackageManagerService.REASON_BACKGROUND_DEXOPT);
             }
 
             // checkProfiles is false to avoid merging profiles during boot which
@@ -9817,7 +9083,7 @@
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
             int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
                     pkg.packageName,
-                    compilerFilter,
+                    pkgCompilerFilter,
                     dexoptFlags));
 
             switch (primaryDexOptStaus) {
@@ -10376,7 +9642,8 @@
         }
     }
 
-    private void addSharedLibraryLPr(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
+    private void addSharedLibraryLPr(Set<String> usesLibraryFiles,
+            SharedLibraryEntry file,
             PackageParser.Package changingLib) {
         if (file.path != null) {
             usesLibraryFiles.add(file.path);
@@ -10405,7 +9672,10 @@
         if (pkg == null) {
             return;
         }
-        ArraySet<String> usesLibraryFiles = null;
+        // The collection used here must maintain the order of addition (so
+        // that libraries are searched in the correct order) and must have no
+        // duplicates.
+        Set<String> usesLibraryFiles = null;
         if (pkg.usesLibraries != null) {
             usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
                     null, null, pkg.packageName, changingLib, true,
@@ -10429,10 +9699,10 @@
         }
     }
 
-    private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
+    private Set<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
             @Nullable int[] requiredVersions, @Nullable String[][] requiredCertDigests,
             @NonNull String packageName, @Nullable PackageParser.Package changingLib,
-            boolean required, int targetSdk, @Nullable ArraySet<String> outUsedLibraries)
+            boolean required, int targetSdk, @Nullable Set<String> outUsedLibraries)
             throws PackageManagerException {
         final int libCount = requestedLibraries.size();
         for (int i = 0; i < libCount; i++) {
@@ -10498,7 +9768,9 @@
                 }
 
                 if (outUsedLibraries == null) {
-                    outUsedLibraries = new ArraySet<>();
+                    // Use LinkedHashSet to preserve the order of files added to
+                    // usesLibraryFiles while eliminating duplicates.
+                    outUsedLibraries = new LinkedHashSet<>();
                 }
                 addSharedLibraryLPr(outUsedLibraries, libEntry, changingLib);
             }
@@ -10543,7 +9815,7 @@
                 // it is better for the user to reinstall than to be in an limbo
                 // state. Also libs disappearing under an app should never happen
                 // - just in case.
-                if (!pkg.isSystemApp() || pkg.isUpdatedSystemApp()) {
+                if (!pkg.isSystem() || pkg.isUpdatedSystemApp()) {
                     final int flags = pkg.isUpdatedSystemApp()
                             ? PackageManager.DELETE_KEEP_DATA : 0;
                     deletePackageLIF(pkg.packageName, null, true, sUserManager.getUserIds(),
@@ -10691,6 +9963,12 @@
 
         assertPackageIsValid(pkg, policyFlags, scanFlags);
 
+        if (Build.IS_DEBUGGABLE &&
+                pkg.isPrivileged() &&
+                !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+            PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
+        }
+
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
         final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -10977,7 +10255,7 @@
                             Slog.i(TAG, "Adopting permissions from " + origName + " to "
                                     + pkg.packageName);
                             // SIDE EFFECTS; updates permissions system state; move elsewhere
-                            mSettings.transferPermissionsLPw(origName, pkg.packageName);
+                            mSettings.mPermissions.transferPermissions(origName, pkg.packageName);
                         }
                     }
                 }
@@ -11779,159 +11057,20 @@
                 if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Activities: " + r);
             }
 
-            N = pkg.permissionGroups.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
-                PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
-                final String curPackageName = cur == null ? null : cur.info.packageName;
-                // Dont allow ephemeral apps to define new permission groups.
-                if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                    Slog.w(TAG, "Permission group " + pg.info.name + " from package "
-                            + pg.info.packageName
-                            + " ignored: instant apps cannot define new permission groups.");
-                    continue;
-                }
-                final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
-                if (cur == null || isPackageUpdate) {
-                    mPermissionGroups.put(pg.info.name, pg);
-                    if (chatty) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        if (isPackageUpdate) {
-                            r.append("UPD:");
-                        }
-                        r.append(pg.info.name);
-                    }
-                } else {
-                    Slog.w(TAG, "Permission group " + pg.info.name + " from package "
-                            + pg.info.packageName + " ignored: original from "
-                            + cur.info.packageName);
-                    if (chatty) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        r.append("DUP:");
-                        r.append(pg.info.name);
-                    }
-                }
-            }
-            if (r != null) {
-                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permission Groups: " + r);
+            // Don't allow ephemeral applications to define new permissions groups.
+            if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+                Slog.w(TAG, "Permission groups from package " + pkg.packageName
+                        + " ignored: instant apps cannot define new permission groups.");
+            } else {
+                mPermissionManager.addAllPermissionGroups(pkg, chatty);
             }
 
-            N = pkg.permissions.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Permission p = pkg.permissions.get(i);
-
-                // Dont allow ephemeral apps to define new permissions.
-                if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                    Slog.w(TAG, "Permission " + p.info.name + " from package "
-                            + p.info.packageName
-                            + " ignored: instant apps cannot define new permissions.");
-                    continue;
-                }
-
-                // Assume by default that we did not install this permission into the system.
-                p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
-
-                // Now that permission groups have a special meaning, we ignore permission
-                // groups for legacy apps to prevent unexpected behavior. In particular,
-                // permissions for one app being granted to someone just because they happen
-                // to be in a group defined by another app (before this had no implications).
-                if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
-                    p.group = mPermissionGroups.get(p.info.group);
-                    // Warn for a permission in an unknown group.
-                    if (DEBUG_PERMISSIONS && p.info.group != null && p.group == null) {
-                        Slog.i(TAG, "Permission " + p.info.name + " from package "
-                                + p.info.packageName + " in an unknown group " + p.info.group);
-                    }
-                }
-
-                ArrayMap<String, BasePermission> permissionMap =
-                        p.tree ? mSettings.mPermissionTrees
-                                : mSettings.mPermissions;
-                BasePermission bp = permissionMap.get(p.info.name);
-
-                // Allow system apps to redefine non-system permissions
-                if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {
-                    final boolean currentOwnerIsSystem = (bp.perm != null
-                            && isSystemApp(bp.perm.owner));
-                    if (isSystemApp(p.owner)) {
-                        if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
-                            // It's a built-in permission and no owner, take ownership now
-                            bp.packageSetting = pkgSetting;
-                            bp.perm = p;
-                            bp.uid = pkg.applicationInfo.uid;
-                            bp.sourcePackage = p.info.packageName;
-                            p.info.flags |= PermissionInfo.FLAG_INSTALLED;
-                        } else if (!currentOwnerIsSystem) {
-                            String msg = "New decl " + p.owner + " of permission  "
-                                    + p.info.name + " is system; overriding " + bp.sourcePackage;
-                            reportSettingsProblem(Log.WARN, msg);
-                            bp = null;
-                        }
-                    }
-                }
-
-                if (bp == null) {
-                    bp = new BasePermission(p.info.name, p.info.packageName,
-                            BasePermission.TYPE_NORMAL);
-                    permissionMap.put(p.info.name, bp);
-                }
-
-                if (bp.perm == null) {
-                    if (bp.sourcePackage == null
-                            || bp.sourcePackage.equals(p.info.packageName)) {
-                        BasePermission tree = findPermissionTreeLP(p.info.name);
-                        if (tree == null
-                                || tree.sourcePackage.equals(p.info.packageName)) {
-                            bp.packageSetting = pkgSetting;
-                            bp.perm = p;
-                            bp.uid = pkg.applicationInfo.uid;
-                            bp.sourcePackage = p.info.packageName;
-                            p.info.flags |= PermissionInfo.FLAG_INSTALLED;
-                            if (chatty) {
-                                if (r == null) {
-                                    r = new StringBuilder(256);
-                                } else {
-                                    r.append(' ');
-                                }
-                                r.append(p.info.name);
-                            }
-                        } else {
-                            Slog.w(TAG, "Permission " + p.info.name + " from package "
-                                    + p.info.packageName + " ignored: base tree "
-                                    + tree.name + " is from package "
-                                    + tree.sourcePackage);
-                        }
-                    } else {
-                        Slog.w(TAG, "Permission " + p.info.name + " from package "
-                                + p.info.packageName + " ignored: original from "
-                                + bp.sourcePackage);
-                    }
-                } else if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append("DUP:");
-                    r.append(p.info.name);
-                }
-                if (bp.perm == p) {
-                    bp.protectionLevel = p.info.protectionLevel;
-                }
-            }
-
-            if (r != null) {
-                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permissions: " + r);
+            // Don't allow ephemeral applications to define new permissions.
+            if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+                Slog.w(TAG, "Permissions from package " + pkg.packageName
+                        + " ignored: instant apps cannot define new permissions.");
+            } else {
+                mPermissionManager.addAllPermissions(pkg, chatty);
             }
 
             N = pkg.instrumentation.size();
@@ -12657,54 +11796,7 @@
             if (DEBUG_REMOVE) Log.d(TAG, "  Activities: " + r);
         }
 
-        N = pkg.permissions.size();
-        r = null;
-        for (i=0; i<N; i++) {
-            PackageParser.Permission p = pkg.permissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(p.info.name);
-            if (bp == null) {
-                bp = mSettings.mPermissionTrees.get(p.info.name);
-            }
-            if (bp != null && bp.perm == p) {
-                bp.perm = null;
-                if (DEBUG_REMOVE && chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(p.info.name);
-                }
-            }
-            if ((p.info.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
-                ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(p.info.name);
-                if (appOpPkgs != null) {
-                    appOpPkgs.remove(pkg.packageName);
-                }
-            }
-        }
-        if (r != null) {
-            if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-        }
-
-        N = pkg.requestedPermissions.size();
-        r = null;
-        for (i=0; i<N; i++) {
-            String perm = pkg.requestedPermissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(perm);
-            if (bp != null && (bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
-                ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm);
-                if (appOpPkgs != null) {
-                    appOpPkgs.remove(pkg.packageName);
-                    if (appOpPkgs.isEmpty()) {
-                        mAppOpPermissionPackages.remove(perm);
-                    }
-                }
-            }
-        }
-        if (r != null) {
-            if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-        }
+        mPermissionManager.removeAllPermissions(pkg, chatty);
 
         N = pkg.instrumentation.size();
         r = null;
@@ -12765,681 +11857,6 @@
         }
     }
 
-    private static boolean hasPermission(PackageParser.Package pkgInfo, String perm) {
-        for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
-            if (pkgInfo.permissions.get(i).info.name.equals(perm)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static final int UPDATE_PERMISSIONS_ALL = 1<<0;
-    static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
-    static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
-
-    private void updatePermissionsLPw(PackageParser.Package pkg, int flags) {
-        // Update the parent permissions
-        updatePermissionsLPw(pkg.packageName, pkg, flags);
-        // Update the child permissions
-        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
-        for (int i = 0; i < childCount; i++) {
-            PackageParser.Package childPkg = pkg.childPackages.get(i);
-            updatePermissionsLPw(childPkg.packageName, childPkg, flags);
-        }
-    }
-
-    private void updatePermissionsLPw(String changingPkg, PackageParser.Package pkgInfo,
-            int flags) {
-        final String volumeUuid = (pkgInfo != null) ? getVolumeUuidForPackage(pkgInfo) : null;
-        updatePermissionsLPw(changingPkg, pkgInfo, volumeUuid, flags);
-    }
-
-    private void updatePermissionsLPw(String changingPkg,
-            PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
-        // Make sure there are no dangling permission trees.
-        Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
-        while (it.hasNext()) {
-            final BasePermission bp = it.next();
-            if (bp.packageSetting == null) {
-                // We may not yet have parsed the package, so just see if
-                // we still know about its settings.
-                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
-            }
-            if (bp.packageSetting == null) {
-                Slog.w(TAG, "Removing dangling permission tree: " + bp.name
-                        + " from package " + bp.sourcePackage);
-                it.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
-                    Slog.i(TAG, "Removing old permission tree: " + bp.name
-                            + " from package " + bp.sourcePackage);
-                    flags |= UPDATE_PERMISSIONS_ALL;
-                    it.remove();
-                }
-            }
-        }
-
-        // Make sure all dynamic permissions have been assigned to a package,
-        // and make sure there are no dangling permissions.
-        it = mSettings.mPermissions.values().iterator();
-        while (it.hasNext()) {
-            final BasePermission bp = it.next();
-            if (bp.type == BasePermission.TYPE_DYNAMIC) {
-                if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
-                        + bp.name + " pkg=" + bp.sourcePackage
-                        + " info=" + bp.pendingInfo);
-                if (bp.packageSetting == null && bp.pendingInfo != null) {
-                    final BasePermission tree = findPermissionTreeLP(bp.name);
-                    if (tree != null && tree.perm != null) {
-                        bp.packageSetting = tree.packageSetting;
-                        bp.perm = new PackageParser.Permission(tree.perm.owner,
-                                new PermissionInfo(bp.pendingInfo));
-                        bp.perm.info.packageName = tree.perm.info.packageName;
-                        bp.perm.info.name = bp.name;
-                        bp.uid = tree.uid;
-                    }
-                }
-            }
-            if (bp.packageSetting == null) {
-                // We may not yet have parsed the package, so just see if
-                // we still know about its settings.
-                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
-            }
-            if (bp.packageSetting == null) {
-                Slog.w(TAG, "Removing dangling permission: " + bp.name
-                        + " from package " + bp.sourcePackage);
-                it.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
-                    Slog.i(TAG, "Removing old permission: " + bp.name
-                            + " from package " + bp.sourcePackage);
-                    flags |= UPDATE_PERMISSIONS_ALL;
-                    it.remove();
-                }
-            }
-        }
-
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
-        // Now update the permissions for all packages, in particular
-        // replace the granted permissions of the system packages.
-        if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
-            for (PackageParser.Package pkg : mPackages.values()) {
-                if (pkg != pkgInfo) {
-                    // Only replace for packages on requested volume
-                    final String volumeUuid = getVolumeUuidForPackage(pkg);
-                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
-                            && Objects.equals(replaceVolumeUuid, volumeUuid);
-                    grantPermissionsLPw(pkg, replace, changingPkg);
-                }
-            }
-        }
-
-        if (pkgInfo != null) {
-            // Only replace for packages on requested volume
-            final String volumeUuid = getVolumeUuidForPackage(pkgInfo);
-            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
-                    && Objects.equals(replaceVolumeUuid, volumeUuid);
-            grantPermissionsLPw(pkgInfo, replace, changingPkg);
-        }
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-    }
-
-    private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
-            String packageOfInterest) {
-        // IMPORTANT: There are two types of permissions: install and runtime.
-        // Install time permissions are granted when the app is installed to
-        // all device users and users added in the future. Runtime permissions
-        // are granted at runtime explicitly to specific users. Normal and signature
-        // protected permissions are install time permissions. Dangerous permissions
-        // are install permissions if the app's target SDK is Lollipop MR1 or older,
-        // otherwise they are runtime permissions. This function does not manage
-        // runtime permissions except for the case an app targeting Lollipop MR1
-        // being upgraded to target a newer SDK, in which case dangerous permissions
-        // are transformed from install time to runtime ones.
-
-        final PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-
-        PermissionsState permissionsState = ps.getPermissionsState();
-        PermissionsState origPermissions = permissionsState;
-
-        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
-
-        boolean runtimePermissionsRevoked = false;
-        int[] changedRuntimePermissionUserIds = EMPTY_INT_ARRAY;
-
-        boolean changedInstallPermission = false;
-
-        if (replace) {
-            ps.installPermissionsFixed = false;
-            if (!ps.isSharedUser()) {
-                origPermissions = new PermissionsState(permissionsState);
-                permissionsState.reset();
-            } else {
-                // We need to know only about runtime permission changes since the
-                // calling code always writes the install permissions state but
-                // the runtime ones are written only if changed. The only cases of
-                // changed runtime permissions here are promotion of an install to
-                // runtime and revocation of a runtime from a shared user.
-                changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw(
-                        ps.sharedUser, UserManagerService.getInstance().getUserIds());
-                if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
-                    runtimePermissionsRevoked = true;
-                }
-            }
-        }
-
-        permissionsState.setGlobalGids(mGlobalGids);
-
-        final int N = pkg.requestedPermissions.size();
-        for (int i=0; i<N; i++) {
-            final String name = pkg.requestedPermissions.get(i);
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
-                    >= Build.VERSION_CODES.M;
-
-            if (DEBUG_INSTALL) {
-                Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
-            }
-
-            if (bp == null || bp.packageSetting == null) {
-                if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, "Unknown permission " + name
-                                + " in package " + pkg.packageName);
-                    }
-                }
-                continue;
-            }
-
-
-            // Limit ephemeral apps to ephemeral allowed permissions.
-            if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying non-ephemeral permission " + bp.name + " for package "
-                            + pkg.packageName);
-                }
-                continue;
-            }
-
-            if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
-                if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying runtime-only permission " + bp.name + " for package "
-                            + pkg.packageName);
-                }
-                continue;
-            }
-
-            final String perm = bp.name;
-            boolean allowedSig = false;
-            int grant = GRANT_DENIED;
-
-            // Keep track of app op permissions.
-            if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
-                ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name);
-                if (pkgs == null) {
-                    pkgs = new ArraySet<>();
-                    mAppOpPermissionPackages.put(bp.name, pkgs);
-                }
-                pkgs.add(pkg.packageName);
-            }
-
-            final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
-            switch (level) {
-                case PermissionInfo.PROTECTION_NORMAL: {
-                    // For all apps normal permissions are install time ones.
-                    grant = GRANT_INSTALL;
-                } break;
-
-                case PermissionInfo.PROTECTION_DANGEROUS: {
-                    // If a permission review is required for legacy apps we represent
-                    // their permissions as always granted runtime ones since we need
-                    // to keep the review required permission flag per user while an
-                    // install permission's state is shared across all users.
-                    if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
-                        // For legacy apps dangerous permissions are install time ones.
-                        grant = GRANT_INSTALL;
-                    } else if (origPermissions.hasInstallPermission(bp.name)) {
-                        // For legacy apps that became modern, install becomes runtime.
-                        grant = GRANT_UPGRADE;
-                    } else if (mPromoteSystemApps
-                            && isSystemApp(ps)
-                            && mExistingSystemPackages.contains(ps.name)) {
-                        // For legacy system apps, install becomes runtime.
-                        // We cannot check hasInstallPermission() for system apps since those
-                        // permissions were granted implicitly and not persisted pre-M.
-                        grant = GRANT_UPGRADE;
-                    } else {
-                        // For modern apps keep runtime permissions unchanged.
-                        grant = GRANT_RUNTIME;
-                    }
-                } break;
-
-                case PermissionInfo.PROTECTION_SIGNATURE: {
-                    // For all apps signature permissions are install time ones.
-                    allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
-                    if (allowedSig) {
-                        grant = GRANT_INSTALL;
-                    }
-                } break;
-            }
-
-            if (DEBUG_PERMISSIONS) {
-                Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
-            }
-
-            if (grant != GRANT_DENIED) {
-                if (!isSystemApp(ps) && ps.installPermissionsFixed) {
-                    // If this is an existing, non-system package, then
-                    // we can't add any new permissions to it.
-                    if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
-                        // Except...  if this is a permission that was added
-                        // to the platform (note: need to only do this when
-                        // updating the platform).
-                        if (!isNewPlatformPermissionForPackage(perm, pkg)) {
-                            grant = GRANT_DENIED;
-                        }
-                    }
-                }
-
-                switch (grant) {
-                    case GRANT_INSTALL: {
-                        // Revoke this as runtime permission to handle the case of
-                        // a runtime permission being downgraded to an install one.
-                        // Also in permission review mode we keep dangerous permissions
-                        // for legacy apps
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            if (origPermissions.getRuntimePermissionState(
-                                    bp.name, userId) != null) {
-                                // Revoke the runtime permission and clear the flags.
-                                origPermissions.revokeRuntimePermission(bp, userId);
-                                origPermissions.updatePermissionFlags(bp, userId,
-                                      PackageManager.MASK_PERMISSION_FLAGS, 0);
-                                // If we revoked a permission permission, we have to write.
-                                changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                        changedRuntimePermissionUserIds, userId);
-                            }
-                        }
-                        // Grant an install permission.
-                        if (permissionsState.grantInstallPermission(bp) !=
-                                PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                            changedInstallPermission = true;
-                        }
-                    } break;
-
-                    case GRANT_RUNTIME: {
-                        // Grant previously granted runtime permissions.
-                        for (int userId : UserManagerService.getInstance().getUserIds()) {
-                            PermissionState permissionState = origPermissions
-                                    .getRuntimePermissionState(bp.name, userId);
-                            int flags = permissionState != null
-                                    ? permissionState.getFlags() : 0;
-                            if (origPermissions.hasRuntimePermission(bp.name, userId)) {
-                                // Don't propagate the permission in a permission review mode if
-                                // the former was revoked, i.e. marked to not propagate on upgrade.
-                                // Note that in a permission review mode install permissions are
-                                // represented as constantly granted runtime ones since we need to
-                                // keep a per user state associated with the permission. Also the
-                                // revoke on upgrade flag is no longer applicable and is reset.
-                                final boolean revokeOnUpgrade = (flags & PackageManager
-                                        .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
-                                if (revokeOnUpgrade) {
-                                    flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
-                                    // Since we changed the flags, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                                if (!mPermissionReviewRequired || !revokeOnUpgrade) {
-                                    if (permissionsState.grantRuntimePermission(bp, userId) ==
-                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                        // If we cannot put the permission as it was,
-                                        // we have to write.
-                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                                changedRuntimePermissionUserIds, userId);
-                                    }
-                                }
-
-                                // If the app supports runtime permissions no need for a review.
-                                if (mPermissionReviewRequired
-                                        && appSupportsRuntimePermissions
-                                        && (flags & PackageManager
-                                                .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                                    flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-                                    // Since we changed the flags, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            } else if (mPermissionReviewRequired
-                                    && !appSupportsRuntimePermissions) {
-                                // For legacy apps that need a permission review, every new
-                                // runtime permission is granted but it is pending a review.
-                                // We also need to review only platform defined runtime
-                                // permissions as these are the only ones the platform knows
-                                // how to disable the API to simulate revocation as legacy
-                                // apps don't expect to run with revoked permissions.
-                                if (PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage)) {
-                                    if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
-                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
-                                        // We changed the flags, hence have to write.
-                                        changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                                changedRuntimePermissionUserIds, userId);
-                                    }
-                                }
-                                if (permissionsState.grantRuntimePermission(bp, userId)
-                                        != PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // We changed the permission, hence have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            }
-                            // Propagate the permission flags.
-                            permissionsState.updatePermissionFlags(bp, userId, flags, flags);
-                        }
-                    } break;
-
-                    case GRANT_UPGRADE: {
-                        // Grant runtime permissions for a previously held install permission.
-                        PermissionState permissionState = origPermissions
-                                .getInstallPermissionState(bp.name);
-                        final int flags = permissionState != null ? permissionState.getFlags() : 0;
-
-                        if (origPermissions.revokeInstallPermission(bp)
-                                != PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                            // We will be transferring the permission flags, so clear them.
-                            origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                                    PackageManager.MASK_PERMISSION_FLAGS, 0);
-                            changedInstallPermission = true;
-                        }
-
-                        // If the permission is not to be promoted to runtime we ignore it and
-                        // also its other flags as they are not applicable to install permissions.
-                        if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
-                            for (int userId : currentUserIds) {
-                                if (permissionsState.grantRuntimePermission(bp, userId) !=
-                                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                                    // Transfer the permission flags.
-                                    permissionsState.updatePermissionFlags(bp, userId,
-                                            flags, flags);
-                                    // If we granted the permission, we have to write.
-                                    changedRuntimePermissionUserIds = ArrayUtils.appendInt(
-                                            changedRuntimePermissionUserIds, userId);
-                                }
-                            }
-                        }
-                    } break;
-
-                    default: {
-                        if (packageOfInterest == null
-                                || packageOfInterest.equals(pkg.packageName)) {
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, "Not granting permission " + perm
-                                        + " to package " + pkg.packageName
-                                        + " because it was previously installed without");
-                            }
-                        }
-                    } break;
-                }
-            } else {
-                if (permissionsState.revokeInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    // Also drop the permission flags.
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            PackageManager.MASK_PERMISSION_FLAGS, 0);
-                    changedInstallPermission = true;
-                    Slog.i(TAG, "Un-granting permission " + perm
-                            + " from package " + pkg.packageName
-                            + " (protectionLevel=" + bp.protectionLevel
-                            + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
-                            + ")");
-                } else if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) == 0) {
-                    // Don't print warning for app op permissions, since it is fine for them
-                    // not to be granted, there is a UI for the user to decide.
-                    if (DEBUG_PERMISSIONS
-                            && (packageOfInterest == null
-                                    || packageOfInterest.equals(pkg.packageName))) {
-                        Slog.i(TAG, "Not granting permission " + perm
-                                + " to package " + pkg.packageName
-                                + " (protectionLevel=" + bp.protectionLevel
-                                + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
-                                + ")");
-                    }
-                }
-            }
-        }
-
-        if ((changedInstallPermission || replace) && !ps.installPermissionsFixed &&
-                !isSystemApp(ps) || isUpdatedSystemApp(ps)){
-            // This is the first that we have heard about this package, so the
-            // permissions we have now selected are fixed until explicitly
-            // changed.
-            ps.installPermissionsFixed = true;
-        }
-
-        // Persist the runtime permissions state for users with changes. If permissions
-        // were revoked because no app in the shared user declares them we have to
-        // write synchronously to avoid losing runtime permissions state.
-        for (int userId : changedRuntimePermissionUserIds) {
-            mSettings.writeRuntimePermissionsForUserLPr(userId, runtimePermissionsRevoked);
-        }
-    }
-
-    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
-        boolean allowed = false;
-        final int NP = PackageParser.NEW_PERMISSIONS.length;
-        for (int ip=0; ip<NP; ip++) {
-            final PackageParser.NewPermissionInfo npi
-                    = PackageParser.NEW_PERMISSIONS[ip];
-            if (npi.name.equals(perm)
-                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
-                allowed = true;
-                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
-                        + pkg.packageName);
-                break;
-            }
-        }
-        return allowed;
-    }
-
-    /**
-     * Determines whether a package is whitelisted for a particular privapp permission.
-     *
-     * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
-     *
-     * <p>This handles parent/child apps.
-     */
-    private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
-        ArraySet<String> wlPermissions = SystemConfig.getInstance()
-                .getPrivAppPermissions(pkg.packageName);
-        // Let's check if this package is whitelisted...
-        boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
-        // If it's not, we'll also tail-recurse to the parent.
-        return whitelisted ||
-                pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
-    }
-
-    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
-            BasePermission bp, PermissionsState origPermissions) {
-        boolean oemPermission = (bp.protectionLevel
-                & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
-        boolean privilegedPermission = (bp.protectionLevel
-                & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
-        boolean privappPermissionsDisable =
-                RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
-        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage);
-        boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
-        if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
-                && !platformPackage && platformPermission) {
-            if (!hasPrivappWhitelistEntry(perm, pkg)) {
-                Slog.w(TAG, "Privileged permission " + perm + " for package "
-                        + pkg.packageName + " - not in privapp-permissions whitelist");
-                // Only report violations for apps on system image
-                if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
-                    // it's only a reportable violation if the permission isn't explicitly denied
-                    final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
-                            .getPrivAppDenyPermissions(pkg.packageName);
-                    final boolean permissionViolation =
-                            deniedPermissions == null || !deniedPermissions.contains(perm);
-                    if (permissionViolation) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
-                    } else {
-                        return false;
-                    }
-                }
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    return false;
-                }
-            }
-        }
-        boolean allowed = (compareSignatures(
-                bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
-                        == PackageManager.SIGNATURE_MATCH)
-                || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
-                        == PackageManager.SIGNATURE_MATCH);
-        if (!allowed && (privilegedPermission || oemPermission)) {
-            if (isSystemApp(pkg)) {
-                // For updated system applications, a privileged/oem permission
-                // is granted only if it had been defined by the original application.
-                if (pkg.isUpdatedSystemApp()) {
-                    final PackageSetting sysPs = mSettings
-                            .getDisabledSystemPkgLPr(pkg.packageName);
-                    if (sysPs != null && sysPs.getPermissionsState().hasInstallPermission(perm)) {
-                        // If the original was granted this permission, we take
-                        // that grant decision as read and propagate it to the
-                        // update.
-                        if ((privilegedPermission && sysPs.isPrivileged())
-                                || (oemPermission && sysPs.isOem()
-                                        && canGrantOemPermission(sysPs, perm))) {
-                            allowed = true;
-                        }
-                    } else {
-                        // The system apk may have been updated with an older
-                        // version of the one on the data partition, but which
-                        // granted a new system permission that it didn't have
-                        // before.  In this case we do want to allow the app to
-                        // now get the new permission if the ancestral apk is
-                        // privileged to get it.
-                        if (sysPs != null && sysPs.pkg != null
-                                && isPackageRequestingPermission(sysPs.pkg, perm)
-                                && ((privilegedPermission && sysPs.isPrivileged())
-                                        || (oemPermission && sysPs.isOem()
-                                                && canGrantOemPermission(sysPs, perm)))) {
-                            allowed = true;
-                        }
-                        // Also if a privileged parent package on the system image or any of
-                        // its children requested a privileged/oem permission, the updated child
-                        // packages can also get the permission.
-                        if (pkg.parentPackage != null) {
-                            final PackageSetting disabledSysParentPs = mSettings
-                                    .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
-                            final PackageParser.Package disabledSysParentPkg =
-                                    (disabledSysParentPs == null || disabledSysParentPs.pkg == null)
-                                    ? null : disabledSysParentPs.pkg;
-                            if (disabledSysParentPkg != null
-                                    && ((privilegedPermission && disabledSysParentPs.isPrivileged())
-                                            || (oemPermission && disabledSysParentPs.isOem()))) {
-                                if (isPackageRequestingPermission(disabledSysParentPkg, perm)
-                                        && canGrantOemPermission(disabledSysParentPs, perm)) {
-                                    allowed = true;
-                                } else if (disabledSysParentPkg.childPackages != null) {
-                                    final int count = disabledSysParentPkg.childPackages.size();
-                                    for (int i = 0; i < count; i++) {
-                                        final PackageParser.Package disabledSysChildPkg =
-                                                disabledSysParentPkg.childPackages.get(i);
-                                        final PackageSetting disabledSysChildPs =
-                                                mSettings.getDisabledSystemPkgLPr(
-                                                        disabledSysChildPkg.packageName);
-                                        if (isPackageRequestingPermission(disabledSysChildPkg, perm)
-                                                && canGrantOemPermission(
-                                                        disabledSysChildPs, perm)) {
-                                            allowed = true;
-                                            break;
-                                        }
-                                    }
-                                }
-                            }
-                        }
-                    }
-                } else {
-                    allowed = (privilegedPermission && isPrivilegedApp(pkg))
-                            || (oemPermission && isOemApp(pkg)
-                                    && canGrantOemPermission(
-                                            mSettings.getPackageLPr(pkg.packageName), perm));
-                }
-            }
-        }
-        if (!allowed) {
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_PRE23) != 0
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                // If this was a previously normal/dangerous permission that got moved
-                // to a system permission as part of the runtime permission redesign, then
-                // we still want to blindly grant it to old apps.
-                allowed = true;
-            }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0
-                    && pkg.packageName.equals(mRequiredInstallerPackage)) {
-                // If this permission is to be granted to the system installer and
-                // this app is an installer, then it gets the permission.
-                allowed = true;
-            }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0
-                    && pkg.packageName.equals(mRequiredVerifierPackage)) {
-                // If this permission is to be granted to the system verifier and
-                // this app is a verifier, then it gets the permission.
-                allowed = true;
-            }
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0
-                    && isSystemApp(pkg)) {
-                // Any pre-installed system app is allowed to get this permission.
-                allowed = true;
-            }
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
-                // For development permissions, a development permission
-                // is granted only if it was already granted.
-                allowed = origPermissions.hasInstallPermission(perm);
-            }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0
-                    && pkg.packageName.equals(mSetupWizardPackage)) {
-                // If this permission is to be granted to the system setup wizard and
-                // this app is a setup wizard, then it gets the permission.
-                allowed = true;
-            }
-        }
-        return allowed;
-    }
-
-    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
-        if (!ps.isOem()) {
-            return false;
-        }
-        // all oem permissions must explicitly be granted or denied
-        final Boolean granted =
-                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
-        if (granted == null) {
-            throw new IllegalStateException("OEM permission" + permission + " requested by package "
-                    + ps.name + " must be explicitly declared granted or not");
-        }
-        return Boolean.TRUE == granted;
-    }
-
-    private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
-        final int permCount = pkg.requestedPermissions.size();
-        for (int j = 0; j < permCount; j++) {
-            String requestedPermission = pkg.requestedPermissions.get(j);
-            if (permission.equals(requestedPermission)) {
-                return true;
-            }
-        }
-        return false;
-    }
 
     final class ActivityIntentResolver
             extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
@@ -14707,7 +13124,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */, "installPackageAsUser");
 
         if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -14835,7 +13252,7 @@
         return installReason;
     }
 
-    void installStage(String packageName, File stagedDir, String stagedCid,
+    void installStage(String packageName, File stagedDir,
             IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
             String installerPackageName, int installerUid, UserHandle user,
             Certificate[][] certificates) {
@@ -14848,12 +13265,7 @@
                 sessionParams.originatingUri, sessionParams.referrerUri,
                 sessionParams.originatingUid, installerUid);
 
-        final OriginInfo origin;
-        if (stagedDir != null) {
-            origin = OriginInfo.fromStagedFile(stagedDir);
-        } else {
-            origin = OriginInfo.fromStagedContainer(stagedCid);
-        }
+        final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir);
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         final int installReason = fixUpInstallReason(installerPackageName, installerUid,
@@ -14951,7 +13363,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         PackageSetting pkgSetting;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setApplicationHiddenSetting for user " + userId);
 
@@ -15053,7 +13465,7 @@
     public boolean getApplicationHiddenSettingAsUser(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getApplicationHidden for user " + userId);
         PackageSetting ps;
@@ -15085,7 +13497,7 @@
                 null);
         PackageSetting pkgSetting;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "installExistingPackage for user " + userId);
         if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -15190,7 +13602,7 @@
             int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setPackagesSuspended for user " + userId);
 
@@ -15251,7 +13663,7 @@
     @Override
     public boolean isPackageSuspendedForUser(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isPackageSuspendedForUser for user " + userId);
         synchronized (mPackages) {
@@ -15698,7 +14110,7 @@
         synchronized (mPackages) {
             boolean result = mSettings.setDefaultBrowserPackageNameLPw(packageName, userId);
             if (packageName != null) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowserLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowser(
                         packageName, userId);
             }
             return result;
@@ -16053,7 +14465,6 @@
          * file, or a cluster directory. This location may be untrusted.
          */
         final File file;
-        final String cid;
 
         /**
          * Flag indicating that {@link #file} or {@link #cid} has already been
@@ -16072,35 +14483,27 @@
         final File resolvedFile;
 
         static OriginInfo fromNothing() {
-            return new OriginInfo(null, null, false, false);
+            return new OriginInfo(null, false, false);
         }
 
         static OriginInfo fromUntrustedFile(File file) {
-            return new OriginInfo(file, null, false, false);
+            return new OriginInfo(file, false, false);
         }
 
         static OriginInfo fromExistingFile(File file) {
-            return new OriginInfo(file, null, false, true);
+            return new OriginInfo(file, false, true);
         }
 
         static OriginInfo fromStagedFile(File file) {
-            return new OriginInfo(file, null, true, false);
+            return new OriginInfo(file, true, false);
         }
 
-        static OriginInfo fromStagedContainer(String cid) {
-            return new OriginInfo(null, cid, true, false);
-        }
-
-        private OriginInfo(File file, String cid, boolean staged, boolean existing) {
+        private OriginInfo(File file, boolean staged, boolean existing) {
             this.file = file;
-            this.cid = cid;
             this.staged = staged;
             this.existing = existing;
 
-            if (cid != null) {
-                resolvedPath = PackageHelper.getSdDir(cid);
-                resolvedFile = new File(resolvedPath);
-            } else if (file != null) {
+            if (file != null) {
                 resolvedPath = file.getAbsolutePath();
                 resolvedFile = file;
             } else {
@@ -16193,7 +14596,7 @@
         @Override
         public String toString() {
             return "InstallParams{" + Integer.toHexString(System.identityHashCode(this))
-                    + " file=" + origin.file + " cid=" + origin.cid + "}";
+                    + " file=" + origin.file + "}";
         }
 
         private int installLocationPolicy(PackageInfoLite pkgLite) {
@@ -16304,9 +14707,6 @@
                 if (origin.file != null) {
                     installFlags |= PackageManager.INSTALL_INTERNAL;
                     installFlags &= ~PackageManager.INSTALL_EXTERNAL;
-                } else if (origin.cid != null) {
-                    installFlags |= PackageManager.INSTALL_EXTERNAL;
-                    installFlags &= ~PackageManager.INSTALL_INTERNAL;
                 } else {
                     throw new IllegalStateException("Invalid stage location");
                 }
@@ -16344,7 +14744,7 @@
                             Environment.getDataDirectory());
 
                     final long sizeBytes = mContainerService.calculateInstalledSize(
-                            origin.resolvedPath, isForwardLocked(), packageAbiOverride);
+                            origin.resolvedPath, packageAbiOverride);
 
                     try {
                         mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
@@ -16581,43 +14981,11 @@
             mArgs = createInstallArgs(this);
             mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
         }
-
-        public boolean isForwardLocked() {
-            return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
-        }
-    }
-
-    /**
-     * Used during creation of InstallArgs
-     *
-     * @param installFlags package installation flags
-     * @return true if should be installed on external storage
-     */
-    private static boolean installOnExternalAsec(int installFlags) {
-        if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-            return false;
-        }
-        if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Used during creation of InstallArgs
-     *
-     * @param installFlags package installation flags
-     * @return true if should be installed as forward locked
-     */
-    private static boolean installForwardLocked(int installFlags) {
-        return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
     }
 
     private InstallArgs createInstallArgs(InstallParams params) {
         if (params.move != null) {
             return new MoveInstallArgs(params);
-        } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
-            return new AsecInstallArgs(params);
         } else {
             return new FileInstallArgs(params);
         }
@@ -16629,27 +14997,7 @@
      */
     private InstallArgs createInstallArgsForExisting(int installFlags, String codePath,
             String resourcePath, String[] instructionSets) {
-        final boolean isInAsec;
-        if (installOnExternalAsec(installFlags)) {
-            /* Apps on SD card are always in ASEC containers. */
-            isInAsec = true;
-        } else if (installForwardLocked(installFlags)
-                && !codePath.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath())) {
-            /*
-             * Forward-locked apps are only in ASEC containers if they're the
-             * new style
-             */
-            isInAsec = true;
-        } else {
-            isInAsec = false;
-        }
-
-        if (isInAsec) {
-            return new AsecInstallArgs(codePath, instructionSets,
-                    installOnExternalAsec(installFlags), installForwardLocked(installFlags));
-        } else {
-            return new FileInstallArgs(codePath, resourcePath, instructionSets);
-        }
+        return new FileInstallArgs(codePath, resourcePath, instructionSets);
     }
 
     static abstract class InstallArgs {
@@ -16983,11 +15331,6 @@
         }
     }
 
-    private boolean isAsecExternal(String cid) {
-        final String asecPath = PackageHelper.getSdFilesystem(cid);
-        return !asecPath.startsWith(mAsecInternalPath);
-    }
-
     private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
             PackageManagerException {
         if (copyRet < 0) {
@@ -17010,308 +15353,6 @@
     }
 
     /**
-     * Logic to handle installation of ASEC applications, including copying and
-     * renaming logic.
-     */
-    class AsecInstallArgs extends InstallArgs {
-        static final String RES_FILE_NAME = "pkg.apk";
-        static final String PUBLIC_RES_FILE_NAME = "res.zip";
-
-        String cid;
-        String packagePath;
-        String resourcePath;
-
-        /** New install */
-        AsecInstallArgs(InstallParams params) {
-            super(params.origin, params.move, params.observer, params.installFlags,
-                    params.installerPackageName, params.volumeUuid,
-                    params.getUser(), null /* instruction sets */, params.packageAbiOverride,
-                    params.grantedRuntimePermissions,
-                    params.traceMethod, params.traceCookie, params.certificates,
-                    params.installReason);
-        }
-
-        /** Existing install */
-        AsecInstallArgs(String fullCodePath, String[] instructionSets,
-                        boolean isExternal, boolean isForwardLocked) {
-            super(OriginInfo.fromNothing(), null, null, (isExternal ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
-                    instructionSets, null, null, null, 0, null /*certificates*/,
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-            // Hackily pretend we're still looking at a full code path
-            if (!fullCodePath.endsWith(RES_FILE_NAME)) {
-                fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath();
-            }
-
-            // Extract cid from fullCodePath
-            int eidx = fullCodePath.lastIndexOf("/");
-            String subStr1 = fullCodePath.substring(0, eidx);
-            int sidx = subStr1.lastIndexOf("/");
-            cid = subStr1.substring(sidx+1, eidx);
-            setMountPath(subStr1);
-        }
-
-        AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
-            super(OriginInfo.fromNothing(), null, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
-                    instructionSets, null, null, null, 0, null /*certificates*/,
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-            this.cid = cid;
-            setMountPath(PackageHelper.getSdDir(cid));
-        }
-
-        void createCopyFile() {
-            cid = mInstallerService.allocateExternalStageCidLegacy();
-        }
-
-        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
-            if (origin.staged && origin.cid != null) {
-                if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
-                cid = origin.cid;
-                setMountPath(PackageHelper.getSdDir(cid));
-                return PackageManager.INSTALL_SUCCEEDED;
-            }
-
-            if (temp) {
-                createCopyFile();
-            } else {
-                /*
-                 * Pre-emptively destroy the container since it's destroyed if
-                 * copying fails due to it existing anyway.
-                 */
-                PackageHelper.destroySdDir(cid);
-            }
-
-            final String newMountPath = imcs.copyPackageToContainer(
-                    origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(),
-                    isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */));
-
-            if (newMountPath != null) {
-                setMountPath(newMountPath);
-                return PackageManager.INSTALL_SUCCEEDED;
-            } else {
-                return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            }
-        }
-
-        @Override
-        String getCodePath() {
-            return packagePath;
-        }
-
-        @Override
-        String getResourcePath() {
-            return resourcePath;
-        }
-
-        int doPreInstall(int status) {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                // Destroy container
-                PackageHelper.destroySdDir(cid);
-            } else {
-                boolean mounted = PackageHelper.isContainerMounted(cid);
-                if (!mounted) {
-                    String newMountPath = PackageHelper.mountSdDir(cid, getEncryptKey(),
-                            Process.SYSTEM_UID);
-                    if (newMountPath != null) {
-                        setMountPath(newMountPath);
-                    } else {
-                        return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                    }
-                }
-            }
-            return status;
-        }
-
-        boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) {
-            String newCacheId = getNextCodePath(oldCodePath, pkg.packageName, "/" + RES_FILE_NAME);
-            String newMountPath = null;
-            if (PackageHelper.isContainerMounted(cid)) {
-                // Unmount the container
-                if (!PackageHelper.unMountSdDir(cid)) {
-                    Slog.i(TAG, "Failed to unmount " + cid + " before renaming");
-                    return false;
-                }
-            }
-            if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId +
-                        " which might be stale. Will try to clean up.");
-                // Clean up the stale container and proceed to recreate.
-                if (!PackageHelper.destroySdDir(newCacheId)) {
-                    Slog.e(TAG, "Very strange. Cannot clean up stale container " + newCacheId);
-                    return false;
-                }
-                // Successfully cleaned up stale container. Try to rename again.
-                if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                    Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId
-                            + " inspite of cleaning it up.");
-                    return false;
-                }
-            }
-            if (!PackageHelper.isContainerMounted(newCacheId)) {
-                Slog.w(TAG, "Mounting container " + newCacheId);
-                newMountPath = PackageHelper.mountSdDir(newCacheId,
-                        getEncryptKey(), Process.SYSTEM_UID);
-            } else {
-                newMountPath = PackageHelper.getSdDir(newCacheId);
-            }
-            if (newMountPath == null) {
-                Slog.w(TAG, "Failed to get cache path for  " + newCacheId);
-                return false;
-            }
-            Log.i(TAG, "Succesfully renamed " + cid +
-                    " to " + newCacheId +
-                    " at new path: " + newMountPath);
-            cid = newCacheId;
-
-            final File beforeCodeFile = new File(packagePath);
-            setMountPath(newMountPath);
-            final File afterCodeFile = new File(packagePath);
-
-            // Reflect the rename in scanned details
-            pkg.setCodePath(afterCodeFile.getAbsolutePath());
-            pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
-                    afterCodeFile, pkg.baseCodePath));
-            pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
-                    afterCodeFile, pkg.splitCodePaths));
-
-            // Reflect the rename in app info
-            pkg.setApplicationVolumeUuid(pkg.volumeUuid);
-            pkg.setApplicationInfoCodePath(pkg.codePath);
-            pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
-            pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
-            pkg.setApplicationInfoResourcePath(pkg.codePath);
-            pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
-            pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
-
-            return true;
-        }
-
-        private void setMountPath(String mountPath) {
-            final File mountFile = new File(mountPath);
-
-            final File monolithicFile = new File(mountFile, RES_FILE_NAME);
-            if (monolithicFile.exists()) {
-                packagePath = monolithicFile.getAbsolutePath();
-                if (isFwdLocked()) {
-                    resourcePath = new File(mountFile, PUBLIC_RES_FILE_NAME).getAbsolutePath();
-                } else {
-                    resourcePath = packagePath;
-                }
-            } else {
-                packagePath = mountFile.getAbsolutePath();
-                resourcePath = packagePath;
-            }
-        }
-
-        int doPostInstall(int status, int uid) {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                cleanUp();
-            } else {
-                final int groupOwner;
-                final String protectedFile;
-                if (isFwdLocked()) {
-                    groupOwner = UserHandle.getSharedAppGid(uid);
-                    protectedFile = RES_FILE_NAME;
-                } else {
-                    groupOwner = -1;
-                    protectedFile = null;
-                }
-
-                if (uid < Process.FIRST_APPLICATION_UID
-                        || !PackageHelper.fixSdPermissions(cid, groupOwner, protectedFile)) {
-                    Slog.e(TAG, "Failed to finalize " + cid);
-                    PackageHelper.destroySdDir(cid);
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-
-                boolean mounted = PackageHelper.isContainerMounted(cid);
-                if (!mounted) {
-                    PackageHelper.mountSdDir(cid, getEncryptKey(), Process.myUid());
-                }
-            }
-            return status;
-        }
-
-        private void cleanUp() {
-            if (DEBUG_SD_INSTALL) Slog.i(TAG, "cleanUp");
-
-            // Destroy secure container
-            PackageHelper.destroySdDir(cid);
-        }
-
-        private List<String> getAllCodePaths() {
-            final File codeFile = new File(getCodePath());
-            if (codeFile != null && codeFile.exists()) {
-                try {
-                    final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
-                    return pkg.getAllCodePaths();
-                } catch (PackageParserException e) {
-                    // Ignored; we tried our best
-                }
-            }
-            return Collections.EMPTY_LIST;
-        }
-
-        void cleanUpResourcesLI() {
-            // Enumerate all code paths before deleting
-            cleanUpResourcesLI(getAllCodePaths());
-        }
-
-        private void cleanUpResourcesLI(List<String> allCodePaths) {
-            cleanUp();
-            removeDexFiles(allCodePaths, instructionSets);
-        }
-
-        String getPackageName() {
-            return getAsecPackageName(cid);
-        }
-
-        boolean doPostDeleteLI(boolean delete) {
-            if (DEBUG_SD_INSTALL) Slog.i(TAG, "doPostDeleteLI() del=" + delete);
-            final List<String> allCodePaths = getAllCodePaths();
-            boolean mounted = PackageHelper.isContainerMounted(cid);
-            if (mounted) {
-                // Unmount first
-                if (PackageHelper.unMountSdDir(cid)) {
-                    mounted = false;
-                }
-            }
-            if (!mounted && delete) {
-                cleanUpResourcesLI(allCodePaths);
-            }
-            return !mounted;
-        }
-
-        @Override
-        int doPreCopy() {
-            if (isFwdLocked()) {
-                if (!PackageHelper.fixSdPermissions(cid, getPackageUid(DEFAULT_CONTAINER_PACKAGE,
-                        MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM), RES_FILE_NAME)) {
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-            }
-
-            return PackageManager.INSTALL_SUCCEEDED;
-        }
-
-        @Override
-        int doPostCopy(int uid) {
-            if (isFwdLocked()) {
-                if (uid < Process.FIRST_APPLICATION_UID
-                        || !PackageHelper.fixSdPermissions(cid, UserHandle.getSharedAppGid(uid),
-                                RES_FILE_NAME)) {
-                    Slog.e(TAG, "Failed to finalize " + cid);
-                    PackageHelper.destroySdDir(cid);
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-            }
-
-            return PackageManager.INSTALL_SUCCEEDED;
-        }
-    }
-
-    /**
      * Logic to handle movement of existing installed applications.
      */
     class MoveInstallArgs extends InstallArgs {
@@ -17615,9 +15656,9 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {
+    private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
         // Can't rotate keys during boot or if sharedUser.
-        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.sharedUser != null
+        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
                 || !oldPs.keySetData.isUsingUpgradeKeySets()) {
             return false;
         }
@@ -17637,7 +15678,7 @@
         return true;
     }
 
-    private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {
+    private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
         // Upgrade keysets are being used.  Determine if new package has a superset of the
         // required keys.
         long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
@@ -17707,7 +15748,7 @@
             }
 
             // don't allow a system upgrade unless the upgrade hash matches
-            if (oldPackage.restrictUpdateHash != null && oldPackage.isSystemApp()) {
+            if (oldPackage.restrictUpdateHash != null && oldPackage.isSystem()) {
                 byte[] digestBytes = null;
                 try {
                     final MessageDigest digest = MessageDigest.getInstance("SHA-512");
@@ -17974,7 +16015,9 @@
                     setInstallerPackageNameLPw(deletedPackage, installerPackageName);
 
                     // Update permissions for restored package
-                    updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+                    mPermissionManager.updatePermissions(
+                            deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+                            mPermissionCallback);
 
                     mSettings.writeLPr();
                 }
@@ -18116,7 +16159,9 @@
                 setInstallerPackageNameLPw(deletedPackage, installerPackageName);
 
                 // Update permissions for restored package
-                updatePermissionsLPw(deletedPackage, UPDATE_PERMISSIONS_ALL);
+                mPermissionManager.updatePermissions(
+                        deletedPackage.packageName, deletedPackage, false, mPackages.values(),
+                        mPermissionCallback);
 
                 mSettings.writeLPr();
             }
@@ -18213,66 +16258,6 @@
         }
     }
 
-    private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) {
-        // Collect all used permissions in the UID
-        ArraySet<String> usedPermissions = new ArraySet<>();
-        final int packageCount = su.packages.size();
-        for (int i = 0; i < packageCount; i++) {
-            PackageSetting ps = su.packages.valueAt(i);
-            if (ps.pkg == null) {
-                continue;
-            }
-            final int requestedPermCount = ps.pkg.requestedPermissions.size();
-            for (int j = 0; j < requestedPermCount; j++) {
-                String permission = ps.pkg.requestedPermissions.get(j);
-                BasePermission bp = mSettings.mPermissions.get(permission);
-                if (bp != null) {
-                    usedPermissions.add(permission);
-                }
-            }
-        }
-
-        PermissionsState permissionsState = su.getPermissionsState();
-        // Prune install permissions
-        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
-        final int installPermCount = installPermStates.size();
-        for (int i = installPermCount - 1; i >= 0;  i--) {
-            PermissionState permissionState = installPermStates.get(i);
-            if (!usedPermissions.contains(permissionState.getName())) {
-                BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
-                if (bp != null) {
-                    permissionsState.revokeInstallPermission(bp);
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            PackageManager.MASK_PERMISSION_FLAGS, 0);
-                }
-            }
-        }
-
-        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
-
-        // Prune runtime permissions
-        for (int userId : allUserIds) {
-            List<PermissionState> runtimePermStates = permissionsState
-                    .getRuntimePermissionStates(userId);
-            final int runtimePermCount = runtimePermStates.size();
-            for (int i = runtimePermCount - 1; i >= 0; i--) {
-                PermissionState permissionState = runtimePermStates.get(i);
-                if (!usedPermissions.contains(permissionState.getName())) {
-                    BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
-                    if (bp != null) {
-                        permissionsState.revokeRuntimePermission(bp, userId);
-                        permissionsState.updatePermissionFlags(bp, userId,
-                                PackageManager.MASK_PERMISSION_FLAGS, 0);
-                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
-                                runtimePermissionChangedUserIds, userId);
-                    }
-                }
-            }
-        }
-
-        return runtimePermissionChangedUserIds;
-    }
-
     private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
             int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
         // Update the parent package setting
@@ -18289,12 +16274,12 @@
         }
     }
 
-    private void updateSettingsInternalLI(PackageParser.Package newPackage,
+    private void updateSettingsInternalLI(PackageParser.Package pkg,
             String installerPackageName, int[] allUsers, int[] installedForUsers,
             PackageInstalledInfo res, UserHandle user, int installReason) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
-        String pkgName = newPackage.packageName;
+        String pkgName = pkg.packageName;
         synchronized (mPackages) {
             //write settings. the installStatus will be incomplete at this stage.
             //note that the new package setting would have already been
@@ -18306,18 +16291,18 @@
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
 
-        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.codePath);
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
         synchronized (mPackages) {
-            updatePermissionsLPw(newPackage.packageName, newPackage,
-                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
-                            ? UPDATE_PERMISSIONS_ALL : 0));
+// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
+            mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+                    mPermissionCallback);
             // For system-bundled packages, we assume that installing an upgraded version
             // of the package implies that the user actually wants to run that new code,
             // so we enable the package.
             PackageSetting ps = mSettings.mPackages.get(pkgName);
             final int userId = user.getIdentifier();
             if (ps != null) {
-                if (isSystemApp(newPackage)) {
+                if (isSystemApp(pkg)) {
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
@@ -18377,8 +16362,8 @@
                 mSettings.writeKernelMappingLPr(ps);
             }
             res.name = pkgName;
-            res.uid = newPackage.applicationInfo.uid;
-            res.pkg = newPackage;
+            res.uid = pkg.applicationInfo.uid;
+            res.pkg = pkg;
             mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
             mSettings.setInstallerPackageName(pkgName, installerPackageName);
             res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
@@ -18673,8 +16658,9 @@
 
             int N = pkg.permissions.size();
             for (int i = N-1; i >= 0; i--) {
-                PackageParser.Permission perm = pkg.permissions.get(i);
-                BasePermission bp = mSettings.mPermissions.get(perm.info.name);
+                final PackageParser.Permission perm = pkg.permissions.get(i);
+                final BasePermission bp =
+                        (BasePermission) mPermissionManager.getPermissionTEMP(perm.info.name);
 
                 // Don't allow anyone but the system to define ephemeral permissions.
                 if ((perm.info.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
@@ -18691,25 +16677,26 @@
                     // also includes the "updating the same package" case, of course.
                     // "updating same package" could also involve key-rotation.
                     final boolean sigsOk;
-                    if (bp.sourcePackage.equals(pkg.packageName)
-                            && (bp.packageSetting instanceof PackageSetting)
-                            && (shouldCheckUpgradeKeySetLP((PackageSetting) bp.packageSetting,
+                    final String sourcePackageName = bp.getSourcePackageName();
+                    final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+                    if (sourcePackageName.equals(pkg.packageName)
+                            && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
                                     scanFlags))) {
-                        sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
+                        sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
                     } else {
-                        sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
+                        sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
                                 pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
                     }
                     if (!sigsOk) {
                         // If the owning package is the system itself, we log but allow
                         // install to proceed; we fail the install on all other permission
                         // redefinitions.
-                        if (!bp.sourcePackage.equals("android")) {
+                        if (!sourcePackageName.equals("android")) {
                             res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
                                     + pkg.packageName + " attempting to redeclare permission "
-                                    + perm.info.name + " already owned by " + bp.sourcePackage);
+                                    + perm.info.name + " already owned by " + sourcePackageName);
                             res.origPermission = perm.info.name;
-                            res.origPackage = bp.sourcePackage;
+                            res.origPackage = sourcePackageName;
                             return;
                         } else {
                             Slog.w(TAG, "Package " + pkg.packageName
@@ -18728,7 +16715,7 @@
                                 Slog.w(TAG, "Package " + pkg.packageName + " trying to change a "
                                         + "non-runtime permission " + perm.info.name
                                         + " to runtime; keeping old protection level");
-                                perm.info.protectionLevel = bp.protectionLevel;
+                                perm.info.protectionLevel = bp.getProtectionLevel();
                             }
                         }
                     }
@@ -18843,7 +16830,13 @@
         // TODO: Layering violation
         BackgroundDexOptService.notifyPackageChanged(pkg.packageName);
 
-        startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+        if (!instantApp) {
+            startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+        } else {
+            if (DEBUG_DOMAIN_VERIFICATION) {
+                Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
+            }
+        }
 
         try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
                 "installPackageLI")) {
@@ -19069,18 +17062,6 @@
         return installFlags;
     }
 
-    private String getVolumeUuidForPackage(PackageParser.Package pkg) {
-        if (isExternal(pkg)) {
-            if (TextUtils.isEmpty(pkg.volumeUuid)) {
-                return StorageManager.UUID_PRIMARY_PHYSICAL;
-            } else {
-                return pkg.volumeUuid;
-            }
-        } else {
-            return StorageManager.UUID_PRIVATE_INTERNAL;
-        }
-    }
-
     private VersionInfo getSettingsVersionForPackage(PackageParser.Package pkg) {
         if (isExternal(pkg)) {
             if (TextUtils.isEmpty(pkg.volumeUuid)) {
@@ -19725,7 +17706,8 @@
                     if (outInfo != null) {
                         outInfo.removedAppId = removedAppId;
                     }
-                    updatePermissionsLPw(deletedPs.name, null, 0);
+                    mPermissionManager.updatePermissions(
+                            deletedPs.name, null, false, mPackages.values(), mPermissionCallback);
                     if (deletedPs.sharedUser != null) {
                         // Remove permissions associated with package. Since runtime
                         // permissions are per user we have to kill the removed package
@@ -19928,21 +17910,21 @@
             parseFlags |= PackageParser.PARSE_IS_OEM;
         }
 
-        final PackageParser.Package newPkg =
+        final PackageParser.Package pkg =
                 scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
 
         try {
             // update shared libraries for the newly re-installed system package
-            updateSharedLibrariesLPr(newPkg, null);
+            updateSharedLibrariesLPr(pkg, null);
         } catch (PackageManagerException e) {
             Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
         }
 
-        prepareAppDataAfterInstallLIF(newPkg);
+        prepareAppDataAfterInstallLIF(pkg);
 
         // writer
         synchronized (mPackages) {
-            PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+            PackageSetting ps = mSettings.mPackages.get(pkg.packageName);
 
             // Propagate the permissions state as we do not want to drop on the floor
             // runtime permissions. The update permissions method below will take
@@ -19950,8 +17932,8 @@
             if (origPermissionState != null) {
                 ps.getPermissionsState().copyFrom(origPermissionState);
             }
-            updatePermissionsLPw(newPkg.packageName, newPkg,
-                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+            mPermissionManager.updatePermissions(pkg.packageName, pkg, true, mPackages.values(),
+                    mPermissionCallback);
 
             final boolean applyUserRestrictions
                     = (allUserHandles != null) && (origUserHandles != null);
@@ -19984,7 +17966,7 @@
                 mSettings.writeLPr();
             }
         }
-        return newPkg;
+        return pkg;
     }
 
     private boolean deleteInstalledPackageLIF(PackageSetting ps,
@@ -20428,7 +18410,7 @@
                 android.Manifest.permission.CLEAR_APP_USER_DATA, null);
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "clear application data");
 
         final PackageSetting ps = mSettings.getPackageLPr(packageName);
@@ -20567,9 +18549,9 @@
 
         final int permissionCount = ps.pkg.requestedPermissions.size();
         for (int i = 0; i < permissionCount; i++) {
-            String permission = ps.pkg.requestedPermissions.get(i);
-
-            BasePermission bp = mSettings.mPermissions.get(permission);
+            final String permName = ps.pkg.requestedPermissions.get(i);
+            final BasePermission bp =
+                    (BasePermission) mPermissionManager.getPermissionTEMP(permName);
             if (bp == null) {
                 continue;
             }
@@ -20581,7 +18563,7 @@
                 for (int j = 0; j < packageCount; j++) {
                     PackageSetting pkg = ps.sharedUser.packages.valueAt(j);
                     if (pkg.pkg != null && !pkg.pkg.packageName.equals(ps.pkg.packageName)
-                            && pkg.pkg.requestedPermissions.contains(permission)) {
+                            && pkg.pkg.requestedPermissions.contains(permName)) {
                         used = true;
                         break;
                     }
@@ -20591,17 +18573,17 @@
                 }
             }
 
-            PermissionsState permissionsState = ps.getPermissionsState();
+            final PermissionsState permissionsState = ps.getPermissionsState();
 
-            final int oldFlags = permissionsState.getPermissionFlags(bp.name, userId);
+            final int oldFlags = permissionsState.getPermissionFlags(permName, userId);
 
             // Always clear the user settable flags.
-            final boolean hasInstallState = permissionsState.getInstallPermissionState(
-                    bp.name) != null;
+            final boolean hasInstallState =
+                    permissionsState.getInstallPermissionState(permName) != null;
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
-            if (mPermissionReviewRequired
+            if (mSettings.mPermissions.mPermissionReviewRequired
                     && ps.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
             }
@@ -20697,7 +18679,7 @@
         final int callingUid = Binder.getCallingUid();
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_CACHE_FILES, null);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 /* requireFullPermission= */ true, /* checkShell= */ false,
                 "delete application cache files");
         final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
@@ -20817,7 +18799,7 @@
             String opname) {
         // writer
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "add preferred activity");
         if (filter.countActions() == 0) {
             Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
@@ -20882,7 +18864,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "replace preferred activity");
         synchronized (mPackages) {
@@ -21559,8 +19541,11 @@
                     newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
                 }
                 if (DEBUG_BACKUP) {
-                    Slog.v(TAG, "  + Restoring grant: pkg=" + pkgName + " perm=" + permName
-                            + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+                    Slog.v(TAG, "  + Restoring grant:"
+                            + " pkg=" + pkgName
+                            + " perm=" + permName
+                            + " granted=" + isGranted
+                            + " bits=0x" + Integer.toHexString(newFlagSet));
                 }
                 final PackageSetting ps = mSettings.mPackages.get(pkgName);
                 if (ps != null) {
@@ -21569,13 +19554,15 @@
                         Slog.v(TAG, "        + already installed; applying");
                     }
                     PermissionsState perms = ps.getPermissionsState();
-                    BasePermission bp = mSettings.mPermissions.get(permName);
+                    BasePermission bp =
+                            (BasePermission) mPermissionManager.getPermissionTEMP(permName);
                     if (bp != null) {
                         if (isGranted) {
                             perms.grantRuntimePermission(bp, userId);
                         }
                         if (newFlagSet != 0) {
-                            perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+                            perms.updatePermissionFlags(
+                                    bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
                         }
                     }
                 } else {
@@ -21604,7 +19591,8 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         int callingUid = Binder.getCallingUid();
         enforceOwnerRights(ownerPackage, callingUid);
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+        PackageManagerServiceUtils.enforceShellRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
         if (intentFilter.countActions() == 0) {
             Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
             return;
@@ -21635,7 +19623,8 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         final int callingUid = Binder.getCallingUid();
         enforceOwnerRights(ownerPackage, callingUid);
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+        PackageManagerServiceUtils.enforceShellRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
         synchronized (mPackages) {
             CrossProfileIntentResolver resolver =
                     mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
@@ -21865,7 +19854,7 @@
             permission = mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         }
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, true /* checkShell */, "set enabled");
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
         boolean sendNow = false;
@@ -21953,7 +19942,7 @@
             // data partition and then replace the version on the system partition.
             final PackageParser.Package deletedPkg = pkgSetting.pkg;
             final boolean isSystemStub = deletedPkg.isStub
-                    && deletedPkg.isSystemApp();
+                    && deletedPkg.isSystem();
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
@@ -21993,22 +19982,23 @@
                     synchronized (mPackages) {
                         disableSystemPackageLPw(deletedPkg, tmpPkg);
                     }
-                    final PackageParser.Package newPkg;
+                    final PackageParser.Package pkg;
                     try (PackageFreezer freezer =
                             freezePackage(deletedPkg.packageName, "setEnabledSetting")) {
                         final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                                 | PackageParser.PARSE_ENFORCE_CODE;
-                        newPkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
+                        pkg = scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/,
                                 0 /*currentTime*/, null /*user*/);
-                        prepareAppDataAfterInstallLIF(newPkg);
+                        prepareAppDataAfterInstallLIF(pkg);
                         synchronized (mPackages) {
                             try {
-                                updateSharedLibrariesLPr(newPkg, null);
+                                updateSharedLibrariesLPr(pkg, null);
                             } catch (PackageManagerException e) {
                                 Slog.e(TAG, "updateAllSharedLibrariesLPw failed: ", e);
                             }
-                            updatePermissionsLPw(newPkg.packageName, newPkg,
-                                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+                            mPermissionManager.updatePermissions(
+                                    pkg.packageName, pkg, true, mPackages.values(),
+                                    mPermissionCallback);
                             mSettings.writeLPr();
                         }
                     } catch (PackageManagerException e) {
@@ -22048,11 +20038,11 @@
                         }
                         return;
                     }
-                    clearAppDataLIF(newPkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
+                    clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
                             | FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-                    clearAppProfilesLIF(newPkg, UserHandle.USER_ALL);
-                    mDexManager.notifyPackageUpdated(newPkg.packageName,
-                            newPkg.baseCodePath, newPkg.splitCodePaths);
+                    clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
+                    mDexManager.notifyPackageUpdated(pkg.packageName,
+                            pkg.baseCodePath, pkg.splitCodePaths);
                 }
             }
             if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
@@ -22154,7 +20144,7 @@
         if (!sUserManager.exists(userId)) {
             return;
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
                 false /* checkShell */, "flushPackageRestrictions");
         synchronized (mPackages) {
             mSettings.writePackageRestrictionsLPr(userId);
@@ -22196,7 +20186,7 @@
         final int permission = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */, "stop package");
         // writer
         synchronized (mPackages) {
@@ -22236,7 +20226,7 @@
     public int getApplicationEnabledSetting(String packageName, int userId) {
         if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get enabled");
         // reader
         synchronized (mPackages) {
@@ -22251,7 +20241,7 @@
     public int getComponentEnabledSetting(ComponentName component, int userId) {
         if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getComponentEnabled");
         synchronized (mPackages) {
             if (filterAppAccessLPr(mSettings.getPackageLPr(component.getPackageName()), callingUid,
@@ -22349,7 +20339,7 @@
 
         // If we upgraded grant all default permissions before kicking off.
         for (int userId : grantPermissionsUserIds) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
+            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
         }
 
         if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22363,8 +20353,9 @@
         // permissions, ensure permissions are updated. Beware of dragons if you
         // try optimizing this.
         synchronized (mPackages) {
-            updatePermissionsLPw(null, null, StorageManager.UUID_PRIVATE_INTERNAL,
-                    UPDATE_PERMISSIONS_ALL);
+            mPermissionManager.updateAllPermissions(
+                    StorageManager.UUID_PRIVATE_INTERNAL, false, mPackages.values(),
+                    mPermissionCallback);
         }
 
         // Kick off any messages waiting for system ready
@@ -22413,11 +20404,7 @@
         sUserManager.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL);
         reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL);
 
-        if (mPrivappPermissionsViolations != null) {
-            Slog.wtf(TAG,"Signature|privileged permissions not in "
-                    + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
-            mPrivappPermissionsViolations = null;
-        }
+        mPermissionManager.systemReady();
     }
 
     public void waitForAppDataPrepared() {
@@ -22453,85 +20440,6 @@
         return buf.toString();
     }
 
-    static class DumpState {
-        public static final int DUMP_LIBS = 1 << 0;
-        public static final int DUMP_FEATURES = 1 << 1;
-        public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
-        public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
-        public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
-        public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
-        public static final int DUMP_PERMISSIONS = 1 << 6;
-        public static final int DUMP_PACKAGES = 1 << 7;
-        public static final int DUMP_SHARED_USERS = 1 << 8;
-        public static final int DUMP_MESSAGES = 1 << 9;
-        public static final int DUMP_PROVIDERS = 1 << 10;
-        public static final int DUMP_VERIFIERS = 1 << 11;
-        public static final int DUMP_PREFERRED = 1 << 12;
-        public static final int DUMP_PREFERRED_XML = 1 << 13;
-        public static final int DUMP_KEYSETS = 1 << 14;
-        public static final int DUMP_VERSION = 1 << 15;
-        public static final int DUMP_INSTALLS = 1 << 16;
-        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
-        public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
-        public static final int DUMP_FROZEN = 1 << 19;
-        public static final int DUMP_DEXOPT = 1 << 20;
-        public static final int DUMP_COMPILER_STATS = 1 << 21;
-        public static final int DUMP_CHANGES = 1 << 22;
-        public static final int DUMP_VOLUMES = 1 << 23;
-
-        public static final int OPTION_SHOW_FILTERS = 1 << 0;
-
-        private int mTypes;
-
-        private int mOptions;
-
-        private boolean mTitlePrinted;
-
-        private SharedUserSetting mSharedUser;
-
-        public boolean isDumping(int type) {
-            if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
-                return true;
-            }
-
-            return (mTypes & type) != 0;
-        }
-
-        public void setDump(int type) {
-            mTypes |= type;
-        }
-
-        public boolean isOptionEnabled(int option) {
-            return (mOptions & option) != 0;
-        }
-
-        public void setOptionEnabled(int option) {
-            mOptions |= option;
-        }
-
-        public boolean onTitlePrinted() {
-            final boolean printed = mTitlePrinted;
-            mTitlePrinted = true;
-            return printed;
-        }
-
-        public boolean getTitlePrinted() {
-            return mTitlePrinted;
-        }
-
-        public void setTitlePrinted(boolean enabled) {
-            mTitlePrinted = enabled;
-        }
-
-        public SharedUserSetting getSharedUser() {
-            return mSharedUser;
-        }
-
-        public void setSharedUser(SharedUserSetting user) {
-            mSharedUser = user;
-        }
-    }
-
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -22988,22 +20896,6 @@
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
                 mSettings.dumpPermissionsLPr(pw, packageName, permissionNames, dumpState);
-                if (packageName == null && permissionNames == null) {
-                    for (int iperm=0; iperm<mAppOpPermissionPackages.size(); iperm++) {
-                        if (iperm == 0) {
-                            if (dumpState.onTitlePrinted())
-                                pw.println();
-                            pw.println("AppOp Permissions:");
-                        }
-                        pw.print("  AppOp Permission ");
-                        pw.print(mAppOpPermissionPackages.keyAt(iperm));
-                        pw.println(":");
-                        ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm);
-                        for (int ipkg=0; ipkg<pkgs.size(); ipkg++) {
-                            pw.print("    "); pw.println(pkgs.valueAt(ipkg));
-                        }
-                    }
-                }
             }
 
             if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
@@ -23233,11 +21125,7 @@
         synchronized (mAvailableFeatures) {
             final int count = mAvailableFeatures.size();
             for (int i = 0; i < count; i++) {
-                final FeatureInfo feat = mAvailableFeatures.valueAt(i);
-                final long featureToken = proto.start(PackageServiceDumpProto.FEATURES);
-                proto.write(PackageServiceDumpProto.FeatureProto.NAME, feat.name);
-                proto.write(PackageServiceDumpProto.FeatureProto.VERSION, feat.version);
-                proto.end(featureToken);
+                mAvailableFeatures.valueAt(i).writeToProto(proto, PackageServiceDumpProto.FEATURES);
             }
         }
     }
@@ -23269,7 +21157,7 @@
     }
 
     private void dumpDexoptStateLPr(PrintWriter pw, String packageName) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println();
         ipw.println("Dexopt state:");
         ipw.increaseIndent();
@@ -23296,7 +21184,7 @@
     }
 
     private void dumpCompilerStatsLPr(PrintWriter pw, String packageName) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
         ipw.println();
         ipw.println("Compiler stats:");
         ipw.increaseIndent();
@@ -23389,135 +21277,6 @@
         }
     }
 
-    /*
-     * Update media status on PackageManager.
-     */
-    @Override
-    public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
-        enforceSystemOrRoot("Media status can only be updated by the system");
-        // reader; this apparently protects mMediaMounted, but should probably
-        // be a different lock in that case.
-        synchronized (mPackages) {
-            Log.i(TAG, "Updating external media status from "
-                    + (mMediaMounted ? "mounted" : "unmounted") + " to "
-                    + (mediaStatus ? "mounted" : "unmounted"));
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus
-                        + ", mMediaMounted=" + mMediaMounted);
-            if (mediaStatus == mMediaMounted) {
-                final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1
-                        : 0, -1);
-                mHandler.sendMessage(msg);
-                return;
-            }
-            mMediaMounted = mediaStatus;
-        }
-        // Queue up an async operation since the package installation may take a
-        // little while.
-        mHandler.post(new Runnable() {
-            public void run() {
-                updateExternalMediaStatusInner(mediaStatus, reportStatus, true);
-            }
-        });
-    }
-
-    /**
-     * Called by StorageManagerService when the initial ASECs to scan are available.
-     * Should block until all the ASEC containers are finished being scanned.
-     */
-    public void scanAvailableAsecs() {
-        updateExternalMediaStatusInner(true, false, false);
-    }
-
-    /*
-     * Collect information of applications on external media, map them against
-     * existing containers and update information based on current mount status.
-     * Please note that we always have to report status if reportStatus has been
-     * set to true especially when unloading packages.
-     */
-    private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus,
-            boolean externalStorage) {
-        ArrayMap<AsecInstallArgs, String> processCids = new ArrayMap<>();
-        int[] uidArr = EmptyArray.INT;
-
-        final String[] list = PackageHelper.getSecureContainerList();
-        if (ArrayUtils.isEmpty(list)) {
-            Log.i(TAG, "No secure containers found");
-        } else {
-            // Process list of secure containers and categorize them
-            // as active or stale based on their package internal state.
-
-            // reader
-            synchronized (mPackages) {
-                for (String cid : list) {
-                    // Leave stages untouched for now; installer service owns them
-                    if (PackageInstallerService.isStageName(cid)) continue;
-
-                    if (DEBUG_SD_INSTALL)
-                        Log.i(TAG, "Processing container " + cid);
-                    String pkgName = getAsecPackageName(cid);
-                    if (pkgName == null) {
-                        Slog.i(TAG, "Found stale container " + cid + " with no package name");
-                        continue;
-                    }
-                    if (DEBUG_SD_INSTALL)
-                        Log.i(TAG, "Looking for pkg : " + pkgName);
-
-                    final PackageSetting ps = mSettings.mPackages.get(pkgName);
-                    if (ps == null) {
-                        Slog.i(TAG, "Found stale container " + cid + " with no matching settings");
-                        continue;
-                    }
-
-                    /*
-                     * Skip packages that are not external if we're unmounting
-                     * external storage.
-                     */
-                    if (externalStorage && !isMounted && !isExternal(ps)) {
-                        continue;
-                    }
-
-                    final AsecInstallArgs args = new AsecInstallArgs(cid,
-                            getAppDexInstructionSets(ps), ps.isForwardLocked());
-                    // The package status is changed only if the code path
-                    // matches between settings and the container id.
-                    if (ps.codePathString != null
-                            && ps.codePathString.startsWith(args.getCodePath())) {
-                        if (DEBUG_SD_INSTALL) {
-                            Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName
-                                    + " at code path: " + ps.codePathString);
-                        }
-
-                        // We do have a valid package installed on sdcard
-                        processCids.put(args, ps.codePathString);
-                        final int uid = ps.appId;
-                        if (uid != -1) {
-                            uidArr = ArrayUtils.appendInt(uidArr, uid);
-                        }
-                    } else {
-                        Slog.i(TAG, "Found stale container " + cid + ": expected codePath="
-                                + ps.codePathString);
-                    }
-                }
-            }
-
-            Arrays.sort(uidArr);
-        }
-
-        // Process packages with valid entries.
-        if (isMounted) {
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Loading packages");
-            loadMediaPackages(processCids, uidArr, externalStorage);
-            startCleaningPackages();
-            mInstallerService.onSecureContainersAvailable();
-        } else {
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Unloading packages");
-            unloadMediaPackages(processCids, uidArr, reportStatus);
-        }
-    }
-
     private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
             ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) {
         final int size = infos.size();
@@ -23557,193 +21316,6 @@
         }
     }
 
-   /*
-     * Look at potentially valid container ids from processCids If package
-     * information doesn't match the one on record or package scanning fails,
-     * the cid is added to list of removeCids. We currently don't delete stale
-     * containers.
-     */
-    private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr,
-            boolean externalStorage) {
-        ArrayList<String> pkgList = new ArrayList<String>();
-        Set<AsecInstallArgs> keys = processCids.keySet();
-
-        for (AsecInstallArgs args : keys) {
-            String codePath = processCids.get(args);
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Loading container : " + args.cid);
-            int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            try {
-                // Make sure there are no container errors first.
-                if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
-                    Slog.e(TAG, "Failed to mount cid : " + args.cid
-                            + " when installing from sdcard");
-                    continue;
-                }
-                // Check code path here.
-                if (codePath == null || !codePath.startsWith(args.getCodePath())) {
-                    Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()
-                            + " does not match one in settings " + codePath);
-                    continue;
-                }
-                // Parse package
-                int parseFlags = mDefParseFlags;
-                if (args.isExternalAsec()) {
-                    parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE;
-                }
-                if (args.isFwdLocked()) {
-                    parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
-                }
-
-                synchronized (mInstallLock) {
-                    PackageParser.Package pkg = null;
-                    try {
-                        // Sadly we don't know the package name yet to freeze it
-                        pkg = scanPackageTracedLI(new File(codePath), parseFlags,
-                                SCAN_IGNORE_FROZEN, 0, null);
-                    } catch (PackageManagerException e) {
-                        Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
-                    }
-                    // Scan the package
-                    if (pkg != null) {
-                        /*
-                         * TODO why is the lock being held? doPostInstall is
-                         * called in other places without the lock. This needs
-                         * to be straightened out.
-                         */
-                        // writer
-                        synchronized (mPackages) {
-                            retCode = PackageManager.INSTALL_SUCCEEDED;
-                            pkgList.add(pkg.packageName);
-                            // Post process args
-                            args.doPostInstall(PackageManager.INSTALL_SUCCEEDED,
-                                    pkg.applicationInfo.uid);
-                        }
-                    } else {
-                        Slog.i(TAG, "Failed to install pkg from  " + codePath + " from sdcard");
-                    }
-                }
-
-            } finally {
-                if (retCode != PackageManager.INSTALL_SUCCEEDED) {
-                    Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode);
-                }
-            }
-        }
-        // writer
-        synchronized (mPackages) {
-            // If the platform SDK has changed since the last time we booted,
-            // we need to re-grant app permission to catch any new ones that
-            // appear. This is really a hack, and means that apps can in some
-            // cases get permissions that the user didn't initially explicitly
-            // allow... it would be nice to have some better way to handle
-            // this situation.
-            final VersionInfo ver = externalStorage ? mSettings.getExternalVersion()
-                    : mSettings.getInternalVersion();
-            final String volumeUuid = externalStorage ? StorageManager.UUID_PRIMARY_PHYSICAL
-                    : StorageManager.UUID_PRIVATE_INTERNAL;
-
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
-                logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
-                        + mSdkVersion + "; regranting permissions for external");
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
-            }
-            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
-
-            // Yay, everything is now upgraded
-            ver.forceCurrent();
-
-            // can downgrade to reader
-            // Persist settings
-            mSettings.writeLPr();
-        }
-        // Send a broadcast to let everyone know we are done processing
-        if (pkgList.size() > 0) {
-            sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null);
-        }
-    }
-
-   /*
-     * Utility method to unload a list of specified containers
-     */
-    private void unloadAllContainers(Set<AsecInstallArgs> cidArgs) {
-        // Just unmount all valid containers.
-        for (AsecInstallArgs arg : cidArgs) {
-            synchronized (mInstallLock) {
-                arg.doPostDeleteLI(false);
-           }
-       }
-   }
-
-    /*
-     * Unload packages mounted on external media. This involves deleting package
-     * data from internal structures, sending broadcasts about disabled packages,
-     * gc'ing to free up references, unmounting all secure containers
-     * corresponding to packages on external media, and posting a
-     * UPDATED_MEDIA_STATUS message if status has been requested. Please note
-     * that we always have to post this message if status has been requested no
-     * matter what.
-     */
-    private void unloadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int uidArr[],
-            final boolean reportStatus) {
-        if (DEBUG_SD_INSTALL)
-            Log.i(TAG, "unloading media packages");
-        ArrayList<String> pkgList = new ArrayList<String>();
-        ArrayList<AsecInstallArgs> failedList = new ArrayList<AsecInstallArgs>();
-        final Set<AsecInstallArgs> keys = processCids.keySet();
-        for (AsecInstallArgs args : keys) {
-            String pkgName = args.getPackageName();
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Trying to unload pkg : " + pkgName);
-            // Delete package internally
-            PackageRemovedInfo outInfo = new PackageRemovedInfo(this);
-            synchronized (mInstallLock) {
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                final boolean res;
-                try (PackageFreezer freezer = freezePackageForDelete(pkgName, deleteFlags,
-                        "unloadMediaPackages")) {
-                    res = deletePackageLIF(pkgName, null, false, null, deleteFlags, outInfo, false,
-                            null);
-                }
-                if (res) {
-                    pkgList.add(pkgName);
-                } else {
-                    Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName);
-                    failedList.add(args);
-                }
-            }
-        }
-
-        // reader
-        synchronized (mPackages) {
-            // We didn't update the settings after removing each package;
-            // write them now for all packages.
-            mSettings.writeLPr();
-        }
-
-        // We have to absolutely send UPDATED_MEDIA_STATUS only
-        // after confirming that all the receivers processed the ordered
-        // broadcast when packages get disabled, force a gc to clean things up.
-        // and unload all the containers.
-        if (pkgList.size() > 0) {
-            sendResourcesChangedBroadcast(false, false, pkgList, uidArr,
-                    new IIntentReceiver.Stub() {
-                public void performReceive(Intent intent, int resultCode, String data,
-                        Bundle extras, boolean ordered, boolean sticky,
-                        int sendingUser) throws RemoteException {
-                    Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS,
-                            reportStatus ? 1 : 0, 1, keys);
-                    mHandler.sendMessage(msg);
-                }
-            });
-        } else {
-            Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1,
-                    keys);
-            mHandler.sendMessage(msg);
-        }
-    }
-
     private void loadPrivatePackages(final VolumeInfo vol) {
         mHandler.post(new Runnable() {
             @Override
@@ -23817,13 +21389,13 @@
         }
 
         synchronized (mPackages) {
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
+            final boolean sdkUpdated = (ver.sdkVersion != mSdkVersion);
+            if (sdkUpdated) {
                 logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
                         + mSdkVersion + "; regranting permissions for " + volumeUuid);
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
             }
-            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
+            mPermissionManager.updateAllPermissions(volumeUuid, sdkUpdated, mPackages.values(),
+                    mPermissionCallback);
 
             // Yay, everything is now upgraded
             ver.forceCurrent();
@@ -24271,7 +21843,7 @@
      * requested by the app.
      */
     private boolean maybeMigrateAppDataLIF(PackageParser.Package pkg, int userId) {
-        if (pkg.isSystemApp() && !StorageManager.isFileEncryptedNativeOrEmulated()
+        if (pkg.isSystem() && !StorageManager.isFileEncryptedNativeOrEmulated()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             final int storageTarget = pkg.applicationInfo.isDefaultToDeviceProtectedStorage()
                     ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
@@ -24829,15 +22401,19 @@
     }
 
     void onNewUserCreated(final int userId) {
-        mDefaultPermissionPolicy.grantDefaultPermissions(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.
-        // Hence, if a new user is added we have to propagate dangerous
-        // permission grants for these legacy apps.
-        if (mPermissionReviewRequired) {
-            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
-                    | UPDATE_PERMISSIONS_REPLACE_ALL);
+        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.
+            // Hence, if a new user is added we have to propagate dangerous
+            // permission grants for these legacy apps.
+            if (mSettings.mPermissions.mPermissionReviewRequired) {
+// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
+                mPermissionManager.updateAllPermissions(
+                        StorageManager.UUID_PRIVATE_INTERNAL, true, mPackages.values(),
+                        mPermissionCallback);
+            }
         }
     }
 
@@ -24861,7 +22437,8 @@
             synchronized (mPackages) {
                 if (mSettings.mReadExternalStorageEnforced == null
                         || mSettings.mReadExternalStorageEnforced != enforced) {
-                    mSettings.mReadExternalStorageEnforced = enforced;
+                    mSettings.mReadExternalStorageEnforced =
+                            enforced ? Boolean.TRUE : Boolean.FALSE;
                     mSettings.writeLPr();
                 }
             }
@@ -25268,70 +22845,122 @@
 
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
-        public void setLocationPackagesProvider(PackagesProvider provider) {
+        public void updatePermissionFlagsTEMP(String permName, String packageName, int flagMask,
+                int flagValues, int userId) {
+            PackageManagerService.this.updatePermissionFlags(
+                    permName, packageName, flagMask, flagValues, userId);
+        }
+
+        @Override
+        public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
+            return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);
+        }
+
+        @Override
+        public boolean isInstantApp(String packageName, int userId) {
+            return PackageManagerService.this.isInstantApp(packageName, userId);
+        }
+
+        @Override
+        public String getInstantAppPackageName(int uid) {
+            return PackageManagerService.this.getInstantAppPackageName(uid);
+        }
+
+        @Override
+        public boolean filterAppAccess(PackageParser.Package pkg, int callingUid, int userId) {
             synchronized (mPackages) {
-                mDefaultPermissionPolicy.setLocationPackagesProviderLPw(provider);
+                return PackageManagerService.this.filterAppAccessLPr(
+                        (PackageSetting) pkg.mExtras, callingUid, userId);
             }
         }
 
         @Override
+        public PackageParser.Package getPackage(String packageName) {
+            synchronized (mPackages) {
+                packageName = resolveInternalPackageNameLPr(
+                        packageName, PackageManager.VERSION_CODE_HIGHEST);
+                return mPackages.get(packageName);
+            }
+        }
+
+        @Override
+        public PackageParser.Package getDisabledPackage(String packageName) {
+            synchronized (mPackages) {
+                final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
+                return (ps != null) ? ps.pkg : null;
+            }
+        }
+
+        @Override
+        public String getKnownPackageName(int knownPackage, int userId) {
+            switch(knownPackage) {
+                case PackageManagerInternal.PACKAGE_BROWSER:
+                    return getDefaultBrowserPackageName(userId);
+                case PackageManagerInternal.PACKAGE_INSTALLER:
+                    return mRequiredInstallerPackage;
+                case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
+                    return mSetupWizardPackage;
+                case PackageManagerInternal.PACKAGE_SYSTEM:
+                    return "android";
+                case PackageManagerInternal.PACKAGE_VERIFIER:
+                    return mRequiredVerifierPackage;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isResolveActivityComponent(ComponentInfo component) {
+            return mResolveActivity.packageName.equals(component.packageName)
+                    && mResolveActivity.name.equals(component.name);
+        }
+
+        @Override
+        public void setLocationPackagesProvider(PackagesProvider provider) {
+            mDefaultPermissionPolicy.setLocationPackagesProvider(provider);
+        }
+
+        @Override
         public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setVoiceInteractionPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setVoiceInteractionPackagesProvider(provider);
         }
 
         @Override
         public void setSmsAppPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSmsAppPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSmsAppPackagesProvider(provider);
         }
 
         @Override
         public void setDialerAppPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setDialerAppPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setDialerAppPackagesProvider(provider);
         }
 
         @Override
         public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSimCallManagerPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSimCallManagerPackagesProvider(provider);
         }
 
         @Override
         public void setSyncAdapterPackagesprovider(SyncAdapterPackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSyncAdapterPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSyncAdapterPackagesProvider(provider);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsAppLPr(
-                        packageName, userId);
-            }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsApp(packageName, userId);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
             synchronized (mPackages) {
                 mSettings.setDefaultDialerPackageNameLPw(packageName, userId);
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerAppLPr(
-                        packageName, userId);
             }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerApp(packageName, userId);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManagerLPr(
-                        packageName, userId);
-            }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManager(
+                    packageName, userId);
         }
 
         @Override
@@ -25365,24 +22994,8 @@
         @Override
         public boolean isPermissionsReviewRequired(String packageName, int userId) {
             synchronized (mPackages) {
-                // If we do not support permission review, done.
-                if (!mPermissionReviewRequired) {
-                    return false;
-                }
-
-                PackageSetting packageSetting = mSettings.mPackages.get(packageName);
-                if (packageSetting == null) {
-                    return false;
-                }
-
-                // Permission review applies only to apps not supporting the new permission model.
-                if (packageSetting.pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
-                    return false;
-                }
-
-                // Legacy apps have the permission and get user consent on launch.
-                PermissionsState permissionsState = packageSetting.getPermissionsState();
-                return permissionsState.isPermissionReviewRequired(userId);
+                return mPermissionManager.isPermissionsReviewRequired(
+                        mPackages.get(packageName), userId);
             }
         }
 
@@ -25418,6 +23031,15 @@
         }
 
         @Override
+        public List<ResolveInfo> queryIntentServices(
+                Intent intent, int flags, int callingUid, int userId) {
+            final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+            return PackageManagerService.this
+                    .queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid,
+                            false);
+        }
+
+        @Override
         public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
                 int userId) {
             return PackageManagerService.this.getHomeActivitiesAsUser(allHomeCandidates, userId);
@@ -25452,17 +23074,19 @@
         }
 
         @Override
-        public void grantRuntimePermission(String packageName, String name, int userId,
+        public void grantRuntimePermission(String packageName, String permName, int userId,
                 boolean overridePolicy) {
-            PackageManagerService.this.grantRuntimePermission(packageName, name, userId,
-                    overridePolicy);
+            PackageManagerService.this.mPermissionManager.grantRuntimePermission(
+                    permName, packageName, overridePolicy, getCallingUid(), userId,
+                    mPermissionCallback);
         }
 
         @Override
-        public void revokeRuntimePermission(String packageName, String name, int userId,
+        public void revokeRuntimePermission(String packageName, String permName, int userId,
                 boolean overridePolicy) {
-            PackageManagerService.this.revokeRuntimePermission(packageName, name, userId,
-                    overridePolicy);
+            mPermissionManager.revokeRuntimePermission(
+                    permName, packageName, overridePolicy, getCallingUid(), userId,
+                    mPermissionCallback);
         }
 
         @Override
@@ -25525,6 +23149,16 @@
         }
 
         @Override
+        public boolean isLegacySystemApp(Package pkg) {
+            synchronized (mPackages) {
+                final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                return mPromoteSystemApps
+                        && ps.isSystem()
+                        && mExistingSystemPackages.contains(ps.name);
+            }
+        }
+
+        @Override
         public List<PackageInfo> getOverlayPackages(int userId) {
             final ArrayList<PackageInfo> overlayPackages = new ArrayList<PackageInfo>();
             synchronized (mPackages) {
@@ -25584,9 +23218,9 @@
 
         @Override
         public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-                int flags, int userId) {
+                int flags, int userId, boolean resolveForStart) {
             return resolveIntentInternal(
-                    intent, resolvedType, flags, userId, true /*resolveForStart*/);
+                    intent, resolvedType, flags, userId, resolveForStart);
         }
 
         @Override
@@ -25596,6 +23230,12 @@
         }
 
         @Override
+        public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+            return PackageManagerService.this.resolveContentProviderInternal(
+                    name, flags, userId);
+        }
+
+        @Override
         public void addIsolatedUid(int isolatedUid, int ownerUid) {
             synchronized (mPackages) {
                 mIsolatedOwners.put(isolatedUid, ownerUid);
@@ -25642,7 +23282,7 @@
         synchronized (mPackages) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierAppsLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierApps(
                         packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -25656,7 +23296,7 @@
         synchronized (mPackages) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServicesLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServices(
                         packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -25731,7 +23371,7 @@
     @Override
     public int getInstallReason(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get install reason");
         synchronized (mPackages) {
@@ -25809,7 +23449,7 @@
     public String getInstantAppAndroidId(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS,
                 "getInstantAppAndroidId");
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppAndroidId");
         // Make sure the target is an Instant App.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 25fef0a..67e06dd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -22,17 +22,24 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
+import android.os.Debug;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.system.ErrnoException;
+import android.system.Os;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
+import android.util.jar.StrictJarFile;
 import dalvik.system.VMRuntime;
 import libcore.io.Libcore;
 
@@ -41,9 +48,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
 
 /**
  * Class containing helper methods for the PackageManagerService.
@@ -224,7 +233,7 @@
      */
     public static String realpath(File path) throws IOException {
         try {
-            return Libcore.os.realpath(path.getAbsolutePath());
+            return Os.realpath(path.getAbsolutePath());
         } catch (ErrnoException ee) {
             throw ee.rethrowAsIOException();
         }
@@ -253,4 +262,73 @@
         }
         return false;
     }
+
+    /**
+     * Checks that the archive located at {@code fileName} has uncompressed dex file and so
+     * files that can be direclty mapped.
+     */
+    public static void logApkHasUncompressedCode(String fileName) {
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(fileName,
+                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+            Iterator<ZipEntry> it = jarFile.iterator();
+            while (it.hasNext()) {
+                ZipEntry entry = it.next();
+                if (entry.getName().endsWith(".dex")) {
+                    if (entry.getMethod() != ZipEntry.STORED) {
+                        Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
+                                entry.getName());
+                    } else if ((entry.getDataOffset() & 0x3) != 0) {
+                        Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
+                                entry.getName());
+                    }
+                } else if (entry.getName().endsWith(".so")) {
+                    if (entry.getMethod() != ZipEntry.STORED) {
+                        Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
+                                entry.getName());
+                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
+                        Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
+                                entry.getName());
+                    }
+                }
+            }
+        } catch (IOException ignore) {
+            Slog.wtf(TAG, "Error when parsing APK " + fileName);
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {}
+        }
+        return;
+    }
+
+    /**
+     * Checks that the APKs in the given package have uncompressed dex file and so
+     * files that can be direclty mapped.
+     */
+    public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
+        logApkHasUncompressedCode(pkg.baseCodePath);
+        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+                logApkHasUncompressedCode(pkg.splitCodePaths[i]);
+            }
+        }
+    }
+
+    public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
+        if (callingUid == Process.SHELL_UID) {
+            if (userHandle >= 0
+                    && PackageManagerService.sUserManager.hasUserRestriction(
+                            restriction, userHandle)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + userHandle);
+            } else if (userHandle < 0) {
+                Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
+                        + userHandle + "\n\t" + Debug.getCallers(3));
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1f03e66..807eb1a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -16,14 +16,17 @@
 
 package com.android.server.pm;
 
+import android.accounts.IAccountManager;
 import android.app.ActivityManager;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
@@ -41,6 +44,7 @@
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.content.pm.VersionedPackage;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
@@ -49,15 +53,24 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IUserManager;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.text.TextUtils;
-import android.util.ArrayMap;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.PrintWriterPrinter;
+
 import com.android.internal.content.PackageHelper;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.SizedInputStream;
 import com.android.server.SystemConfig;
 
@@ -66,7 +79,6 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -81,11 +93,15 @@
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
 
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+
 class PackageManagerShellCommand extends ShellCommand {
     /** Path for streaming APK content */
     private static final String STDIN_PATH = "-";
-    /** Whether or not APK content must be streamed from stdin */
-    private static final boolean FORCE_STREAM_INSTALL = true;
 
     final IPackageManager mInterface;
     final private WeakHashMap<String, Resources> mResourceCache =
@@ -107,6 +123,20 @@
         final PrintWriter pw = getOutPrintWriter();
         try {
             switch(cmd) {
+                case "path":
+                    return runPath();
+                case "dump":
+                    return runDump();
+                case "list":
+                    return runList();
+                case "resolve-activity":
+                    return runResolveActivity();
+                case "query-activities":
+                    return runQueryIntentActivities();
+                case "query-services":
+                    return runQueryIntentServices();
+                case "query-receivers":
+                    return runQueryIntentReceivers();
                 case "install":
                     return runInstall();
                 case "install-abandon":
@@ -122,44 +152,99 @@
                     return runInstallWrite();
                 case "install-existing":
                     return runInstallExisting();
+                case "set-install-location":
+                    return runSetInstallLocation();
+                case "get-install-location":
+                    return runGetInstallLocation();
+                case "move-package":
+                    return runMovePackage();
+                case "move-primary-storage":
+                    return runMovePrimaryStorage();
                 case "compile":
                     return runCompile();
                 case "reconcile-secondary-dex-files":
                     return runreconcileSecondaryDexFiles();
+                case "force-dex-opt":
+                    return runForceDexOpt();
                 case "bg-dexopt-job":
                     return runDexoptJob();
                 case "dump-profiles":
                     return runDumpProfiles();
-                case "list":
-                    return runList();
                 case "uninstall":
                     return runUninstall();
-                case "resolve-activity":
-                    return runResolveActivity();
-                case "query-activities":
-                    return runQueryIntentActivities();
-                case "query-services":
-                    return runQueryIntentServices();
-                case "query-receivers":
-                    return runQueryIntentReceivers();
+                case "clear":
+                    return runClear();
+                case "enable":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+                case "disable":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+                case "disable-user":
+                    return runSetEnabledSetting(
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
+                case "disable-until-used":
+                    return runSetEnabledSetting(
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+                case "default-state":
+                    return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+                case "hide":
+                    return runSetHiddenSetting(true);
+                case "unhide":
+                    return runSetHiddenSetting(false);
                 case "suspend":
                     return runSuspend(true);
                 case "unsuspend":
                     return runSuspend(false);
-                case "set-home-activity":
-                    return runSetHomeActivity();
+                case "grant":
+                    return runGrantRevokePermission(true);
+                case "revoke":
+                    return runGrantRevokePermission(false);
+                case "reset-permissions":
+                    return runResetPermissions();
+                case "set-permission-enforced":
+                    return runSetPermissionEnforced();
                 case "get-privapp-permissions":
                     return runGetPrivappPermissions();
                 case "get-privapp-deny-permissions":
                     return runGetPrivappDenyPermissions();
                 case "get-oem-permissions":
                     return runGetOemPermissions();
+                case "set-app-link":
+                    return runSetAppLink();
+                case "get-app-link":
+                    return runGetAppLink();
+                case "trim-caches":
+                    return runTrimCaches();
+                case "create-user":
+                    return runCreateUser();
+                case "remove-user":
+                    return runRemoveUser();
+                case "set-user-restriction":
+                    return runSetUserRestriction();
+                case "get-max-users":
+                    return runGetMaxUsers();
+                case "set-home-activity":
+                    return runSetHomeActivity();
+                case "set-installer":
+                    return runSetInstaller();
                 case "get-instantapp-resolver":
                     return runGetInstantAppResolver();
                 case "has-feature":
                     return runHasFeature();
-                default:
+                default: {
+                    String nextArg = getNextArg();
+                    if (nextArg == null) {
+                        if (cmd.equalsIgnoreCase("-l")) {
+                            return runListPackages(false);
+                        } else if (cmd.equalsIgnoreCase("-lf")) {
+                            return runListPackages(true);
+                        }
+                    } else if (getNextArg() == null) {
+                        if (cmd.equalsIgnoreCase("-p")) {
+                            return displayPackageFilePath(nextArg, UserHandle.USER_SYSTEM);
+                        }
+                    }
                     return handleDefaultCommands(cmd);
+                }
             }
         } catch (RemoteException e) {
             pw.println("Remote exception: " + e);
@@ -168,373 +253,64 @@
     }
 
     private void setParamsSize(InstallParams params, String inPath) {
-        // If we're forced to stream the package, the params size
-        // must be set via command-line argument. There's nothing
-        // to do here.
-        if (FORCE_STREAM_INSTALL) {
-            return;
-        }
-        final PrintWriter pw = getOutPrintWriter();
         if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
-            File file = new File(inPath);
-            if (file.isFile()) {
+            final ParcelFileDescriptor fd = openFileForSystem(inPath, "r");
+            if (fd == null) {
+                getErrPrintWriter().println("Error: Can't open file: " + inPath);
+                throw new IllegalArgumentException("Error: Can't open file: " + inPath);
+            }
+            try {
+                ApkLite baseApk = PackageParser.parseApkLite(fd.getFileDescriptor(), inPath, 0);
+                PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
+                        null, null);
+                params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
+                        pkgLite, params.sessionParams.abiOverride));
+            } catch (PackageParserException | IOException e) {
+                getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
+                throw new IllegalArgumentException(
+                        "Error: Failed to parse APK file: " + inPath, e);
+            } finally {
                 try {
-                    ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
-                            null, null);
-                    params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
-                            pkgLite, false, params.sessionParams.abiOverride));
-                } catch (PackageParserException | IOException e) {
-                    pw.println("Error: Failed to parse APK file: " + file);
-                    throw new IllegalArgumentException(
-                            "Error: Failed to parse APK file: " + file, e);
+                    fd.close();
+                } catch (IOException e) {
                 }
-            } else {
-                pw.println("Error: Can't open non-file: " + inPath);
-                throw new IllegalArgumentException("Error: Can't open non-file: " + inPath);
             }
         }
     }
-
-    private int runInstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams params = makeInstallParams();
-        final String inPath = getNextArg();
-
-        setParamsSize(params, inPath);
-        final int sessionId = doCreateSession(params.sessionParams,
-                params.installerPackageName, params.userId);
-        boolean abandonSession = true;
-        try {
-            if (inPath == null && params.sessionParams.sizeBytes == -1) {
-                pw.println("Error: must either specify a package size or an APK file");
-                return 1;
+    /**
+     * Displays the package file for a package.
+     * @param pckg
+     */
+    private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
+        PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+        if (info != null && info.applicationInfo != null) {
+            final PrintWriter pw = getOutPrintWriter();
+            pw.print("package:");
+            pw.println(info.applicationInfo.sourceDir);
+            if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
+                for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
+                    pw.print("package:");
+                    pw.println(splitSourceDir);
+                }
             }
-            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
-                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            if (doCommitSession(sessionId, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            abandonSession = false;
-            pw.println("Success");
             return 0;
-        } finally {
-            if (abandonSession) {
-                try {
-                    doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
-                }
-            }
         }
+        return 1;
     }
 
-    private int runSuspend(boolean suspendedState) {
-        final PrintWriter pw = getOutPrintWriter();
+    private int runPath() throws RemoteException {
         int userId = UserHandle.USER_SYSTEM;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
         }
 
-        String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
+        String pkg = getNextArgRequired();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
             return 1;
         }
-
-        try {
-            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
-                    userId);
-            pw.println("Package " + packageName + " new suspended state: "
-                    + mInterface.isPackageSuspendedForUser(packageName, userId));
-            return 0;
-        } catch (RemoteException | IllegalArgumentException e) {
-            pw.println(e.toString());
-            return 1;
-        }
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doAbandonSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(getNextArg());
-        return doCommitSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final InstallParams installParams = makeInstallParams();
-        final int sessionId = doCreateSession(installParams.sessionParams,
-                installParams.installerPackageName, installParams.userId);
-
-        // NOTE: adb depends on parsing this string
-        pw.println("Success: created install session [" + sessionId + "]");
-        return 0;
-    }
-
-    private int runInstallWrite() throws RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(getNextArg());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(getNextArg());
-        final String splitName = getNextArg();
-        final String path = getNextArg();
-        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
-    }
-
-    private int runInstallRemove() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-
-        final int sessionId = Integer.parseInt(getNextArg());
-
-        final String splitName = getNextArg();
-        if (splitName == null) {
-            pw.println("Error: split name not specified");
-            return 1;
-        }
-        return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
-    }
-
-    private int runInstallExisting() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int userId = UserHandle.USER_SYSTEM;
-        int installFlags = 0;
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                case "--ephemeral":
-                case "--instant":
-                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
-                    installFlags &= ~PackageManager.INSTALL_FULL_APP;
-                    break;
-                case "--full":
-                    installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
-                    installFlags |= PackageManager.INSTALL_FULL_APP;
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        try {
-            final int res = mInterface.installExistingPackageAsUser(packageName, userId,
-                    installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
-            if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
-                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
-            }
-            pw.println("Package " + packageName + " installed for user: " + userId);
-            return 0;
-        } catch (RemoteException | NameNotFoundException e) {
-            pw.println(e.toString());
-            return 1;
-        }
-    }
-
-    private int runCompile() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
-        boolean forceCompilation = false;
-        boolean allPackages = false;
-        boolean clearProfileData = false;
-        String compilerFilter = null;
-        String compilationReason = null;
-        String checkProfilesRaw = null;
-        boolean secondaryDex = false;
-        String split = null;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-a":
-                    allPackages = true;
-                    break;
-                case "-c":
-                    clearProfileData = true;
-                    break;
-                case "-f":
-                    forceCompilation = true;
-                    break;
-                case "-m":
-                    compilerFilter = getNextArgRequired();
-                    break;
-                case "-r":
-                    compilationReason = getNextArgRequired();
-                    break;
-                case "--check-prof":
-                    checkProfilesRaw = getNextArgRequired();
-                    break;
-                case "--reset":
-                    forceCompilation = true;
-                    clearProfileData = true;
-                    compilationReason = "install";
-                    break;
-                case "--secondary-dex":
-                    secondaryDex = true;
-                    break;
-                case "--split":
-                    split = getNextArgRequired();
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        if (checkProfilesRaw != null) {
-            if ("true".equals(checkProfilesRaw)) {
-                checkProfiles = true;
-            } else if ("false".equals(checkProfilesRaw)) {
-                checkProfiles = false;
-            } else {
-                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
-                return 1;
-            }
-        }
-
-        if (compilerFilter != null && compilationReason != null) {
-            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
-                    "at the same time");
-            return 1;
-        }
-        if (compilerFilter == null && compilationReason == null) {
-            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
-                    "reason (\"-r\") at the same time");
-            return 1;
-        }
-
-        if (allPackages && split != null) {
-            pw.println("-a cannot be specified together with --split");
-            return 1;
-        }
-
-        if (secondaryDex && split != null) {
-            pw.println("--secondary-dex cannot be specified together with --split");
-            return 1;
-        }
-
-        String targetCompilerFilter;
-        if (compilerFilter != null) {
-            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
-                pw.println("Error: \"" + compilerFilter +
-                        "\" is not a valid compilation filter.");
-                return 1;
-            }
-            targetCompilerFilter = compilerFilter;
-        } else {
-            int reason = -1;
-            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
-                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
-                        compilationReason)) {
-                    reason = i;
-                    break;
-                }
-            }
-            if (reason == -1) {
-                pw.println("Error: Unknown compilation reason: " + compilationReason);
-                return 1;
-            }
-            targetCompilerFilter =
-                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
-        }
-
-
-        List<String> packageNames = null;
-        if (allPackages) {
-            packageNames = mInterface.getAllPackages();
-        } else {
-            String packageName = getNextArg();
-            if (packageName == null) {
-                pw.println("Error: package name not specified");
-                return 1;
-            }
-            packageNames = Collections.singletonList(packageName);
-        }
-
-        List<String> failedPackages = new ArrayList<>();
-        for (String packageName : packageNames) {
-            if (clearProfileData) {
-                mInterface.clearApplicationProfileData(packageName);
-            }
-
-            boolean result = secondaryDex
-                    ? mInterface.performDexOptSecondary(packageName,
-                            targetCompilerFilter, forceCompilation)
-                    : mInterface.performDexOptMode(packageName,
-                            checkProfiles, targetCompilerFilter, forceCompilation,
-                            true /* bootComplete */, split);
-            if (!result) {
-                failedPackages.add(packageName);
-            }
-        }
-
-        if (failedPackages.isEmpty()) {
-            pw.println("Success");
-            return 0;
-        } else if (failedPackages.size() == 1) {
-            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
-            return 1;
-        } else {
-            pw.print("Failure: the following packages could not be compiled: ");
-            boolean is_first = true;
-            for (String packageName : failedPackages) {
-                if (is_first) {
-                    is_first = false;
-                } else {
-                    pw.print(", ");
-                }
-                pw.print(packageName);
-            }
-            pw.println();
-            return 1;
-        }
-    }
-
-    private int runreconcileSecondaryDexFiles() throws RemoteException {
-        String packageName = getNextArg();
-        mInterface.reconcileSecondaryDexFiles(packageName);
-        return 0;
-    }
-
-    private int runDexoptJob() throws RemoteException {
-        boolean result = mInterface.runBackgroundDexoptJob();
-        return result ? 0 : -1;
-    }
-
-    private int runDumpProfiles() throws RemoteException {
-        String packageName = getNextArg();
-        mInterface.dumpProfiles(packageName);
-        return 0;
+        return displayPackageFilePath(pkg, userId);
     }
 
     private int runList() throws RemoteException {
@@ -558,6 +334,11 @@
                 return runListPermissionGroups();
             case "permissions":
                 return runListPermissions();
+            case "users":
+                ServiceManager.getService("user").shellCommand(
+                        getInFileDescriptor(), getOutFileDescriptor(), getErrFileDescriptor(),
+                        new String[] { "list" }, getShellCallback(), adoptResultReceiver());
+                return 0;
         }
         pw.println("Error: unknown list type '" + type + "'");
         return -1;
@@ -590,7 +371,7 @@
                 pw.println();
             } else {
                 pw.println("reqGlEsVersion=0x"
-                    + Integer.toHexString(fi.reqGlEsVersion));
+                        + Integer.toHexString(fi.reqGlEsVersion));
             }
         }
         return 0;
@@ -872,111 +653,6 @@
         return 0;
     }
 
-    private int runUninstall() throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        int flags = 0;
-        int userId = UserHandle.USER_ALL;
-        int versionCode = PackageManager.VERSION_CODE_HIGHEST;
-
-        String opt;
-        while ((opt = getNextOption()) != null) {
-            switch (opt) {
-                case "-k":
-                    flags |= PackageManager.DELETE_KEEP_DATA;
-                    break;
-                case "--user":
-                    userId = UserHandle.parseUserArg(getNextArgRequired());
-                    break;
-                case "--versionCode":
-                    versionCode = Integer.parseInt(getNextArgRequired());
-                    break;
-                default:
-                    pw.println("Error: Unknown option: " + opt);
-                    return 1;
-            }
-        }
-
-        final String packageName = getNextArg();
-        if (packageName == null) {
-            pw.println("Error: package name not specified");
-            return 1;
-        }
-
-        // if a split is specified, just remove it and not the whole package
-        final String splitName = getNextArg();
-        if (splitName != null) {
-            return runRemoveSplit(packageName, splitName);
-        }
-
-        userId = translateUserId(userId, "runUninstall");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            flags |= PackageManager.DELETE_ALL_USERS;
-        } else {
-            final PackageInfo info = mInterface.getPackageInfo(packageName,
-                    PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
-            if (info == null) {
-                pw.println("Failure [not installed for " + userId + "]");
-                return 1;
-            }
-            final boolean isSystem =
-                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            // If we are being asked to delete a system app for just one
-            // user set flag so it disables rather than reverting to system
-            // version of the app.
-            if (isSystem) {
-                flags |= PackageManager.DELETE_SYSTEM_APP;
-            }
-        }
-
-        final LocalIntentReceiver receiver = new LocalIntentReceiver();
-        mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
-                versionCode), null /*callerPackageName*/, flags,
-                receiver.getIntentSender(), userId);
-
-        final Intent result = receiver.getResult();
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == PackageInstaller.STATUS_SUCCESS) {
-            pw.println("Success");
-            return 0;
-        } else {
-            pw.println("Failure ["
-                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            return 1;
-        }
-    }
-
-    private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
-        final PrintWriter pw = getOutPrintWriter();
-        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
-        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-        sessionParams.appPackageName = packageName;
-        final int sessionId =
-                doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
-        boolean abandonSession = true;
-        try {
-            if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            if (doCommitSession(sessionId, false /*logSuccess*/)
-                    != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            abandonSession = false;
-            pw.println("Success");
-            return 0;
-        } finally {
-            if (abandonSession) {
-                try {
-                    doAbandonSession(sessionId, false /*logSuccess*/);
-                } catch (Exception ignore) {
-                }
-            }
-        }
-    }
-
     private Intent parseIntentAndUser() throws URISyntaxException {
         mTargetUser = UserHandle.USER_CURRENT;
         mBrief = false;
@@ -1154,6 +830,1029 @@
         return 0;
     }
 
+    private int runInstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams params = makeInstallParams();
+        final String inPath = getNextArg();
+
+        setParamsSize(params, inPath);
+        final int sessionId = doCreateSession(params.sessionParams,
+                params.installerPackageName, params.userId);
+        boolean abandonSession = true;
+        try {
+            if (inPath == null && params.sessionParams.sizeBytes == -1) {
+                pw.println("Error: must either specify a package size or an APK file");
+                return 1;
+            }
+            if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
+                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            if (doCommitSession(sessionId, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            abandonSession = false;
+            pw.println("Success");
+            return 0;
+        } finally {
+            if (abandonSession) {
+                try {
+                    doAbandonSession(sessionId, false /*logSuccess*/);
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    private int runInstallAbandon() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doAbandonSession(sessionId, true /*logSuccess*/);
+    }
+
+    private int runInstallCommit() throws RemoteException {
+        final int sessionId = Integer.parseInt(getNextArg());
+        return doCommitSession(sessionId, true /*logSuccess*/);
+    }
+
+    private int runInstallCreate() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final InstallParams installParams = makeInstallParams();
+        final int sessionId = doCreateSession(installParams.sessionParams,
+                installParams.installerPackageName, installParams.userId);
+
+        // NOTE: adb depends on parsing this string
+        pw.println("Success: created install session [" + sessionId + "]");
+        return 0;
+    }
+
+    private int runInstallWrite() throws RemoteException {
+        long sizeBytes = -1;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("-S")) {
+                sizeBytes = Long.parseLong(getNextArg());
+            } else {
+                throw new IllegalArgumentException("Unknown option: " + opt);
+            }
+        }
+
+        final int sessionId = Integer.parseInt(getNextArg());
+        final String splitName = getNextArg();
+        final String path = getNextArg();
+        return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
+    }
+
+    private int runInstallRemove() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+
+        final int sessionId = Integer.parseInt(getNextArg());
+
+        final String splitName = getNextArg();
+        if (splitName == null) {
+            pw.println("Error: split name not specified");
+            return 1;
+        }
+        return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
+    }
+
+    private int runInstallExisting() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        int installFlags = 0;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--ephemeral":
+                case "--instant":
+                    installFlags |= PackageManager.INSTALL_INSTANT_APP;
+                    installFlags &= ~PackageManager.INSTALL_FULL_APP;
+                    break;
+                case "--full":
+                    installFlags &= ~PackageManager.INSTALL_INSTANT_APP;
+                    installFlags |= PackageManager.INSTALL_FULL_APP;
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        try {
+            final int res = mInterface.installExistingPackageAsUser(packageName, userId,
+                    installFlags, PackageManager.INSTALL_REASON_UNKNOWN);
+            if (res == PackageManager.INSTALL_FAILED_INVALID_URI) {
+                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+            }
+            pw.println("Package " + packageName + " installed for user: " + userId);
+            return 0;
+        } catch (RemoteException | NameNotFoundException e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
+    private int runSetInstallLocation() throws RemoteException {
+        int loc;
+
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no install location specified.");
+            return 1;
+        }
+        try {
+            loc = Integer.parseInt(arg);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: install location has to be a number.");
+            return 1;
+        }
+        if (!mInterface.setInstallLocation(loc)) {
+            getErrPrintWriter().println("Error: install location has to be a number.");
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runGetInstallLocation() throws RemoteException {
+        int loc = mInterface.getInstallLocation();
+        String locStr = "invalid";
+        if (loc == PackageHelper.APP_INSTALL_AUTO) {
+            locStr = "auto";
+        } else if (loc == PackageHelper.APP_INSTALL_INTERNAL) {
+            locStr = "internal";
+        } else if (loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+            locStr = "external";
+        }
+        getOutPrintWriter().println(loc + "[" + locStr + "]");
+        return 0;
+    }
+
+    public int runMovePackage() throws RemoteException {
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            getErrPrintWriter().println("Error: package name not specified");
+            return 1;
+        }
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final int moveId = mInterface.movePackage(packageName, volumeUuid);
+
+        int status = mInterface.getMoveStatus(moveId);
+        while (!PackageManager.isMoveStatusFinished(status)) {
+            SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+            status = mInterface.getMoveStatus(moveId);
+        }
+
+        if (status == PackageManager.MOVE_SUCCEEDED) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failure [" + status + "]");
+            return 1;
+        }
+    }
+
+    public int runMovePrimaryStorage() throws RemoteException {
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final int moveId = mInterface.movePrimaryStorage(volumeUuid);
+
+        int status = mInterface.getMoveStatus(moveId);
+        while (!PackageManager.isMoveStatusFinished(status)) {
+            SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+            status = mInterface.getMoveStatus(moveId);
+        }
+
+        if (status == PackageManager.MOVE_SUCCEEDED) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failure [" + status + "]");
+            return 1;
+        }
+    }
+
+    private int runCompile() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        boolean checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+        boolean forceCompilation = false;
+        boolean allPackages = false;
+        boolean clearProfileData = false;
+        String compilerFilter = null;
+        String compilationReason = null;
+        String checkProfilesRaw = null;
+        boolean secondaryDex = false;
+        String split = null;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-a":
+                    allPackages = true;
+                    break;
+                case "-c":
+                    clearProfileData = true;
+                    break;
+                case "-f":
+                    forceCompilation = true;
+                    break;
+                case "-m":
+                    compilerFilter = getNextArgRequired();
+                    break;
+                case "-r":
+                    compilationReason = getNextArgRequired();
+                    break;
+                case "--check-prof":
+                    checkProfilesRaw = getNextArgRequired();
+                    break;
+                case "--reset":
+                    forceCompilation = true;
+                    clearProfileData = true;
+                    compilationReason = "install";
+                    break;
+                case "--secondary-dex":
+                    secondaryDex = true;
+                    break;
+                case "--split":
+                    split = getNextArgRequired();
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        if (checkProfilesRaw != null) {
+            if ("true".equals(checkProfilesRaw)) {
+                checkProfiles = true;
+            } else if ("false".equals(checkProfilesRaw)) {
+                checkProfiles = false;
+            } else {
+                pw.println("Invalid value for \"--check-prof\". Expected \"true\" or \"false\".");
+                return 1;
+            }
+        }
+
+        if (compilerFilter != null && compilationReason != null) {
+            pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") " +
+                    "at the same time");
+            return 1;
+        }
+        if (compilerFilter == null && compilationReason == null) {
+            pw.println("Cannot run without any of compilation filter (\"-m\") and compilation " +
+                    "reason (\"-r\") at the same time");
+            return 1;
+        }
+
+        if (allPackages && split != null) {
+            pw.println("-a cannot be specified together with --split");
+            return 1;
+        }
+
+        if (secondaryDex && split != null) {
+            pw.println("--secondary-dex cannot be specified together with --split");
+            return 1;
+        }
+
+        String targetCompilerFilter;
+        if (compilerFilter != null) {
+            if (!DexFile.isValidCompilerFilter(compilerFilter)) {
+                pw.println("Error: \"" + compilerFilter +
+                        "\" is not a valid compilation filter.");
+                return 1;
+            }
+            targetCompilerFilter = compilerFilter;
+        } else {
+            int reason = -1;
+            for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) {
+                if (PackageManagerServiceCompilerMapping.REASON_STRINGS[i].equals(
+                        compilationReason)) {
+                    reason = i;
+                    break;
+                }
+            }
+            if (reason == -1) {
+                pw.println("Error: Unknown compilation reason: " + compilationReason);
+                return 1;
+            }
+            targetCompilerFilter =
+                    PackageManagerServiceCompilerMapping.getCompilerFilterForReason(reason);
+        }
+
+
+        List<String> packageNames = null;
+        if (allPackages) {
+            packageNames = mInterface.getAllPackages();
+        } else {
+            String packageName = getNextArg();
+            if (packageName == null) {
+                pw.println("Error: package name not specified");
+                return 1;
+            }
+            packageNames = Collections.singletonList(packageName);
+        }
+
+        List<String> failedPackages = new ArrayList<>();
+        for (String packageName : packageNames) {
+            if (clearProfileData) {
+                mInterface.clearApplicationProfileData(packageName);
+            }
+
+            boolean result = secondaryDex
+                    ? mInterface.performDexOptSecondary(packageName,
+                            targetCompilerFilter, forceCompilation)
+                    : mInterface.performDexOptMode(packageName,
+                            checkProfiles, targetCompilerFilter, forceCompilation,
+                            true /* bootComplete */, split);
+            if (!result) {
+                failedPackages.add(packageName);
+            }
+        }
+
+        if (failedPackages.isEmpty()) {
+            pw.println("Success");
+            return 0;
+        } else if (failedPackages.size() == 1) {
+            pw.println("Failure: package " + failedPackages.get(0) + " could not be compiled");
+            return 1;
+        } else {
+            pw.print("Failure: the following packages could not be compiled: ");
+            boolean is_first = true;
+            for (String packageName : failedPackages) {
+                if (is_first) {
+                    is_first = false;
+                } else {
+                    pw.print(", ");
+                }
+                pw.print(packageName);
+            }
+            pw.println();
+            return 1;
+        }
+    }
+
+    private int runreconcileSecondaryDexFiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.reconcileSecondaryDexFiles(packageName);
+        return 0;
+    }
+
+    public int runForceDexOpt() throws RemoteException {
+        mInterface.forceDexOpt(getNextArgRequired());
+        return 0;
+    }
+
+    private int runDexoptJob() throws RemoteException {
+        boolean result = mInterface.runBackgroundDexoptJob();
+        return result ? 0 : -1;
+    }
+
+    private int runDumpProfiles() throws RemoteException {
+        String packageName = getNextArg();
+        mInterface.dumpProfiles(packageName);
+        return 0;
+    }
+
+    private int runUninstall() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int flags = 0;
+        int userId = UserHandle.USER_ALL;
+        int versionCode = PackageManager.VERSION_CODE_HIGHEST;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-k":
+                    flags |= PackageManager.DELETE_KEEP_DATA;
+                    break;
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--versionCode":
+                    versionCode = Integer.parseInt(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        // if a split is specified, just remove it and not the whole package
+        final String splitName = getNextArg();
+        if (splitName != null) {
+            return runRemoveSplit(packageName, splitName);
+        }
+
+        userId = translateUserId(userId, "runUninstall");
+        if (userId == UserHandle.USER_ALL) {
+            userId = UserHandle.USER_SYSTEM;
+            flags |= PackageManager.DELETE_ALL_USERS;
+        } else {
+            final PackageInfo info = mInterface.getPackageInfo(packageName,
+                    PackageManager.MATCH_STATIC_SHARED_LIBRARIES, userId);
+            if (info == null) {
+                pw.println("Failure [not installed for " + userId + "]");
+                return 1;
+            }
+            final boolean isSystem =
+                    (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // If we are being asked to delete a system app for just one
+            // user set flag so it disables rather than reverting to system
+            // version of the app.
+            if (isSystem) {
+                flags |= PackageManager.DELETE_SYSTEM_APP;
+            }
+        }
+
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+        mInterface.getPackageInstaller().uninstall(new VersionedPackage(packageName,
+                versionCode), null /*callerPackageName*/, flags,
+                receiver.getIntentSender(), userId);
+
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            pw.println("Success");
+            return 0;
+        } else {
+            pw.println("Failure ["
+                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            return 1;
+        }
+    }
+
+    private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+        sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+        sessionParams.appPackageName = packageName;
+        final int sessionId =
+                doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
+        boolean abandonSession = true;
+        try {
+            if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            if (doCommitSession(sessionId, false /*logSuccess*/)
+                    != PackageInstaller.STATUS_SUCCESS) {
+                return 1;
+            }
+            abandonSession = false;
+            pw.println("Success");
+            return 0;
+        } finally {
+            if (abandonSession) {
+                try {
+                    doAbandonSession(sessionId, false /*logSuccess*/);
+                } catch (Exception ignore) {
+                }
+            }
+        }
+    }
+
+    static class ClearDataObserver extends IPackageDataObserver.Stub {
+        boolean finished;
+        boolean result;
+
+        @Override
+        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
+            synchronized (this) {
+                finished = true;
+                result = succeeded;
+                notifyAll();
+            }
+        }
+    }
+
+    private int runClear() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+
+        ClearDataObserver obs = new ClearDataObserver();
+        ActivityManager.getService().clearApplicationUserData(pkg, obs, userId);
+        synchronized (obs) {
+            while (!obs.finished) {
+                try {
+                    obs.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        if (obs.result) {
+            getOutPrintWriter().println("Success");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Failed");
+            return 1;
+        }
+    }
+
+    private static String enabledSettingToString(int state) {
+        switch (state) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DEFAULT:
+                return "default";
+            case PackageManager.COMPONENT_ENABLED_STATE_ENABLED:
+                return "enabled";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+                return "disabled";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+                return "disabled-user";
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return "disabled-until-used";
+        }
+        return "unknown";
+    }
+
+    private int runSetEnabledSetting(int state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package or component specified");
+            return 1;
+        }
+        ComponentName cn = ComponentName.unflattenFromString(pkg);
+        if (cn == null) {
+            mInterface.setApplicationEnabledSetting(pkg, state, 0, userId,
+                    "shell:" + android.os.Process.myUid());
+            getOutPrintWriter().println("Package " + pkg + " new state: "
+                    + enabledSettingToString(
+                    mInterface.getApplicationEnabledSetting(pkg, userId)));
+            return 0;
+        } else {
+            mInterface.setComponentEnabledSetting(cn, state, 0, userId);
+            getOutPrintWriter().println("Component " + cn.toShortString() + " new state: "
+                    + enabledSettingToString(
+                    mInterface.getComponentEnabledSetting(cn, userId)));
+            return 0;
+        }
+    }
+
+    private int runSetHiddenSetting(boolean state) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String option = getNextOption();
+        if (option != null && option.equals("--user")) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package or component specified");
+            return 1;
+        }
+        mInterface.setApplicationHiddenSettingAsUser(pkg, state, userId);
+        getOutPrintWriter().println("Package " + pkg + " new hidden state: "
+                + mInterface.getApplicationHiddenSettingAsUser(pkg, userId));
+        return 0;
+    }
+
+    private int runSuspend(boolean suspendedState) {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        try {
+            mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
+                    userId);
+            pw.println("Package " + packageName + " new suspended state: "
+                    + mInterface.isPackageSuspendedForUser(packageName, userId));
+            return 0;
+        } catch (RemoteException | IllegalArgumentException e) {
+            pw.println(e.toString());
+            return 1;
+        }
+    }
+
+    private int runGrantRevokePermission(boolean grant) throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt = null;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            }
+        }
+
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        String perm = getNextArg();
+        if (perm == null) {
+            getErrPrintWriter().println("Error: no permission specified");
+            return 1;
+        }
+
+        if (grant) {
+            mInterface.grantRuntimePermission(pkg, perm, userId);
+        } else {
+            mInterface.revokeRuntimePermission(pkg, perm, userId);
+        }
+        return 0;
+    }
+
+    private int runResetPermissions() throws RemoteException {
+        mInterface.resetRuntimePermissions();
+        return 0;
+    }
+
+    private int runSetPermissionEnforced() throws RemoteException {
+        final String permission = getNextArg();
+        if (permission == null) {
+            getErrPrintWriter().println("Error: no permission specified");
+            return 1;
+        }
+        final String enforcedRaw = getNextArg();
+        if (enforcedRaw == null) {
+            getErrPrintWriter().println("Error: no enforcement specified");
+            return 1;
+        }
+        mInterface.setPermissionEnforced(permission, Boolean.parseBoolean(enforcedRaw));
+        return 0;
+    }
+
+    private int runGetPrivappPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
+        getOutPrintWriter().println(privAppPermissions == null
+                ? "{}" : privAppPermissions.toString());
+        return 0;
+    }
+
+    private int runGetPrivappDenyPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        ArraySet<String> privAppDenyPermissions =
+                SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
+        getOutPrintWriter().println(privAppDenyPermissions == null
+                ? "{}" : privAppDenyPermissions.toString());
+        return 0;
+    }
+
+    private int runGetOemPermissions() {
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+        final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
+                .getOemPermissions(pkg);
+        if (oemPermissions == null || oemPermissions.isEmpty()) {
+            getOutPrintWriter().println("{}");
+        } else {
+            oemPermissions.forEach((permission, granted) ->
+                    getOutPrintWriter().println(permission + " granted:" + granted)
+            );
+        }
+        return 0;
+    }
+
+    private String linkStateToString(int state) {
+        switch (state) {
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED: return "undefined";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK: return "ask";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS: return "always";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER: return "never";
+            case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK : return "always ask";
+        }
+        return "Unknown link state: " + state;
+    }
+
+    // pm set-app-link [--user USER_ID] PACKAGE {always|ask|always-ask|never|undefined}
+    private int runSetAppLink() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        // Package name to act on; required
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+
+        // State to apply; {always|ask|never|undefined}, required
+        final String modeString = getNextArg();
+        if (modeString == null) {
+            getErrPrintWriter().println("Error: no app link state specified.");
+            return 1;
+        }
+
+        final int newMode;
+        switch (modeString.toLowerCase()) {
+            case "undefined":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+                break;
+
+            case "always":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+                break;
+
+            case "ask":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+                break;
+
+            case "always-ask":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
+                break;
+
+            case "never":
+                newMode = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+                break;
+
+            default:
+                getErrPrintWriter().println("Error: unknown app link state '" + modeString + "'");
+                return 1;
+        }
+
+        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+        if (info == null) {
+            getErrPrintWriter().println("Error: package " + pkg + " not found.");
+            return 1;
+        }
+
+        if ((info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+            return 1;
+        }
+
+        if (!mInterface.updateIntentVerificationStatus(pkg, newMode, userId)) {
+            getErrPrintWriter().println("Error: unable to update app link status for " + pkg);
+            return 1;
+        }
+
+        return 0;
+    }
+
+    // pm get-app-link [--user USER_ID] PACKAGE
+    private int runGetAppLink() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else {
+                getErrPrintWriter().println("Error: unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        // Package name to act on; required
+        final String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified.");
+            return 1;
+        }
+
+        final PackageInfo info = mInterface.getPackageInfo(pkg, 0, userId);
+        if (info == null) {
+            getErrPrintWriter().println("Error: package " + pkg + " not found.");
+            return 1;
+        }
+
+        if ((info.applicationInfo.privateFlags
+                & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) == 0) {
+            getErrPrintWriter().println("Error: package " + pkg + " does not handle web links.");
+            return 1;
+        }
+
+        getOutPrintWriter().println(linkStateToString(
+                mInterface.getIntentVerificationStatus(pkg, userId)));
+
+        return 0;
+    }
+
+    private int runTrimCaches() throws RemoteException {
+        String size = getNextArg();
+        if (size == null) {
+            getErrPrintWriter().println("Error: no size specified");
+            return 1;
+        }
+        long multiplier = 1;
+        int len = size.length();
+        char c = size.charAt(len - 1);
+        if (c < '0' || c > '9') {
+            if (c == 'K' || c == 'k') {
+                multiplier = 1024L;
+            } else if (c == 'M' || c == 'm') {
+                multiplier = 1024L*1024L;
+            } else if (c == 'G' || c == 'g') {
+                multiplier = 1024L*1024L*1024L;
+            } else {
+                getErrPrintWriter().println("Invalid suffix: " + c);
+                return 1;
+            }
+            size = size.substring(0, len-1);
+        }
+        long sizeVal;
+        try {
+            sizeVal = Long.parseLong(size) * multiplier;
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: expected number at: " + size);
+            return 1;
+        }
+        String volumeUuid = getNextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+        ClearDataObserver obs = new ClearDataObserver();
+        mInterface.freeStorageAndNotify(volumeUuid, sizeVal,
+                StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED, obs);
+        synchronized (obs) {
+            while (!obs.finished) {
+                try {
+                    obs.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        return 0;
+    }
+
+    private static boolean isNumber(String s) {
+        try {
+            Integer.parseInt(s);
+        } catch (NumberFormatException nfe) {
+            return false;
+        }
+        return true;
+    }
+
+    public int runCreateUser() throws RemoteException {
+        String name;
+        int userId = -1;
+        int flags = 0;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if ("--profileOf".equals(opt)) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+            } else if ("--managed".equals(opt)) {
+                flags |= UserInfo.FLAG_MANAGED_PROFILE;
+            } else if ("--restricted".equals(opt)) {
+                flags |= UserInfo.FLAG_RESTRICTED;
+            } else if ("--ephemeral".equals(opt)) {
+                flags |= UserInfo.FLAG_EPHEMERAL;
+            } else if ("--guest".equals(opt)) {
+                flags |= UserInfo.FLAG_GUEST;
+            } else if ("--demo".equals(opt)) {
+                flags |= UserInfo.FLAG_DEMO;
+            } else {
+                getErrPrintWriter().println("Error: unknown option " + opt);
+                return 1;
+            }
+        }
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user name specified.");
+            return 1;
+        }
+        name = arg;
+        UserInfo info;
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        IAccountManager accm = IAccountManager.Stub.asInterface(
+                ServiceManager.getService(Context.ACCOUNT_SERVICE));
+        if ((flags & UserInfo.FLAG_RESTRICTED) != 0) {
+            // In non-split user mode, userId can only be SYSTEM
+            int parentUserId = userId >= 0 ? userId : UserHandle.USER_SYSTEM;
+            info = um.createRestrictedProfile(name, parentUserId);
+            accm.addSharedAccountsFromParentUser(parentUserId, userId,
+                    (Process.myUid() == Process.ROOT_UID) ? "root" : "com.android.shell");
+        } else if (userId < 0) {
+            info = um.createUser(name, flags);
+        } else {
+            info = um.createProfileForUser(name, flags, userId, null);
+        }
+
+        if (info != null) {
+            getOutPrintWriter().println("Success: created user id " + info.id);
+            return 0;
+        } else {
+            getErrPrintWriter().println("Error: couldn't create User.");
+            return 1;
+        }
+    }
+
+    public int runRemoveUser() throws RemoteException {
+        int userId;
+        String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: no user id specified.");
+            return 1;
+        }
+        userId = UserHandle.parseUserArg(arg);
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        if (um.removeUser(userId)) {
+            getOutPrintWriter().println("Success: removed user");
+            return 0;
+        } else {
+            getErrPrintWriter().println("Error: couldn't remove user id " + userId);
+            return 1;
+        }
+    }
+
+    public int runSetUserRestriction() throws RemoteException {
+        int userId = UserHandle.USER_SYSTEM;
+        String opt = getNextOption();
+        if (opt != null && "--user".equals(opt)) {
+            userId = UserHandle.parseUserArg(getNextArgRequired());
+        }
+
+        String restriction = getNextArg();
+        String arg = getNextArg();
+        boolean value;
+        if ("1".equals(arg)) {
+            value = true;
+        } else if ("0".equals(arg)) {
+            value = false;
+        } else {
+            getErrPrintWriter().println("Error: valid value not specified");
+            return 1;
+        }
+        IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+        um.setUserRestriction(restriction, value, userId);
+        return 0;
+    }
+
+    public int runGetMaxUsers() {
+        getOutPrintWriter().println("Maximum supported users: "
+                + UserManager.getMaxSupportedUsers());
+        return 0;
+    }
+
     private static class InstallParams {
         SessionParams sessionParams;
         String installerPackageName;
@@ -1210,6 +1909,12 @@
                         throw new IllegalArgumentException("Missing inherit package name");
                     }
                     break;
+                case "--pkg":
+                    sessionParams.appPackageName = getNextArg();
+                    if (sessionParams.appPackageName == null) {
+                        throw new IllegalArgumentException("Missing package name");
+                    }
+                    break;
                 case "-S":
                     final long sizeBytes = Long.parseLong(getNextArg());
                     if (sizeBytes <= 0) {
@@ -1221,6 +1926,7 @@
                     sessionParams.abiOverride = checkAbiArgument(getNextArg());
                     break;
                 case "--ephemeral":
+                case "--instant":
                 case "--instantapp":
                     sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
                     break;
@@ -1287,46 +1993,17 @@
         }
     }
 
-    private int runGetPrivappPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return 1;
-        }
-        ArraySet<String> privAppPermissions = SystemConfig.getInstance().getPrivAppPermissions(pkg);
-        getOutPrintWriter().println(privAppPermissions == null
-                ? "{}" : privAppPermissions.toString());
-        return 0;
-    }
+    private int runSetInstaller() throws RemoteException {
+        final String targetPackage = getNextArg();
+        final String installerPackageName = getNextArg();
 
-    private int runGetPrivappDenyPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
+        if (targetPackage == null || installerPackageName == null) {
+            getErrPrintWriter().println("Must provide both target and installer package names");
             return 1;
         }
-        ArraySet<String> privAppDenyPermissions =
-                SystemConfig.getInstance().getPrivAppDenyPermissions(pkg);
-        getOutPrintWriter().println(privAppDenyPermissions == null
-                ? "{}" : privAppDenyPermissions.toString());
-        return 0;
-    }
 
-    private int runGetOemPermissions() {
-        final String pkg = getNextArg();
-        if (pkg == null) {
-            System.err.println("Error: no package specified.");
-            return 1;
-        }
-        final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
-                .getOemPermissions(pkg);
-        if (oemPermissions == null || oemPermissions.isEmpty()) {
-            getOutPrintWriter().println("{}");
-        } else {
-            oemPermissions.forEach((permission, granted) ->
-                getOutPrintWriter().println(permission + " granted:" + granted)
-            );
-        }
+        mInterface.setInstallerPackageName(targetPackage, installerPackageName);
+        getOutPrintWriter().println("Success");
         return 0;
     }
 
@@ -1367,6 +2044,16 @@
         }
     }
 
+    private int runDump() {
+        String pkg = getNextArg();
+        if (pkg == null) {
+            getErrPrintWriter().println("Error: no package specified");
+            return 1;
+        }
+        ActivityManager.dumpPackageStateStatic(getOutFileDescriptor(), pkg);
+        return 0;
+    }
+
     private static String checkAbiArgument(String abi) {
         if (TextUtils.isEmpty(abi)) {
             throw new IllegalArgumentException("Missing ABI argument");
@@ -1407,20 +2094,24 @@
     private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
             boolean logSuccess) throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
-        if (FORCE_STREAM_INSTALL && inPath != null && !STDIN_PATH.equals(inPath)) {
-            pw.println("Error: APK content must be streamed");
-            return 1;
-        }
+        final ParcelFileDescriptor fd;
         if (STDIN_PATH.equals(inPath)) {
-            inPath = null;
+            fd = null;
         } else if (inPath != null) {
-            final File file = new File(inPath);
-            if (file.isFile()) {
-                sizeBytes = file.length();
+            fd = openFileForSystem(inPath, "r");
+            if (fd == null) {
+                return -1;
             }
+            sizeBytes = fd.getStatSize();
+            if (sizeBytes < 0) {
+                getErrPrintWriter().println("Unable to get size of: " + inPath);
+                return -1;
+            }
+        } else {
+            fd = null;
         }
         if (sizeBytes <= 0) {
-            pw.println("Error: must specify a APK size");
+            getErrPrintWriter().println("Error: must specify a APK size");
             return 1;
         }
 
@@ -1433,8 +2124,8 @@
             session = new PackageInstaller.Session(
                     mInterface.getPackageInstaller().openSession(sessionId));
 
-            if (inPath != null) {
-                in = new FileInputStream(inPath);
+            if (fd != null) {
+                in = new ParcelFileDescriptor.AutoCloseInputStream(fd);
             } else {
                 in = new SizedInputStream(getRawInputStream(), sizeBytes);
             }
@@ -1459,7 +2150,7 @@
             }
             return 0;
         } catch (IOException e) {
-            pw.println("Error: failed to write; " + e.getMessage());
+            getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
             return 1;
         } finally {
             IoUtils.closeQuietly(out);
@@ -1663,10 +2354,203 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println("");
+        pw.println("  path [--user USER_ID] PACKAGE");
+        pw.println("    Print the path to the .apk of the given PACKAGE.");
+        pw.println("");
+        pw.println("  dump PACKAGE");
+        pw.println("    Print various system state associated with the given PACKAGE.");
+        pw.println("");
+        pw.println("  list features");
+        pw.println("    Prints all features of the system.");
+        pw.println("");
+        pw.println("  has-feature FEATURE_NAME [version]");
+        pw.println("    Prints true and returns exit status 0 when system has a FEATURE_NAME,");
+        pw.println("    otherwise prints false and returns exit status 1");
+        pw.println("");
+        pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
+        pw.println("    Prints all test packages; optionally only those targeting TARGET-PACKAGE");
+        pw.println("    Options:");
+        pw.println("      -f: dump the name of the .apk file containing the test package");
+        pw.println("");
+        pw.println("  list libraries");
+        pw.println("    Prints all system libraries.");
+        pw.println("");
+        pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] ");
+        pw.println("      [--uid UID] [--user USER_ID] [FILTER]");
+        pw.println("    Prints all packages; optionally only those whose name contains");
+        pw.println("    the text in FILTER.  Options are:");
+        pw.println("      -f: see their associated file");
+        pw.println("      -d: filter to only show disabled packages");
+        pw.println("      -e: filter to only show enabled packages");
+        pw.println("      -s: filter to only show system packages");
+        pw.println("      -3: filter to only show third party packages");
+        pw.println("      -i: see the installer for the packages");
+        pw.println("      -l: ignored (used for compatibility with older releases)");
+        pw.println("      -U: also show the package UID");
+        pw.println("      -u: also include uninstalled packages");
+        pw.println("      --uid UID: filter to only show packages with the given UID");
+        pw.println("      --user USER_ID: only list packages belonging to the given user");
+        pw.println("");
+        pw.println("  list permission-groups");
+        pw.println("    Prints all known permission groups.");
+        pw.println("");
+        pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
+        pw.println("    Prints all known permissions; optionally only those in GROUP.  Options are:");
+        pw.println("      -g: organize by group");
+        pw.println("      -f: print all information");
+        pw.println("      -s: short summary");
+        pw.println("      -d: only list dangerous permissions");
+        pw.println("      -u: list only the permissions users will see");
+        pw.println("");
+        pw.println("  resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints the activity that resolves to the given INTENT.");
+        pw.println("");
+        pw.println("  query-activities [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all activities that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  query-services [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all services that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  query-receivers [--brief] [--components] [--user USER_ID] INTENT");
+        pw.println("    Prints all broadcast receivers that can handle the given INTENT.");
+        pw.println("");
+        pw.println("  install [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+        pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        pw.println("       [--originating-uri URI] [---referrer URI]");
+        pw.println("       [--abi ABI_NAME] [--force-sdk]");
+        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
+        pw.println("    Install an application.  Must provide the apk data to install, either as a");
+        pw.println("    file path or '-' to read from stdin.  Options are:");
+        pw.println("      -l: forward lock application");
+        pw.println("      -r: allow replacement of existing application");
+        pw.println("      -t: allow test packages");
+        pw.println("      -i: specify package name of installer owning the app");
+        pw.println("      -s: install application on sdcard");
+        pw.println("      -f: install application on internal flash");
+        pw.println("      -d: allow version code downgrade (debuggable packages only)");
+        pw.println("      -p: partial application install (new split on top of existing pkg)");
+        pw.println("      -g: grant all runtime permissions");
+        pw.println("      -S: size in bytes of package, required for stdin");
+        pw.println("      --user: install under the given user.");
+        pw.println("      --dont-kill: installing a new feature split, don't kill running app");
+        pw.println("      --originating-uri: set URI where app was downloaded from");
+        pw.println("      --referrer: set URI that instigated the install of the app");
+        pw.println("      --pkg: specify expected package name of app being installed");
+        pw.println("      --abi: override the default ABI of the platform");
+        pw.println("      --instantapp: cause the app to be installed as an ephemeral install app");
+        pw.println("      --full: cause the app to be installed as a non-ephemeral full app");
+        pw.println("      --install-location: force the install location:");
+        pw.println("          0=auto, 1=internal only, 2=prefer external");
+        pw.println("      --force-uuid: force install on to disk volume with given UUID");
+        pw.println("      --force-sdk: allow install even when existing app targets platform");
+        pw.println("          codename but new one targets a final API level");
+        pw.println("");
+        pw.println("  install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID|all|current]");
+        pw.println("       [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
+        pw.println("       [--originating-uri URI] [---referrer URI]");
+        pw.println("       [--abi ABI_NAME] [--force-sdk]");
+        pw.println("       [--preload] [--instantapp] [--full] [--dont-kill]");
+        pw.println("       [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
+        pw.println("    Like \"install\", but starts an install session.  Use \"install-write\"");
+        pw.println("    to push data into the session, and \"install-commit\" to finish.");
+        pw.println("");
+        pw.println("  install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
+        pw.println("    Write an apk into the given install session.  If the path is '-', data");
+        pw.println("    will be read from stdin.  Options are:");
+        pw.println("      -S: size in bytes of package, required for stdin");
+        pw.println("");
+        pw.println("  install-commit SESSION_ID");
+        pw.println("    Commit the given active install session, installing the app.");
+        pw.println("");
+        pw.println("  install-abandon SESSION_ID");
+        pw.println("    Delete the given active install session.");
+        pw.println("");
+        pw.println("  set-install-location LOCATION");
+        pw.println("    Changes the default install location.  NOTE this is only intended for debugging;");
+        pw.println("    using this can cause applications to break and other undersireable behavior.");
+        pw.println("    LOCATION is one of:");
+        pw.println("    0 [auto]: Let system decide the best location");
+        pw.println("    1 [internal]: Install on internal device storage");
+        pw.println("    2 [external]: Install on external media");
+        pw.println("");
+        pw.println("  get-install-location");
+        pw.println("    Returns the current install location: 0, 1 or 2 as per set-install-location.");
+        pw.println("");
+        pw.println("  move-package PACKAGE [internal|UUID]");
+        pw.println("");
+        pw.println("  move-primary-storage [internal|UUID]");
+        pw.println("");
+        pw.println("  pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE [SPLIT]");
+        pw.println("    Remove the given package name from the system.  May remove an entire app");
+        pw.println("    if no SPLIT name is specified, otherwise will remove only the split of the");
+        pw.println("    given app.  Options are:");
+        pw.println("      -k: keep the data and cache directories around after package removal.");
+        pw.println("      --user: remove the app from the given user.");
+        pw.println("      --versionCode: only uninstall if the app has the given version code.");
+        pw.println("");
+        pw.println("  clear [--user USER_ID] PACKAGE");
+        pw.println("    Deletes all data associated with a package.");
+        pw.println("");
+        pw.println("  enable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("    These commands change the enabled state of a given package or");
+        pw.println("    component (written as \"package/class\").");
+        pw.println("");
+        pw.println("  hide [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("  unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
+        pw.println("");
+        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
+        pw.println("    Suspends the specified package (as user).");
+        pw.println("");
+        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
+        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("");
+        pw.println("  grant [--user USER_ID] PACKAGE PERMISSION");
+        pw.println("  revoke [--user USER_ID] PACKAGE PERMISSION");
+        pw.println("    These commands either grant or revoke permissions to apps.  The permissions");
+        pw.println("    must be declared as used in the app's manifest, be runtime permissions");
+        pw.println("    (protection level dangerous), and the app targeting SDK greater than Lollipop MR1.");
+        pw.println("");
+        pw.println("  reset-permissions");
+        pw.println("    Revert all runtime permissions to their default state.");
+        pw.println("");
+        pw.println("  set-permission-enforced PERMISSION [true|false]");
+        pw.println("");
+        pw.println("  get-privapp-permissions TARGET-PACKAGE");
+        pw.println("    Prints all privileged permissions for a package.");
+        pw.println("");
+        pw.println("  get-privapp-deny-permissions TARGET-PACKAGE");
+        pw.println("    Prints all privileged permissions that are denied for a package.");
+        pw.println("");
+        pw.println("  get-oem-permissions TARGET-PACKAGE");
+        pw.println("    Prints all OEM permissions for a package.");
+        pw.println("");
+        pw.println("  set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
+        pw.println("  get-app-link [--user USER_ID] PACKAGE");
+        pw.println("");
+        pw.println("  trim-caches DESIRED_FREE_SPACE [internal|UUID]");
+        pw.println("    Trim cache files to reach the given free space.");
+        pw.println("");
+        pw.println("  create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral]");
+        pw.println("      [--guest] USER_NAME");
+        pw.println("    Create a new user with the given USER_NAME, printing the new user identifier");
+        pw.println("    of the user.");
+        pw.println("");
+        pw.println("  remove-user USER_ID");
+        pw.println("    Remove the user with the given USER_IDENTIFIER, deleting all data");
+        pw.println("    associated with that user");
+        pw.println("");
+        pw.println("  set-user-restriction [--user USER_ID] RESTRICTION VALUE");
+        pw.println("");
+        pw.println("  get-max-users");
+        pw.println("");
         pw.println("  compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]");
         pw.println("          [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)");
-        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".");
-        pw.println("    Options:");
+        pw.println("    Trigger compilation of TARGET-PACKAGE or all packages if \"-a\".  Options are:");
         pw.println("      -a: compile all packages");
         pw.println("      -c: clear profile data before compiling");
         pw.println("      -f: force compilation even if not needed");
@@ -1690,72 +2574,32 @@
         pw.println("      --check-prof (true | false): look at profiles when doing dexopt?");
         pw.println("      --secondary-dex: compile app secondary dex files");
         pw.println("      --split SPLIT: compile only the given split name");
+        pw.println("");
+        pw.println("  force-dex-opt PACKAGE");
+        pw.println("    Force immediate execution of dex opt for the given PACKAGE.");
+        pw.println("");
         pw.println("  bg-dexopt-job");
         pw.println("    Execute the background optimizations immediately.");
         pw.println("    Note that the command only runs the background optimizer logic. It may");
         pw.println("    overlap with the actual job but the job scheduler will not be able to");
         pw.println("    cancel it. It will also run even if the device is not in the idle");
         pw.println("    maintenance mode.");
-        pw.println("  list features");
-        pw.println("    Prints all features of the system.");
-        pw.println("  list instrumentation [-f] [TARGET-PACKAGE]");
-        pw.println("    Prints all test packages; optionally only those targeting TARGET-PACKAGE");
-        pw.println("    Options:");
-        pw.println("      -f: dump the name of the .apk file containing the test package");
-        pw.println("  list libraries");
-        pw.println("    Prints all system libraries.");
-        pw.println("  list packages [-f] [-d] [-e] [-s] [-3] [-i] [-l] [-u] [-U] "
-                + "[--uid UID] [--user USER_ID] [FILTER]");
-        pw.println("    Prints all packages; optionally only those whose name contains");
-        pw.println("    the text in FILTER.");
-        pw.println("    Options:");
-        pw.println("      -f: see their associated file");
-        pw.println("      -d: filter to only show disabled packages");
-        pw.println("      -e: filter to only show enabled packages");
-        pw.println("      -s: filter to only show system packages");
-        pw.println("      -3: filter to only show third party packages");
-        pw.println("      -i: see the installer for the packages");
-        pw.println("      -l: ignored (used for compatibility with older releases)");
-        pw.println("      -U: also show the package UID");
-        pw.println("      -u: also include uninstalled packages");
-        pw.println("      --uid UID: filter to only show packages with the given UID");
-        pw.println("      --user USER_ID: only list packages belonging to the given user");
+        pw.println("");
         pw.println("  reconcile-secondary-dex-files TARGET-PACKAGE");
         pw.println("    Reconciles the package secondary dex files with the generated oat files.");
-        pw.println("  list permission-groups");
-        pw.println("    Prints all known permission groups.");
-        pw.println("  list permissions [-g] [-f] [-d] [-u] [GROUP]");
-        pw.println("    Prints all known permissions; optionally only those in GROUP.");
-        pw.println("    Options:");
-        pw.println("      -g: organize by group");
-        pw.println("      -f: print all information");
-        pw.println("      -s: short summary");
-        pw.println("      -d: only list dangerous permissions");
-        pw.println("      -u: list only the permissions users will see");
+        pw.println("");
         pw.println("  dump-profiles TARGET-PACKAGE");
         pw.println("    Dumps method/class profile files to");
         pw.println("    /data/misc/profman/TARGET-PACKAGE.txt");
-        pw.println("  resolve-activity [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints the activity that resolves to the given Intent.");
-        pw.println("  query-activities [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all activities that can handle the given Intent.");
-        pw.println("  query-services [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all services that can handle the given Intent.");
-        pw.println("  query-receivers [--brief] [--components] [--user USER_ID] INTENT");
-        pw.println("    Prints all broadcast receivers that can handle the given Intent.");
-        pw.println("  suspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Suspends the specified package (as user).");
-        pw.println("  unsuspend [--user USER_ID] TARGET-PACKAGE");
-        pw.println("    Unsuspends the specified package (as user).");
+        pw.println("");
         pw.println("  set-home-activity [--user USER_ID] TARGET-COMPONENT");
-        pw.println("    set the default home activity (aka launcher).");
-        pw.println("  has-feature FEATURE_NAME [version]");
-        pw.println("   prints true and returns exit status 0 when system has a FEATURE_NAME,");
-        pw.println("   otherwise prints false and returns exit status 1");
-        pw.println("  get-privileged-permissions TARGET-PACKAGE");
-        pw.println("   prints all privileged permissions for a package.");
-        pw.println("  get-oem-permissions TARGET-PACKAGE");
-        pw.println("   prints all OEM permissions for a package.");
+        pw.println("    Set the default home activity (aka launcher).");
+        pw.println("");
+        pw.println("  set-installer PACKAGE INSTALLER");
+        pw.println("    Set installer package name");
+        pw.println("");
+        pw.println("  get-instantapp-resolver");
+        pw.println("    Return the name of the component that is the current instant app installer.");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 52bf641..3b414e9 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -23,13 +23,15 @@
 import android.service.pm.PackageProto;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.pm.permission.PermissionsState;
+
 import java.io.File;
 import java.util.List;
 
 /**
  * Settings data for a particular package we know about.
  */
-final class PackageSetting extends PackageSettingBase {
+public final class PackageSetting extends PackageSettingBase {
     int appId;
     PackageParser.Package pkg;
     /**
@@ -84,6 +86,10 @@
         return sharedUserId;
     }
 
+    public SharedUserSetting getSharedUser() {
+        return sharedUser;
+    }
+
     @Override
     public String toString() {
         return "PackageSetting{"
@@ -103,12 +109,29 @@
         sharedUserId = orig.sharedUserId;
     }
 
+    @Override
     public PermissionsState getPermissionsState() {
         return (sharedUser != null)
                 ? sharedUser.getPermissionsState()
                 : super.getPermissionsState();
     }
 
+    public PackageParser.Package getPackage() {
+        return pkg;
+    }
+
+    public int getAppId() {
+        return appId;
+    }
+
+    public void setInstallPermissionsFixed(boolean fixed) {
+        installPermissionsFixed = fixed;
+    }
+
+    public boolean areInstallPermissionsFixed() {
+        return installPermissionsFixed;
+    }
+
     public boolean isPrivileged() {
         return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
@@ -125,6 +148,11 @@
         return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
+    public boolean isUpdatedSystem() {
+        return (pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
+    @Override
     public boolean isSharedUser() {
         return sharedUser != null;
     }
@@ -136,6 +164,10 @@
         return true;
     }
 
+    public boolean hasChildPackages() {
+        return childPackageNames != null && !childPackageNames.isEmpty();
+    }
+
     public void writeToProto(ProtoOutputStream proto, long fieldId, List<UserInfo> users) {
         final long packageToken = proto.start(fieldId);
         proto.write(PackageProto.NAME, (realName != null ? realName : name));
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index d3ca1fd..a838768 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -24,14 +24,13 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
-import android.os.storage.VolumeInfo;
+import android.content.pm.Signature;
 import android.service.pm.PackageProto;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Lists;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -42,7 +41,7 @@
 /**
  * Settings base class for pending and resolved classes.
  */
-abstract class PackageSettingBase extends SettingBase {
+public abstract class PackageSettingBase extends SettingBase {
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
@@ -59,7 +58,7 @@
     static final int PKG_INSTALL_COMPLETE = 1;
     static final int PKG_INSTALL_INCOMPLETE = 0;
 
-    final String name;
+    public final String name;
     final String realName;
 
     String parentPackageName;
@@ -230,6 +229,14 @@
         return updateAvailable;
     }
 
+    public boolean isSharedUser() {
+        return false;
+    }
+
+    public Signature[] getSignatures() {
+        return signatures.mSignatures;
+    }
+
     /**
      * Makes a shallow copy of the given package settings.
      *
@@ -412,7 +419,7 @@
         modifyUserState(userId).suspended = suspended;
     }
 
-    boolean getInstantApp(int userId) {
+    public boolean getInstantApp(int userId) {
         return readUserState(userId).instantApp;
     }
 
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
deleted file mode 100644
index f4d2ad2..0000000
--- a/services/core/java/com/android/server/pm/PermissionsState.java
+++ /dev/null
@@ -1,813 +0,0 @@
-/*
- * Copyright (C) 2015 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.server.pm;
-
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import com.android.internal.util.ArrayUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class encapsulates the permissions for a package or a shared user.
- * <p>
- * There are two types of permissions: install (granted at installation)
- * and runtime (granted at runtime). Install permissions are granted to
- * all device users while runtime permissions are granted explicitly to
- * specific users.
- * </p>
- * <p>
- * The permissions are kept on a per device user basis. For example, an
- * application may have some runtime permissions granted under the device
- * owner but not granted under the secondary user.
- * <p>
- * This class is also responsible for keeping track of the Linux gids per
- * user for a package or a shared user. The gids are computed as a set of
- * the gids for all granted permissions' gids on a per user basis.
- * </p>
- */
-public final class PermissionsState {
-
-    /** The permission operation failed. */
-    public static final int PERMISSION_OPERATION_FAILURE = -1;
-
-    /** The permission operation succeeded and no gids changed. */
-    public static final int PERMISSION_OPERATION_SUCCESS = 0;
-
-    /** The permission operation succeeded and gids changed. */
-    public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
-
-    private static final int[] NO_GIDS = {};
-
-    private ArrayMap<String, PermissionData> mPermissions;
-
-    private int[] mGlobalGids = NO_GIDS;
-
-    private SparseBooleanArray mPermissionReviewRequired;
-
-    public PermissionsState() {
-        /* do nothing */
-    }
-
-    public PermissionsState(PermissionsState prototype) {
-        copyFrom(prototype);
-    }
-
-    /**
-     * Sets the global gids, applicable to all users.
-     *
-     * @param globalGids The global gids.
-     */
-    public void setGlobalGids(int[] globalGids) {
-        if (!ArrayUtils.isEmpty(globalGids)) {
-            mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
-        }
-    }
-
-    /**
-     * Initialized this instance from another one.
-     *
-     * @param other The other instance.
-     */
-    public void copyFrom(PermissionsState other) {
-        if (other == this) {
-            return;
-        }
-        if (mPermissions != null) {
-            if (other.mPermissions == null) {
-                mPermissions = null;
-            } else {
-                mPermissions.clear();
-            }
-        }
-        if (other.mPermissions != null) {
-            if (mPermissions == null) {
-                mPermissions = new ArrayMap<>();
-            }
-            final int permissionCount = other.mPermissions.size();
-            for (int i = 0; i < permissionCount; i++) {
-                String name = other.mPermissions.keyAt(i);
-                PermissionData permissionData = other.mPermissions.valueAt(i);
-                mPermissions.put(name, new PermissionData(permissionData));
-            }
-        }
-
-        mGlobalGids = NO_GIDS;
-        if (other.mGlobalGids != NO_GIDS) {
-            mGlobalGids = Arrays.copyOf(other.mGlobalGids,
-                    other.mGlobalGids.length);
-        }
-
-        if (mPermissionReviewRequired != null) {
-            if (other.mPermissionReviewRequired == null) {
-                mPermissionReviewRequired = null;
-            } else {
-                mPermissionReviewRequired.clear();
-            }
-        }
-        if (other.mPermissionReviewRequired != null) {
-            if (mPermissionReviewRequired == null) {
-                mPermissionReviewRequired = new SparseBooleanArray();
-            }
-            final int userCount = other.mPermissionReviewRequired.size();
-            for (int i = 0; i < userCount; i++) {
-                final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i);
-                mPermissionReviewRequired.put(i, reviewRequired);
-            }
-        }
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final PermissionsState other = (PermissionsState) obj;
-
-        if (mPermissions == null) {
-            if (other.mPermissions != null) {
-                return false;
-            }
-        } else if (!mPermissions.equals(other.mPermissions)) {
-            return false;
-        }
-        if (mPermissionReviewRequired == null) {
-            if (other.mPermissionReviewRequired != null) {
-                return false;
-            }
-        } else if (!mPermissionReviewRequired.equals(other.mPermissionReviewRequired)) {
-            return false;
-        }
-        return Arrays.equals(mGlobalGids, other.mGlobalGids);
-    }
-
-    public boolean isPermissionReviewRequired(int userId) {
-        return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
-    }
-
-    /**
-     * Grant an install permission.
-     *
-     * @param permission The permission to grant.
-     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
-     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
-     *     #PERMISSION_OPERATION_FAILURE}.
-     */
-    public int grantInstallPermission(BasePermission permission) {
-        return grantPermission(permission, UserHandle.USER_ALL);
-    }
-
-    /**
-     * Revoke an install permission.
-     *
-     * @param permission The permission to revoke.
-     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
-     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
-     *     #PERMISSION_OPERATION_FAILURE}.
-     */
-    public int revokeInstallPermission(BasePermission permission) {
-        return revokePermission(permission, UserHandle.USER_ALL);
-    }
-
-    /**
-     * Grant a runtime permission for a given device user.
-     *
-     * @param permission The permission to grant.
-     * @param userId The device user id.
-     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
-     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
-     *     #PERMISSION_OPERATION_FAILURE}.
-     */
-    public int grantRuntimePermission(BasePermission permission, int userId) {
-        enforceValidUserId(userId);
-        if (userId == UserHandle.USER_ALL) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-        return grantPermission(permission, userId);
-    }
-
-    /**
-     *  Revoke a runtime permission for a given device user.
-     *
-     * @param permission The permission to revoke.
-     * @param userId The device user id.
-     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
-     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
-     *     #PERMISSION_OPERATION_FAILURE}.
-     */
-    public int revokeRuntimePermission(BasePermission permission, int userId) {
-        enforceValidUserId(userId);
-        if (userId == UserHandle.USER_ALL) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-        return revokePermission(permission, userId);
-    }
-
-    /**
-     * Gets whether this state has a given runtime permission for a
-     * given device user id.
-     *
-     * @param name The permission name.
-     * @param userId The device user id.
-     * @return Whether this state has the permission.
-     */
-    public boolean hasRuntimePermission(String name, int userId) {
-        enforceValidUserId(userId);
-        return !hasInstallPermission(name) && hasPermission(name, userId);
-    }
-
-    /**
-     * Gets whether this state has a given install permission.
-     *
-     * @param name The permission name.
-     * @return Whether this state has the permission.
-     */
-    public boolean hasInstallPermission(String name) {
-        return hasPermission(name, UserHandle.USER_ALL);
-    }
-
-    /**
-     * Gets whether the state has a given permission for the specified
-     * user, regardless if this is an install or a runtime permission.
-     *
-     * @param name The permission name.
-     * @param userId The device user id.
-     * @return Whether the user has the permission.
-     */
-    public boolean hasPermission(String name, int userId) {
-        enforceValidUserId(userId);
-
-        if (mPermissions == null) {
-            return false;
-        }
-
-        PermissionData permissionData = mPermissions.get(name);
-        return permissionData != null && permissionData.isGranted(userId);
-    }
-
-    /**
-     * Returns whether the state has any known request for the given permission name,
-     * whether or not it has been granted.
-     */
-    public boolean hasRequestedPermission(ArraySet<String> names) {
-        if (mPermissions == null) {
-            return false;
-        }
-        for (int i=names.size()-1; i>=0; i--) {
-            if (mPermissions.get(names.valueAt(i)) != null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Gets all permissions for a given device user id regardless if they
-     * are install time or runtime permissions.
-     *
-     * @param userId The device user id.
-     * @return The permissions or an empty set.
-     */
-    public Set<String> getPermissions(int userId) {
-        enforceValidUserId(userId);
-
-        if (mPermissions == null) {
-            return Collections.emptySet();
-        }
-
-        Set<String> permissions = new ArraySet<>(mPermissions.size());
-
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            String permission = mPermissions.keyAt(i);
-
-            if (hasInstallPermission(permission)) {
-                permissions.add(permission);
-                continue;
-            }
-
-            if (userId != UserHandle.USER_ALL) {
-                if (hasRuntimePermission(permission, userId)) {
-                    permissions.add(permission);
-                }
-            }
-        }
-
-        return permissions;
-    }
-
-    /**
-     * Gets the state for an install permission or null if no such.
-     *
-     * @param name The permission name.
-     * @return The permission state.
-     */
-    public PermissionState getInstallPermissionState(String name) {
-        return getPermissionState(name, UserHandle.USER_ALL);
-    }
-
-    /**
-     * Gets the state for a runtime permission or null if no such.
-     *
-     * @param name The permission name.
-     * @param userId The device user id.
-     * @return The permission state.
-     */
-    public PermissionState getRuntimePermissionState(String name, int userId) {
-        enforceValidUserId(userId);
-        return getPermissionState(name, userId);
-    }
-
-    /**
-     * Gets all install permission states.
-     *
-     * @return The permission states or an empty set.
-     */
-    public List<PermissionState> getInstallPermissionStates() {
-        return getPermissionStatesInternal(UserHandle.USER_ALL);
-    }
-
-    /**
-     * Gets all runtime permission states.
-     *
-     * @return The permission states or an empty set.
-     */
-    public List<PermissionState> getRuntimePermissionStates(int userId) {
-        enforceValidUserId(userId);
-        return getPermissionStatesInternal(userId);
-    }
-
-    /**
-     * Gets the flags for a permission regardless if it is install or
-     * runtime permission.
-     *
-     * @param name The permission name.
-     * @return The permission state or null if no such.
-     */
-    public int getPermissionFlags(String name, int userId) {
-        PermissionState installPermState = getInstallPermissionState(name);
-        if (installPermState != null) {
-            return installPermState.getFlags();
-        }
-        PermissionState runtimePermState = getRuntimePermissionState(name, userId);
-        if (runtimePermState != null) {
-            return runtimePermState.getFlags();
-        }
-        return 0;
-    }
-
-    /**
-     * Update the flags associated with a given permission.
-     * @param permission The permission whose flags to update.
-     * @param userId The user for which to update.
-     * @param flagMask Mask for which flags to change.
-     * @param flagValues New values for the mask flags.
-     * @return Whether the permission flags changed.
-     */
-    public boolean updatePermissionFlags(BasePermission permission, int userId,
-            int flagMask, int flagValues) {
-        enforceValidUserId(userId);
-
-        final boolean mayChangeFlags = flagValues != 0 || flagMask != 0;
-
-        if (mPermissions == null) {
-            if (!mayChangeFlags) {
-                return false;
-            }
-            ensurePermissionData(permission);
-        }
-
-        PermissionData permissionData = mPermissions.get(permission.name);
-        if (permissionData == null) {
-            if (!mayChangeFlags) {
-                return false;
-            }
-            permissionData = ensurePermissionData(permission);
-        }
-
-        final int oldFlags = permissionData.getFlags(userId);
-
-        final boolean updated = permissionData.updateFlags(userId, flagMask, flagValues);
-        if (updated) {
-            final int newFlags = permissionData.getFlags(userId);
-            if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
-                    && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                if (mPermissionReviewRequired == null) {
-                    mPermissionReviewRequired = new SparseBooleanArray();
-                }
-                mPermissionReviewRequired.put(userId, true);
-            } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
-                    && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
-                if (mPermissionReviewRequired != null && !hasPermissionRequiringReview(userId)) {
-                    mPermissionReviewRequired.delete(userId);
-                    if (mPermissionReviewRequired.size() <= 0) {
-                        mPermissionReviewRequired = null;
-                    }
-                }
-            }
-        }
-        return updated;
-    }
-
-    private boolean hasPermissionRequiringReview(int userId) {
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            final PermissionData permission = mPermissions.valueAt(i);
-            if ((permission.getFlags(userId)
-                    & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public boolean updatePermissionFlagsForAllPermissions(
-            int userId, int flagMask, int flagValues) {
-        enforceValidUserId(userId);
-
-        if (mPermissions == null) {
-            return false;
-        }
-        boolean changed = false;
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            PermissionData permissionData = mPermissions.valueAt(i);
-            changed |= permissionData.updateFlags(userId, flagMask, flagValues);
-        }
-        return changed;
-    }
-
-    /**
-     * Compute the Linux gids for a given device user from the permissions
-     * granted to this user. Note that these are computed to avoid additional
-     * state as they are rarely accessed.
-     *
-     * @param userId The device user id.
-     * @return The gids for the device user.
-     */
-    public int[] computeGids(int userId) {
-        enforceValidUserId(userId);
-
-        int[] gids = mGlobalGids;
-
-        if (mPermissions != null) {
-            final int permissionCount = mPermissions.size();
-            for (int i = 0; i < permissionCount; i++) {
-                String permission = mPermissions.keyAt(i);
-                if (!hasPermission(permission, userId)) {
-                    continue;
-                }
-                PermissionData permissionData = mPermissions.valueAt(i);
-                final int[] permGids = permissionData.computeGids(userId);
-                if (permGids != NO_GIDS) {
-                    gids = appendInts(gids, permGids);
-                }
-            }
-        }
-
-        return gids;
-    }
-
-    /**
-     * Compute the Linux gids for all device users from the permissions
-     * granted to these users.
-     *
-     * @return The gids for all device users.
-     */
-    public int[] computeGids(int[] userIds) {
-        int[] gids = mGlobalGids;
-
-        for (int userId : userIds) {
-            final int[] userGids = computeGids(userId);
-            gids = appendInts(gids, userGids);
-        }
-
-        return gids;
-    }
-
-    /**
-     * Resets the internal state of this object.
-     */
-    public void reset() {
-        mGlobalGids = NO_GIDS;
-        mPermissions = null;
-        mPermissionReviewRequired = null;
-    }
-
-    private PermissionState getPermissionState(String name, int userId) {
-        if (mPermissions == null) {
-            return null;
-        }
-        PermissionData permissionData = mPermissions.get(name);
-        if (permissionData == null) {
-            return null;
-        }
-        return permissionData.getPermissionState(userId);
-    }
-
-    private List<PermissionState> getPermissionStatesInternal(int userId) {
-        enforceValidUserId(userId);
-
-        if (mPermissions == null) {
-            return Collections.emptyList();
-        }
-
-        List<PermissionState> permissionStates = new ArrayList<>();
-
-        final int permissionCount = mPermissions.size();
-        for (int i = 0; i < permissionCount; i++) {
-            PermissionData permissionData = mPermissions.valueAt(i);
-
-            PermissionState permissionState = permissionData.getPermissionState(userId);
-            if (permissionState != null) {
-                permissionStates.add(permissionState);
-            }
-        }
-
-        return permissionStates;
-    }
-
-    private int grantPermission(BasePermission permission, int userId) {
-        if (hasPermission(permission.name, userId)) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-
-        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
-        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
-
-        PermissionData permissionData = ensurePermissionData(permission);
-
-        if (!permissionData.grant(userId)) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-
-        if (hasGids) {
-            final int[] newGids = computeGids(userId);
-            if (oldGids.length != newGids.length) {
-                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-            }
-        }
-
-        return PERMISSION_OPERATION_SUCCESS;
-    }
-
-    private int revokePermission(BasePermission permission, int userId) {
-        if (!hasPermission(permission.name, userId)) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-
-        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
-        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
-
-        PermissionData permissionData = mPermissions.get(permission.name);
-
-        if (!permissionData.revoke(userId)) {
-            return PERMISSION_OPERATION_FAILURE;
-        }
-
-        if (permissionData.isDefault()) {
-            ensureNoPermissionData(permission.name);
-        }
-
-        if (hasGids) {
-            final int[] newGids = computeGids(userId);
-            if (oldGids.length != newGids.length) {
-                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-            }
-        }
-
-        return PERMISSION_OPERATION_SUCCESS;
-    }
-
-    // TODO: fix this to use arraycopy and append all ints in one go
-    private static int[] appendInts(int[] current, int[] added) {
-        if (current != null && added != null) {
-            for (int guid : added) {
-                current = ArrayUtils.appendInt(current, guid);
-            }
-        }
-        return current;
-    }
-
-    private static void enforceValidUserId(int userId) {
-        if (userId != UserHandle.USER_ALL && userId < 0) {
-            throw new IllegalArgumentException("Invalid userId:" + userId);
-        }
-    }
-
-    private PermissionData ensurePermissionData(BasePermission permission) {
-        if (mPermissions == null) {
-            mPermissions = new ArrayMap<>();
-        }
-        PermissionData permissionData = mPermissions.get(permission.name);
-        if (permissionData == null) {
-            permissionData = new PermissionData(permission);
-            mPermissions.put(permission.name, permissionData);
-        }
-        return permissionData;
-    }
-
-    private void ensureNoPermissionData(String name) {
-        if (mPermissions == null) {
-            return;
-        }
-        mPermissions.remove(name);
-        if (mPermissions.isEmpty()) {
-            mPermissions = null;
-        }
-    }
-
-    private static final class PermissionData {
-        private final BasePermission mPerm;
-        private SparseArray<PermissionState> mUserStates = new SparseArray<>();
-
-        public PermissionData(BasePermission perm) {
-            mPerm = perm;
-        }
-
-        public PermissionData(PermissionData other) {
-            this(other.mPerm);
-            final int otherStateCount = other.mUserStates.size();
-            for (int i = 0; i < otherStateCount; i++) {
-                final int otherUserId = other.mUserStates.keyAt(i);
-                PermissionState otherState = other.mUserStates.valueAt(i);
-                mUserStates.put(otherUserId, new PermissionState(otherState));
-            }
-        }
-
-        public int[] computeGids(int userId) {
-            return mPerm.computeGids(userId);
-        }
-
-        public boolean isGranted(int userId) {
-            if (isInstallPermission()) {
-                userId = UserHandle.USER_ALL;
-            }
-
-            PermissionState userState = mUserStates.get(userId);
-            if (userState == null) {
-                return false;
-            }
-
-            return userState.mGranted;
-        }
-
-        public boolean grant(int userId) {
-            if (!isCompatibleUserId(userId)) {
-                return false;
-            }
-
-            if (isGranted(userId)) {
-                return false;
-            }
-
-            PermissionState userState = mUserStates.get(userId);
-            if (userState == null) {
-                userState = new PermissionState(mPerm.name);
-                mUserStates.put(userId, userState);
-            }
-
-            userState.mGranted = true;
-
-            return true;
-        }
-
-        public boolean revoke(int userId) {
-            if (!isCompatibleUserId(userId)) {
-                return false;
-            }
-
-            if (!isGranted(userId)) {
-                return false;
-            }
-
-            PermissionState userState = mUserStates.get(userId);
-            userState.mGranted = false;
-
-            if (userState.isDefault()) {
-                mUserStates.remove(userId);
-            }
-
-            return true;
-        }
-
-        public PermissionState getPermissionState(int userId) {
-            return mUserStates.get(userId);
-        }
-
-        public int getFlags(int userId) {
-            PermissionState userState = mUserStates.get(userId);
-            if (userState != null) {
-                return userState.mFlags;
-            }
-            return 0;
-        }
-
-        public boolean isDefault() {
-            return mUserStates.size() <= 0;
-        }
-
-        public static boolean isInstallPermissionKey(int userId) {
-            return userId == UserHandle.USER_ALL;
-        }
-
-        public boolean updateFlags(int userId, int flagMask, int flagValues) {
-            if (isInstallPermission()) {
-                userId = UserHandle.USER_ALL;
-            }
-
-            if (!isCompatibleUserId(userId)) {
-                return false;
-            }
-
-            final int newFlags = flagValues & flagMask;
-
-            PermissionState userState = mUserStates.get(userId);
-            if (userState != null) {
-                final int oldFlags = userState.mFlags;
-                userState.mFlags = (userState.mFlags & ~flagMask) | newFlags;
-                if (userState.isDefault()) {
-                    mUserStates.remove(userId);
-                }
-                return userState.mFlags != oldFlags;
-            } else if (newFlags != 0) {
-                userState = new PermissionState(mPerm.name);
-                userState.mFlags = newFlags;
-                mUserStates.put(userId, userState);
-                return true;
-            }
-
-            return false;
-        }
-
-        private boolean isCompatibleUserId(int userId) {
-            return isDefault() || !(isInstallPermission() ^ isInstallPermissionKey(userId));
-        }
-
-        private boolean isInstallPermission() {
-            return mUserStates.size() == 1
-                    && mUserStates.get(UserHandle.USER_ALL) != null;
-        }
-    }
-
-    public static final class PermissionState {
-        private final String mName;
-        private boolean mGranted;
-        private int mFlags;
-
-        public PermissionState(String name) {
-            mName = name;
-        }
-
-        public PermissionState(PermissionState other) {
-            mName = other.mName;
-            mGranted = other.mGranted;
-            mFlags = other.mFlags;
-        }
-
-        public boolean isDefault() {
-            return !mGranted && mFlags == 0;
-        }
-
-        public String getName() {
-            return mName;
-        }
-
-        public boolean isGranted() {
-            return mGranted;
-        }
-
-        public int getFlags() {
-            return mFlags;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index e17cec0..c97f5e5 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -18,6 +18,8 @@
 
 import android.content.pm.ApplicationInfo;
 
+import com.android.server.pm.permission.PermissionsState;
+
 abstract class SettingBase {
     int pkgFlags;
     int pkgPrivateFlags;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 51d3e10..7077fd1 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -64,7 +63,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
 import android.service.pm.PackageServiceDumpProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -87,10 +85,11 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
-import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PackageManagerService.DumpState;
-import com.android.server.pm.PermissionsState.PermissionState;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.PermissionSettings;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
 
 import libcore.io.IoUtils;
 
@@ -127,7 +126,7 @@
 /**
  * Holds information about dynamic settings.
  */
-final class Settings {
+public final class Settings {
     private static final String TAG = "PackageSettings";
 
     /**
@@ -176,7 +175,7 @@
     private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
     private static final String ATTR_ENFORCEMENT = "enforcement";
 
-    private static final String TAG_ITEM = "item";
+    public static final String TAG_ITEM = "item";
     private static final String TAG_DISABLED_COMPONENTS = "disabled-components";
     private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
     private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
@@ -201,7 +200,8 @@
     private static final String TAG_DEFAULT_DIALER = "default-dialer";
     private static final String TAG_VERSION = "version";
 
-    private static final String ATTR_NAME = "name";
+    public static final String ATTR_NAME = "name";
+    public static final String ATTR_PACKAGE = "package";
     private static final String ATTR_USER = "user";
     private static final String ATTR_CODE = "code";
     private static final String ATTR_GRANTED = "granted";
@@ -233,7 +233,6 @@
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_SDK_VERSION = "sdkVersion";
     private static final String ATTR_DATABASE_VERSION = "databaseVersion";
-    private static final String ATTR_DONE = "done";
 
     // Bookkeeping for restored permission grants
     private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
@@ -379,14 +378,6 @@
     private final ArrayMap<Long, Integer> mKeySetRefs =
             new ArrayMap<Long, Integer>();
 
-    // Mapping from permission names to info about them.
-    final ArrayMap<String, BasePermission> mPermissions =
-            new ArrayMap<String, BasePermission>();
-
-    // Mapping from permission tree names to info about them.
-    final ArrayMap<String, BasePermission> mPermissionTrees =
-            new ArrayMap<String, BasePermission>();
-
     // Packages that have been uninstalled and still need their external
     // storage data deleted.
     final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>();
@@ -420,14 +411,16 @@
     private final File mSystemDir;
 
     public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
+    /** Settings and other information about permissions */
+    final PermissionSettings mPermissions;
 
-    Settings(Object lock) {
-        this(Environment.getDataDirectory(), lock);
+    Settings(PermissionSettings permissions, Object lock) {
+        this(Environment.getDataDirectory(), permissions, lock);
     }
 
-    Settings(File dataDir, Object lock) {
+    Settings(File dataDir, PermissionSettings permission, Object lock) {
         mLock = lock;
-
+        mPermissions = permission;
         mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
 
         mSystemDir = new File(dataDir, "system");
@@ -490,7 +483,7 @@
         final PermissionsState perms = ps.getPermissionsState();
 
         for (RestoredPermissionGrant grant : grants) {
-            BasePermission bp = mPermissions.get(grant.permissionName);
+            BasePermission bp = mPermissions.getPermission(grant.permissionName);
             if (bp != null) {
                 if (grant.granted) {
                     perms.grantRuntimePermission(bp, userId);
@@ -507,6 +500,10 @@
         writeRuntimePermissionsForUserLPr(userId, false);
     }
 
+    public boolean canPropagatePermissionToInstantApp(String permName) {
+        return mPermissions.canPropagatePermissionToInstantApp(permName);
+    }
+
     void setInstallerPackageName(String pkgName, String installerPkgName) {
         PackageSetting p = mPackages.get(pkgName);
         if (p != null) {
@@ -547,7 +544,7 @@
         }
         final PackageSetting dp = mDisabledSysPackages.get(name);
         // always make sure the system package code and resource paths dont change
-        if (dp == null && p.pkg != null && p.pkg.isSystemApp() && !p.pkg.isUpdatedSystemApp()) {
+        if (dp == null && p.pkg != null && p.pkg.isSystem() && !p.pkg.isUpdatedSystemApp()) {
             if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
                 p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
             }
@@ -621,6 +618,10 @@
         return null;
     }
 
+    void addAppOpPackage(String permName, String packageName) {
+        mPermissions.addAppOpPackage(permName, packageName);
+    }
+
     SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
         SharedUserSetting s = mSharedUsers.get(name);
         if (s != null) {
@@ -664,31 +665,6 @@
         }
     }
 
-    // Transfer ownership of permissions from one package to another.
-    void transferPermissionsLPw(String origPkg, String newPkg) {
-        // Transfer ownership of permissions to the new package.
-        for (int i=0; i<2; i++) {
-            ArrayMap<String, BasePermission> permissions =
-                    i == 0 ? mPermissionTrees : mPermissions;
-            for (BasePermission bp : permissions.values()) {
-                if (origPkg.equals(bp.sourcePackage)) {
-                    if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG,
-                            "Moving permission " + bp.name
-                            + " from pkg " + bp.sourcePackage
-                            + " to " + newPkg);
-                    bp.sourcePackage = newPkg;
-                    bp.packageSetting = null;
-                    bp.perm = null;
-                    if (bp.pendingInfo != null) {
-                        bp.pendingInfo.packageName = newPkg;
-                    }
-                    bp.uid = 0;
-                    bp.setGids(null, false);
-                }
-            }
-        }
-    }
-
     /**
      * Creates a new {@code PackageSetting} object.
      * Use this method instead of the constructor to ensure a settings object is created
@@ -1074,7 +1050,7 @@
 
         // Update permissions
         for (String eachPerm : deletedPs.pkg.requestedPermissions) {
-            BasePermission bp = mPermissions.get(eachPerm);
+            BasePermission bp = mPermissions.getPermission(eachPerm);
             if (bp == null) {
                 continue;
             }
@@ -2022,8 +1998,7 @@
 
     // Specifically for backup/restore
     public void processRestoredPermissionGrantLPr(String pkgName, String permission,
-            boolean isGranted, int restoredFlagSet, int userId)
-            throws IOException, XmlPullParserException {
+            boolean isGranted, int restoredFlagSet, int userId) {
         mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
                 pkgName, permission, isGranted, restoredFlagSet, userId);
     }
@@ -2225,7 +2200,7 @@
             if (tagName.equals(TAG_ITEM)) {
                 String name = parser.getAttributeValue(null, ATTR_NAME);
 
-                BasePermission bp = mPermissions.get(name);
+                BasePermission bp = mPermissions.getPermission(name);
                 if (bp == null) {
                     Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
                     XmlUtils.skipCurrentTag(parser);
@@ -2514,15 +2489,11 @@
             }
 
             serializer.startTag(null, "permission-trees");
-            for (BasePermission bp : mPermissionTrees.values()) {
-                writePermissionLPr(serializer, bp);
-            }
+            mPermissions.writePermissionTrees(serializer);
             serializer.endTag(null, "permission-trees");
 
             serializer.startTag(null, "permissions");
-            for (BasePermission bp : mPermissions.values()) {
-                writePermissionLPr(serializer, bp);
-            }
+            mPermissions.writePermissions(serializer);
             serializer.endTag(null, "permissions");
 
             for (final PackageSetting pkg : mPackages.values()) {
@@ -2605,9 +2576,6 @@
             writeAllRuntimePermissionsLPr();
             return;
 
-        } catch(XmlPullParserException e) {
-            Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
-                    + "current changes will be lost at reboot", e);
         } catch(java.io.IOException e) {
             Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
                     + "current changes will be lost at reboot", e);
@@ -2951,7 +2919,6 @@
 
     void writeUpgradeKeySetsLPr(XmlSerializer serializer,
             PackageKeySetData data) throws IOException {
-        long properSigning = data.getProperSigningKeySet();
         if (data.isUsingUpgradeKeySets()) {
             for (long id : data.getUpgradeKeySets()) {
                 serializer.startTag(null, "upgrade-keyset");
@@ -2971,32 +2938,8 @@
         }
     }
 
-    void writePermissionLPr(XmlSerializer serializer, BasePermission bp)
-            throws XmlPullParserException, java.io.IOException {
-        if (bp.sourcePackage != null) {
-            serializer.startTag(null, TAG_ITEM);
-            serializer.attribute(null, ATTR_NAME, bp.name);
-            serializer.attribute(null, "package", bp.sourcePackage);
-            if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
-                serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel));
-            }
-            if (PackageManagerService.DEBUG_SETTINGS)
-                Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type="
-                        + bp.type);
-            if (bp.type == BasePermission.TYPE_DYNAMIC) {
-                final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo;
-                if (pi != null) {
-                    serializer.attribute(null, "type", "dynamic");
-                    if (pi.icon != 0) {
-                        serializer.attribute(null, "icon", Integer.toString(pi.icon));
-                    }
-                    if (pi.nonLocalizedLabel != null) {
-                        serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
-                    }
-                }
-            }
-            serializer.endTag(null, TAG_ITEM);
-        }
+    void writePermissionLPr(XmlSerializer serializer, BasePermission bp) throws IOException {
+        bp.writeLPr(serializer);
     }
 
     ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() {
@@ -3088,9 +3031,9 @@
                 if (tagName.equals("package")) {
                     readPackageLPw(parser);
                 } else if (tagName.equals("permissions")) {
-                    readPermissionsLPw(mPermissions, parser);
+                    mPermissions.readPermissions(parser);
                 } else if (tagName.equals("permission-trees")) {
-                    readPermissionsLPw(mPermissionTrees, parser);
+                    mPermissions.readPermissionTrees(parser);
                 } else if (tagName.equals("shared-user")) {
                     readSharedUserLPw(parser);
                 } else if (tagName.equals("preferred-packages")) {
@@ -3169,7 +3112,8 @@
                     }
                 } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                     final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
-                    mReadExternalStorageEnforced = "1".equals(enforcement);
+                    mReadExternalStorageEnforced =
+                            "1".equals(enforcement) ? Boolean.TRUE : Boolean.FALSE;
                 } else if (tagName.equals("keyset-settings")) {
                     mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
                 } else if (TAG_VERSION.equals(tagName)) {
@@ -3593,72 +3537,6 @@
         }
     }
 
-    private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
-        String v = parser.getAttributeValue(ns, name);
-        try {
-            if (v == null) {
-                return defValue;
-            }
-            return Integer.parseInt(v);
-        } catch (NumberFormatException e) {
-            PackageManagerService.reportSettingsProblem(Log.WARN,
-                    "Error in package manager settings: attribute " + name
-                            + " has bad integer value " + v + " at "
-                            + parser.getPositionDescription());
-        }
-        return defValue;
-    }
-
-    private void readPermissionsLPw(ArrayMap<String, BasePermission> out, XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            final String tagName = parser.getName();
-            if (tagName.equals(TAG_ITEM)) {
-                final String name = parser.getAttributeValue(null, ATTR_NAME);
-                final String sourcePackage = parser.getAttributeValue(null, "package");
-                final String ptype = parser.getAttributeValue(null, "type");
-                if (name != null && sourcePackage != null) {
-                    final boolean dynamic = "dynamic".equals(ptype);
-                    BasePermission bp = out.get(name);
-                    // If the permission is builtin, do not clobber it.
-                    if (bp == null || bp.type != BasePermission.TYPE_BUILTIN) {
-                        bp = new BasePermission(name.intern(), sourcePackage,
-                                dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
-                    }
-                    bp.protectionLevel = readInt(parser, null, "protection",
-                            PermissionInfo.PROTECTION_NORMAL);
-                    bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
-                    if (dynamic) {
-                        PermissionInfo pi = new PermissionInfo();
-                        pi.packageName = sourcePackage.intern();
-                        pi.name = name.intern();
-                        pi.icon = readInt(parser, null, "icon", 0);
-                        pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
-                        pi.protectionLevel = bp.protectionLevel;
-                        bp.pendingInfo = pi;
-                    }
-                    out.put(bp.name, bp);
-                } else {
-                    PackageManagerService.reportSettingsProblem(Log.WARN,
-                            "Error in package manager settings: permissions has" + " no name at "
-                                    + parser.getPositionDescription());
-                }
-            } else {
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "Unknown element reading permissions: " + parser.getName() + " at "
-                                + parser.getPositionDescription());
-            }
-            XmlUtils.skipCurrentTag(parser);
-        }
-    }
-
     private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
             IOException {
         String name = parser.getAttributeValue(null, ATTR_NAME);
@@ -4385,10 +4263,6 @@
         return ps;
     }
 
-    private String compToString(ArraySet<String> cmp) {
-        return cmp != null ? Arrays.toString(cmp.toArray()) : "[]";
-    }
-
     boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) {
         final PackageSetting ps = mPackages.get(componentInfo.packageName);
         if (ps == null) return false;
@@ -5001,45 +4875,8 @@
 
     void dumpPermissionsLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
             DumpState dumpState) {
-        boolean printedSomething = false;
-        for (BasePermission p : mPermissions.values()) {
-            if (packageName != null && !packageName.equals(p.sourcePackage)) {
-                continue;
-            }
-            if (permissionNames != null && !permissionNames.contains(p.name)) {
-                continue;
-            }
-            if (!printedSomething) {
-                if (dumpState.onTitlePrinted())
-                    pw.println();
-                pw.println("Permissions:");
-                printedSomething = true;
-            }
-            pw.print("  Permission ["); pw.print(p.name); pw.print("] (");
-                    pw.print(Integer.toHexString(System.identityHashCode(p)));
-                    pw.println("):");
-            pw.print("    sourcePackage="); pw.println(p.sourcePackage);
-            pw.print("    uid="); pw.print(p.uid);
-                    pw.print(" gids="); pw.print(Arrays.toString(
-                            p.computeGids(UserHandle.USER_SYSTEM)));
-                    pw.print(" type="); pw.print(p.type);
-                    pw.print(" prot=");
-                    pw.println(PermissionInfo.protectionToString(p.protectionLevel));
-            if (p.perm != null) {
-                pw.print("    perm="); pw.println(p.perm);
-                if ((p.perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
-                        || (p.perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
-                    pw.print("    flags=0x"); pw.println(Integer.toHexString(p.perm.info.flags));
-                }
-            }
-            if (p.packageSetting != null) {
-                pw.print("    packageSetting="); pw.println(p.packageSetting);
-            }
-            if (READ_EXTERNAL_STORAGE.equals(p.name)) {
-                pw.print("    enforced=");
-                pw.println(mReadExternalStorageEnforced);
-            }
-        }
+        mPermissions.dumpPermissions(pw, packageName, permissionNames,
+                (mReadExternalStorageEnforced == Boolean.TRUE), dumpState);
     }
 
     void dumpSharedUsersLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
@@ -5092,11 +4929,7 @@
     void dumpSharedUsersProto(ProtoOutputStream proto) {
         final int count = mSharedUsers.size();
         for (int i = 0; i < count; i++) {
-            final SharedUserSetting su = mSharedUsers.valueAt(i);
-            final long sharedUserToken = proto.start(PackageServiceDumpProto.SHARED_USERS);
-            proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, su.userId);
-            proto.write(PackageServiceDumpProto.SharedUserProto.NAME, su.name);
-            proto.end(sharedUserToken);
+            mSharedUsers.valueAt(i).writeToProto(proto, PackageServiceDumpProto.SHARED_USERS);
         }
     }
 
@@ -5248,7 +5081,7 @@
 
         private final Handler mHandler = new MyHandler();
 
-        private final Object mLock;
+        private final Object mPersistenceLock;
 
         @GuardedBy("mLock")
         private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
@@ -5265,8 +5098,8 @@
         // The mapping keys are user ids.
         private final SparseBooleanArray mDefaultPermissionsGranted = new SparseBooleanArray();
 
-        public RuntimePermissionPersistence(Object lock) {
-            mLock = lock;
+        public RuntimePermissionPersistence(Object persistenceLock) {
+            mPersistenceLock = persistenceLock;
         }
 
         public boolean areDefaultRuntimPermissionsGrantedLPr(int userId) {
@@ -5321,7 +5154,7 @@
             ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
             ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
 
-            synchronized (mLock) {
+            synchronized (mPersistenceLock) {
                 mWriteScheduled.delete(userId);
 
                 final int packageCount = mPackages.size();
@@ -5470,7 +5303,7 @@
             PermissionsState permissionsState = sb.getPermissionsState();
             for (PermissionState permissionState
                     : permissionsState.getRuntimePermissionStates(userId)) {
-                BasePermission bp = mPermissions.get(permissionState.getName());
+                BasePermission bp = mPermissions.getPermission(permissionState.getName());
                 if (bp != null) {
                     permissionsState.revokeRuntimePermission(bp, userId);
                     permissionsState.updatePermissionFlags(bp, userId,
@@ -5631,7 +5464,7 @@
                 switch (parser.getName()) {
                     case TAG_ITEM: {
                         String name = parser.getAttributeValue(null, ATTR_NAME);
-                        BasePermission bp = mPermissions.get(name);
+                        BasePermission bp = mPermissions.getPermission(name);
                         if (bp == null) {
                             Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
                             XmlUtils.skipCurrentTag(parser);
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index 06e020a..877da14 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -16,12 +16,20 @@
 
 package com.android.server.pm;
 
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.service.pm.PackageServiceDumpProto;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * Settings data for a particular shared user ID we know about.
  */
-final class SharedUserSetting extends SettingBase {
+public final class SharedUserSetting extends SettingBase {
     final String name;
 
     int userId;
@@ -47,6 +55,13 @@
                 + name + "/" + userId + "}";
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(PackageServiceDumpProto.SharedUserProto.USER_ID, userId);
+        proto.write(PackageServiceDumpProto.SharedUserProto.NAME, name);
+        proto.end(token);
+    }
+
     void removePackage(PackageSetting packageSetting) {
         if (packages.remove(packageSetting)) {
             // recalculate the pkgFlags for this shared user if needed
@@ -73,4 +88,18 @@
             setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags);
         }
     }
+
+    public @Nullable List<PackageParser.Package> getPackages() {
+        if (packages == null || packages.size() == 0) {
+            return null;
+        }
+        final ArrayList<PackageParser.Package> pkgList = new ArrayList<>(packages.size());
+        for (PackageSetting ps : packages) {
+            if (ps == null) {
+                continue;
+            }
+            pkgList.add(ps.pkg);
+        }
+        return pkgList;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
index 4f5d156..815f885 100644
--- a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
+++ b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
@@ -21,6 +21,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.drawable.Icon;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.Slog;
@@ -165,7 +167,13 @@
 
         // Compress it and enqueue to the requests.
         final byte[] bytes;
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
         try {
+            // compress() triggers a slow call, but in this case it's needed to save RAM and also
+            // the target bitmap is of an icon size, so let's just permit it.
+            StrictMode.setThreadPolicy(new ThreadPolicy.Builder(oldPolicy)
+                    .permitCustomSlowCalls()
+                    .build());
             final Bitmap shrunk = mService.shrinkBitmap(original, maxDimension);
             try {
                 try (final ByteArrayOutputStream out = new ByteArrayOutputStream(64 * 1024)) {
@@ -184,6 +192,8 @@
         } catch (IOException | RuntimeException | OutOfMemoryError e) {
             Slog.wtf(ShortcutService.TAG, "Unable to write bitmap to file", e);
             return;
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
         }
 
         shortcut.addFlags(
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 3060840..cedf476 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import org.json.JSONException;
@@ -82,11 +83,16 @@
         return mOwnerUserId;
     }
 
+    @Override
+    protected boolean canRestoreAnyVersion() {
+        // Launcher's pinned shortcuts can be restored to an older version.
+        return true;
+    }
+
     /**
      * Called when the new package can't receive the backup, due to signature or version mismatch.
      */
-    @Override
-    protected void onRestoreBlocked() {
+    private void onRestoreBlocked() {
         final ArrayList<PackageWithUser> pinnedPackages =
                 new ArrayList<>(mPinnedShortcuts.keySet());
         mPinnedShortcuts.clear();
@@ -100,15 +106,21 @@
     }
 
     @Override
-    protected void onRestored() {
-        // Nothing to do.
+    protected void onRestored(int restoreBlockReason) {
+        // For launcher, possible reasons here are DISABLED_REASON_SIGNATURE_MISMATCH or
+        // DISABLED_REASON_BACKUP_NOT_SUPPORTED.
+        // DISABLED_REASON_VERSION_LOWER will NOT happen because we don't check version
+        // code for launchers.
+        if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+            onRestoreBlocked();
+        }
     }
 
     /**
      * Pin the given shortcuts, replacing the current pinned ones.
      */
     public void pinShortcuts(@UserIdInt int packageUserId,
-            @NonNull String packageName, @NonNull List<String> ids) {
+            @NonNull String packageName, @NonNull List<String> ids, boolean forPinRequest) {
         final ShortcutPackage packageShortcuts =
                 mShortcutUser.getPackageShortcutsIfExists(packageName);
         if (packageShortcuts == null) {
@@ -123,8 +135,12 @@
         } else {
             final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
 
-            // Pin shortcuts.  Make sure only pin the ones that were visible to the caller.
-            // i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
+            // Actually pin shortcuts.
+            // This logic here is to make sure a launcher cannot pin a shortcut that is floating
+            // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher.
+            // In this case, technically the shortcut doesn't exist to this launcher, so it can't
+            // pin it.
+            // (Maybe unnecessarily strict...)
 
             final ArraySet<String> newSet = new ArraySet<>();
 
@@ -134,8 +150,10 @@
                 if (si == null) {
                     continue;
                 }
-                if (si.isDynamic() || si.isManifestShortcut()
-                        || (prevSet != null && prevSet.contains(id))) {
+                if (si.isDynamic()
+                        || si.isManifestShortcut()
+                        || (prevSet != null && prevSet.contains(id))
+                        || forPinRequest) {
                     newSet.add(id);
                 }
             }
@@ -154,7 +172,7 @@
     }
 
     /**
-     * Return true if the given shortcut is pinned by this launcher.
+     * Return true if the given shortcut is pinned by this launcher.<code></code>
      */
     public boolean hasPinned(ShortcutInfo shortcut) {
         final ArraySet<String> pinned =
@@ -163,10 +181,10 @@
     }
 
     /**
-     * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List)}
+     * Additionally pin a shortcut. c.f. {@link #pinShortcuts(int, String, List, boolean)}
      */
     public void addPinnedShortcut(@NonNull String packageName, @UserIdInt int packageUserId,
-            String id) {
+            String id, boolean forPinRequest) {
         final ArraySet<String> pinnedSet = getPinnedShortcutIds(packageName, packageUserId);
         final ArrayList<String> pinnedList;
         if (pinnedSet != null) {
@@ -177,21 +195,21 @@
         }
         pinnedList.add(id);
 
-        pinShortcuts(packageUserId, packageName, pinnedList);
+        pinShortcuts(packageUserId, packageName, pinnedList, forPinRequest);
     }
 
     boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
         return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
     }
 
-    public void ensureVersionInfo() {
+    public void ensurePackageInfo() {
         final PackageInfo pi = mShortcutUser.mService.getPackageInfoWithSignatures(
                 getPackageName(), getPackageUserId());
         if (pi == null) {
             Slog.w(TAG, "Package not found: " + getPackageName());
             return;
         }
-        getPackageInfo().updateVersionInfo(pi);
+        getPackageInfo().updateFromPackageInfo(pi);
     }
 
     /**
@@ -200,6 +218,10 @@
     @Override
     public void saveToXml(XmlSerializer out, boolean forBackup)
             throws IOException {
+        if (forBackup && !getPackageInfo().isBackupAllowed()) {
+            // If an launcher app doesn't support backup&restore, then nothing to do.
+            return;
+        }
         final int size = mPinnedShortcuts.size();
         if (size == 0) {
             return; // Nothing to write.
@@ -208,7 +230,7 @@
         out.startTag(null, TAG_ROOT);
         ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, getPackageName());
         ShortcutService.writeAttr(out, ATTR_LAUNCHER_USER_ID, getPackageUserId());
-        getPackageInfo().saveToXml(out);
+        getPackageInfo().saveToXml(out, forBackup);
 
         for (int i = 0; i < size; i++) {
             final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
@@ -293,7 +315,7 @@
         return ret;
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
         pw.print(prefix);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 6f70f4c..a3585bc 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.ShortcutOperation;
 import com.android.server.pm.ShortcutService.Stats;
 
@@ -83,6 +84,7 @@
     private static final String ATTR_DISABLED_MESSAGE = "dmessage";
     private static final String ATTR_DISABLED_MESSAGE_RES_ID = "dmessageid";
     private static final String ATTR_DISABLED_MESSAGE_RES_NAME = "dmessagename";
+    private static final String ATTR_DISABLED_REASON = "disabled-reason";
     private static final String ATTR_INTENT_LEGACY = "intent";
     private static final String ATTR_INTENT_NO_EXTRA = "intent-base";
     private static final String ATTR_RANK = "rank";
@@ -155,13 +157,25 @@
     }
 
     @Override
-    protected void onRestoreBlocked() {
-        // Can't restore due to version/signature mismatch.  Remove all shortcuts.
-        mShortcuts.clear();
+    protected boolean canRestoreAnyVersion() {
+        return false;
     }
 
     @Override
-    protected void onRestored() {
+    protected void onRestored(int restoreBlockReason) {
+        // Shortcuts have been restored.
+        // - Unshadow all shortcuts.
+        // - Set disabled reason.
+        // - Disable if needed.
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            ShortcutInfo si = mShortcuts.valueAt(i);
+            si.clearFlags(ShortcutInfo.FLAG_SHADOW);
+
+            si.setDisabledReason(restoreBlockReason);
+            if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                si.addFlags(ShortcutInfo.FLAG_DISABLED);
+            }
+        }
         // Because some launchers may not have been restored (e.g. allowBackup=false),
         // we need to re-calculate the pinned shortcuts.
         refreshPinnedFlags();
@@ -175,31 +189,47 @@
         return mShortcuts.get(id);
     }
 
-    private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
-        if (shortcut != null && shortcut.isImmutable()) {
+    public boolean isShortcutExistsAndInvisibleToPublisher(String id) {
+        ShortcutInfo si = findShortcutById(id);
+        return si != null && !si.isVisibleToPublisher();
+    }
+
+    public boolean isShortcutExistsAndVisibleToPublisher(String id) {
+        ShortcutInfo si = findShortcutById(id);
+        return si != null && si.isVisibleToPublisher();
+    }
+
+    private void ensureNotImmutable(@Nullable ShortcutInfo shortcut, boolean ignoreInvisible) {
+        if (shortcut != null && shortcut.isImmutable()
+                && (!ignoreInvisible || shortcut.isVisibleToPublisher())) {
             throw new IllegalArgumentException(
                     "Manifest shortcut ID=" + shortcut.getId()
                             + " may not be manipulated via APIs");
         }
     }
 
-    public void ensureNotImmutable(@NonNull String id) {
-        ensureNotImmutable(mShortcuts.get(id));
+    public void ensureNotImmutable(@NonNull String id, boolean ignoreInvisible) {
+        ensureNotImmutable(mShortcuts.get(id), ignoreInvisible);
     }
 
-    public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
+    public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds,
+            boolean ignoreInvisible) {
         for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-            ensureNotImmutable(shortcutIds.get(i));
+            ensureNotImmutable(shortcutIds.get(i), ignoreInvisible);
         }
     }
 
-    public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
+    public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts,
+            boolean ignoreInvisible) {
         for (int i = shortcuts.size() - 1; i >= 0; i--) {
-            ensureNotImmutable(shortcuts.get(i).getId());
+            ensureNotImmutable(shortcuts.get(i).getId(), ignoreInvisible);
         }
     }
 
-    private ShortcutInfo deleteShortcutInner(@NonNull String id) {
+    /**
+     * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
+     */
+    private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) {
         final ShortcutInfo shortcut = mShortcuts.remove(id);
         if (shortcut != null) {
             mShortcutUser.mService.removeIconLocked(shortcut);
@@ -209,10 +239,14 @@
         return shortcut;
     }
 
-    private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
+    /**
+     * Force replace a shortcut. If there's already a shortcut with the same ID, it'll be removed,
+     * even if it's invisible.
+     */
+    private void forceReplaceShortcutInner(@NonNull ShortcutInfo newShortcut) {
         final ShortcutService s = mShortcutUser.mService;
 
-        deleteShortcutInner(newShortcut.getId());
+        forceDeleteShortcutInner(newShortcut.getId());
 
         // Extract Icon and update the icon res ID and the bitmap path.
         s.saveIconAndFixUpShortcutLocked(newShortcut);
@@ -221,11 +255,12 @@
     }
 
     /**
-     * Add a shortcut, or update one with the same ID, with taking over existing flags.
+     * Add a shortcut. If there's already a one with the same ID, it'll be removed, even if it's
+     * invisible.
      *
      * It checks the max number of dynamic shortcuts.
      */
-    public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
+    public void addOrReplaceDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
 
         Preconditions.checkArgument(newShortcut.isEnabled(),
                 "add/setDynamicShortcuts() cannot publish disabled shortcuts");
@@ -241,7 +276,7 @@
         } else {
             // It's an update case.
             // Make sure the target is updatable. (i.e. should be mutable.)
-            oldShortcut.ensureUpdatableWith(newShortcut);
+            oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
 
             wasPinned = oldShortcut.isPinned();
         }
@@ -251,7 +286,7 @@
             newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
         }
 
-        addShortcutInner(newShortcut);
+        forceReplaceShortcutInner(newShortcut);
     }
 
     /**
@@ -272,7 +307,7 @@
         }
         if (removeList != null) {
             for (int i = removeList.size() - 1; i >= 0; i--) {
-                deleteShortcutInner(removeList.get(i));
+                forceDeleteShortcutInner(removeList.get(i));
             }
         }
     }
@@ -280,13 +315,13 @@
     /**
      * Remove all dynamic shortcuts.
      */
-    public void deleteAllDynamicShortcuts() {
+    public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
         final long now = mShortcutUser.mService.injectCurrentTimeMillis();
 
         boolean changed = false;
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
-            if (si.isDynamic()) {
+            if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) {
                 changed = true;
 
                 si.setTimestamp(now);
@@ -306,9 +341,10 @@
      * @return true if it's actually removed because it wasn't pinned, or false if it's still
      * pinned.
      */
-    public boolean deleteDynamicWithId(@NonNull String shortcutId) {
+    public boolean deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
         final ShortcutInfo removed = deleteOrDisableWithId(
-                shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
+                shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
+                ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
         return removed == null;
     }
 
@@ -319,9 +355,11 @@
      * @return true if it's actually removed because it wasn't pinned, or false if it's still
      * pinned.
      */
-    private boolean disableDynamicWithId(@NonNull String shortcutId) {
+    private boolean disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible,
+            int disabledReason) {
         final ShortcutInfo disabled = deleteOrDisableWithId(
-                shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false);
+                shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, ignoreInvisible,
+                disabledReason);
         return disabled == null;
     }
 
@@ -330,9 +368,10 @@
      * is pinned, it'll remain as a pinned shortcut but will be disabled.
      */
     public void disableWithId(@NonNull String shortcutId, String disabledMessage,
-            int disabledMessageResId, boolean overrideImmutable) {
+            int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible,
+            int disabledReason) {
         final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
-                overrideImmutable);
+                overrideImmutable, ignoreInvisible, disabledReason);
 
         if (disabled != null) {
             if (disabledMessage != null) {
@@ -347,14 +386,18 @@
 
     @Nullable
     private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
-            boolean overrideImmutable) {
+            boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) {
+        Preconditions.checkState(
+                (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)),
+                "disable and disabledReason disagree: " + disable + " vs " + disabledReason);
         final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
 
-        if (oldShortcut == null || !oldShortcut.isEnabled()) {
+        if (oldShortcut == null || !oldShortcut.isEnabled()
+                && (ignoreInvisible && !oldShortcut.isVisibleToPublisher())) {
             return null; // Doesn't exist or already disabled.
         }
         if (!overrideImmutable) {
-            ensureNotImmutable(oldShortcut);
+            ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true);
         }
         if (oldShortcut.isPinned()) {
 
@@ -362,6 +405,10 @@
             oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
             if (disable) {
                 oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
+                // Do not overwrite the disabled reason if one is alreay set.
+                if (oldShortcut.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED) {
+                    oldShortcut.setDisabledReason(disabledReason);
+                }
             }
             oldShortcut.setTimestamp(mShortcutUser.mService.injectCurrentTimeMillis());
 
@@ -372,7 +419,7 @@
 
             return oldShortcut;
         } else {
-            deleteShortcutInner(shortcutId);
+            forceDeleteShortcutInner(shortcutId);
             return null;
         }
     }
@@ -380,11 +427,25 @@
     public void enableWithId(@NonNull String shortcutId) {
         final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
         if (shortcut != null) {
-            ensureNotImmutable(shortcut);
+            ensureNotImmutable(shortcut, /*ignoreInvisible=*/ true);
             shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
+            shortcut.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
         }
     }
 
+    public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
+        final ShortcutInfo source = mShortcuts.get(shortcut.getId());
+        Preconditions.checkNotNull(source);
+
+        mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
+
+        shortcut.addFlags(ShortcutInfo.FLAG_PINNED);
+
+        forceReplaceShortcutInner(shortcut);
+
+        adjustRanks();
+    }
+
     /**
      * Called after a launcher updates the pinned set.  For each shortcut in this package,
      * set FLAG_PINNED if any launcher has pinned it.  Otherwise, clear it.
@@ -509,7 +570,7 @@
      */
     public void findAll(@NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag) {
-        findAll(result, query, cloneFlag, null, 0);
+        findAll(result, query, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false);
     }
 
     /**
@@ -521,7 +582,7 @@
      */
     public void findAll(@NonNull List<ShortcutInfo> result,
             @Nullable Predicate<ShortcutInfo> query, int cloneFlag,
-            @Nullable String callingLauncher, int launcherUserId) {
+            @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) {
         if (getPackageInfo().isShadow()) {
             // Restored and the app not installed yet, so don't return any.
             return;
@@ -543,9 +604,11 @@
             final boolean isPinnedByCaller = (callingLauncher == null)
                     || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
 
-            if (si.isFloating()) {
-                if (!isPinnedByCaller) {
-                    continue;
+            if (!getPinnedByAnyLauncher) {
+                if (si.isFloating()) {
+                    if (!isPinnedByCaller) {
+                        continue;
+                    }
                 }
             }
             final ShortcutInfo clone = si.clone(cloneFlag);
@@ -611,7 +674,8 @@
             }
             checked.add(activity);
 
-            if (!s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
+            if ((activity != null)
+                    && !s.injectIsActivityEnabledAndExported(activity, getOwnerUserId())) {
                 return false;
             }
         }
@@ -692,7 +756,27 @@
                     getPackageInfo().getVersionCode(), pi.versionCode));
         }
 
-        getPackageInfo().updateVersionInfo(pi);
+        getPackageInfo().updateFromPackageInfo(pi);
+        final int newVersionCode = getPackageInfo().getVersionCode();
+
+        // See if there are any shortcuts that were prevented restoring because the app was of a
+        // lower version, and re-enable them.
+        for (int i = mShortcuts.size() - 1; i >= 0; i--) {
+            final ShortcutInfo si = mShortcuts.valueAt(i);
+            if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+                continue;
+            }
+            if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) {
+                if (ShortcutService.DEBUG) {
+                    Slog.d(TAG, String.format("Shortcut %s require version %s, still not restored.",
+                            si.getId(), getPackageInfo().getBackupSourceVersionCode()));
+                }
+                continue;
+            }
+            Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId()));
+            si.clearFlags(ShortcutInfo.FLAG_DISABLED);
+            si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
+        }
 
         // For existing shortcuts, update timestamps if they have any resources.
         // Also check if shortcuts' activities are still main activities.  Otherwise, disable them.
@@ -712,7 +796,8 @@
                         Slog.w(TAG, String.format(
                                 "%s is no longer main activity. Disabling shorcut %s.",
                                 getPackageName(), si.getId()));
-                        if (disableDynamicWithId(si.getId())) {
+                        if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false,
+                                ShortcutInfo.DISABLED_REASON_APP_CHANGED)) {
                             continue; // Actually removed.
                         }
                         // Still pinned, so fall-through and possibly update the resources.
@@ -808,7 +893,7 @@
 
                 // Note even if enabled=false, we still need to update all fields, so do it
                 // regardless.
-                addShortcutInner(newShortcut); // This will clean up the old one too.
+                forceReplaceShortcutInner(newShortcut); // This will clean up the old one too.
 
                 if (!newDisabled && toDisableList != null) {
                     // Still alive, don't remove.
@@ -830,7 +915,8 @@
                 final String id = toDisableList.valueAt(i);
 
                 disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
-                        /* overrideImmutable=*/ true);
+                        /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false,
+                        ShortcutInfo.DISABLED_REASON_APP_CHANGED);
             }
             removeOrphans();
         }
@@ -868,7 +954,7 @@
                     service.wtf("Found manifest shortcuts in excess list.");
                     continue;
                 }
-                deleteDynamicWithId(shortcut.getId());
+                deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true);
             }
         }
 
@@ -1074,7 +1160,7 @@
         if (ret != 0) {
             return ret;
         }
-        // If they're stil tie, just sort by their IDs.
+        // If they're still tie, just sort by their IDs.
         // This may happen with updateShortcuts() -- see
         // the testUpdateShortcuts_noManifestShortcuts() test.
         return a.getId().compareTo(b.getId());
@@ -1144,7 +1230,7 @@
         return false;
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
         pw.print(prefix);
@@ -1186,9 +1272,7 @@
         final int size = shortcuts.size();
         for (int i = 0; i < size; i++) {
             final ShortcutInfo si = shortcuts.valueAt(i);
-            pw.print(prefix);
-            pw.print("    ");
-            pw.println(si.toInsecureString());
+            pw.println(si.toDumpString(prefix + "    "));
             if (si.getBitmapPath() != null) {
                 final long len = new File(si.getBitmapPath()).length();
                 pw.print(prefix);
@@ -1258,25 +1342,34 @@
         ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
         ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
         ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
-        getPackageInfo().saveToXml(out);
+        getPackageInfo().saveToXml(out, forBackup);
 
         for (int j = 0; j < size; j++) {
-            saveShortcut(out, mShortcuts.valueAt(j), forBackup);
+            saveShortcut(out, mShortcuts.valueAt(j), forBackup,
+                    getPackageInfo().isBackupAllowed());
         }
 
         out.endTag(null, TAG_ROOT);
     }
 
-    private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup)
+    private void saveShortcut(XmlSerializer out, ShortcutInfo si, boolean forBackup,
+            boolean appSupportsBackup)
             throws IOException, XmlPullParserException {
 
         final ShortcutService s = mShortcutUser.mService;
 
         if (forBackup) {
             if (!(si.isPinned() && si.isEnabled())) {
-                return; // We only backup pinned shortcuts that are enabled.
+                // We only backup pinned shortcuts that are enabled.
+                // Note, this means, shortcuts that are restored but are blocked restore, e.g. due
+                // to a lower version code, will not be ported to a new device.
+                return;
             }
         }
+        final boolean shouldBackupDetails =
+                !forBackup // It's not backup
+                || appSupportsBackup; // Or, it's a backup and app supports backup.
+
         // Note: at this point no shortcuts should have bitmaps pending save, but if they do,
         // just remove the bitmap.
         if (si.isIconPendingSave()) {
@@ -1293,20 +1386,31 @@
         ShortcutService.writeAttr(out, ATTR_TEXT, si.getText());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_ID, si.getTextResId());
         ShortcutService.writeAttr(out, ATTR_TEXT_RES_NAME, si.getTextResName());
-        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
-        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
-                si.getDisabledMessageResourceId());
-        ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
-                si.getDisabledMessageResName());
+        if (shouldBackupDetails) {
+            ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE, si.getDisabledMessage());
+            ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_ID,
+                    si.getDisabledMessageResourceId());
+            ShortcutService.writeAttr(out, ATTR_DISABLED_MESSAGE_RES_NAME,
+                    si.getDisabledMessageResName());
+        }
+        ShortcutService.writeAttr(out, ATTR_DISABLED_REASON, si.getDisabledReason());
         ShortcutService.writeAttr(out, ATTR_TIMESTAMP,
                 si.getLastChangedTimestamp());
         if (forBackup) {
             // Don't write icon information.  Also drop the dynamic flag.
-            ShortcutService.writeAttr(out, ATTR_FLAGS,
-                    si.getFlags() &
-                            ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
+
+            int flags = si.getFlags() &
+                    ~(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES
                             | ShortcutInfo.FLAG_ICON_FILE_PENDING_SAVE
-                            | ShortcutInfo.FLAG_DYNAMIC));
+                            | ShortcutInfo.FLAG_DYNAMIC);
+            ShortcutService.writeAttr(out, ATTR_FLAGS, flags);
+
+            // Set the publisher version code at every backup.
+            final int packageVersionCode = getPackageInfo().getVersionCode();
+            if (packageVersionCode == 0) {
+                s.wtf("Package version code should be available at this point.");
+                // However, 0 is a valid version code, so we just go ahead with it...
+            }
         } else {
             // When writing for backup, ranks shouldn't be saved, since shortcuts won't be restored
             // as dynamic.
@@ -1318,26 +1422,28 @@
             ShortcutService.writeAttr(out, ATTR_BITMAP_PATH, si.getBitmapPath());
         }
 
-        {
-            final Set<String> cat = si.getCategories();
-            if (cat != null && cat.size() > 0) {
-                out.startTag(null, TAG_CATEGORIES);
-                XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
-                        NAME_CATEGORIES, out);
-                out.endTag(null, TAG_CATEGORIES);
+        if (shouldBackupDetails) {
+            {
+                final Set<String> cat = si.getCategories();
+                if (cat != null && cat.size() > 0) {
+                    out.startTag(null, TAG_CATEGORIES);
+                    XmlUtils.writeStringArrayXml(cat.toArray(new String[cat.size()]),
+                            NAME_CATEGORIES, out);
+                    out.endTag(null, TAG_CATEGORIES);
+                }
             }
-        }
-        final Intent[] intentsNoExtras = si.getIntentsNoExtras();
-        final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
-        final int numIntents = intentsNoExtras.length;
-        for (int i = 0; i < numIntents; i++) {
-            out.startTag(null, TAG_INTENT);
-            ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
-            ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
-            out.endTag(null, TAG_INTENT);
-        }
+            final Intent[] intentsNoExtras = si.getIntentsNoExtras();
+            final PersistableBundle[] intentsExtras = si.getIntentPersistableExtrases();
+            final int numIntents = intentsNoExtras.length;
+            for (int i = 0; i < numIntents; i++) {
+                out.startTag(null, TAG_INTENT);
+                ShortcutService.writeAttr(out, ATTR_INTENT_NO_EXTRA, intentsNoExtras[i]);
+                ShortcutService.writeTagExtra(out, TAG_EXTRAS, intentsExtras[i]);
+                out.endTag(null, TAG_INTENT);
+            }
 
-        ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+            ShortcutService.writeTagExtra(out, TAG_EXTRAS, si.getExtras());
+        }
 
         out.endTag(null, TAG_SHORTCUT);
     }
@@ -1357,6 +1463,7 @@
         ret.mLastResetTime =
                 ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
 
+
         final int outerDepth = parser.getDepth();
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1370,10 +1477,11 @@
                 switch (tag) {
                     case ShortcutPackageInfo.TAG_ROOT:
                         ret.getPackageInfo().loadFromXml(parser, fromBackup);
+
                         continue;
                     case TAG_SHORTCUT:
                         final ShortcutInfo si = parseShortcut(parser, packageName,
-                                shortcutUser.getUserId());
+                                shortcutUser.getUserId(), fromBackup);
 
                         // Don't use addShortcut(), we don't need to save the icon.
                         ret.mShortcuts.put(si.getId(), si);
@@ -1386,7 +1494,8 @@
     }
 
     private static ShortcutInfo parseShortcut(XmlPullParser parser, String packageName,
-            @UserIdInt int userId) throws IOException, XmlPullParserException {
+            @UserIdInt int userId, boolean fromBackup)
+            throws IOException, XmlPullParserException {
         String id;
         ComponentName activityComponent;
         // Icon icon;
@@ -1399,6 +1508,7 @@
         String disabledMessage;
         int disabledMessageResId;
         String disabledMessageResName;
+        int disabledReason;
         Intent intentLegacy;
         PersistableBundle intentPersistableExtrasLegacy = null;
         ArrayList<Intent> intents = new ArrayList<>();
@@ -1409,6 +1519,7 @@
         int iconResId;
         String iconResName;
         String bitmapPath;
+        int backupVersionCode;
         ArraySet<String> categories = null;
 
         id = ShortcutService.parseStringAttribute(parser, ATTR_ID);
@@ -1425,6 +1536,7 @@
                 ATTR_DISABLED_MESSAGE_RES_ID);
         disabledMessageResName = ShortcutService.parseStringAttribute(parser,
                 ATTR_DISABLED_MESSAGE_RES_NAME);
+        disabledReason = ShortcutService.parseIntAttribute(parser, ATTR_DISABLED_REASON);
         intentLegacy = ShortcutService.parseIntentAttributeNoDefault(parser, ATTR_INTENT_LEGACY);
         rank = (int) ShortcutService.parseLongAttribute(parser, ATTR_RANK);
         lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP);
@@ -1481,6 +1593,19 @@
             intents.add(intentLegacy);
         }
 
+
+        if ((disabledReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)
+                && ((flags & ShortcutInfo.FLAG_DISABLED) != 0)) {
+            // We didn't used to have the disabled reason, so if a shortcut is disabled
+            // and has no reason, we assume it was disabled by publisher.
+            disabledReason = ShortcutInfo.DISABLED_REASON_BY_APP;
+        }
+
+        // All restored shortcuts are initially "shadow".
+        if (fromBackup) {
+            flags |= ShortcutInfo.FLAG_SHADOW;
+        }
+
         return new ShortcutInfo(
                 userId, id, packageName, activityComponent, /* icon =*/ null,
                 title, titleResId, titleResName, text, textResId, textResName,
@@ -1488,7 +1613,7 @@
                 categories,
                 intents.toArray(new Intent[intents.size()]),
                 rank, extras, lastChangedTimestamp, flags,
-                iconResId, iconResName, bitmapPath);
+                iconResId, iconResName, bitmapPath, disabledReason);
     }
 
     private static Intent parseIntent(XmlPullParser parser)
@@ -1603,6 +1728,20 @@
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
                         + " has both resource and bitmap icons");
             }
+            if (si.isEnabled()
+                    != (si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_NOT_DISABLED)) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " isEnabled() and getDisabledReason() disagree: "
+                        + si.isEnabled() + " vs " + si.getDisabledReason());
+            }
+            if ((si.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+                    && (getPackageInfo().getBackupSourceVersionCode()
+                    == ShortcutInfo.VERSION_CODE_UNKNOWN)) {
+                failed = true;
+                Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
+                        + " RESTORED_VERSION_LOWER with no backup source version code.");
+            }
             if (s.isDummyMainActivity(si.getActivity())) {
                 failed = true;
                 Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId()
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
index e5a2f5a..3a9bbc8 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageInfo.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -45,32 +46,45 @@
     static final String TAG_ROOT = "package-info";
     private static final String ATTR_VERSION = "version";
     private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
+    private static final String ATTR_BACKUP_SOURCE_VERSION = "bk_src_version";
+    private static final String ATTR_BACKUP_ALLOWED = "allow-backup";
+    private static final String ATTR_BACKUP_SOURCE_BACKUP_ALLOWED = "bk_src_backup-allowed";
     private static final String ATTR_SHADOW = "shadow";
 
     private static final String TAG_SIGNATURE = "signature";
     private static final String ATTR_SIGNATURE_HASH = "hash";
 
-    private static final int VERSION_UNKNOWN = -1;
-
     /**
      * When true, this package information was restored from the previous device, and the app hasn't
      * been installed yet.
      */
     private boolean mIsShadow;
-    private int mVersionCode = VERSION_UNKNOWN;
+    private int mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+    private int mBackupSourceVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
     private long mLastUpdateTime;
     private ArrayList<byte[]> mSigHashes;
 
+    // mBackupAllowed didn't used to be parsisted, so we don't restore it from a file.
+    // mBackupAllowed will always start with false, and will have been updated before making a
+    // backup next time, which works file.
+    // We just don't want to print an uninitialzied mBackupAlldowed value on dumpsys, so
+    // we use this boolean to control dumpsys.
+    private boolean mBackupAllowedInitialized;
+    private boolean mBackupAllowed;
+    private boolean mBackupSourceBackupAllowed;
+
     private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
             ArrayList<byte[]> sigHashes, boolean isShadow) {
         mVersionCode = versionCode;
         mLastUpdateTime = lastUpdateTime;
         mIsShadow = isShadow;
         mSigHashes = sigHashes;
+        mBackupAllowed = false; // By default, we assume false.
+        mBackupSourceBackupAllowed = false;
     }
 
     public static ShortcutPackageInfo newEmpty() {
-        return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
+        return new ShortcutPackageInfo(ShortcutInfo.VERSION_CODE_UNKNOWN, /* last update time =*/ 0,
                 new ArrayList<>(0), /* isShadow */ false);
     }
 
@@ -86,15 +100,33 @@
         return mVersionCode;
     }
 
+    public int getBackupSourceVersionCode() {
+        return mBackupSourceVersionCode;
+    }
+
+    @VisibleForTesting
+    public boolean isBackupSourceBackupAllowed() {
+        return mBackupSourceBackupAllowed;
+    }
+
     public long getLastUpdateTime() {
         return mLastUpdateTime;
     }
 
-    /** Set {@link #mVersionCode} and {@link #mLastUpdateTime} from a {@link PackageInfo}. */
-    public void updateVersionInfo(@NonNull PackageInfo pi) {
+    public boolean isBackupAllowed() {
+        return mBackupAllowed;
+    }
+
+    /**
+     * Set {@link #mVersionCode}, {@link #mLastUpdateTime} and {@link #mBackupAllowed}
+     * from a {@link PackageInfo}.
+     */
+    public void updateFromPackageInfo(@NonNull PackageInfo pi) {
         if (pi != null) {
             mVersionCode = pi.versionCode;
             mLastUpdateTime = pi.lastUpdateTime;
+            mBackupAllowed = ShortcutService.shouldBackupApp(pi);
+            mBackupAllowedInitialized = true;
         }
     }
 
@@ -102,23 +134,24 @@
         return mSigHashes.size() > 0;
     }
 
-    public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
-        if (!s.shouldBackupApp(target)) {
-            // "allowBackup" was true when backed up, but now false.
-            Slog.w(TAG, "Can't restore: package no longer allows backup");
-            return false;
+    //@DisabledReason
+    public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
+        if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) {
+            Slog.w(TAG, "Can't restore: Package signature mismatch");
+            return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
         }
-        if (target.versionCode < mVersionCode) {
+        if (!ShortcutService.shouldBackupApp(currentPackage) || !mBackupSourceBackupAllowed) {
+            // "allowBackup" was true when backed up, but now false.
+            Slog.w(TAG, "Can't restore: package didn't or doesn't allow backup");
+            return ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
+        }
+        if (!anyVersionOkay && (currentPackage.versionCode < mBackupSourceVersionCode)) {
             Slog.w(TAG, String.format(
                     "Can't restore: package current version %d < backed up version %d",
-                    target.versionCode, mVersionCode));
-            return false;
+                    currentPackage.versionCode, mBackupSourceVersionCode));
+            return ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
         }
-        if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
-            Slog.w(TAG, "Can't restore: Package signature mismatch");
-            return false;
-        }
-        return true;
+        return ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
     }
 
     @VisibleForTesting
@@ -132,6 +165,8 @@
         final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
                 BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
 
+        ret.mBackupSourceBackupAllowed = s.shouldBackupApp(pi);
+        ret.mBackupSourceVersionCode = pi.versionCode;
         return ret;
     }
 
@@ -151,13 +186,19 @@
         mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
     }
 
-    public void saveToXml(XmlSerializer out) throws IOException {
+    public void saveToXml(XmlSerializer out, boolean forBackup) throws IOException {
 
         out.startTag(null, TAG_ROOT);
 
         ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
         ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
         ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
+        ShortcutService.writeAttr(out, ATTR_BACKUP_ALLOWED, mBackupAllowed);
+
+        ShortcutService.writeAttr(out, ATTR_BACKUP_SOURCE_VERSION, mBackupSourceVersionCode);
+        ShortcutService.writeAttr(out,
+                ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, mBackupSourceBackupAllowed);
+
 
         for (int i = 0; i < mSigHashes.size(); i++) {
             out.startTag(null, TAG_SIGNATURE);
@@ -171,7 +212,9 @@
     public void loadFromXml(XmlPullParser parser, boolean fromBackup)
             throws IOException, XmlPullParserException {
 
-        final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
+        // Don't use the version code from the backup file.
+        final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION,
+                ShortcutInfo.VERSION_CODE_UNKNOWN);
 
         final long lastUpdateTime = ShortcutService.parseLongAttribute(
                 parser, ATTR_LAST_UPDATE_TIME);
@@ -180,6 +223,20 @@
         final boolean shadow =
                 fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
 
+        // We didn't used to save these attributes, and all backed up shortcuts were from
+        // apps that support backups, so the default values take this fact into consideration.
+        final int backupSourceVersion = ShortcutService.parseIntAttribute(parser,
+                ATTR_BACKUP_SOURCE_VERSION, ShortcutInfo.VERSION_CODE_UNKNOWN);
+
+        // Note the only time these "true" default value is used is when restoring from an old
+        // build that didn't save ATTR_BACKUP_ALLOWED, and that means all the data included in
+        // a backup file were from apps that support backup, so we can just use "true" as the
+        // default.
+        final boolean backupAllowed = ShortcutService.parseBooleanAttribute(
+                parser, ATTR_BACKUP_ALLOWED, true);
+        final boolean backupSourceBackupAllowed = ShortcutService.parseBooleanAttribute(
+                parser, ATTR_BACKUP_SOURCE_BACKUP_ALLOWED, true);
+
         final ArrayList<byte[]> hashes = new ArrayList<>();
 
         final int outerDepth = parser.getDepth();
@@ -207,11 +264,28 @@
             ShortcutService.warnForInvalidTag(depth, tag);
         }
 
-        // Successfully loaded; replace the feilds.
-        mVersionCode = versionCode;
+        // Successfully loaded; replace the fields.
+        if (fromBackup) {
+            mVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
+            mBackupSourceVersionCode = versionCode;
+            mBackupSourceBackupAllowed = backupAllowed;
+        } else {
+            mVersionCode = versionCode;
+            mBackupSourceVersionCode = backupSourceVersion;
+            mBackupSourceBackupAllowed = backupSourceBackupAllowed;
+        }
         mLastUpdateTime = lastUpdateTime;
         mIsShadow = shadow;
         mSigHashes = hashes;
+
+        // Note we don't restore it from the file because it didn't used to be saved.
+        // We always start by assuming backup is disabled for the current package,
+        // and this field will have been updated before we actually create a backup, at the same
+        // time when we update the version code.
+        // Until then, the value of mBackupAllowed shouldn't matter, but we don't want to print
+        // a false flag on dumpsys, so set mBackupAllowedInitialized to false.
+        mBackupAllowed = false;
+        mBackupAllowedInitialized = false;
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -223,6 +297,7 @@
         pw.print(prefix);
         pw.print("  IsShadow: ");
         pw.print(mIsShadow);
+        pw.print(mIsShadow ? " (not installed)" : " (installed)");
         pw.println();
 
         pw.print(prefix);
@@ -230,6 +305,25 @@
         pw.print(mVersionCode);
         pw.println();
 
+        if (mBackupAllowedInitialized) {
+            pw.print(prefix);
+            pw.print("  Backup Allowed: ");
+            pw.print(mBackupAllowed);
+            pw.println();
+        }
+
+        if (mBackupSourceVersionCode != ShortcutInfo.VERSION_CODE_UNKNOWN) {
+            pw.print(prefix);
+            pw.print("  Backup source version: ");
+            pw.print(mBackupSourceVersionCode);
+            pw.println();
+
+            pw.print(prefix);
+            pw.print("  Backup source backup allowed: ");
+            pw.print(mBackupSourceBackupAllowed);
+            pw.println();
+        }
+
         pw.print(prefix);
         pw.print("  Last package update time: ");
         pw.print(mLastUpdateTime);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index e59d69f..689099c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
 import android.util.Slog;
 
 import com.android.internal.util.Preconditions;
@@ -101,51 +102,42 @@
         final ShortcutService s = mShortcutUser.mService;
         if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
             if (ShortcutService.DEBUG) {
-                Slog.d(TAG, String.format("Package still not installed: %s user=%d",
+                Slog.d(TAG, String.format("Package still not installed: %s/u%d",
                         mPackageName, mPackageUserId));
             }
             return; // Not installed, no need to restore yet.
         }
-        boolean blockRestore = false;
-        if (!mPackageInfo.hasSignatures()) {
-            s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
-                    + " but signatures not found in the restore data.");
-            blockRestore = true;
-        }
-        if (!blockRestore) {
-            final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
-            if (!mPackageInfo.canRestoreTo(s, pi)) {
-                // Package is now installed, but can't restore.  Let the subclass do the cleanup.
-                blockRestore = true;
-            }
-        }
-        if (blockRestore) {
-            onRestoreBlocked();
-        } else {
-            if (ShortcutService.DEBUG) {
-                Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
-                        mPackageUserId, getOwnerUserId()));
-            }
+        int restoreBlockReason;
+        int currentVersionCode = ShortcutInfo.VERSION_CODE_UNKNOWN;
 
-            onRestored();
+        if (!mPackageInfo.hasSignatures()) {
+            s.wtf("Attempted to restore package " + mPackageName + "/u" + mPackageUserId
+                    + " but signatures not found in the restore data.");
+            restoreBlockReason = ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+        } else {
+            final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
+            currentVersionCode = pi.versionCode;
+            restoreBlockReason = mPackageInfo.canRestoreTo(s, pi, canRestoreAnyVersion());
         }
 
+        if (ShortcutService.DEBUG) {
+            Slog.d(TAG, String.format("Restoring package: %s/u%d (version=%d) %s for u%d",
+                    mPackageName, mPackageUserId, currentVersionCode,
+                    ShortcutInfo.getDisabledReasonDebugString(restoreBlockReason),
+                    getOwnerUserId()));
+        }
+
+        onRestored(restoreBlockReason);
+
         // Either way, it's no longer a shadow.
         mPackageInfo.setShadow(false);
 
         s.scheduleSaveUser(mPackageUserId);
     }
 
-    /**
-     * Called when the new package can't be restored because it has a lower version number
-     * or different signatures.
-     */
-    protected abstract void onRestoreBlocked();
+    protected abstract boolean canRestoreAnyVersion();
 
-    /**
-     * Called when the new package is successfully restored.
-     */
-    protected abstract void onRestored();
+    protected abstract void onRestored(int restoreBlockReason);
 
     public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
             throws IOException, XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/ShortcutParser.java b/services/core/java/com/android/server/pm/ShortcutParser.java
index 3cf4200..866c46c 100644
--- a/services/core/java/com/android/server/pm/ShortcutParser.java
+++ b/services/core/java/com/android/server/pm/ShortcutParser.java
@@ -337,6 +337,9 @@
                 (enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
                 | ShortcutInfo.FLAG_IMMUTABLE
                 | ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
+        final int disabledReason =
+                enabled ? ShortcutInfo.DISABLED_REASON_NOT_DISABLED
+                        : ShortcutInfo.DISABLED_REASON_BY_APP;
 
         // Note we don't need to set resource names here yet.  They'll be set when they're about
         // to be published.
@@ -363,6 +366,7 @@
                 flags,
                 iconResId,
                 null, // icon res name
-                null); // bitmap path
+                null, // bitmap path
+                disabledReason);
     }
 }
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 8a8128d..3e44de9 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -300,10 +300,12 @@
 
         final ShortcutInfo existing = ps.findShortcutById(inShortcut.getId());
         final boolean existsAlready = existing != null;
+        final boolean existingIsVisible = existsAlready && existing.isVisibleToPublisher();
 
         if (DEBUG) {
             Slog.d(TAG, "requestPinnedShortcut: package=" + inShortcut.getPackage()
                     + " existsAlready=" + existsAlready
+                    + " existingIsVisible=" + existingIsVisible
                     + " shortcut=" + inShortcut.toInsecureString());
         }
 
@@ -378,7 +380,6 @@
         // manifest shortcut.)
         Preconditions.checkArgument(shortcutInfo.isEnabled(),
                 "Shortcut ID=" + shortcutInfo + " already exists but disabled.");
-
     }
 
     private boolean startRequestConfirmActivity(ComponentName activity, int launcherUserId,
@@ -463,7 +464,7 @@
             launcher.attemptToRestoreIfNeededAndSave();
             if (launcher.hasPinned(original)) {
                 if (DEBUG) {
-                    Slog.d(TAG, "Shortcut " + original + " already pinned.");
+                    Slog.d(TAG, "Shortcut " + original + " already pinned.");                       // This too.
                 }
                 return true;
             }
@@ -497,7 +498,7 @@
                 if (original.getActivity() == null) {
                     original.setActivity(mService.getDummyMainActivity(appPackageName));
                 }
-                ps.addOrUpdateDynamicShortcut(original);
+                ps.addOrReplaceDynamicShortcut(original);
             }
 
             // Pin the shortcut.
@@ -505,13 +506,14 @@
                 Slog.d(TAG, "Pinning " + shortcutId);
             }
 
-            launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId);
+            launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId,
+                    /*forPinRequest=*/ true);
 
             if (current == null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Removing " + shortcutId + " as dynamic");
                 }
-                ps.deleteDynamicWithId(shortcutId);
+                ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false);
             }
 
             ps.adjustRanks(); // Shouldn't be needed, but just in case.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 0e572d8..1c002aa 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -40,8 +40,8 @@
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -134,6 +134,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 /**
  * TODO:
@@ -484,12 +485,13 @@
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
-            handleOnUidStateChanged(uid, procState);
+            injectPostToHandler(() -> handleOnUidStateChanged(uid, procState));
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
+            injectPostToHandler(() ->
+                    handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT));
         }
 
         @Override
@@ -551,6 +553,9 @@
 
         public Lifecycle(Context context) {
             super(context);
+            if (DEBUG) {
+                Binder.LOG_RUNTIME_EXCEPTION = true;
+            }
             mService = new ShortcutService(context);
         }
 
@@ -736,6 +741,10 @@
         return parseLongAttribute(parser, attribute) == 1;
     }
 
+    static boolean parseBooleanAttribute(XmlPullParser parser, String attribute, boolean def) {
+        return parseLongAttribute(parser, attribute, (def ? 1 : 0)) == 1;
+    }
+
     static int parseIntAttribute(XmlPullParser parser, String attribute) {
         return (int) parseLongAttribute(parser, attribute);
     }
@@ -833,6 +842,8 @@
     static void writeAttr(XmlSerializer out, String name, boolean value) throws IOException {
         if (value) {
             writeAttr(out, name, "1");
+        } else {
+            writeAttr(out, name, "0");
         }
     }
 
@@ -1687,7 +1698,7 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
 
             fillInDefaultActivity(newShortcuts);
 
@@ -1707,12 +1718,12 @@
             }
 
             // First, remove all un-pinned; dynamic shortcuts
-            ps.deleteAllDynamicShortcuts();
+            ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
 
             // Then, add/update all.  We need to make sure to take over "pinned" flag.
             for (int i = 0; i < size; i++) {
                 final ShortcutInfo newShortcut = newShortcuts.get(i);
-                ps.addOrUpdateDynamicShortcut(newShortcut);
+                ps.addOrReplaceDynamicShortcut(newShortcut);
             }
 
             // Lastly, adjust the ranks.
@@ -1738,7 +1749,7 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
 
             // For update, don't fill in the default activity.  Having null activity means
             // "don't update the activity" here.
@@ -1759,7 +1770,9 @@
                 fixUpIncomingShortcutInfo(source, /* forUpdate= */ true);
 
                 final ShortcutInfo target = ps.findShortcutById(source.getId());
-                if (target == null) {
+
+                // Invisible shortcuts can't be updated.
+                if (target == null || !target.isVisibleToPublisher()) {
                     continue;
                 }
 
@@ -1806,7 +1819,7 @@
     }
 
     @Override
-    public boolean addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
+    public boolean  addDynamicShortcuts(String packageName, ParceledListSlice shortcutInfoList,
             @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
@@ -1818,7 +1831,7 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
+            ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
 
             fillInDefaultActivity(newShortcuts);
 
@@ -1843,7 +1856,7 @@
                 newShortcut.setRankChanged();
 
                 // Add it.
-                ps.addOrUpdateDynamicShortcut(newShortcut);
+                ps.addOrReplaceDynamicShortcut(newShortcut);
             }
 
             // Lastly, adjust the ranks.
@@ -1899,6 +1912,22 @@
             Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
                     "Calling application must have a foreground activity or a foreground service");
 
+            // If it's a pin shortcut request, and there's already a shortcut with the same ID
+            // that's not visible to the caller (i.e. restore-blocked; meaning it's pinned by
+            // someone already), then we just replace the existing one with this new one,
+            // and then proceed the rest of the process.
+            if (shortcut != null) {
+                final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
+                        packageName, userId);
+                final String id = shortcut.getId();
+                if (ps.isShortcutExistsAndInvisibleToPublisher(id)) {
+
+                    ps.updateInvisibleShortcutForPinRequestWith(shortcut);
+
+                    packageShortcutsChanged(packageName, userId);
+                }
+            }
+
             // Send request to the launcher, if supported.
             ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
                     userId, resultIntent);
@@ -1920,15 +1949,21 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+                    /*ignoreInvisible=*/ true);
 
             final String disabledMessageString =
                     (disabledMessage == null) ? null : disabledMessage.toString();
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
+                final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+                if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+                    continue;
+                }
+                ps.disableWithId(id,
                         disabledMessageString, disabledMessageResId,
-                        /* overrideImmutable=*/ false);
+                        /* overrideImmutable=*/ false, /*ignoreInvisible=*/ true,
+                        ShortcutInfo.DISABLED_REASON_BY_APP);
             }
 
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1949,10 +1984,15 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+                    /*ignoreInvisible=*/ true);
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                ps.enableWithId((String) shortcutIds.get(i));
+                final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+                if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+                    continue;
+                }
+                ps.enableWithId(id);
             }
         }
         packageShortcutsChanged(packageName, userId);
@@ -1971,11 +2011,15 @@
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
 
-            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
+            ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds,
+                    /*ignoreInvisible=*/ true);
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
-                ps.deleteDynamicWithId(
-                        Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
+                final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
+                if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
+                    continue;
+                }
+                ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
             }
 
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
@@ -1994,7 +2038,7 @@
             throwIfUserLockedL(userId);
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
-            ps.deleteAllDynamicShortcuts();
+            ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
         }
         packageShortcutsChanged(packageName, userId);
 
@@ -2011,7 +2055,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isDynamic);
+                    ShortcutInfo::isDynamicVisible);
         }
     }
 
@@ -2025,7 +2069,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isManifestShortcut);
+                    ShortcutInfo::isManifestVisible);
         }
     }
 
@@ -2039,7 +2083,7 @@
 
             return getShortcutsWithQueryLocked(
                     packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isPinned);
+                    ShortcutInfo::isPinnedVisible);
         }
     }
 
@@ -2188,7 +2232,11 @@
     }
 
     // We override this method in unit tests to do a simpler check.
-    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
+    boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId,
+            int callingPid, int callingUid) {
+        if (injectCheckAccessShortcutsPermission(callingPid, callingUid)) {
+            return true;
+        }
         final long start = injectElapsedRealtime();
         try {
             return hasShortcutHostPermissionInner(callingPackage, userId);
@@ -2197,6 +2245,14 @@
         }
     }
 
+    /**
+     * Returns true if the caller has the "ACCESS_SHORTCUTS" permission.
+     */
+    boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) {
+        return mContext.checkPermission(android.Manifest.permission.ACCESS_SHORTCUTS,
+                callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
+    }
+
     // This method is extracted so we can directly call this method from unit tests,
     // even when hasShortcutPermission() is overridden.
     @VisibleForTesting
@@ -2377,7 +2433,7 @@
                 @NonNull String callingPackage, long changedSince,
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
                 @Nullable ComponentName componentName,
-                int queryFlags, int userId) {
+                int queryFlags, int userId, int callingPid, int callingUid) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
             final boolean cloneKeyFieldOnly =
@@ -2398,13 +2454,15 @@
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
                             callingPackage, packageName, shortcutIds, changedSince,
-                            componentName, queryFlags, userId, ret, cloneFlag);
+                            componentName, queryFlags, userId, ret, cloneFlag,
+                            callingPid, callingUid);
                 } else {
                     final List<String> shortcutIdsF = shortcutIds;
                     getUserShortcutsLocked(userId).forAllPackages(p -> {
                         getShortcutsInnerLocked(launcherUserId,
                                 callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
-                                componentName, queryFlags, userId, ret, cloneFlag);
+                                componentName, queryFlags, userId, ret, cloneFlag,
+                                callingPid, callingUid);
                     });
                 }
             }
@@ -2414,7 +2472,8 @@
         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
                 @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
                 @Nullable ComponentName componentName, int queryFlags,
-                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag) {
+                int userId, ArrayList<ShortcutInfo> ret, int cloneFlag,
+                int callingPid, int callingUid) {
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
 
@@ -2423,6 +2482,13 @@
             if (p == null) {
                 return; // No need to instantiate ShortcutPackage.
             }
+            final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+            final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+            final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+
+            final boolean getPinnedByAnyLauncher =
+                    ((queryFlags & ShortcutQuery.FLAG_MATCH_ALL_PINNED) != 0)
+                    && injectCheckAccessShortcutsPermission(callingPid, callingUid);
 
             p.findAll(ret,
                     (ShortcutInfo si) -> {
@@ -2438,20 +2504,17 @@
                                 return false;
                             }
                         }
-                        if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
-                                && si.isDynamic()) {
+                        if (matchDynamic && si.isDynamic()) {
                             return true;
                         }
-                        if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
-                                && si.isPinned()) {
+                        if ((matchPinned && si.isPinned()) || getPinnedByAnyLauncher) {
                             return true;
                         }
-                        if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
-                                && si.isManifestShortcut()) {
+                        if (matchManifest && si.isDeclaredInManifest()) {
                             return true;
                         }
                         return false;
-                    }, cloneFlag, callingPackage, launcherUserId);
+                    }, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher);
         }
 
         @Override
@@ -2468,14 +2531,16 @@
                         .attemptToRestoreIfNeededAndSave();
 
                 final ShortcutInfo si = getShortcutInfoLocked(
-                        launcherUserId, callingPackage, packageName, shortcutId, userId);
+                        launcherUserId, callingPackage, packageName, shortcutId, userId,
+                        /*getPinnedByAnyLauncher=*/ false);
                 return si != null && si.isPinned();
             }
         }
 
         private ShortcutInfo getShortcutInfoLocked(
                 int launcherUserId, @NonNull String callingPackage,
-                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+                @NonNull String packageName, @NonNull String shortcutId, int userId,
+                boolean getPinnedByAnyLauncher) {
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
 
@@ -2491,7 +2556,7 @@
             final ArrayList<ShortcutInfo> list = new ArrayList<>(1);
             p.findAll(list,
                     (ShortcutInfo si) -> shortcutId.equals(si.getId()),
-                    /* clone flags=*/ 0, callingPackage, launcherUserId);
+                    /* clone flags=*/ 0, callingPackage, launcherUserId, getPinnedByAnyLauncher);
             return list.size() == 0 ? null : list.get(0);
         }
 
@@ -2511,7 +2576,7 @@
                         getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
                 launcher.attemptToRestoreIfNeededAndSave();
 
-                launcher.pinShortcuts(userId, packageName, shortcutIds);
+                launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false);
             }
             packageShortcutsChanged(packageName, userId);
 
@@ -2521,7 +2586,8 @@
         @Override
         public Intent[] createShortcutIntents(int launcherUserId,
                 @NonNull String callingPackage,
-                @NonNull String packageName, @NonNull String shortcutId, int userId) {
+                @NonNull String packageName, @NonNull String shortcutId, int userId,
+                int callingPid, int callingUid) {
             // Calling permission must be checked by LauncherAppsImpl.
             Preconditions.checkStringNotEmpty(packageName, "packageName can't be empty");
             Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
@@ -2533,9 +2599,13 @@
                 getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
                         .attemptToRestoreIfNeededAndSave();
 
+                final boolean getPinnedByAnyLauncher =
+                        injectCheckAccessShortcutsPermission(callingPid, callingUid);
+
                 // Make sure the shortcut is actually visible to the launcher.
                 final ShortcutInfo si = getShortcutInfoLocked(
-                        launcherUserId, callingPackage, packageName, shortcutId, userId);
+                        launcherUserId, callingPackage, packageName, shortcutId, userId,
+                        getPinnedByAnyLauncher);
                 // "si == null" should suffice here, but check the flags too just to make sure.
                 if (si == null || !si.isEnabled() || !si.isAlive()) {
                     Log.e(TAG, "Shortcut " + shortcutId + " does not exist or disabled");
@@ -2621,8 +2691,9 @@
 
         @Override
         public boolean hasShortcutHostPermission(int launcherUserId,
-                @NonNull String callingPackage) {
-            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId);
+                @NonNull String callingPackage, int callingPid, int callingUid) {
+            return ShortcutService.this.hasShortcutHostPermission(callingPackage, launcherUserId,
+                    callingPid, callingUid);
         }
 
         @Override
@@ -3341,7 +3412,7 @@
         return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
     }
 
-    boolean shouldBackupApp(PackageInfo pi) {
+    static boolean shouldBackupApp(PackageInfo pi) {
         return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
     }
 
@@ -3369,7 +3440,7 @@
             // Set the version code for the launchers.
             // We shouldn't do this for publisher packages, because we don't want to update the
             // version code without rescanning the manifest.
-            user.forAllLaunchers(launcher -> launcher.ensureVersionInfo());
+            user.forAllLaunchers(launcher -> launcher.ensurePackageInfo());
 
             // Save to the filesystem.
             scheduleSaveUser(userId);
@@ -3454,121 +3525,265 @@
 
     @VisibleForTesting
     void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final DumpFilter filter = parseDumpArgs(args);
 
-        boolean dumpMain = true;
-        boolean checkin = false;
-        boolean clear = false;
-        boolean dumpUid = false;
-        boolean dumpFiles = false;
-
-        if (args != null) {
-            for (String arg : args) {
-                if ("-c".equals(arg)) {
-                    checkin = true;
-
-                } else if ("--checkin".equals(arg)) {
-                    checkin = true;
-                    clear = true;
-
-                } else if ("-a".equals(arg) || "--all".equals(arg)) {
-                    dumpUid = true;
-                    dumpFiles = true;
-
-                } else if ("-u".equals(arg) || "--uid".equals(arg)) {
-                    dumpUid = true;
-
-                } else if ("-f".equals(arg) || "--files".equals(arg)) {
-                    dumpFiles = true;
-
-                } else if ("-n".equals(arg) || "--no-main".equals(arg)) {
-                    dumpMain = false;
-                }
-            }
-        }
-
-        if (checkin) {
+        if (filter.shouldDumpCheckIn()) {
             // Other flags are not supported for checkin.
-            dumpCheckin(pw, clear);
+            dumpCheckin(pw, filter.shouldCheckInClear());
         } else {
-            if (dumpMain) {
-                dumpInner(pw);
+            if (filter.shouldDumpMain()) {
+                dumpInner(pw, filter);
                 pw.println();
             }
-            if (dumpUid) {
+            if (filter.shouldDumpUid()) {
                 dumpUid(pw);
                 pw.println();
             }
-            if (dumpFiles) {
+            if (filter.shouldDumpFiles()) {
                 dumpDumpFiles(pw);
                 pw.println();
             }
         }
     }
 
-    private void dumpInner(PrintWriter pw) {
-        synchronized (mLock) {
-            final long now = injectCurrentTimeMillis();
-            pw.print("Now: [");
-            pw.print(now);
-            pw.print("] ");
-            pw.print(formatTime(now));
+    private static DumpFilter parseDumpArgs(String[] args) {
+        final DumpFilter filter = new DumpFilter();
+        if (args == null) {
+            return filter;
+        }
 
-            pw.print("  Raw last reset: [");
-            pw.print(mRawLastResetTime);
-            pw.print("] ");
-            pw.print(formatTime(mRawLastResetTime));
+        int argIndex = 0;
+        while (argIndex < args.length) {
+            final String arg = args[argIndex++];
 
-            final long last = getLastResetTimeLocked();
-            pw.print("  Last reset: [");
-            pw.print(last);
-            pw.print("] ");
-            pw.print(formatTime(last));
+            if ("-c".equals(arg)) {
+                filter.setDumpCheckIn(true);
+                continue;
+            }
+            if ("--checkin".equals(arg)) {
+                filter.setDumpCheckIn(true);
+                filter.setCheckInClear(true);
+                continue;
+            }
+            if ("-a".equals(arg) || "--all".equals(arg)) {
+                filter.setDumpUid(true);
+                filter.setDumpFiles(true);
+                continue;
+            }
+            if ("-u".equals(arg) || "--uid".equals(arg)) {
+                filter.setDumpUid(true);
+                continue;
+            }
+            if ("-f".equals(arg) || "--files".equals(arg)) {
+                filter.setDumpFiles(true);
+                continue;
+            }
+            if ("-n".equals(arg) || "--no-main".equals(arg)) {
+                filter.setDumpMain(false);
+                continue;
+            }
+            if ("--user".equals(arg)) {
+                if (argIndex >= args.length) {
+                    throw new IllegalArgumentException("Missing user ID for --user");
+                }
+                try {
+                    filter.addUser(Integer.parseInt(args[argIndex++]));
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("Invalid user ID", e);
+                }
+                continue;
+            }
+            if ("-p".equals(arg) || "--package".equals(arg)) {
+                if (argIndex >= args.length) {
+                    throw new IllegalArgumentException("Missing package name for --package");
+                }
+                filter.addPackageRegex(args[argIndex++]);
+                filter.setDumpDetails(false);
+                continue;
+            }
+            if (arg.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown option " + arg);
+            }
+            break;
+        }
+        while (argIndex < args.length) {
+            filter.addPackage(args[argIndex++]);
+        }
+        return filter;
+    }
 
-            final long next = getNextResetTimeLocked();
-            pw.print("  Next reset: [");
-            pw.print(next);
-            pw.print("] ");
-            pw.print(formatTime(next));
+    static class DumpFilter {
+        private boolean mDumpCheckIn = false;
+        private boolean mCheckInClear = false;
 
-            pw.print("  Config:");
-            pw.print("    Max icon dim: ");
-            pw.println(mMaxIconDimension);
-            pw.print("    Icon format: ");
-            pw.println(mIconPersistFormat);
-            pw.print("    Icon quality: ");
-            pw.println(mIconPersistQuality);
-            pw.print("    saveDelayMillis: ");
-            pw.println(mSaveDelayMillis);
-            pw.print("    resetInterval: ");
-            pw.println(mResetInterval);
-            pw.print("    maxUpdatesPerInterval: ");
-            pw.println(mMaxUpdatesPerInterval);
-            pw.print("    maxShortcutsPerActivity: ");
-            pw.println(mMaxShortcuts);
-            pw.println();
+        private boolean mDumpMain = true;
+        private boolean mDumpUid = false;
+        private boolean mDumpFiles = false;
 
-            pw.println("  Stats:");
-            synchronized (mStatLock) {
-                for (int i = 0; i < Stats.COUNT; i++) {
-                    dumpStatLS(pw, "    ", i);
+        private boolean mDumpDetails = true;
+        private List<Pattern> mPackagePatterns = new ArrayList<>();
+        private List<Integer> mUsers = new ArrayList<>();
+
+        void addPackageRegex(String regex) {
+            mPackagePatterns.add(Pattern.compile(regex));
+        }
+
+        public void addPackage(String packageName) {
+            addPackageRegex(Pattern.quote(packageName));
+        }
+
+        void addUser(int userId) {
+            mUsers.add(userId);
+        }
+
+        boolean isPackageMatch(String packageName) {
+            if (mPackagePatterns.size() == 0) {
+                return true;
+            }
+            for (int i = 0; i < mPackagePatterns.size(); i++) {
+                if (mPackagePatterns.get(i).matcher(packageName).find()) {
+                    return true;
                 }
             }
+            return false;
+        }
 
-            pw.println();
-            pw.print("  #Failures: ");
-            pw.println(mWtfCount);
+        boolean isUserMatch(int userId) {
+            if (mUsers.size() == 0) {
+                return true;
+            }
+            for (int i = 0; i < mUsers.size(); i++) {
+                if (mUsers.get(i) == userId) {
+                    return true;
+                }
+            }
+            return false;
+        }
 
-            if (mLastWtfStacktrace != null) {
-                pw.print("  Last failure stack trace: ");
-                pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+        public boolean shouldDumpCheckIn() {
+            return mDumpCheckIn;
+        }
+
+        public void setDumpCheckIn(boolean dumpCheckIn) {
+            mDumpCheckIn = dumpCheckIn;
+        }
+
+        public boolean shouldCheckInClear() {
+            return mCheckInClear;
+        }
+
+        public void setCheckInClear(boolean checkInClear) {
+            mCheckInClear = checkInClear;
+        }
+
+        public boolean shouldDumpMain() {
+            return mDumpMain;
+        }
+
+        public void setDumpMain(boolean dumpMain) {
+            mDumpMain = dumpMain;
+        }
+
+        public boolean shouldDumpUid() {
+            return mDumpUid;
+        }
+
+        public void setDumpUid(boolean dumpUid) {
+            mDumpUid = dumpUid;
+        }
+
+        public boolean shouldDumpFiles() {
+            return mDumpFiles;
+        }
+
+        public void setDumpFiles(boolean dumpFiles) {
+            mDumpFiles = dumpFiles;
+        }
+
+        public boolean shouldDumpDetails() {
+            return mDumpDetails;
+        }
+
+        public void setDumpDetails(boolean dumpDetails) {
+            mDumpDetails = dumpDetails;
+        }
+    }
+
+    private void dumpInner(PrintWriter pw) {
+        dumpInner(pw, new DumpFilter());
+    }
+
+    private void dumpInner(PrintWriter pw, DumpFilter filter) {
+        synchronized (mLock) {
+            if (filter.shouldDumpDetails()) {
+                final long now = injectCurrentTimeMillis();
+                pw.print("Now: [");
+                pw.print(now);
+                pw.print("] ");
+                pw.print(formatTime(now));
+
+                pw.print("  Raw last reset: [");
+                pw.print(mRawLastResetTime);
+                pw.print("] ");
+                pw.print(formatTime(mRawLastResetTime));
+
+                final long last = getLastResetTimeLocked();
+                pw.print("  Last reset: [");
+                pw.print(last);
+                pw.print("] ");
+                pw.print(formatTime(last));
+
+                final long next = getNextResetTimeLocked();
+                pw.print("  Next reset: [");
+                pw.print(next);
+                pw.print("] ");
+                pw.print(formatTime(next));
+
+                pw.print("  Config:");
+                pw.print("    Max icon dim: ");
+                pw.println(mMaxIconDimension);
+                pw.print("    Icon format: ");
+                pw.println(mIconPersistFormat);
+                pw.print("    Icon quality: ");
+                pw.println(mIconPersistQuality);
+                pw.print("    saveDelayMillis: ");
+                pw.println(mSaveDelayMillis);
+                pw.print("    resetInterval: ");
+                pw.println(mResetInterval);
+                pw.print("    maxUpdatesPerInterval: ");
+                pw.println(mMaxUpdatesPerInterval);
+                pw.print("    maxShortcutsPerActivity: ");
+                pw.println(mMaxShortcuts);
+                pw.println();
+
+                pw.println("  Stats:");
+                synchronized (mStatLock) {
+                    for (int i = 0; i < Stats.COUNT; i++) {
+                        dumpStatLS(pw, "    ", i);
+                    }
+                }
+
+                pw.println();
+                pw.print("  #Failures: ");
+                pw.println(mWtfCount);
+
+                if (mLastWtfStacktrace != null) {
+                    pw.print("  Last failure stack trace: ");
+                    pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+                }
+
+                pw.println();
+                mShortcutBitmapSaver.dumpLocked(pw, "  ");
+
+                pw.println();
             }
 
-            pw.println();
-            mShortcutBitmapSaver.dumpLocked(pw, "  ");
-
             for (int i = 0; i < mUsers.size(); i++) {
-                pw.println();
-                mUsers.valueAt(i).dump(pw, "  ");
+                final ShortcutUser user = mUsers.valueAt(i);
+                if (filter.isUserMatch(user.getUserId())) {
+                    user.dump(pw, "  ", filter);
+                    pw.println();
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 2c388c4..48eccd0 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.InvalidFileFormatException;
 
 import libcore.util.Objects;
@@ -363,9 +364,6 @@
     private void saveShortcutPackageItem(XmlSerializer out,
             ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException {
         if (forBackup) {
-            if (!mService.shouldBackupApp(spi.getPackageName(), spi.getPackageUserId())) {
-                return; // Don't save.
-            }
             if (spi.getPackageUserId() != spi.getOwnerUserId()) {
                 return; // Don't save cross-user information.
             }
@@ -531,44 +529,54 @@
                 + " S=" + restoredShortcuts[0]);
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-        pw.print(prefix);
-        pw.print("User: ");
-        pw.print(mUserId);
-        pw.print("  Known locales: ");
-        pw.print(mKnownLocales);
-        pw.print("  Last app scan: [");
-        pw.print(mLastAppScanTime);
-        pw.print("] ");
-        pw.print(ShortcutService.formatTime(mLastAppScanTime));
-        pw.print("  Last app scan FP: ");
-        pw.print(mLastAppScanOsFingerprint);
-        pw.println();
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
+        if (filter.shouldDumpDetails()) {
+            pw.print(prefix);
+            pw.print("User: ");
+            pw.print(mUserId);
+            pw.print("  Known locales: ");
+            pw.print(mKnownLocales);
+            pw.print("  Last app scan: [");
+            pw.print(mLastAppScanTime);
+            pw.print("] ");
+            pw.print(ShortcutService.formatTime(mLastAppScanTime));
+            pw.print("  Last app scan FP: ");
+            pw.print(mLastAppScanOsFingerprint);
+            pw.println();
 
-        prefix += prefix + "  ";
+            prefix += prefix + "  ";
 
-        pw.print(prefix);
-        pw.print("Cached launcher: ");
-        pw.print(mCachedLauncher);
-        pw.println();
+            pw.print(prefix);
+            pw.print("Cached launcher: ");
+            pw.print(mCachedLauncher);
+            pw.println();
 
-        pw.print(prefix);
-        pw.print("Last known launcher: ");
-        pw.print(mLastKnownLauncher);
-        pw.println();
+            pw.print(prefix);
+            pw.print("Last known launcher: ");
+            pw.print(mLastKnownLauncher);
+            pw.println();
+        }
 
         for (int i = 0; i < mLaunchers.size(); i++) {
-            mLaunchers.valueAt(i).dump(pw, prefix);
+            ShortcutLauncher launcher = mLaunchers.valueAt(i);
+            if (filter.isPackageMatch(launcher.getPackageName())) {
+                launcher.dump(pw, prefix, filter);
+            }
         }
 
         for (int i = 0; i < mPackages.size(); i++) {
-            mPackages.valueAt(i).dump(pw, prefix);
+            ShortcutPackage pkg = mPackages.valueAt(i);
+            if (filter.isPackageMatch(pkg.getPackageName())) {
+                pkg.dump(pw, prefix, filter);
+            }
         }
 
-        pw.println();
-        pw.print(prefix);
-        pw.println("Bitmap directories: ");
-        dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
+        if (filter.shouldDumpDetails()) {
+            pw.println();
+            pw.print(prefix);
+            pw.println("Bitmap directories: ");
+            dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
+        }
     }
 
     private void dumpDirectorySize(@NonNull PrintWriter pw,
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index b8b00af..bfe09b8 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -132,11 +132,9 @@
                 if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
                     FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
                     FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
-                    FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
                 }
                 if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
                     FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
-                    FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
                 }
             }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f2d527b..1e5245c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1055,7 +1055,7 @@
 
     /** Called by PackageManagerService */
     public boolean exists(int userId) {
-        return getUserInfoNoChecks(userId) != null;
+        return mLocalService.exists(userId);
     }
 
     @Override
@@ -3502,8 +3502,8 @@
      * @param userId
      * @return whether the user has been initialized yet
      */
-    boolean isInitialized(int userId) {
-        return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+    boolean isUserInitialized(int userId) {
+        return mLocalService.isUserInitialized(userId);
     }
 
     private class LocalService extends UserManagerInternal {
@@ -3715,6 +3715,16 @@
             }
             return state == UserState.STATE_RUNNING_UNLOCKED;
         }
+
+        @Override
+        public boolean isUserInitialized(int userId) {
+            return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+        }
+
+        @Override
+        public boolean exists(int userId) {
+            return getUserInfoNoChecks(userId) != null;
+        }
     }
 
     /* Remove all the users except of the system one. */
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index a6b05d7..c86122f 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -80,6 +80,7 @@
             UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
             UserManager.DISALLOW_DEBUGGING_FEATURES,
             UserManager.DISALLOW_CONFIG_VPN,
+            UserManager.DISALLOW_CONFIG_DATE_TIME,
             UserManager.DISALLOW_CONFIG_TETHERING,
             UserManager.DISALLOW_NETWORK_RESET,
             UserManager.DISALLOW_FACTORY_RESET,
@@ -96,6 +97,7 @@
             UserManager.DISALLOW_SMS,
             UserManager.DISALLOW_FUN,
             UserManager.DISALLOW_CREATE_WINDOWS,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
             UserManager.DISALLOW_OUTGOING_BEAM,
             UserManager.DISALLOW_WALLPAPER,
@@ -156,6 +158,8 @@
     private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
             UserManager.DISALLOW_ADJUST_VOLUME,
             UserManager.DISALLOW_BLUETOOTH_SHARING,
+            UserManager.DISALLOW_CONFIG_DATE_TIME,
+            UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS,
             UserManager.DISALLOW_RUN_IN_BACKGROUND,
             UserManager.DISALLOW_UNMUTE_MICROPHONE,
             UserManager.DISALLOW_UNMUTE_DEVICE
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index 4fa47b5..0966770 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -56,6 +56,9 @@
     // actually shared at runtime.
     public static final int DEXOPT_AS_SHARED_LIBRARY = 1 << 6;
 
+    // When set, indicates that dexopt is invoked from the background service.
+    public static final int DEXOPT_IDLE_BACKGROUND_JOB = 1 << 9;
+
     // The name of package to optimize.
     private final String mPackageName;
 
@@ -86,7 +89,8 @@
                 DEXOPT_ONLY_SECONDARY_DEX |
                 DEXOPT_ONLY_SHARED_DEX |
                 DEXOPT_DOWNGRADE |
-                DEXOPT_AS_SHARED_LIBRARY;
+                DEXOPT_AS_SHARED_LIBRARY |
+                DEXOPT_IDLE_BACKGROUND_JOB;
         if ((flags & (~validityMask)) != 0) {
             throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags));
         }
@@ -133,6 +137,10 @@
         return (mFlags & DEXOPT_AS_SHARED_LIBRARY) != 0;
     }
 
+    public boolean isDexoptIdleBackgroundJob() {
+        return (mFlags & DEXOPT_IDLE_BACKGROUND_JOB) != 0;
+    }
+
     public String getSplitName() {
         return mSplitName;
     }
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
new file mode 100644
index 0000000..8c86db6
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2006 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.server.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.content.pm.PermissionInfo.PROTECTION_NORMAL;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM;
+
+import static com.android.server.pm.Settings.ATTR_NAME;
+import static com.android.server.pm.Settings.ATTR_PACKAGE;
+import static com.android.server.pm.Settings.TAG_ITEM;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Permission;
+import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSettingBase;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public final class BasePermission {
+    static final String TAG = "PackageManager";
+
+    public static final int TYPE_NORMAL = 0;
+    public static final int TYPE_BUILTIN = 1;
+    public static final int TYPE_DYNAMIC = 2;
+    @IntDef(value = {
+        TYPE_NORMAL,
+        TYPE_BUILTIN,
+        TYPE_DYNAMIC,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PermissionType {}
+
+    @IntDef(value = {
+        PROTECTION_DANGEROUS,
+        PROTECTION_NORMAL,
+        PROTECTION_SIGNATURE,
+        PROTECTION_SIGNATURE_OR_SYSTEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProtectionLevel {}
+
+    final String name;
+
+    final @PermissionType int type;
+
+    String sourcePackageName;
+
+    // TODO: Can we get rid of this? Seems we only use some signature info from the setting
+    PackageSettingBase sourcePackageSetting;
+
+    int protectionLevel;
+
+    PackageParser.Permission perm;
+
+    PermissionInfo pendingPermissionInfo;
+
+    /** UID that owns the definition of this permission */
+    int uid;
+
+    /** Additional GIDs given to apps granted this permission */
+    private int[] gids;
+
+    /**
+     * Flag indicating that {@link #gids} should be adjusted based on the
+     * {@link UserHandle} the granted app is running as.
+     */
+    private boolean perUser;
+
+    public BasePermission(String _name, String _sourcePackageName, @PermissionType int _type) {
+        name = _name;
+        sourcePackageName = _sourcePackageName;
+        type = _type;
+        // Default to most conservative protection level.
+        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    @Override
+    public String toString() {
+        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+                + "}";
+    }
+
+    public String getName() {
+        return name;
+    }
+    public int getProtectionLevel() {
+        return protectionLevel;
+    }
+    public String getSourcePackageName() {
+        return sourcePackageName;
+    }
+    public PackageSettingBase getSourcePackageSetting() {
+        return sourcePackageSetting;
+    }
+    public Signature[] getSourceSignatures() {
+        return sourcePackageSetting.getSignatures();
+    }
+    public int getType() {
+        return type;
+    }
+    public int getUid() {
+        return uid;
+    }
+    public void setGids(int[] gids, boolean perUser) {
+        this.gids = gids;
+        this.perUser = perUser;
+    }
+    public void setPermission(@Nullable Permission perm) {
+        this.perm = perm;
+    }
+    public void setSourcePackageSetting(PackageSettingBase sourcePackageSetting) {
+        this.sourcePackageSetting = sourcePackageSetting;
+    }
+
+    public int[] computeGids(int userId) {
+        if (perUser) {
+            final int[] userGids = new int[gids.length];
+            for (int i = 0; i < gids.length; i++) {
+                userGids[i] = UserHandle.getUid(userId, gids[i]);
+            }
+            return userGids;
+        } else {
+            return gids;
+        }
+    }
+
+    public int calculateFootprint(BasePermission perm) {
+        if (uid == perm.uid) {
+            return perm.name.length() + perm.perm.info.calculateFootprint();
+        }
+        return 0;
+    }
+
+    public boolean isPermission(Permission perm) {
+        return this.perm == perm;
+    }
+
+    public boolean isDynamic() {
+        return type == TYPE_DYNAMIC;
+    }
+
+
+    public boolean isNormal() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_NORMAL;
+    }
+    public boolean isRuntime() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_DANGEROUS;
+    }
+    public boolean isSignature() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
+                PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    public boolean isAppOp() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+    }
+    public boolean isDevelopment() {
+        return isSignature()
+                && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
+    }
+    public boolean isInstaller() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0;
+    }
+    public boolean isInstant() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
+    }
+    public boolean isOEM() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
+    }
+    public boolean isPre23() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0;
+    }
+    public boolean isPreInstalled() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0;
+    }
+    public boolean isPrivileged() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+    }
+    public boolean isRuntimeOnly() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
+    }
+    public boolean isSetup() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0;
+    }
+    public boolean isVerifier() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0;
+    }
+
+    public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
+        if (!origPackageName.equals(sourcePackageName)) {
+            return;
+        }
+        sourcePackageName = newPackageName;
+        sourcePackageSetting = null;
+        perm = null;
+        if (pendingPermissionInfo != null) {
+            pendingPermissionInfo.packageName = newPackageName;
+        }
+        uid = 0;
+        setGids(null, false);
+    }
+
+    public boolean addToTree(@ProtectionLevel int protectionLevel,
+            @NonNull PermissionInfo info, @NonNull BasePermission tree) {
+        final boolean changed =
+                (this.protectionLevel != protectionLevel
+                    || perm == null
+                    || uid != tree.uid
+                    || !perm.owner.equals(tree.perm.owner)
+                    || !comparePermissionInfos(perm.info, info));
+        this.protectionLevel = protectionLevel;
+        info = new PermissionInfo(info);
+        info.protectionLevel = protectionLevel;
+        perm = new PackageParser.Permission(tree.perm.owner, info);
+        perm.info.packageName = tree.perm.info.packageName;
+        uid = tree.uid;
+        return changed;
+    }
+
+    public void updateDynamicPermission(Collection<BasePermission> permissionTrees) {
+        if (PackageManagerService.DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
+                + getName() + " pkg=" + getSourcePackageName()
+                + " info=" + pendingPermissionInfo);
+        if (sourcePackageSetting == null && pendingPermissionInfo != null) {
+            final BasePermission tree = findPermissionTree(permissionTrees, name);
+            if (tree != null && tree.perm != null) {
+                sourcePackageSetting = tree.sourcePackageSetting;
+                perm = new PackageParser.Permission(tree.perm.owner,
+                        new PermissionInfo(pendingPermissionInfo));
+                perm.info.packageName = tree.perm.info.packageName;
+                perm.info.name = name;
+                uid = tree.uid;
+            }
+        }
+    }
+
+    static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
+            @NonNull PackageParser.Package pkg, Collection<BasePermission> permissionTrees,
+            boolean chatty) {
+        final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras;
+        // Allow system apps to redefine non-system permissions
+        if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
+            final boolean currentOwnerIsSystem = (bp.perm != null
+                    && bp.perm.owner.isSystem());
+            if (p.owner.isSystem()) {
+                if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
+                    // It's a built-in permission and no owner, take ownership now
+                    bp.sourcePackageSetting = pkgSetting;
+                    bp.perm = p;
+                    bp.uid = pkg.applicationInfo.uid;
+                    bp.sourcePackageName = p.info.packageName;
+                    p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+                } else if (!currentOwnerIsSystem) {
+                    String msg = "New decl " + p.owner + " of permission  "
+                            + p.info.name + " is system; overriding " + bp.sourcePackageName;
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    bp = null;
+                }
+            }
+        }
+        if (bp == null) {
+            bp = new BasePermission(p.info.name, p.info.packageName, TYPE_NORMAL);
+        }
+        StringBuilder r = null;
+        if (bp.perm == null) {
+            if (bp.sourcePackageName == null
+                    || bp.sourcePackageName.equals(p.info.packageName)) {
+                final BasePermission tree = findPermissionTree(permissionTrees, p.info.name);
+                if (tree == null
+                        || tree.sourcePackageName.equals(p.info.packageName)) {
+                    bp.sourcePackageSetting = pkgSetting;
+                    bp.perm = p;
+                    bp.uid = pkg.applicationInfo.uid;
+                    bp.sourcePackageName = p.info.packageName;
+                    p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+                    if (chatty) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(p.info.name);
+                    }
+                } else {
+                    Slog.w(TAG, "Permission " + p.info.name + " from package "
+                            + p.info.packageName + " ignored: base tree "
+                            + tree.name + " is from package "
+                            + tree.sourcePackageName);
+                }
+            } else {
+                Slog.w(TAG, "Permission " + p.info.name + " from package "
+                        + p.info.packageName + " ignored: original from "
+                        + bp.sourcePackageName);
+            }
+        } else if (chatty) {
+            if (r == null) {
+                r = new StringBuilder(256);
+            } else {
+                r.append(' ');
+            }
+            r.append("DUP:");
+            r.append(p.info.name);
+        }
+        if (bp.perm == p) {
+            bp.protectionLevel = p.info.protectionLevel;
+        }
+        if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
+            Log.d(TAG, "  Permissions: " + r);
+        }
+        return bp;
+    }
+
+    static BasePermission enforcePermissionTree(
+            Collection<BasePermission> permissionTrees, String permName, int callingUid) {
+        if (permName != null) {
+            BasePermission bp = findPermissionTree(permissionTrees, permName);
+            if (bp != null) {
+                if (bp.uid == UserHandle.getAppId(callingUid)) {
+                    return bp;
+                }
+                throw new SecurityException("Calling uid " + callingUid
+                        + " is not allowed to add to permission tree "
+                        + bp.name + " owned by uid " + bp.uid);
+            }
+        }
+        throw new SecurityException("No permission tree found for " + permName);
+    }
+
+    public void enforceDeclaredUsedAndRuntimeOrDevelopment(PackageParser.Package pkg) {
+        int index = pkg.requestedPermissions.indexOf(name);
+        if (index == -1) {
+            throw new SecurityException("Package " + pkg.packageName
+                    + " has not requested permission " + name);
+        }
+        if (!isRuntime() && !isDevelopment()) {
+            throw new SecurityException("Permission " + name
+                    + " is not a changeable permission type");
+        }
+    }
+
+    private static BasePermission findPermissionTree(
+            Collection<BasePermission> permissionTrees, String permName) {
+        for (BasePermission bp : permissionTrees) {
+            if (permName.startsWith(bp.name) &&
+                    permName.length() > bp.name.length() &&
+                    permName.charAt(bp.name.length()) == '.') {
+                return bp;
+            }
+        }
+        return null;
+    }
+
+    public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) {
+        if (groupName == null) {
+            if (perm == null || perm.info.group == null) {
+                return generatePermissionInfo(protectionLevel, flags);
+            }
+        } else {
+            if (perm != null && groupName.equals(perm.info.group)) {
+                return PackageParser.generatePermissionInfo(perm, flags);
+            }
+        }
+        return null;
+    }
+
+    public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) {
+        final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel;
+        // if we return different protection level, don't use the cached info
+        if (perm != null && !protectionLevelChanged) {
+            return PackageParser.generatePermissionInfo(perm, flags);
+        }
+        final PermissionInfo pi = new PermissionInfo();
+        pi.name = name;
+        pi.packageName = sourcePackageName;
+        pi.nonLocalizedLabel = name;
+        pi.protectionLevel = protectionLevelChanged ? adjustedProtectionLevel : protectionLevel;
+        return pi;
+    }
+
+    public static boolean readLPw(@NonNull Map<String, BasePermission> out,
+            @NonNull XmlPullParser parser) {
+        final String tagName = parser.getName();
+        if (!tagName.equals(TAG_ITEM)) {
+            return false;
+        }
+        final String name = parser.getAttributeValue(null, ATTR_NAME);
+        final String sourcePackage = parser.getAttributeValue(null, ATTR_PACKAGE);
+        final String ptype = parser.getAttributeValue(null, "type");
+        if (name == null || sourcePackage == null) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: permissions has" + " no name at "
+                            + parser.getPositionDescription());
+            return false;
+        }
+        final boolean dynamic = "dynamic".equals(ptype);
+        BasePermission bp = out.get(name);
+        // If the permission is builtin, do not clobber it.
+        if (bp == null || bp.type != TYPE_BUILTIN) {
+            bp = new BasePermission(name.intern(), sourcePackage,
+                    dynamic ? TYPE_DYNAMIC : TYPE_NORMAL);
+        }
+        bp.protectionLevel = readInt(parser, null, "protection",
+                PermissionInfo.PROTECTION_NORMAL);
+        bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
+        if (dynamic) {
+            final PermissionInfo pi = new PermissionInfo();
+            pi.packageName = sourcePackage.intern();
+            pi.name = name.intern();
+            pi.icon = readInt(parser, null, "icon", 0);
+            pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
+            pi.protectionLevel = bp.protectionLevel;
+            bp.pendingPermissionInfo = pi;
+        }
+        out.put(bp.name, bp);
+        return true;
+    }
+
+    private static int readInt(XmlPullParser parser, String ns, String name, int defValue) {
+        String v = parser.getAttributeValue(ns, name);
+        try {
+            if (v == null) {
+                return defValue;
+            }
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: attribute " + name
+                            + " has bad integer value " + v + " at "
+                            + parser.getPositionDescription());
+        }
+        return defValue;
+    }
+
+    public void writeLPr(@NonNull XmlSerializer serializer) throws IOException {
+        if (sourcePackageName == null) {
+            return;
+        }
+        serializer.startTag(null, TAG_ITEM);
+        serializer.attribute(null, ATTR_NAME, name);
+        serializer.attribute(null, ATTR_PACKAGE, sourcePackageName);
+        if (protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
+            serializer.attribute(null, "protection", Integer.toString(protectionLevel));
+        }
+        if (type == BasePermission.TYPE_DYNAMIC) {
+            final PermissionInfo pi = perm != null ? perm.info : pendingPermissionInfo;
+            if (pi != null) {
+                serializer.attribute(null, "type", "dynamic");
+                if (pi.icon != 0) {
+                    serializer.attribute(null, "icon", Integer.toString(pi.icon));
+                }
+                if (pi.nonLocalizedLabel != null) {
+                    serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
+                }
+            }
+        }
+        serializer.endTag(null, TAG_ITEM);
+    }
+
+    private static boolean compareStrings(CharSequence s1, CharSequence s2) {
+        if (s1 == null) {
+            return s2 == null;
+        }
+        if (s2 == null) {
+            return false;
+        }
+        if (s1.getClass() != s2.getClass()) {
+            return false;
+        }
+        return s1.equals(s2);
+    }
+
+    private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
+        if (pi1.icon != pi2.icon) return false;
+        if (pi1.logo != pi2.logo) return false;
+        if (pi1.protectionLevel != pi2.protectionLevel) return false;
+        if (!compareStrings(pi1.name, pi2.name)) return false;
+        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
+        // We'll take care of setting this one.
+        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+        // These are not currently stored in settings.
+        //if (!compareStrings(pi1.group, pi2.group)) return false;
+        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
+        //if (pi1.labelRes != pi2.labelRes) return false;
+        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
+        return true;
+    }
+
+    public boolean dumpPermissionsLPr(@NonNull PrintWriter pw, @NonNull String packageName,
+            @NonNull Set<String> permissionNames, boolean readEnforced,
+            boolean printedSomething, @NonNull DumpState dumpState) {
+        if (packageName != null && !packageName.equals(sourcePackageName)) {
+            return false;
+        }
+        if (permissionNames != null && !permissionNames.contains(name)) {
+            return false;
+        }
+        if (!printedSomething) {
+            if (dumpState.onTitlePrinted())
+                pw.println();
+            pw.println("Permissions:");
+            printedSomething = true;
+        }
+        pw.print("  Permission ["); pw.print(name); pw.print("] (");
+                pw.print(Integer.toHexString(System.identityHashCode(this)));
+                pw.println("):");
+        pw.print("    sourcePackage="); pw.println(sourcePackageName);
+        pw.print("    uid="); pw.print(uid);
+                pw.print(" gids="); pw.print(Arrays.toString(
+                        computeGids(UserHandle.USER_SYSTEM)));
+                pw.print(" type="); pw.print(type);
+                pw.print(" prot=");
+                pw.println(PermissionInfo.protectionToString(protectionLevel));
+        if (perm != null) {
+            pw.print("    perm="); pw.println(perm);
+            if ((perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
+                    || (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
+                pw.print("    flags=0x"); pw.println(Integer.toHexString(perm.info.flags));
+            }
+        }
+        if (sourcePackageSetting != null) {
+            pw.print("    packageSetting="); pw.println(sourcePackageSetting);
+        }
+        if (READ_EXTERNAL_STORAGE.equals(name)) {
+            pw.print("    enforced=");
+            pw.println(readEnforced);
+        }
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
new file mode 100644
index 0000000..533b261
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -0,0 +1,1296 @@
+/*
+ * Copyright (C) 2015 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.server.pm.permission;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.DownloadManager;
+import android.app.admin.DevicePolicyManager;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManagerInternal.PackagesProvider;
+import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.print.PrintManager;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.TelephonyManager;
+import android.security.Credentials;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSetting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+/**
+ * This class is the policy for granting runtime permissions to
+ * platform components and default handlers in the system such
+ * that the device is usable out-of-the-box. For example, the
+ * shell UID is a part of the system and the Phone app should
+ * have phone related permission by default.
+ * <p>
+ * NOTE: This class is at the wrong abstraction level. It is a part of the package manager
+ * service but knows about lots of higher level subsystems. The correct way to do this is
+ * to have an interface defined in the package manager but have the impl next to other
+ * policy stuff like PhoneWindowManager
+ */
+public final class DefaultPermissionGrantPolicy {
+    private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
+    private static final boolean DEBUG = false;
+
+    private static final int DEFAULT_FLAGS =
+            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
+
+    private static final String TAG_EXCEPTIONS = "exceptions";
+    private static final String TAG_EXCEPTION = "exception";
+    private static final String TAG_PERMISSION = "permission";
+    private static final String ATTR_PACKAGE = "package";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_FIXED = "fixed";
+
+    private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
+    static {
+        PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
+        PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
+        PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
+        PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
+        PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
+        PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
+        PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
+    }
+
+    private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
+    static {
+        CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
+        CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
+        CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
+    }
+
+    private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
+    static {
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
+
+    private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
+    static {
+        CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
+        CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
+    }
+
+    private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
+    static {
+        SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
+        SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
+    }
+
+    private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
+    static {
+        MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
+    }
+
+    private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
+    static {
+        CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
+    }
+
+    private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
+    static {
+        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+    }
+
+    private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
+    static {
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+    }
+
+    private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
+
+    private static final String ACTION_TRACK = "com.android.fitness.TRACK";
+
+    private final Handler mHandler;
+
+    private PackagesProvider mLocationPackagesProvider;
+    private PackagesProvider mVoiceInteractionPackagesProvider;
+    private PackagesProvider mSmsAppPackagesProvider;
+    private PackagesProvider mDialerAppPackagesProvider;
+    private PackagesProvider mSimCallManagerPackagesProvider;
+    private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
+
+    private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
+    private final Context mContext;
+    private final Object mLock = new Object();
+    private final PackageManagerInternal mServiceInternal;
+    private final PermissionManagerService mPermissionManager;
+    private final DefaultPermissionGrantedCallback mPermissionGrantedCallback;
+    public interface DefaultPermissionGrantedCallback {
+        /** Callback when permissions have been granted */
+        public void onDefaultRuntimePermissionsGranted(int userId);
+    }
+
+    public DefaultPermissionGrantPolicy(Context context, Looper looper,
+            @Nullable DefaultPermissionGrantedCallback callback,
+            @NonNull PermissionManagerService permissionManager) {
+        mContext = context;
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
+                    synchronized (mLock) {
+                        if (mGrantExceptions == null) {
+                            mGrantExceptions = readDefaultPermissionExceptionsLocked();
+                        }
+                    }
+                }
+            }
+        };
+        mPermissionGrantedCallback = callback;
+        mPermissionManager = permissionManager;
+        mServiceInternal = LocalServices.getService(PackageManagerInternal.class);
+    }
+
+    public void setLocationPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mLocationPackagesProvider = provider;
+        }
+    }
+
+    public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mVoiceInteractionPackagesProvider = provider;
+        }
+    }
+
+    public void setSmsAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mSmsAppPackagesProvider = provider;
+        }
+    }
+
+    public void setDialerAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mDialerAppPackagesProvider = provider;
+        }
+    }
+
+    public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mSimCallManagerPackagesProvider = provider;
+        }
+    }
+
+    public void setSyncAdapterPackagesProvider(SyncAdapterPackagesProvider provider) {
+        synchronized (mLock) {
+            mSyncAdapterPackagesProvider = provider;
+        }
+    }
+
+    public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
+            grantAllRuntimePermissions(packages, userId);
+        } else {
+            grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+            grantDefaultSystemHandlerPermissions(userId);
+            grantDefaultPermissionExceptions(userId);
+        }
+    }
+
+    private void grantRuntimePermissionsForPackage(int userId, PackageParser.Package pkg) {
+        Set<String> permissions = new ArraySet<>();
+        for (String permission :  pkg.requestedPermissions) {
+            final BasePermission bp = mPermissionManager.getPermission(permission);
+            if (bp == null) {
+                continue;
+            }
+            if (bp.isRuntime()) {
+                permissions.add(permission);
+            }
+        }
+        if (!permissions.isEmpty()) {
+            grantRuntimePermissions(pkg, permissions, true, userId);
+        }
+    }
+
+    private void grantAllRuntimePermissions(
+            Collection<PackageParser.Package> packages, int userId) {
+        Log.i(TAG, "Granting all runtime permissions for user " + userId);
+        for (PackageParser.Package pkg : packages) {
+            grantRuntimePermissionsForPackage(userId, pkg);
+        }
+    }
+
+    public void scheduleReadDefaultPermissionExceptions() {
+        mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+    }
+
+    private void grantPermissionsToSysComponentsAndPrivApps(
+            Collection<PackageParser.Package> packages, int userId) {
+        Log.i(TAG, "Granting permissions to platform components for user " + userId);
+        for (PackageParser.Package pkg : packages) {
+            if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
+                    || !doesPackageSupportRuntimePermissions(pkg)
+                    || pkg.requestedPermissions.isEmpty()) {
+                continue;
+            }
+            grantRuntimePermissionsForPackage(userId, pkg);
+        }
+    }
+
+    private void grantDefaultSystemHandlerPermissions(int userId) {
+        Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
+
+        final PackagesProvider locationPackagesProvider;
+        final PackagesProvider voiceInteractionPackagesProvider;
+        final PackagesProvider smsAppPackagesProvider;
+        final PackagesProvider dialerAppPackagesProvider;
+        final PackagesProvider simCallManagerPackagesProvider;
+        final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
+
+        synchronized (mLock) {
+            locationPackagesProvider = mLocationPackagesProvider;
+            voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
+            smsAppPackagesProvider = mSmsAppPackagesProvider;
+            dialerAppPackagesProvider = mDialerAppPackagesProvider;
+            simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
+            syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
+        }
+
+        String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
+                ? voiceInteractionPackagesProvider.getPackages(userId) : null;
+        String[] locationPackageNames = (locationPackagesProvider != null)
+                ? locationPackagesProvider.getPackages(userId) : null;
+        String[] smsAppPackageNames = (smsAppPackagesProvider != null)
+                ? smsAppPackagesProvider.getPackages(userId) : null;
+        String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
+                ? dialerAppPackagesProvider.getPackages(userId) : null;
+        String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
+                ? simCallManagerPackagesProvider.getPackages(userId) : null;
+        String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+                syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
+        String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+                syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
+
+        // Installer
+        final String installerPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_INSTALLER, userId);
+        PackageParser.Package installerPackage = getSystemPackage(installerPackageName);
+        if (installerPackage != null
+                && doesPackageSupportRuntimePermissions(installerPackage)) {
+            grantRuntimePermissions(installerPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Verifier
+        final String verifierPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_VERIFIER, userId);
+        PackageParser.Package verifierPackage = getSystemPackage(verifierPackageName);
+        if (verifierPackage != null
+                && doesPackageSupportRuntimePermissions(verifierPackage)) {
+            grantRuntimePermissions(verifierPackage, STORAGE_PERMISSIONS, true, userId);
+            grantRuntimePermissions(verifierPackage, PHONE_PERMISSIONS, false, userId);
+            grantRuntimePermissions(verifierPackage, SMS_PERMISSIONS, false, userId);
+        }
+
+        // SetupWizard
+        final String setupWizardPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId);
+        PackageParser.Package setupPackage = getSystemPackage(setupWizardPackageName);
+        if (setupPackage != null
+                && doesPackageSupportRuntimePermissions(setupPackage)) {
+            grantRuntimePermissions(setupPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, LOCATION_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, CAMERA_PERMISSIONS, userId);
+        }
+
+        // Camera
+        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+        PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackage(
+                cameraIntent, userId);
+        if (cameraPackage != null
+                && doesPackageSupportRuntimePermissions(cameraPackage)) {
+            grantRuntimePermissions(cameraPackage, CAMERA_PERMISSIONS, userId);
+            grantRuntimePermissions(cameraPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(cameraPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Media provider
+        PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackage(
+                MediaStore.AUTHORITY, userId);
+        if (mediaStorePackage != null) {
+            grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Downloads provider
+        PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackage(
+                "downloads", userId);
+        if (downloadsPackage != null) {
+            grantRuntimePermissions(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Downloads UI
+        Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+        PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackage(
+                downloadsUiIntent, userId);
+        if (downloadsUiPackage != null
+                && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
+            grantRuntimePermissions(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Storage provider
+        PackageParser.Package storagePackage = getDefaultProviderAuthorityPackage(
+                "com.android.externalstorage.documents", userId);
+        if (storagePackage != null) {
+            grantRuntimePermissions(storagePackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // CertInstaller
+        Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
+        PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackage(
+                certInstallerIntent, userId);
+        if (certInstallerPackage != null
+                && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
+            grantRuntimePermissions(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Dialer
+        if (dialerAppPackageNames == null) {
+            Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
+            PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackage(
+                    dialerIntent, userId);
+            if (dialerPackage != null) {
+                grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+            }
+        } else {
+            for (String dialerAppPackageName : dialerAppPackageNames) {
+                PackageParser.Package dialerPackage = getSystemPackage(dialerAppPackageName);
+                if (dialerPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+                }
+            }
+        }
+
+        // Sim call manager
+        if (simCallManagerPackageNames != null) {
+            for (String simCallManagerPackageName : simCallManagerPackageNames) {
+                PackageParser.Package simCallManagerPackage =
+                        getSystemPackage(simCallManagerPackageName);
+                if (simCallManagerPackage != null) {
+                    grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage,
+                            userId);
+                }
+            }
+        }
+
+        // SMS
+        if (smsAppPackageNames == null) {
+            Intent smsIntent = new Intent(Intent.ACTION_MAIN);
+            smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
+            PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackage(
+                    smsIntent, userId);
+            if (smsPackage != null) {
+               grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+            }
+        } else {
+            for (String smsPackageName : smsAppPackageNames) {
+                PackageParser.Package smsPackage = getSystemPackage(smsPackageName);
+                if (smsPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+                }
+            }
+        }
+
+        // Cell Broadcast Receiver
+        Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+        PackageParser.Package cbrPackage =
+                getDefaultSystemHandlerActivityPackage(cbrIntent, userId);
+        if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
+            grantRuntimePermissions(cbrPackage, SMS_PERMISSIONS, userId);
+        }
+
+        // Carrier Provisioning Service
+        Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
+        PackageParser.Package carrierProvPackage =
+                getDefaultSystemHandlerServicePackage(carrierProvIntent, userId);
+        if (carrierProvPackage != null
+                && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
+            grantRuntimePermissions(carrierProvPackage, SMS_PERMISSIONS, false, userId);
+        }
+
+        // Calendar
+        Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
+        calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
+        PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackage(
+                calendarIntent, userId);
+        if (calendarPackage != null
+                && doesPackageSupportRuntimePermissions(calendarPackage)) {
+            grantRuntimePermissions(calendarPackage, CALENDAR_PERMISSIONS, userId);
+            grantRuntimePermissions(calendarPackage, CONTACTS_PERMISSIONS, userId);
+        }
+
+        // Calendar provider
+        PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackage(
+                CalendarContract.AUTHORITY, userId);
+        if (calendarProviderPackage != null) {
+            grantRuntimePermissions(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(calendarProviderPackage, CALENDAR_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Calendar provider sync adapters
+        List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackages(
+                calendarSyncAdapterPackages, userId);
+        final int calendarSyncAdapterCount = calendarSyncAdapters.size();
+        for (int i = 0; i < calendarSyncAdapterCount; i++) {
+            PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
+            if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
+                grantRuntimePermissions(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
+            }
+        }
+
+        // Contacts
+        Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
+        contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
+        PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackage(
+                contactsIntent, userId);
+        if (contactsPackage != null
+                && doesPackageSupportRuntimePermissions(contactsPackage)) {
+            grantRuntimePermissions(contactsPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(contactsPackage, PHONE_PERMISSIONS, userId);
+        }
+
+        // Contacts provider sync adapters
+        List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackages(
+                contactsSyncAdapterPackages, userId);
+        final int contactsSyncAdapterCount = contactsSyncAdapters.size();
+        for (int i = 0; i < contactsSyncAdapterCount; i++) {
+            PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
+            if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
+                grantRuntimePermissions(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
+            }
+        }
+
+        // Contacts provider
+        PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackage(
+                ContactsContract.AUTHORITY, userId);
+        if (contactsProviderPackage != null) {
+            grantRuntimePermissions(contactsProviderPackage, CONTACTS_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(contactsProviderPackage, PHONE_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Device provisioning
+        Intent deviceProvisionIntent = new Intent(
+                DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
+        PackageParser.Package deviceProvisionPackage =
+                getDefaultSystemHandlerActivityPackage(deviceProvisionIntent, userId);
+        if (deviceProvisionPackage != null
+                && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
+            grantRuntimePermissions(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
+        }
+
+        // Maps
+        Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
+        mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
+        PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackage(
+                mapsIntent, userId);
+        if (mapsPackage != null
+                && doesPackageSupportRuntimePermissions(mapsPackage)) {
+            grantRuntimePermissions(mapsPackage, LOCATION_PERMISSIONS, userId);
+        }
+
+        // Gallery
+        Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
+        galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
+        PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackage(
+                galleryIntent, userId);
+        if (galleryPackage != null
+                && doesPackageSupportRuntimePermissions(galleryPackage)) {
+            grantRuntimePermissions(galleryPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Email
+        Intent emailIntent = new Intent(Intent.ACTION_MAIN);
+        emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
+        PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackage(
+                emailIntent, userId);
+        if (emailPackage != null
+                && doesPackageSupportRuntimePermissions(emailPackage)) {
+            grantRuntimePermissions(emailPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(emailPackage, CALENDAR_PERMISSIONS, userId);
+        }
+
+        // Browser
+        PackageParser.Package browserPackage = null;
+        String defaultBrowserPackage = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_BROWSER, userId);
+        if (defaultBrowserPackage != null) {
+            browserPackage = getPackage(defaultBrowserPackage);
+        }
+        if (browserPackage == null) {
+            Intent browserIntent = new Intent(Intent.ACTION_MAIN);
+            browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
+            browserPackage = getDefaultSystemHandlerActivityPackage(
+                    browserIntent, userId);
+        }
+        if (browserPackage != null
+                && doesPackageSupportRuntimePermissions(browserPackage)) {
+            grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, userId);
+        }
+
+        // Voice interaction
+        if (voiceInteractPackageNames != null) {
+            for (String voiceInteractPackageName : voiceInteractPackageNames) {
+                PackageParser.Package voiceInteractPackage = getSystemPackage(
+                        voiceInteractPackageName);
+                if (voiceInteractPackage != null
+                        && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
+                    grantRuntimePermissions(voiceInteractPackage,
+                            CONTACTS_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            CALENDAR_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            MICROPHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            PHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            SMS_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            LOCATION_PERMISSIONS, userId);
+                }
+            }
+        }
+
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            // Allow voice search on low-ram devices
+            Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
+            PackageParser.Package globalSearchPickerPackage =
+                getDefaultSystemHandlerActivityPackage(globalSearchIntent, userId);
+
+            if (globalSearchPickerPackage != null
+                    && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
+                grantRuntimePermissions(globalSearchPickerPackage,
+                    MICROPHONE_PERMISSIONS, true, userId);
+                grantRuntimePermissions(globalSearchPickerPackage,
+                    LOCATION_PERMISSIONS, true, userId);
+            }
+        }
+
+        // Voice recognition
+        Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
+        voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackage(
+                voiceRecoIntent, userId);
+        if (voiceRecoPackage != null
+                && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
+            grantRuntimePermissions(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
+        }
+
+        // Location
+        if (locationPackageNames != null) {
+            for (String packageName : locationPackageNames) {
+                PackageParser.Package locationPackage = getSystemPackage(packageName);
+                if (locationPackage != null
+                        && doesPackageSupportRuntimePermissions(locationPackage)) {
+                    grantRuntimePermissions(locationPackage, CONTACTS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, CALENDAR_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, MICROPHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, PHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, SMS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, LOCATION_PERMISSIONS,
+                            true, userId);
+                    grantRuntimePermissions(locationPackage, CAMERA_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, SENSORS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, STORAGE_PERMISSIONS, userId);
+                }
+            }
+        }
+
+        // Music
+        Intent musicIntent = new Intent(Intent.ACTION_VIEW);
+        musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
+                AUDIO_MIME_TYPE);
+        PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackage(
+                musicIntent, userId);
+        if (musicPackage != null
+                && doesPackageSupportRuntimePermissions(musicPackage)) {
+            grantRuntimePermissions(musicPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Home
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
+        PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackage(
+                homeIntent, userId);
+        if (homePackage != null
+                && doesPackageSupportRuntimePermissions(homePackage)) {
+            grantRuntimePermissions(homePackage, LOCATION_PERMISSIONS, false, userId);
+        }
+
+        // Watches
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
+            // Home application on watches
+            Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
+            wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
+
+            PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackage(
+                    wearHomeIntent, userId);
+
+            if (wearHomePackage != null
+                    && doesPackageSupportRuntimePermissions(wearHomePackage)) {
+                grantRuntimePermissions(wearHomePackage, CONTACTS_PERMISSIONS, false,
+                        userId);
+                grantRuntimePermissions(wearHomePackage, PHONE_PERMISSIONS, true, userId);
+                grantRuntimePermissions(wearHomePackage, MICROPHONE_PERMISSIONS, false,
+                        userId);
+                grantRuntimePermissions(wearHomePackage, LOCATION_PERMISSIONS, false,
+                        userId);
+            }
+
+            // Fitness tracking on watches
+            Intent trackIntent = new Intent(ACTION_TRACK);
+            PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackage(
+                    trackIntent, userId);
+            if (trackPackage != null
+                    && doesPackageSupportRuntimePermissions(trackPackage)) {
+                grantRuntimePermissions(trackPackage, SENSORS_PERMISSIONS, false, userId);
+                grantRuntimePermissions(trackPackage, LOCATION_PERMISSIONS, false, userId);
+            }
+        }
+
+        // Print Spooler
+        PackageParser.Package printSpoolerPackage = getSystemPackage(
+                PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
+        if (printSpoolerPackage != null
+                && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
+            grantRuntimePermissions(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
+        }
+
+        // EmergencyInfo
+        Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+        PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackage(
+                emergencyInfoIntent, userId);
+        if (emergencyInfoPckg != null
+                && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
+            grantRuntimePermissions(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
+            grantRuntimePermissions(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
+        }
+
+        // NFC Tag viewer
+        Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
+        nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
+        PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackage(
+                nfcTagIntent, userId);
+        if (nfcTagPkg != null
+                && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
+            grantRuntimePermissions(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
+            grantRuntimePermissions(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
+        }
+
+        // Storage Manager
+        Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+        PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackage(
+                storageManagerIntent, userId);
+        if (storageManagerPckg != null
+                && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
+            grantRuntimePermissions(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Companion devices
+        PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackage(
+                CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
+        if (companionDeviceDiscoveryPackage != null
+                && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
+            grantRuntimePermissions(companionDeviceDiscoveryPackage,
+                    LOCATION_PERMISSIONS, true, userId);
+        }
+
+        // Ringtone Picker
+        Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+        PackageParser.Package ringtonePickerPackage =
+                getDefaultSystemHandlerActivityPackage(ringtonePickerIntent, userId);
+        if (ringtonePickerPackage != null
+                && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
+            grantRuntimePermissions(ringtonePickerPackage,
+                    STORAGE_PERMISSIONS, true, userId);
+        }
+
+        if (mPermissionGrantedCallback != null) {
+            mPermissionGrantedCallback.onDefaultRuntimePermissionsGranted(userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSystemDialerApp(
+            PackageParser.Package dialerPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(dialerPackage)) {
+            boolean isPhonePermFixed =
+                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
+            grantRuntimePermissions(
+                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
+            grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSystemSmsApp(
+            PackageParser.Package smsPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(smsPackage)) {
+            grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package smsPackage = getPackage(packageName);
+        if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
+            grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package dialerPackage = getPackage(packageName);
+        if (dialerPackage != null
+                && doesPackageSupportRuntimePermissions(dialerPackage)) {
+            grantRuntimePermissions(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSimCallManager(
+            PackageParser.Package simCallManagerPackage, int userId) {
+        Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
+        if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
+            grantRuntimePermissions(simCallManagerPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package simCallManagerPackage = getPackage(packageName);
+        if (simCallManagerPackage != null) {
+            grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package carrierPackage = getSystemPackage(packageName);
+            if (carrierPackage != null
+                    && doesPackageSupportRuntimePermissions(carrierPackage)) {
+                grantRuntimePermissions(carrierPackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(carrierPackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissions(carrierPackage, SMS_PERMISSIONS, userId);
+            }
+        }
+    }
+
+    public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package imsServicePackage = getSystemPackage(packageName);
+            if (imsServicePackage != null
+                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
+                grantRuntimePermissions(imsServicePackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, CAMERA_PERMISSIONS, userId);
+            }
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default browser for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package browserPackage = getSystemPackage(packageName);
+        if (browserPackage != null
+                && doesPackageSupportRuntimePermissions(browserPackage)) {
+            grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
+        }
+    }
+
+    private PackageParser.Package getDefaultSystemHandlerActivityPackage(
+            Intent intent, int userId) {
+        ResolveInfo handler = mServiceInternal.resolveIntent(intent,
+                intent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS, userId, false);
+        if (handler == null || handler.activityInfo == null) {
+            return null;
+        }
+        if (mServiceInternal.isResolveActivityComponent(handler.activityInfo)) {
+            return null;
+        }
+        return getSystemPackage(handler.activityInfo.packageName);
+    }
+
+    private PackageParser.Package getDefaultSystemHandlerServicePackage(
+            Intent intent, int userId) {
+        List<ResolveInfo> handlers = mServiceInternal.queryIntentServices(
+                intent, DEFAULT_FLAGS, Binder.getCallingUid(), userId);
+        if (handlers == null) {
+            return null;
+        }
+        final int handlerCount = handlers.size();
+        for (int i = 0; i < handlerCount; i++) {
+            ResolveInfo handler = handlers.get(i);
+            PackageParser.Package handlerPackage = getSystemPackage(
+                    handler.serviceInfo.packageName);
+            if (handlerPackage != null) {
+                return handlerPackage;
+            }
+        }
+        return null;
+    }
+
+    private List<PackageParser.Package> getHeadlessSyncAdapterPackages(
+            String[] syncAdapterPackageNames, int userId) {
+        List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
+
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+        for (String syncAdapterPackageName : syncAdapterPackageNames) {
+            homeIntent.setPackage(syncAdapterPackageName);
+
+            ResolveInfo homeActivity = mServiceInternal.resolveIntent(homeIntent,
+                    homeIntent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS,
+                    userId, false);
+            if (homeActivity != null) {
+                continue;
+            }
+
+            PackageParser.Package syncAdapterPackage = getSystemPackage(syncAdapterPackageName);
+            if (syncAdapterPackage != null) {
+                syncAdapterPackages.add(syncAdapterPackage);
+            }
+        }
+
+        return syncAdapterPackages;
+    }
+
+    private PackageParser.Package getDefaultProviderAuthorityPackage(
+            String authority, int userId) {
+        ProviderInfo provider =
+                mServiceInternal.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
+        if (provider != null) {
+            return getSystemPackage(provider.packageName);
+        }
+        return null;
+    }
+
+    private PackageParser.Package getPackage(String packageName) {
+        return mServiceInternal.getPackage(packageName);
+    }
+
+    private PackageParser.Package getSystemPackage(String packageName) {
+        PackageParser.Package pkg = getPackage(packageName);
+        if (pkg != null && pkg.isSystem()) {
+            return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
+        }
+        return null;
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            int userId) {
+        grantRuntimePermissions(pkg, permissions, false, false, userId);
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            boolean systemFixed, int userId) {
+        grantRuntimePermissions(pkg, permissions, systemFixed, false, userId);
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
+        if (pkg.requestedPermissions.isEmpty()) {
+            return;
+        }
+
+        List<String> requestedPermissions = pkg.requestedPermissions;
+        Set<String> grantablePermissions = null;
+
+        // If this is the default Phone or SMS app we grant permissions regardless
+        // whether the version on the system image declares the permission as used since
+        // selecting the app as the default Phone or SMS the user makes a deliberate
+        // choice to grant this app the permissions needed to function. For all other
+        // apps, (default grants on first boot and user creation) we don't grant default
+        // permissions if the version on the system image does not declare them.
+        if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
+            final PackageParser.Package disabledPkg =
+                    mServiceInternal.getDisabledPackage(pkg.packageName);
+            if (disabledPkg != null) {
+                if (disabledPkg.requestedPermissions.isEmpty()) {
+                    return;
+                }
+                if (!requestedPermissions.equals(disabledPkg.requestedPermissions)) {
+                    grantablePermissions = new ArraySet<>(requestedPermissions);
+                    requestedPermissions = disabledPkg.requestedPermissions;
+                }
+            }
+        }
+
+        final int grantablePermissionCount = requestedPermissions.size();
+        for (int i = 0; i < grantablePermissionCount; i++) {
+            String permission = requestedPermissions.get(i);
+
+            // If there is a disabled system app it may request a permission the updated
+            // version ot the data partition doesn't, In this case skip the permission.
+            if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
+                continue;
+            }
+
+            if (permissions.contains(permission)) {
+                final int flags = mServiceInternal.getPermissionFlagsTEMP(
+                        permission, pkg.packageName, userId);
+
+                // If any flags are set to the permission, then it is either set in
+                // its current state by the system or device/profile owner or the user.
+                // In all these cases we do not want to clobber the current state.
+                // Unless the caller wants to override user choices. The override is
+                // to make sure we can grant the needed permission to the default
+                // sms and phone apps after the user chooses this in the UI.
+                if (flags == 0 || isDefaultPhoneOrSms) {
+                    // Never clobber policy or system.
+                    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                    if ((flags & fixedFlags) != 0) {
+                        continue;
+                    }
+
+                    mServiceInternal.grantRuntimePermission(
+                            pkg.packageName, permission, userId, false);
+                    if (DEBUG) {
+                        Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
+                                + permission + " to default handler " + pkg.packageName);
+                    }
+
+                    int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+                    if (systemFixed) {
+                        newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+                    }
+
+                    mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+                            newFlags, newFlags, userId);
+                }
+
+                // If a component gets a permission for being the default handler A
+                // and also default handler B, we grant the weaker grant form.
+                if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
+                        && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                        && !systemFixed) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Granted not fixed " + permission + " to default handler "
+                                + pkg.packageName);
+                    }
+                    mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+                            PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
+                }
+            }
+        }
+    }
+
+    private boolean isSysComponentOrPersistentPlatformSignedPrivApp(PackageParser.Package pkg) {
+        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
+            return true;
+        }
+        if (!pkg.isPrivileged()) {
+            return false;
+        }
+        final PackageParser.Package disabledPkg =
+                mServiceInternal.getDisabledPackage(pkg.packageName);
+        if (disabledPkg != null) {
+            if ((disabledPkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+                return false;
+            }
+        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+            return false;
+        }
+        final String systemPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        final PackageParser.Package systemPackage = getPackage(systemPackageName);
+        return PackageManagerService.compareSignatures(systemPackage.mSignatures,
+                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+    }
+
+    private void grantDefaultPermissionExceptions(int userId) {
+        mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+
+        synchronized (mLock) {
+            // mGrantExceptions is null only before the first read and then
+            // it serves as a cache of the default grants that should be
+            // performed for every user. If there is an entry then the app
+            // is on the system image and supports runtime permissions.
+            if (mGrantExceptions == null) {
+                mGrantExceptions = readDefaultPermissionExceptionsLocked();
+            }
+        }
+
+        Set<String> permissions = null;
+        final int exceptionCount = mGrantExceptions.size();
+        for (int i = 0; i < exceptionCount; i++) {
+            String packageName = mGrantExceptions.keyAt(i);
+            PackageParser.Package pkg = getSystemPackage(packageName);
+            List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
+            final int permissionGrantCount = permissionGrants.size();
+            for (int j = 0; j < permissionGrantCount; j++) {
+                DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
+                if (permissions == null) {
+                    permissions = new ArraySet<>();
+                } else {
+                    permissions.clear();
+                }
+                permissions.add(permissionGrant.name);
+                grantRuntimePermissions(pkg, permissions,
+                        permissionGrant.fixed, userId);
+            }
+        }
+    }
+
+    private File[] getDefaultPermissionFiles() {
+        ArrayList<File> ret = new ArrayList<File>();
+        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
+        }
+        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
+        }
+        return ret.isEmpty() ? null : ret.toArray(new File[0]);
+    }
+
+    private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
+            readDefaultPermissionExceptionsLocked() {
+        File[] files = getDefaultPermissionFiles();
+        if (files == null) {
+            return new ArrayMap<>(0);
+        }
+
+        ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
+
+        // Iterate over the files in the directory and scan .xml files
+        for (File file : files) {
+            if (!file.getPath().endsWith(".xml")) {
+                Slog.i(TAG, "Non-xml file " + file
+                        + " in " + file.getParent() + " directory, ignoring");
+                continue;
+            }
+            if (!file.canRead()) {
+                Slog.w(TAG, "Default permissions file " + file + " cannot be read");
+                continue;
+            }
+            try (
+                InputStream str = new BufferedInputStream(new FileInputStream(file))
+            ) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(str, null);
+                parse(parser, grantExceptions);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.w(TAG, "Error reading default permissions file " + file, e);
+            }
+        }
+
+        return grantExceptions;
+    }
+
+    private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+            outGrantExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_EXCEPTIONS.equals(parser.getName())) {
+                parseExceptions(parser, outGrantExceptions);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName());
+            }
+        }
+    }
+
+    private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+            outGrantExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_EXCEPTION.equals(parser.getName())) {
+                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+
+                List<DefaultPermissionGrant> packageExceptions =
+                        outGrantExceptions.get(packageName);
+                if (packageExceptions == null) {
+                    // The package must be on the system image
+                    PackageParser.Package pkg = getSystemPackage(packageName);
+                    if (pkg == null) {
+                        Log.w(TAG, "Unknown package:" + packageName);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+
+                    // The package must support runtime permissions
+                    if (!doesPackageSupportRuntimePermissions(pkg)) {
+                        Log.w(TAG, "Skipping non supporting runtime permissions package:"
+                                + packageName);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    packageExceptions = new ArrayList<>();
+                    outGrantExceptions.put(packageName, packageExceptions);
+                }
+
+                parsePermission(parser, packageExceptions);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
+            }
+        }
+    }
+
+    private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
+            outPackageExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (TAG_PERMISSION.contains(parser.getName())) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name == null) {
+                    Log.w(TAG, "Mandatory name attribute missing for permission tag");
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
+                final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
+
+                DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
+                outPackageExceptions.add(exception);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
+            }
+        }
+    }
+
+    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
+        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
+    }
+
+    private static final class DefaultPermissionGrant {
+        final String name;
+        final boolean fixed;
+
+        public DefaultPermissionGrant(String name, boolean fixed) {
+            this.name = name;
+            this.fixed = fixed;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/OWNERS b/services/core/java/com/android/server/pm/permission/OWNERS
new file mode 100644
index 0000000..6c8c9b2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/OWNERS
@@ -0,0 +1,7 @@
+per-file DefaultPermissionGrantPolicy.java = bpoiesz@google.com
+per-file DefaultPermissionGrantPolicy.java = fkupolov@google.com
+per-file DefaultPermissionGrantPolicy.java = hackbod@android.com
+per-file DefaultPermissionGrantPolicy.java = jsharkey@android.com
+per-file DefaultPermissionGrantPolicy.java = svetoslavganov@google.com
+per-file DefaultPermissionGrantPolicy.java = toddke@google.com
+per-file DefaultPermissionGrantPolicy.java = yamasani@google.com
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
new file mode 100644
index 0000000..60c118b
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -0,0 +1,166 @@
+/*
+ * 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.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PermissionInfoFlags;
+import android.content.pm.PackageParser.Permission;
+
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Internal interfaces to be used by other components within the system server.
+ */
+public abstract class PermissionManagerInternal {
+    /**
+     * Callbacks invoked when interesting actions have been taken on a permission.
+     * <p>
+     * NOTE: The current arguments are merely to support the existing use cases. This
+     * needs to be properly thought out with appropriate arguments for each of the
+     * callback methods.
+     */
+    public static class PermissionCallback {
+        public void onGidsChanged(int appId, int userId) {
+        }
+        public void onPermissionChanged() {
+        }
+        public void onPermissionGranted(int uid, int userId) {
+        }
+        public void onInstallPermissionGranted() {
+        }
+        public void onPermissionRevoked(int uid, int userId) {
+        }
+        public void onInstallPermissionRevoked() {
+        }
+        public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
+        }
+        public void onPermissionRemoved() {
+        }
+        public void onInstallPermissionUpdated() {
+        }
+    }
+
+    public abstract void systemReady();
+
+    public abstract boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId);
+
+    public abstract void grantRuntimePermission(
+            @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
+            int callingUid, int userId, @Nullable PermissionCallback callback);
+    public abstract void grantRuntimePermissionsGrantedToDisabledPackage(
+            @NonNull PackageParser.Package pkg, int callingUid,
+            @Nullable PermissionCallback callback);
+    public abstract void grantRequestedRuntimePermissions(
+            @NonNull PackageParser.Package pkg, @NonNull int[] userIds,
+            @NonNull String[] grantedPermissions, int callingUid,
+            @Nullable PermissionCallback callback);
+    public abstract void revokeRuntimePermission(@NonNull String permName,
+            @NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
+            @Nullable PermissionCallback callback);
+
+    public abstract void updatePermissions(@Nullable String packageName,
+            @Nullable PackageParser.Package pkg, boolean replaceGrant,
+            @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
+    public abstract void updateAllPermissions(@Nullable String volumeUuid, boolean sdkUpdated,
+            @NonNull Collection<PackageParser.Package> allPacakges, PermissionCallback callback);
+
+    /**
+     * Add all permissions in the given package.
+     * <p>
+     * NOTE: argument {@code groupTEMP} is temporary until mPermissionGroups is moved to
+     * the permission settings.
+     */
+    public abstract void addAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+    public abstract void addAllPermissionGroups(@NonNull PackageParser.Package pkg, boolean chatty);
+    public abstract void removeAllPermissions(@NonNull PackageParser.Package pkg, boolean chatty);
+    public abstract boolean addDynamicPermission(@NonNull PermissionInfo info, boolean async,
+            int callingUid, @Nullable PermissionCallback callback);
+    public abstract void removeDynamicPermission(@NonNull String permName, int callingUid,
+            @Nullable PermissionCallback callback);
+
+    public abstract @Nullable String[] getAppOpPermissionPackages(@NonNull String permName);
+
+    public abstract int getPermissionFlags(@NonNull String permName,
+            @NonNull String packageName, int callingUid, int userId);
+    /**
+     * Retrieve all of the information we know about a particular group of permissions.
+     */
+    public abstract @Nullable PermissionGroupInfo getPermissionGroupInfo(
+            @NonNull String groupName, int flags, int callingUid);
+    /**
+     * Retrieve all of the known permission groups in the system.
+     */
+    public abstract @Nullable List<PermissionGroupInfo> getAllPermissionGroups(int flags,
+            int callingUid);
+    /**
+     * Retrieve all of the information we know about a particular permission.
+     */
+    public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
+            @NonNull String packageName, @PermissionInfoFlags int flags, int callingUid);
+    /**
+     * Retrieve all of the permissions associated with a particular group.
+     */
+    public abstract @Nullable List<PermissionInfo> getPermissionInfoByGroup(@NonNull String group,
+            @PermissionInfoFlags int flags, int callingUid);
+
+    /**
+     * Updates the flags associated with a permission by replacing the flags in
+     * the specified mask with the provided flag values.
+     */
+    public abstract void updatePermissionFlags(@NonNull String permName,
+            @NonNull String packageName, int flagMask, int flagValues, int callingUid, int userId,
+            @Nullable PermissionCallback callback);
+    /**
+     * Updates the flags for all applications by replacing the flags in the specified mask
+     * with the provided flag values.
+     */
+    public abstract boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues,
+            int callingUid, int userId, @NonNull Collection<PackageParser.Package> packages,
+            @Nullable PermissionCallback callback);
+
+    public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
+            int callingUid, int userId);
+    public abstract int checkUidPermission(String permName, int uid, int callingUid);
+
+    /**
+     * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userid} is not for the caller.
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    public abstract void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+    public abstract void enforceGrantRevokeRuntimePermissionPermissions(@NonNull String message);
+
+    public abstract @NonNull PermissionSettings getPermissionSettings();
+    public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
+
+    /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
+    public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
new file mode 100644
index 0000000..76805ce
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -0,0 +1,2147 @@
+/*
+ * 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.server.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
+import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageParser.Package;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.RoSystemProperties;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.ProcessLoggingHandler;
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.UserManagerService;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
+
+import libcore.util.EmptyArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Manages all permissions and handles permissions related tasks.
+ */
+public class PermissionManagerService {
+    private static final String TAG = "PackageManager";
+
+    /** All dangerous permission names in the same order as the events in MetricsEvent */
+    private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
+            Manifest.permission.READ_CALENDAR,
+            Manifest.permission.WRITE_CALENDAR,
+            Manifest.permission.CAMERA,
+            Manifest.permission.READ_CONTACTS,
+            Manifest.permission.WRITE_CONTACTS,
+            Manifest.permission.GET_ACCOUNTS,
+            Manifest.permission.ACCESS_FINE_LOCATION,
+            Manifest.permission.ACCESS_COARSE_LOCATION,
+            Manifest.permission.RECORD_AUDIO,
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.CALL_PHONE,
+            Manifest.permission.READ_CALL_LOG,
+            Manifest.permission.WRITE_CALL_LOG,
+            Manifest.permission.ADD_VOICEMAIL,
+            Manifest.permission.USE_SIP,
+            Manifest.permission.PROCESS_OUTGOING_CALLS,
+            Manifest.permission.READ_CELL_BROADCASTS,
+            Manifest.permission.BODY_SENSORS,
+            Manifest.permission.SEND_SMS,
+            Manifest.permission.RECEIVE_SMS,
+            Manifest.permission.READ_SMS,
+            Manifest.permission.RECEIVE_WAP_PUSH,
+            Manifest.permission.RECEIVE_MMS,
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.READ_PHONE_NUMBERS,
+            Manifest.permission.ANSWER_PHONE_CALLS);
+
+    /** Permission grant: not grant the permission. */
+    private static final int GRANT_DENIED = 1;
+    /** Permission grant: grant the permission as an install permission. */
+    private static final int GRANT_INSTALL = 2;
+    /** Permission grant: grant the permission as a runtime one. */
+    private static final int GRANT_RUNTIME = 3;
+    /** Permission grant: grant as runtime a permission that was granted as an install time one. */
+    private static final int GRANT_UPGRADE = 4;
+
+    /** Cap the size of permission trees that 3rd party apps can define; in characters of text */
+    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;
+    /** Empty array to avoid allocations */
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+    /** Lock to protect internal data access */
+    private final Object mLock;
+
+    /** Internal connection to the package manager */
+    private final PackageManagerInternal mPackageManagerInt;
+
+    /** Internal connection to the user manager */
+    private final UserManagerInternal mUserManagerInt;
+
+    /** Default permission policy to provide proper behaviour out-of-the-box */
+    private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
+
+    /**
+     * Built-in permissions. Read from system configuration files. Mapping is from
+     * UID to permission name.
+     */
+    private final SparseArray<ArraySet<String>> mSystemPermissions;
+
+    /** Built-in group IDs given to all packages. Read from system configuration files. */
+    private final int[] mGlobalGids;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+
+    /** Internal storage for permissions and related settings */
+    @GuardedBy("mLock")
+    private final PermissionSettings mSettings;
+
+    @GuardedBy("mLock")
+    private ArraySet<String> mPrivappPermissionsViolations;
+
+    @GuardedBy("mLock")
+    private boolean mSystemReady;
+
+    PermissionManagerService(Context context,
+            @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+            @NonNull Object externalLock) {
+        mContext = context;
+        mLock = externalLock;
+        mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
+        mSettings = new PermissionSettings(context, mLock);
+
+        mHandlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        Watchdog.getInstance().addThread(mHandler);
+
+        mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
+                context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+        SystemConfig systemConfig = SystemConfig.getInstance();
+        mSystemPermissions = systemConfig.getSystemPermissions();
+        mGlobalGids = systemConfig.getGlobalGids();
+
+        // propagate permission configuration
+        final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
+                SystemConfig.getInstance().getPermissions();
+        synchronized (mLock) {
+            for (int i=0; i<permConfig.size(); i++) {
+                final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
+                BasePermission bp = mSettings.getPermissionLocked(perm.name);
+                if (bp == null) {
+                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
+                    mSettings.putPermissionLocked(perm.name, bp);
+                }
+                if (perm.gids != null) {
+                    bp.setGids(perm.gids, perm.perUser);
+                }
+            }
+        }
+
+        LocalServices.addService(
+                PermissionManagerInternal.class, new PermissionManagerInternalImpl());
+    }
+
+    /**
+     * Creates and returns an initialized, internal service for use by other components.
+     * <p>
+     * The object returned is identical to the one returned by the LocalServices class using:
+     * {@code LocalServices.getService(PermissionManagerInternal.class);}
+     * <p>
+     * NOTE: The external lock is temporary and should be removed. This needs to be a
+     * lock created by the permission manager itself.
+     */
+    public static PermissionManagerInternal create(Context context,
+            @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+            @NonNull Object externalLock) {
+        final PermissionManagerInternal permMgrInt =
+                LocalServices.getService(PermissionManagerInternal.class);
+        if (permMgrInt != null) {
+            return permMgrInt;
+        }
+        new PermissionManagerService(context, defaultGrantCallback, externalLock);
+        return LocalServices.getService(PermissionManagerInternal.class);
+    }
+
+    @Nullable BasePermission getPermission(String permName) {
+        synchronized (mLock) {
+            return mSettings.getPermissionLocked(permName);
+        }
+    }
+
+    private int checkPermission(String permName, String pkgName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+        if (pkg != null && pkg.mExtras != null) {
+            if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            final boolean instantApp = ps.getInstantApp(userId);
+            final PermissionsState permissionsState = ps.getPermissionsState();
+            if (permissionsState.hasPermission(permName, userId)) {
+                if (instantApp) {
+                    synchronized (mLock) {
+                        BasePermission bp = mSettings.getPermissionLocked(permName);
+                        if (bp != null && bp.isInstant()) {
+                            return PackageManager.PERMISSION_GRANTED;
+                        }
+                    }
+                } else {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+            // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+            if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                    .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    private int checkUidPermission(String permName, int uid, int callingUid) {
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        final boolean isCallerInstantApp =
+                mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
+        final boolean isUidInstantApp =
+                mPackageManagerInt.getInstantAppPackageName(uid) != null;
+        final int userId = UserHandle.getUserId(uid);
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
+        if (packages != null && packages.length > 0) {
+            PackageParser.Package pkg = null;
+            for (String packageName : packages) {
+                pkg = mPackageManagerInt.getPackage(packageName);
+                if (pkg != null) {
+                    break;
+                }
+            }
+            if (pkg == null) {
+Slog.e(TAG, "TODD: No package not found; UID: " + uid);
+Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages));
+                return PackageManager.PERMISSION_DENIED;
+            }
+            if (pkg.mSharedUserId != null) {
+                if (isCallerInstantApp) {
+                    return PackageManager.PERMISSION_DENIED;
+                }
+            } else {
+                if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
+                    return PackageManager.PERMISSION_DENIED;
+                }
+            }
+            final PermissionsState permissionsState =
+                    ((PackageSetting) pkg.mExtras).getPermissionsState();
+            if (permissionsState.hasPermission(permName, userId)) {
+                if (isUidInstantApp) {
+                    if (mSettings.isPermissionInstant(permName)) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                } else {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+            // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+            if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                    .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        } else {
+            ArraySet<String> perms = mSystemPermissions.get(uid);
+            if (perms != null) {
+                if (perms.contains(permName)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
+                        .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+        }
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    private PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+            int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            return PackageParser.generatePermissionGroupInfo(
+                    mSettings.mPermissionGroups.get(groupName), flags);
+        }
+    }
+
+    private List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            final int N = mSettings.mPermissionGroups.size();
+            final ArrayList<PermissionGroupInfo> out
+                    = new ArrayList<PermissionGroupInfo>(N);
+            for (PackageParser.PermissionGroup pg : mSettings.mPermissionGroups.values()) {
+                out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
+            }
+            return out;
+        }
+    }
+
+    private PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
+            int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        // reader
+        synchronized (mLock) {
+            final BasePermission bp = mSettings.getPermissionLocked(permName);
+            if (bp == null) {
+                return null;
+            }
+            final int adjustedProtectionLevel = adjustPermissionProtectionFlagsLocked(
+                    bp.getProtectionLevel(), packageName, callingUid);
+            return bp.generatePermissionInfo(adjustedProtectionLevel, flags);
+        }
+    }
+
+    private List<PermissionInfo> getPermissionInfoByGroup(
+            String groupName, int flags, int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            if (groupName != null && !mSettings.mPermissionGroups.containsKey(groupName)) {
+                return null;
+            }
+            final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
+            for (BasePermission bp : mSettings.mPermissions.values()) {
+                final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
+                if (pi != null) {
+                    out.add(pi);
+                }
+            }
+            return out;
+        }
+    }
+
+    private int adjustPermissionProtectionFlagsLocked(
+            int protectionLevel, String packageName, int uid) {
+        // Signature permission flags area always reported
+        final int protectionLevelMasked = protectionLevel
+                & (PermissionInfo.PROTECTION_NORMAL
+                | PermissionInfo.PROTECTION_DANGEROUS
+                | PermissionInfo.PROTECTION_SIGNATURE);
+        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
+            return protectionLevel;
+        }
+        // System sees all flags.
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
+                || appId == Process.SHELL_UID) {
+            return protectionLevel;
+        }
+        // Normalize package name to handle renamed packages and static libs
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return protectionLevel;
+        }
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            return protectionLevelMasked;
+        }
+        // Apps that target O see flags for all protection levels.
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return protectionLevel;
+        }
+        if (ps.getAppId() != appId) {
+            return protectionLevel;
+        }
+        return protectionLevel;
+    }
+
+    private void addAllPermissions(PackageParser.Package pkg, boolean chatty) {
+        final int N = pkg.permissions.size();
+        for (int i=0; i<N; i++) {
+            PackageParser.Permission p = pkg.permissions.get(i);
+
+            // Assume by default that we did not install this permission into the system.
+            p.info.flags &= ~PermissionInfo.FLAG_INSTALLED;
+
+            synchronized (PermissionManagerService.this.mLock) {
+                // Now that permission groups have a special meaning, we ignore permission
+                // groups for legacy apps to prevent unexpected behavior. In particular,
+                // permissions for one app being granted to someone just because they happen
+                // to be in a group defined by another app (before this had no implications).
+                if (pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
+                    p.group = mSettings.mPermissionGroups.get(p.info.group);
+                    // Warn for a permission in an unknown group.
+                    if (DEBUG_PERMISSIONS
+                            && p.info.group != null && p.group == null) {
+                        Slog.i(TAG, "Permission " + p.info.name + " from package "
+                                + p.info.packageName + " in an unknown group " + p.info.group);
+                    }
+                }
+
+                if (p.tree) {
+                    final BasePermission bp = BasePermission.createOrUpdate(
+                            mSettings.getPermissionTreeLocked(p.info.name), p, pkg,
+                            mSettings.getAllPermissionTreesLocked(), chatty);
+                    mSettings.putPermissionTreeLocked(p.info.name, bp);
+                } else {
+                    final BasePermission bp = BasePermission.createOrUpdate(
+                            mSettings.getPermissionLocked(p.info.name),
+                            p, pkg, mSettings.getAllPermissionTreesLocked(), chatty);
+                    mSettings.putPermissionLocked(p.info.name, bp);
+                }
+            }
+        }
+    }
+
+    private void addAllPermissionGroups(PackageParser.Package pkg, boolean chatty) {
+        final int N = pkg.permissionGroups.size();
+        StringBuilder r = null;
+        for (int i=0; i<N; i++) {
+            final PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
+            final PackageParser.PermissionGroup cur = mSettings.mPermissionGroups.get(pg.info.name);
+            final String curPackageName = (cur == null) ? null : cur.info.packageName;
+            final boolean isPackageUpdate = pg.info.packageName.equals(curPackageName);
+            if (cur == null || isPackageUpdate) {
+                mSettings.mPermissionGroups.put(pg.info.name, pg);
+                if (chatty && DEBUG_PACKAGE_SCANNING) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    if (isPackageUpdate) {
+                        r.append("UPD:");
+                    }
+                    r.append(pg.info.name);
+                }
+            } else {
+                Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+                        + pg.info.packageName + " ignored: original from "
+                        + cur.info.packageName);
+                if (chatty && DEBUG_PACKAGE_SCANNING) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append("DUP:");
+                    r.append(pg.info.name);
+                }
+            }
+        }
+        if (r != null && DEBUG_PACKAGE_SCANNING) {
+            Log.d(TAG, "  Permission Groups: " + r);
+        }
+
+    }
+
+    private void removeAllPermissions(PackageParser.Package pkg, boolean chatty) {
+        synchronized (mLock) {
+            int N = pkg.permissions.size();
+            StringBuilder r = null;
+            for (int i=0; i<N; i++) {
+                PackageParser.Permission p = pkg.permissions.get(i);
+                BasePermission bp = (BasePermission) mSettings.mPermissions.get(p.info.name);
+                if (bp == null) {
+                    bp = mSettings.mPermissionTrees.get(p.info.name);
+                }
+                if (bp != null && bp.isPermission(p)) {
+                    bp.setPermission(null);
+                    if (DEBUG_REMOVE && chatty) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(p.info.name);
+                    }
+                }
+                if (p.isAppOp()) {
+                    ArraySet<String> appOpPkgs =
+                            mSettings.mAppOpPermissionPackages.get(p.info.name);
+                    if (appOpPkgs != null) {
+                        appOpPkgs.remove(pkg.packageName);
+                    }
+                }
+            }
+            if (r != null) {
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+            }
+
+            N = pkg.requestedPermissions.size();
+            r = null;
+            for (int i=0; i<N; i++) {
+                String perm = pkg.requestedPermissions.get(i);
+                if (mSettings.isPermissionAppOp(perm)) {
+                    ArraySet<String> appOpPkgs = mSettings.mAppOpPermissionPackages.get(perm);
+                    if (appOpPkgs != null) {
+                        appOpPkgs.remove(pkg.packageName);
+                        if (appOpPkgs.isEmpty()) {
+                            mSettings.mAppOpPermissionPackages.remove(perm);
+                        }
+                    }
+                }
+            }
+            if (r != null) {
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+            }
+        }
+    }
+
+    private boolean addDynamicPermission(
+            PermissionInfo info, int callingUid, PermissionCallback callback) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant apps can't add permissions");
+        }
+        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
+            throw new SecurityException("Label must be specified in permission");
+        }
+        final BasePermission tree = mSettings.enforcePermissionTree(info.name, callingUid);
+        final boolean added;
+        final boolean changed;
+        synchronized (mLock) {
+            BasePermission bp = mSettings.getPermissionLocked(info.name);
+            added = bp == null;
+            int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+            if (added) {
+                enforcePermissionCapLocked(info, tree);
+                bp = new BasePermission(info.name, tree.getSourcePackageName(),
+                        BasePermission.TYPE_DYNAMIC);
+            } else if (bp.isDynamic()) {
+                // TODO: switch this back to SecurityException
+                Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
+                        + info.name);
+            }
+            changed = bp.addToTree(fixedLevel, info, tree);
+            if (added) {
+                mSettings.putPermissionLocked(info.name, bp);
+            }
+        }
+        if (changed && callback != null) {
+            callback.onPermissionChanged();
+        }
+        return added;
+    }
+
+    private void removeDynamicPermission(
+            String permName, int callingUid, PermissionCallback callback) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant applications don't have access to this method");
+        }
+        final BasePermission tree = mSettings.enforcePermissionTree(permName, callingUid);
+        synchronized (mLock) {
+            final BasePermission bp = mSettings.getPermissionLocked(permName);
+            if (bp == null) {
+                return;
+            }
+            if (bp.isDynamic()) {
+                // TODO: switch this back to SecurityException
+                Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
+                        + permName);
+            }
+            mSettings.removePermissionLocked(permName);
+            if (callback != null) {
+                callback.onPermissionRemoved();
+            }
+        }
+    }
+
+    private void grantPermissions(PackageParser.Package pkg, boolean replace,
+            String packageOfInterest, PermissionCallback callback) {
+        // IMPORTANT: There are two types of permissions: install and runtime.
+        // Install time permissions are granted when the app is installed to
+        // all device users and users added in the future. Runtime permissions
+        // are granted at runtime explicitly to specific users. Normal and signature
+        // protected permissions are install time permissions. Dangerous permissions
+        // are install permissions if the app's target SDK is Lollipop MR1 or older,
+        // otherwise they are runtime permissions. This function does not manage
+        // runtime permissions except for the case an app targeting Lollipop MR1
+        // being upgraded to target a newer SDK, in which case dangerous permissions
+        // are transformed from install time to runtime ones.
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+        final boolean isLegacySystemApp = mPackageManagerInt.isLegacySystemApp(pkg);
+
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        PermissionsState origPermissions = permissionsState;
+
+        final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
+
+        boolean runtimePermissionsRevoked = false;
+        int[] updatedUserIds = EMPTY_INT_ARRAY;
+
+        boolean changedInstallPermission = false;
+
+        if (replace) {
+            ps.setInstallPermissionsFixed(false);
+            if (!ps.isSharedUser()) {
+                origPermissions = new PermissionsState(permissionsState);
+                permissionsState.reset();
+            } else {
+                // We need to know only about runtime permission changes since the
+                // calling code always writes the install permissions state but
+                // the runtime ones are written only if changed. The only cases of
+                // changed runtime permissions here are promotion of an install to
+                // runtime and revocation of a runtime from a shared user.
+                synchronized (mLock) {
+                    updatedUserIds = revokeUnusedSharedUserPermissionsLocked(
+                            ps.getSharedUser(), UserManagerService.getInstance().getUserIds());
+                    if (!ArrayUtils.isEmpty(updatedUserIds)) {
+                        runtimePermissionsRevoked = true;
+                    }
+                }
+            }
+        }
+
+        permissionsState.setGlobalGids(mGlobalGids);
+
+        synchronized (mLock) {
+            final int N = pkg.requestedPermissions.size();
+            for (int i = 0; i < N; i++) {
+                final String permName = pkg.requestedPermissions.get(i);
+                final BasePermission bp = mSettings.getPermissionLocked(permName);
+                final boolean appSupportsRuntimePermissions =
+                        pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
+
+                if (DEBUG_INSTALL) {
+                    Log.i(TAG, "Package " + pkg.packageName + " checking " + permName + ": " + bp);
+                }
+
+                if (bp == null || bp.getSourcePackageSetting() == null) {
+                    if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
+                        if (DEBUG_PERMISSIONS) {
+                            Slog.i(TAG, "Unknown permission " + permName
+                                    + " in package " + pkg.packageName);
+                        }
+                    }
+                    continue;
+                }
+
+                // Limit ephemeral apps to ephemeral allowed permissions.
+                if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+                                + " for package " + pkg.packageName);
+                    }
+                    continue;
+                }
+
+                if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+                    if (DEBUG_PERMISSIONS) {
+                        Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+                                + " for package " + pkg.packageName);
+                    }
+                    continue;
+                }
+
+                final String perm = bp.getName();
+                boolean allowedSig = false;
+                int grant = GRANT_DENIED;
+
+                // Keep track of app op permissions.
+                if (bp.isAppOp()) {
+                    mSettings.addAppOpPackage(perm, pkg.packageName);
+                }
+
+                if (bp.isNormal()) {
+                    // For all apps normal permissions are install time ones.
+                    grant = GRANT_INSTALL;
+                } else if (bp.isRuntime()) {
+                    // If a permission review is required for legacy apps we represent
+                    // their permissions as always granted runtime ones since we need
+                    // to keep the review required permission flag per user while an
+                    // install permission's state is shared across all users.
+                    if (!appSupportsRuntimePermissions && !mSettings.mPermissionReviewRequired) {
+                        // For legacy apps dangerous permissions are install time ones.
+                        grant = GRANT_INSTALL;
+                    } else if (origPermissions.hasInstallPermission(bp.getName())) {
+                        // For legacy apps that became modern, install becomes runtime.
+                        grant = GRANT_UPGRADE;
+                    } else if (isLegacySystemApp) {
+                        // For legacy system apps, install becomes runtime.
+                        // We cannot check hasInstallPermission() for system apps since those
+                        // permissions were granted implicitly and not persisted pre-M.
+                        grant = GRANT_UPGRADE;
+                    } else {
+                        // For modern apps keep runtime permissions unchanged.
+                        grant = GRANT_RUNTIME;
+                    }
+                } else if (bp.isSignature()) {
+                    // For all apps signature permissions are install time ones.
+                    allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+                    if (allowedSig) {
+                        grant = GRANT_INSTALL;
+                    }
+                }
+
+                if (DEBUG_PERMISSIONS) {
+                    Slog.i(TAG, "Granting permission " + perm + " to package " + pkg.packageName);
+                }
+
+                if (grant != GRANT_DENIED) {
+                    if (!ps.isSystem() && ps.areInstallPermissionsFixed()) {
+                        // If this is an existing, non-system package, then
+                        // we can't add any new permissions to it.
+                        if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
+                            // Except...  if this is a permission that was added
+                            // to the platform (note: need to only do this when
+                            // updating the platform).
+                            if (!isNewPlatformPermissionForPackage(perm, pkg)) {
+                                grant = GRANT_DENIED;
+                            }
+                        }
+                    }
+
+                    switch (grant) {
+                        case GRANT_INSTALL: {
+                            // Revoke this as runtime permission to handle the case of
+                            // a runtime permission being downgraded to an install one.
+                            // Also in permission review mode we keep dangerous permissions
+                            // for legacy apps
+                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                                if (origPermissions.getRuntimePermissionState(
+                                        perm, userId) != null) {
+                                    // Revoke the runtime permission and clear the flags.
+                                    origPermissions.revokeRuntimePermission(bp, userId);
+                                    origPermissions.updatePermissionFlags(bp, userId,
+                                          PackageManager.MASK_PERMISSION_FLAGS, 0);
+                                    // If we revoked a permission permission, we have to write.
+                                    updatedUserIds = ArrayUtils.appendInt(
+                                            updatedUserIds, userId);
+                                }
+                            }
+                            // Grant an install permission.
+                            if (permissionsState.grantInstallPermission(bp) !=
+                                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                changedInstallPermission = true;
+                            }
+                        } break;
+
+                        case GRANT_RUNTIME: {
+                            // Grant previously granted runtime permissions.
+                            for (int userId : UserManagerService.getInstance().getUserIds()) {
+                                final PermissionState permissionState = origPermissions
+                                        .getRuntimePermissionState(perm, userId);
+                                int flags = permissionState != null
+                                        ? permissionState.getFlags() : 0;
+                                if (origPermissions.hasRuntimePermission(perm, userId)) {
+                                    // Don't propagate the permission in a permission review
+                                    // mode if the former was revoked, i.e. marked to not
+                                    // propagate on upgrade. Note that in a permission review
+                                    // mode install permissions are represented as constantly
+                                    // granted runtime ones since we need to keep a per user
+                                    // state associated with the permission. Also the revoke
+                                    // on upgrade flag is no longer applicable and is reset.
+                                    final boolean revokeOnUpgrade = (flags & PackageManager
+                                            .FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+                                    if (revokeOnUpgrade) {
+                                        flags &= ~PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+                                        // Since we changed the flags, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                    if (!mSettings.mPermissionReviewRequired || !revokeOnUpgrade) {
+                                        if (permissionsState.grantRuntimePermission(bp, userId) ==
+                                                PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                            // If we cannot put the permission as it was,
+                                            // we have to write.
+                                            updatedUserIds = ArrayUtils.appendInt(
+                                                    updatedUserIds, userId);
+                                        }
+                                    }
+
+                                    // If the app supports runtime permissions no need for a review.
+                                    if (mSettings.mPermissionReviewRequired
+                                            && appSupportsRuntimePermissions
+                                            && (flags & PackageManager
+                                                    .FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                                        flags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+                                        // Since we changed the flags, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                } else if (mSettings.mPermissionReviewRequired
+                                        && !appSupportsRuntimePermissions) {
+                                    // For legacy apps that need a permission review, every new
+                                    // runtime permission is granted but it is pending a review.
+                                    // We also need to review only platform defined runtime
+                                    // permissions as these are the only ones the platform knows
+                                    // how to disable the API to simulate revocation as legacy
+                                    // apps don't expect to run with revoked permissions.
+                                    if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
+                                        if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                                            flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+                                            // We changed the flags, hence have to write.
+                                            updatedUserIds = ArrayUtils.appendInt(
+                                                    updatedUserIds, userId);
+                                        }
+                                    }
+                                    if (permissionsState.grantRuntimePermission(bp, userId)
+                                            != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // We changed the permission, hence have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                }
+                                // Propagate the permission flags.
+                                permissionsState.updatePermissionFlags(bp, userId, flags, flags);
+                            }
+                        } break;
+
+                        case GRANT_UPGRADE: {
+                            // Grant runtime permissions for a previously held install permission.
+                            final PermissionState permissionState = origPermissions
+                                    .getInstallPermissionState(perm);
+                            final int flags =
+                                    (permissionState != null) ? permissionState.getFlags() : 0;
+
+                            if (origPermissions.revokeInstallPermission(bp)
+                                    != PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                // We will be transferring the permission flags, so clear them.
+                                origPermissions.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                        PackageManager.MASK_PERMISSION_FLAGS, 0);
+                                changedInstallPermission = true;
+                            }
+
+                            // If the permission is not to be promoted to runtime we ignore it and
+                            // also its other flags as they are not applicable to install permissions.
+                            if ((flags & PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE) == 0) {
+                                for (int userId : currentUserIds) {
+                                    if (permissionsState.grantRuntimePermission(bp, userId) !=
+                                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                                        // Transfer the permission flags.
+                                        permissionsState.updatePermissionFlags(bp, userId,
+                                                flags, flags);
+                                        // If we granted the permission, we have to write.
+                                        updatedUserIds = ArrayUtils.appendInt(
+                                                updatedUserIds, userId);
+                                    }
+                                }
+                            }
+                        } break;
+
+                        default: {
+                            if (packageOfInterest == null
+                                    || packageOfInterest.equals(pkg.packageName)) {
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, "Not granting permission " + perm
+                                            + " to package " + pkg.packageName
+                                            + " because it was previously installed without");
+                                }
+                            }
+                        } break;
+                    }
+                } else {
+                    if (permissionsState.revokeInstallPermission(bp) !=
+                            PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                        // Also drop the permission flags.
+                        permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        changedInstallPermission = true;
+                        Slog.i(TAG, "Un-granting permission " + perm
+                                + " from package " + pkg.packageName
+                                + " (protectionLevel=" + bp.getProtectionLevel()
+                                + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                                + ")");
+                    } else if (bp.isAppOp()) {
+                        // Don't print warning for app op permissions, since it is fine for them
+                        // not to be granted, there is a UI for the user to decide.
+                        if (DEBUG_PERMISSIONS
+                                && (packageOfInterest == null
+                                        || packageOfInterest.equals(pkg.packageName))) {
+                            Slog.i(TAG, "Not granting permission " + perm
+                                    + " to package " + pkg.packageName
+                                    + " (protectionLevel=" + bp.getProtectionLevel()
+                                    + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                                    + ")");
+                        }
+                    }
+                }
+            }
+
+            if ((changedInstallPermission || replace) && !ps.areInstallPermissionsFixed() &&
+                    !ps.isSystem() || ps.isUpdatedSystem()) {
+                // This is the first that we have heard about this package, so the
+                // permissions we have now selected are fixed until explicitly
+                // changed.
+                ps.setInstallPermissionsFixed(true);
+            }
+        }
+
+        // Persist the runtime permissions state for users with changes. If permissions
+        // were revoked because no app in the shared user declares them we have to
+        // write synchronously to avoid losing runtime permissions state.
+        if (callback != null) {
+            callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked);
+        }
+    }
+
+    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
+        boolean allowed = false;
+        final int NP = PackageParser.NEW_PERMISSIONS.length;
+        for (int ip=0; ip<NP; ip++) {
+            final PackageParser.NewPermissionInfo npi
+                    = PackageParser.NEW_PERMISSIONS[ip];
+            if (npi.name.equals(perm)
+                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
+                allowed = true;
+                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+                        + pkg.packageName);
+                break;
+            }
+        }
+        return allowed;
+    }
+
+    /**
+     * Determines whether a package is whitelisted for a particular privapp permission.
+     *
+     * <p>Does NOT check whether the package is a privapp, just whether it's whitelisted.
+     *
+     * <p>This handles parent/child apps.
+     */
+    private boolean hasPrivappWhitelistEntry(String perm, PackageParser.Package pkg) {
+        ArraySet<String> wlPermissions = SystemConfig.getInstance()
+                .getPrivAppPermissions(pkg.packageName);
+        // Let's check if this package is whitelisted...
+        boolean whitelisted = wlPermissions != null && wlPermissions.contains(perm);
+        // If it's not, we'll also tail-recurse to the parent.
+        return whitelisted ||
+                pkg.parentPackage != null && hasPrivappWhitelistEntry(perm, pkg.parentPackage);
+    }
+
+    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
+            BasePermission bp, PermissionsState origPermissions) {
+        boolean oemPermission = bp.isOEM();
+        boolean privilegedPermission = bp.isPrivileged();
+        boolean privappPermissionsDisable =
+                RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
+        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
+        boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
+        if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivileged()
+                && !platformPackage && platformPermission) {
+            if (!hasPrivappWhitelistEntry(perm, pkg)) {
+                // Only report violations for apps on system image
+                if (!mSystemReady && !pkg.isUpdatedSystemApp()) {
+                    // it's only a reportable violation if the permission isn't explicitly denied
+                    final ArraySet<String> deniedPermissions = SystemConfig.getInstance()
+                            .getPrivAppDenyPermissions(pkg.packageName);
+                    final boolean permissionViolation =
+                            deniedPermissions == null || !deniedPermissions.contains(perm);
+                    if (permissionViolation) {
+                        Slog.w(TAG, "Privileged permission " + perm + " for package "
+                                + pkg.packageName + " - not in privapp-permissions whitelist");
+
+                        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                            if (mPrivappPermissionsViolations == null) {
+                                mPrivappPermissionsViolations = new ArraySet<>();
+                            }
+                            mPrivappPermissionsViolations.add(pkg.packageName + ": " + perm);
+                        }
+                    } else {
+                        return false;
+                    }
+                }
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    return false;
+                }
+            }
+        }
+        final String systemPackageName = mPackageManagerInt.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        final PackageParser.Package systemPackage =
+                mPackageManagerInt.getPackage(systemPackageName);
+        boolean allowed = (PackageManagerService.compareSignatures(
+                                bp.getSourceSignatures(), pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH)
+                || (PackageManagerService.compareSignatures(
+                                systemPackage.mSignatures, pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH);
+        if (!allowed && (privilegedPermission || oemPermission)) {
+            if (pkg.isSystem()) {
+                // For updated system applications, a privileged/oem permission
+                // is granted only if it had been defined by the original application.
+                if (pkg.isUpdatedSystemApp()) {
+                    final PackageParser.Package disabledPkg =
+                            mPackageManagerInt.getDisabledPackage(pkg.packageName);
+                    final PackageSetting disabledPs =
+                            (disabledPkg != null) ? (PackageSetting) disabledPkg.mExtras : null;
+                    if (disabledPs != null
+                            && disabledPs.getPermissionsState().hasInstallPermission(perm)) {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        if ((privilegedPermission && disabledPs.isPrivileged())
+                                || (oemPermission && disabledPs.isOem()
+                                        && canGrantOemPermission(disabledPs, perm))) {
+                            allowed = true;
+                        }
+                    } else {
+                        // The system apk may have been updated with an older
+                        // version of the one on the data partition, but which
+                        // granted a new system permission that it didn't have
+                        // before.  In this case we do want to allow the app to
+                        // now get the new permission if the ancestral apk is
+                        // privileged to get it.
+                        if (disabledPs != null && disabledPkg != null
+                                && isPackageRequestingPermission(disabledPkg, perm)
+                                && ((privilegedPermission && disabledPs.isPrivileged())
+                                        || (oemPermission && disabledPs.isOem()
+                                                && canGrantOemPermission(disabledPs, perm)))) {
+                            allowed = true;
+                        }
+                        // Also if a privileged parent package on the system image or any of
+                        // its children requested a privileged/oem permission, the updated child
+                        // packages can also get the permission.
+                        if (pkg.parentPackage != null) {
+                            final PackageParser.Package disabledParentPkg = mPackageManagerInt
+                                    .getDisabledPackage(pkg.parentPackage.packageName);
+                            final PackageSetting disabledParentPs = (disabledParentPkg != null)
+                                    ? (PackageSetting) disabledParentPkg.mExtras : null;
+                            if (disabledParentPkg != null
+                                    && ((privilegedPermission && disabledParentPs.isPrivileged())
+                                            || (oemPermission && disabledParentPs.isOem()))) {
+                                if (isPackageRequestingPermission(disabledParentPkg, perm)
+                                        && canGrantOemPermission(disabledParentPs, perm)) {
+                                    allowed = true;
+                                } else if (disabledParentPkg.childPackages != null) {
+                                    for (PackageParser.Package disabledChildPkg
+                                            : disabledParentPkg.childPackages) {
+                                        final PackageSetting disabledChildPs =
+                                                (disabledChildPkg != null)
+                                                        ? (PackageSetting) disabledChildPkg.mExtras
+                                                        : null;
+                                        if (isPackageRequestingPermission(disabledChildPkg, perm)
+                                                && canGrantOemPermission(
+                                                        disabledChildPs, perm)) {
+                                            allowed = true;
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    final PackageSetting ps = (PackageSetting) pkg.mExtras;
+                    allowed = (privilegedPermission && pkg.isPrivileged())
+                            || (oemPermission && pkg.isOem()
+                                    && canGrantOemPermission(ps, perm));
+                }
+            }
+        }
+        if (!allowed) {
+            if (!allowed
+                    && bp.isPre23()
+                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+                // If this was a previously normal/dangerous permission that got moved
+                // to a system permission as part of the runtime permission redesign, then
+                // we still want to blindly grant it to old apps.
+                allowed = true;
+            }
+            if (!allowed && bp.isInstaller()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system installer and
+                // this app is an installer, then it gets the permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isVerifier()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system verifier and
+                // this app is a verifier, then it gets the permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isPreInstalled()
+                    && pkg.isSystem()) {
+                // Any pre-installed system app is allowed to get this permission.
+                allowed = true;
+            }
+            if (!allowed && bp.isDevelopment()) {
+                // For development permissions, a development permission
+                // is granted only if it was already granted.
+                allowed = origPermissions.hasInstallPermission(perm);
+            }
+            if (!allowed && bp.isSetup()
+                    && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+                            PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+                // If this permission is to be granted to the system setup wizard and
+                // this app is a setup wizard, then it gets the permission.
+                allowed = true;
+            }
+        }
+        return allowed;
+    }
+
+    private static boolean canGrantOemPermission(PackageSetting ps, String permission) {
+        if (!ps.isOem()) {
+            return false;
+        }
+        // all oem permissions must explicitly be granted or denied
+        final Boolean granted =
+                SystemConfig.getInstance().getOemPermissions(ps.name).get(permission);
+        if (granted == null) {
+            throw new IllegalStateException("OEM permission" + permission + " requested by package "
+                    + ps.name + " must be explicitly declared granted or not");
+        }
+        return Boolean.TRUE == granted;
+    }
+
+    private boolean isPermissionsReviewRequired(PackageParser.Package pkg, int userId) {
+        if (!mSettings.mPermissionReviewRequired) {
+            return false;
+        }
+
+        // Permission review applies only to apps not supporting the new permission model.
+        if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        // Legacy apps have the permission and get user consent on launch.
+        if (pkg == null || pkg.mExtras == null) {
+            return false;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.isPermissionReviewRequired(userId);
+    }
+
+    private boolean isPackageRequestingPermission(PackageParser.Package pkg, String permission) {
+        final int permCount = pkg.requestedPermissions.size();
+        for (int j = 0; j < permCount; j++) {
+            String requestedPermission = pkg.requestedPermissions.get(j);
+            if (permission.equals(requestedPermission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
+            PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
+        if (pkg.parentPackage == null) {
+            return;
+        }
+        if (pkg.requestedPermissions == null) {
+            return;
+        }
+        final PackageParser.Package disabledPkg =
+                mPackageManagerInt.getDisabledPackage(pkg.parentPackage.packageName);
+        if (disabledPkg == null || disabledPkg.mExtras == null) {
+            return;
+        }
+        final PackageSetting disabledPs = (PackageSetting) disabledPkg.mExtras;
+        if (!disabledPs.isPrivileged() || disabledPs.hasChildPackages()) {
+            return;
+        }
+        final int permCount = pkg.requestedPermissions.size();
+        for (int i = 0; i < permCount; i++) {
+            String permission = pkg.requestedPermissions.get(i);
+            BasePermission bp = mSettings.getPermissionLocked(permission);
+            if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
+                continue;
+            }
+            for (int userId : mUserManagerInt.getUserIds()) {
+                if (disabledPs.getPermissionsState().hasRuntimePermission(permission, userId)) {
+                    grantRuntimePermission(
+                            permission, pkg.packageName, false, callingUid, userId, callback);
+                }
+            }
+        }
+    }
+
+    private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+            String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+        for (int userId : userIds) {
+            grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions, callingUid,
+                    callback);
+        }
+    }
+
+    private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
+            String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+        PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+
+        PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+        final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+                >= Build.VERSION_CODES.M;
+
+        final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.packageName, userId);
+
+        for (String permission : pkg.requestedPermissions) {
+            final BasePermission bp;
+            synchronized (mLock) {
+                bp = mSettings.getPermissionLocked(permission);
+            }
+            if (bp != null && (bp.isRuntime() || bp.isDevelopment())
+                    && (!instantApp || bp.isInstant())
+                    && (supportsRuntimePermissions || !bp.isRuntimeOnly())
+                    && (grantedPermissions == null
+                           || ArrayUtils.contains(grantedPermissions, permission))) {
+                final int flags = permissionsState.getPermissionFlags(permission, userId);
+                if (supportsRuntimePermissions) {
+                    // Installer cannot change immutable permissions.
+                    if ((flags & immutableFlags) == 0) {
+                        grantRuntimePermission(permission, pkg.packageName, false, callingUid,
+                                userId, callback);
+                    }
+                } else if (mSettings.mPermissionReviewRequired) {
+                    // In permission review mode we clear the review flag when we
+                    // are asked to install the app with all permissions granted.
+                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                        updatePermissionFlags(permission, pkg.packageName,
+                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
+                                userId, callback);
+                    }
+                }
+            }
+        }
+    }
+
+    private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
+            int callingUid, final int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                "grantRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "grantRuntimePermission");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp;
+        synchronized(mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // If a permission review is required for legacy apps we represent
+        // their permissions as always granted runtime ones since we need
+        // to keep the review required permission flag per user while an
+        // install permission's state is shared across all users.
+        if (mSettings.mPermissionReviewRequired
+                && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            throw new SecurityException("Cannot grant system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot grant policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.grantInstallPermission(bp) !=
+                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionGranted();
+                }
+            }
+            return;
+        }
+
+        if (ps.getInstantApp(userId) && !bp.isInstant()) {
+            throw new SecurityException("Cannot grant non-ephemeral permission"
+                    + permName + " for package " + packageName);
+        }
+
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+            Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+            return;
+        }
+
+        final int result = permissionsState.grantRuntimePermission(bp, userId);
+        switch (result) {
+            case PermissionsState.PERMISSION_OPERATION_FAILURE: {
+                return;
+            }
+
+            case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+                if (callback != null) {
+                    callback.onGidsChanged(UserHandle.getAppId(pkg.applicationInfo.uid), userId);
+                }
+            }
+            break;
+        }
+
+        if (bp.isRuntime()) {
+            logPermissionGranted(mContext, permName, packageName);
+        }
+
+        if (callback != null) {
+            callback.onPermissionGranted(uid, userId);
+        }
+
+        // Only need to do this if user is initialized. Otherwise it's a new user
+        // and there are no processes running as the user yet and there's no need
+        // to make an expensive call to remount processes for the changed permissions.
+        if (READ_EXTERNAL_STORAGE.equals(permName)
+                || WRITE_EXTERNAL_STORAGE.equals(permName)) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mUserManagerInt.isUserInitialized(userId)) {
+                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
+                            StorageManagerInternal.class);
+                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+    }
+
+    private void revokeRuntimePermission(String permName, String packageName,
+            boolean overridePolicy, int callingUid, int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                "revokeRuntimePermission");
+
+        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "revokeRuntimePermission");
+
+        final int appId;
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, Binder.getCallingUid(), userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp = mSettings.getPermissionLocked(permName);
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // If a permission review is required for legacy apps we represent
+        // their permissions as always granted runtime ones since we need
+        // to keep the review required permission flag per user while an
+        // install permission's state is shared across all users.
+        if (mSettings.mPermissionReviewRequired
+                && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.revokeInstallPermission(bp) !=
+                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionRevoked();
+                }
+            }
+            return;
+        }
+
+        if (permissionsState.revokeRuntimePermission(bp, userId) ==
+                PermissionsState.PERMISSION_OPERATION_FAILURE) {
+            return;
+        }
+
+        if (bp.isRuntime()) {
+            logPermissionRevoked(mContext, permName, packageName);
+        }
+
+        if (callback != null) {
+            final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+            callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
+        }
+    }
+
+    private int[] revokeUnusedSharedUserPermissionsLocked(
+            SharedUserSetting suSetting, int[] allUserIds) {
+        // Collect all used permissions in the UID
+        final ArraySet<String> usedPermissions = new ArraySet<>();
+        final List<PackageParser.Package> pkgList = suSetting.getPackages();
+        if (pkgList == null || pkgList.size() == 0) {
+            return EmptyArray.INT;
+        }
+        for (PackageParser.Package pkg : pkgList) {
+            final int requestedPermCount = pkg.requestedPermissions.size();
+            for (int j = 0; j < requestedPermCount; j++) {
+                String permission = pkg.requestedPermissions.get(j);
+                BasePermission bp = mSettings.getPermissionLocked(permission);
+                if (bp != null) {
+                    usedPermissions.add(permission);
+                }
+            }
+        }
+
+        PermissionsState permissionsState = suSetting.getPermissionsState();
+        // Prune install permissions
+        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
+        final int installPermCount = installPermStates.size();
+        for (int i = installPermCount - 1; i >= 0;  i--) {
+            PermissionState permissionState = installPermStates.get(i);
+            if (!usedPermissions.contains(permissionState.getName())) {
+                BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+                if (bp != null) {
+                    permissionsState.revokeInstallPermission(bp);
+                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                            PackageManager.MASK_PERMISSION_FLAGS, 0);
+                }
+            }
+        }
+
+        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
+
+        // Prune runtime permissions
+        for (int userId : allUserIds) {
+            List<PermissionState> runtimePermStates = permissionsState
+                    .getRuntimePermissionStates(userId);
+            final int runtimePermCount = runtimePermStates.size();
+            for (int i = runtimePermCount - 1; i >= 0; i--) {
+                PermissionState permissionState = runtimePermStates.get(i);
+                if (!usedPermissions.contains(permissionState.getName())) {
+                    BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+                    if (bp != null) {
+                        permissionsState.revokeRuntimePermission(bp, userId);
+                        permissionsState.updatePermissionFlags(bp, userId,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
+                                runtimePermissionChangedUserIds, userId);
+                    }
+                }
+            }
+        }
+
+        return runtimePermissionChangedUserIds;
+    }
+
+    private String[] getAppOpPermissionPackages(String permName) {
+        if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            return null;
+        }
+        synchronized (mLock) {
+            final ArraySet<String> pkgs = mSettings.mAppOpPermissionPackages.get(permName);
+            if (pkgs == null) {
+                return null;
+            }
+            return pkgs.toArray(new String[pkgs.size()]);
+        }
+    }
+
+    private int getPermissionFlags(
+            String permName, String packageName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return 0;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, false /* checkShell */,
+                "getPermissionFlags");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            return 0;
+        }
+        synchronized (mLock) {
+            if (mSettings.getPermissionLocked(permName) == null) {
+                return 0;
+            }
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            return 0;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.getPermissionFlags(permName, userId);
+    }
+
+    private static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+    private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+    private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+
+    private void updatePermissions(String packageName, PackageParser.Package pkg,
+            boolean replaceGrant, Collection<PackageParser.Package> allPackages,
+            PermissionCallback callback) {
+        final int flags = (pkg != null ? UPDATE_PERMISSIONS_ALL : 0) |
+                (replaceGrant ? UPDATE_PERMISSIONS_REPLACE_PKG : 0);
+        updatePermissions(
+                packageName, pkg, getVolumeUuidForPackage(pkg), flags, allPackages, callback);
+        if (pkg != null && pkg.childPackages != null) {
+            for (PackageParser.Package childPkg : pkg.childPackages) {
+                updatePermissions(childPkg.packageName, childPkg,
+                        getVolumeUuidForPackage(childPkg), flags, allPackages, callback);
+            }
+        }
+    }
+
+    private void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+            Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+        final int flags = UPDATE_PERMISSIONS_ALL |
+                (sdkUpdated
+                        ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
+                        : 0);
+        updatePermissions(null, null, volumeUuid, flags, allPackages, callback);
+    }
+
+    private void updatePermissions(String changingPkgName, PackageParser.Package changingPkg,
+            String replaceVolumeUuid, int flags, Collection<PackageParser.Package> allPackages,
+            PermissionCallback callback) {
+        // TODO: Most of the methods exposing BasePermission internals [source package name,
+        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+        // have package settings, we should make note of it elsewhere [map between
+        // source package name and BasePermission] and cycle through that here. Then we
+        // define a single method on BasePermission that takes a PackageSetting, changing
+        // package name and a package.
+        // NOTE: With this approach, we also don't need to tree trees differently than
+        // normal permissions. Today, we need two separate loops because these BasePermission
+        // objects are stored separately.
+        // Make sure there are no dangling permission trees.
+        flags = updatePermissionTrees(changingPkgName, changingPkg, flags);
+
+        // Make sure all dynamic permissions have been assigned to a package,
+        // and make sure there are no dangling permissions.
+        flags = updatePermissions(changingPkgName, changingPkg, flags);
+
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "grantPermissions");
+        // Now update the permissions for all packages, in particular
+        // replace the granted permissions of the system packages.
+        if ((flags & UPDATE_PERMISSIONS_ALL) != 0) {
+            for (PackageParser.Package pkg : allPackages) {
+                if (pkg != changingPkg) {
+                    // Only replace for packages on requested volume
+                    final String volumeUuid = getVolumeUuidForPackage(pkg);
+                    final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0)
+                            && Objects.equals(replaceVolumeUuid, volumeUuid);
+                    grantPermissions(pkg, replace, changingPkgName, callback);
+                }
+            }
+        }
+
+        if (changingPkg != null) {
+            // Only replace for packages on requested volume
+            final String volumeUuid = getVolumeUuidForPackage(changingPkg);
+            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+                    && Objects.equals(replaceVolumeUuid, volumeUuid);
+            grantPermissions(changingPkg, replace, changingPkgName, callback);
+        }
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    }
+
+    private int updatePermissions(String packageName, PackageParser.Package pkg, int flags) {
+        Set<BasePermission> needsUpdate = null;
+        synchronized (mLock) {
+            final Iterator<BasePermission> it = mSettings.mPermissions.values().iterator();
+            while (it.hasNext()) {
+                final BasePermission bp = it.next();
+                if (bp.isDynamic()) {
+                    bp.updateDynamicPermission(mSettings.mPermissionTrees.values());
+                }
+                if (bp.getSourcePackageSetting() != null) {
+                    if (packageName != null && packageName.equals(bp.getSourcePackageName())
+                        && (pkg == null || !hasPermission(pkg, bp.getName()))) {
+                        Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+                                + " from package " + bp.getSourcePackageName());
+                        flags |= UPDATE_PERMISSIONS_ALL;
+                        it.remove();
+                    }
+                    continue;
+                }
+                if (needsUpdate == null) {
+                    needsUpdate = new ArraySet<>(mSettings.mPermissions.size());
+                }
+                needsUpdate.add(bp);
+            }
+        }
+        if (needsUpdate != null) {
+            for (final BasePermission bp : needsUpdate) {
+                final PackageParser.Package sourcePkg =
+                        mPackageManagerInt.getPackage(bp.getSourcePackageName());
+                synchronized (mLock) {
+                    if (sourcePkg != null && sourcePkg.mExtras != null) {
+                        final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
+                        if (bp.getSourcePackageSetting() == null) {
+                            bp.setSourcePackageSetting(sourcePs);
+                        }
+                        continue;
+                    }
+                    Slog.w(TAG, "Removing dangling permission: " + bp.getName()
+                            + " from package " + bp.getSourcePackageName());
+                    mSettings.removePermissionLocked(bp.getName());
+                }
+            }
+        }
+        return flags;
+    }
+
+    private int updatePermissionTrees(String packageName, PackageParser.Package pkg,
+            int flags) {
+        Set<BasePermission> needsUpdate = null;
+        synchronized (mLock) {
+            final Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
+            while (it.hasNext()) {
+                final BasePermission bp = it.next();
+                if (bp.getSourcePackageSetting() != null) {
+                    if (packageName != null && packageName.equals(bp.getSourcePackageName())
+                        && (pkg == null || !hasPermission(pkg, bp.getName()))) {
+                        Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+                                + " from package " + bp.getSourcePackageName());
+                        flags |= UPDATE_PERMISSIONS_ALL;
+                        it.remove();
+                    }
+                    continue;
+                }
+                if (needsUpdate == null) {
+                    needsUpdate = new ArraySet<>(mSettings.mPermissionTrees.size());
+                }
+                needsUpdate.add(bp);
+            }
+        }
+        if (needsUpdate != null) {
+            for (final BasePermission bp : needsUpdate) {
+                final PackageParser.Package sourcePkg =
+                        mPackageManagerInt.getPackage(bp.getSourcePackageName());
+                synchronized (mLock) {
+                    if (sourcePkg != null && sourcePkg.mExtras != null) {
+                        final PackageSetting sourcePs = (PackageSetting) sourcePkg.mExtras;
+                        if (bp.getSourcePackageSetting() == null) {
+                            bp.setSourcePackageSetting(sourcePs);
+                        }
+                        continue;
+                    }
+                    Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
+                            + " from package " + bp.getSourcePackageName());
+                    mSettings.removePermissionLocked(bp.getName());
+                }
+            }
+        }
+        return flags;
+    }
+
+    private void updatePermissionFlags(String permName, String packageName, int flagMask,
+            int flagValues, int callingUid, int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "updatePermissionFlags");
+
+        // Only the system can change these flags and nothing else.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        final BasePermission bp;
+        synchronized (mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        final boolean hadState =
+                permissionsState.getRuntimePermissionState(permName, userId) != null;
+        final boolean permissionUpdated =
+                permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+        if (permissionUpdated && callback != null) {
+            // Install and runtime permissions are stored in different places,
+            // so figure out what permission changed and persist the change.
+            if (permissionsState.getInstallPermissionState(permName) != null) {
+                callback.onInstallPermissionUpdated();
+            } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
+                    || hadState) {
+                callback.onPermissionUpdated(new int[] { userId }, false);
+            }
+        }
+    }
+
+    private boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+            int userId, Collection<Package> packages, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            return false;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions(
+                "updatePermissionFlagsForAllApps");
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "updatePermissionFlagsForAllApps");
+
+        // Only the system can change system fixed flags.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+        }
+
+        boolean changed = false;
+        for (PackageParser.Package pkg : packages) {
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            if (ps == null) {
+                continue;
+            }
+            PermissionsState permissionsState = ps.getPermissionsState();
+            changed |= permissionsState.updatePermissionFlagsForAllPermissions(
+                    userId, flagMask, flagValues);
+        }
+        return changed;
+    }
+
+    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED
+            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(message + " requires "
+                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+        }
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        if (userId == UserHandle.getUserId(callingUid)) return;
+        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+            if (requireFullPermission) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+            } else {
+                try {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+                } catch (SecurityException se) {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
+                }
+            }
+        }
+    }
+
+    private int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
+        int size = 0;
+        for (BasePermission perm : mSettings.mPermissions.values()) {
+            size += tree.calculateFootprint(perm);
+        }
+        return size;
+    }
+
+    private void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
+        // We calculate the max size of permissions defined by this uid and throw
+        // if that plus the size of 'info' would exceed our stated maximum.
+        if (tree.getUid() != Process.SYSTEM_UID) {
+            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
+            if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) {
+                throw new SecurityException("Permission tree size cap exceeded");
+            }
+        }
+    }
+
+    private void systemReady() {
+        mSystemReady = true;
+        if (mPrivappPermissionsViolations != null) {
+            throw new IllegalStateException("Signature|privileged permissions not in "
+                    + "privapp-permissions whitelist: " + mPrivappPermissionsViolations);
+        }
+    }
+
+    private static String getVolumeUuidForPackage(PackageParser.Package pkg) {
+        if (pkg == null) {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+        if (pkg.isExternal()) {
+            if (TextUtils.isEmpty(pkg.volumeUuid)) {
+                return StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                return pkg.volumeUuid;
+            }
+        } else {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+    }
+
+    private static boolean hasPermission(PackageParser.Package pkgInfo, String permName) {
+        for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
+            if (pkgInfo.permissions.get(i).info.name.equals(permName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get the first event id for the permission.
+     *
+     * <p>There are four events for each permission: <ul>
+     *     <li>Request permission: first id + 0</li>
+     *     <li>Grant permission: first id + 1</li>
+     *     <li>Request for permission denied: first id + 2</li>
+     *     <li>Revoke permission: first id + 3</li>
+     * </ul></p>
+     *
+     * @param name name of the permission
+     *
+     * @return The first event id for the permission
+     */
+    private static int getBaseEventId(@NonNull String name) {
+        int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
+
+        if (eventIdIndex == -1) {
+            if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
+                    || Build.IS_USER) {
+                Log.i(TAG, "Unknown permission " + name);
+
+                return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
+            } else {
+                // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
+                //
+                // Also update
+                // - EventLogger#ALL_DANGEROUS_PERMISSIONS
+                // - metrics_constants.proto
+                throw new IllegalStateException("Unknown permission " + name);
+            }
+        }
+
+        return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
+    }
+
+    /**
+     * Log that a permission was revoked.
+     *
+     * @param context Context of the caller
+     * @param name name of the permission
+     * @param packageName package permission if for
+     */
+    private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
+            @NonNull String packageName) {
+        MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
+    }
+
+    /**
+     * Log that a permission request was granted.
+     *
+     * @param context Context of the caller
+     * @param name name of the permission
+     * @param packageName package permission if for
+     */
+    private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
+            @NonNull String packageName) {
+        MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
+    }
+
+    private class PermissionManagerInternalImpl extends PermissionManagerInternal {
+        @Override
+        public void systemReady() {
+            PermissionManagerService.this.systemReady();
+        }
+        @Override
+        public boolean isPermissionsReviewRequired(Package pkg, int userId) {
+            return PermissionManagerService.this.isPermissionsReviewRequired(pkg, userId);
+        }
+        @Override
+        public void addAllPermissions(Package pkg, boolean chatty) {
+            PermissionManagerService.this.addAllPermissions(pkg, chatty);
+        }
+        @Override
+        public void addAllPermissionGroups(Package pkg, boolean chatty) {
+            PermissionManagerService.this.addAllPermissionGroups(pkg, chatty);
+        }
+        @Override
+        public void removeAllPermissions(Package pkg, boolean chatty) {
+            PermissionManagerService.this.removeAllPermissions(pkg, chatty);
+        }
+        @Override
+        public boolean addDynamicPermission(PermissionInfo info, boolean async, int callingUid,
+                PermissionCallback callback) {
+            return PermissionManagerService.this.addDynamicPermission(info, callingUid, callback);
+        }
+        @Override
+        public void removeDynamicPermission(String permName, int callingUid,
+                PermissionCallback callback) {
+            PermissionManagerService.this.removeDynamicPermission(permName, callingUid, callback);
+        }
+        @Override
+        public void grantRuntimePermission(String permName, String packageName,
+                boolean overridePolicy, int callingUid, int userId,
+                PermissionCallback callback) {
+            PermissionManagerService.this.grantRuntimePermission(
+                    permName, packageName, overridePolicy, callingUid, userId, callback);
+        }
+        @Override
+        public void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+                String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+            PermissionManagerService.this.grantRequestedRuntimePermissions(
+                    pkg, userIds, grantedPermissions, callingUid, callback);
+        }
+        @Override
+        public void grantRuntimePermissionsGrantedToDisabledPackage(PackageParser.Package pkg,
+                int callingUid, PermissionCallback callback) {
+            PermissionManagerService.this.grantRuntimePermissionsGrantedToDisabledPackageLocked(
+                    pkg, callingUid, callback);
+        }
+        @Override
+        public void revokeRuntimePermission(String permName, String packageName,
+                boolean overridePolicy, int callingUid, int userId,
+                PermissionCallback callback) {
+            PermissionManagerService.this.revokeRuntimePermission(permName, packageName,
+                    overridePolicy, callingUid, userId, callback);
+        }
+        @Override
+        public void updatePermissions(String packageName, Package pkg, boolean replaceGrant,
+                Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+            PermissionManagerService.this.updatePermissions(
+                    packageName, pkg, replaceGrant, allPackages, callback);
+        }
+        @Override
+        public void updateAllPermissions(String volumeUuid, boolean sdkUpdated,
+                Collection<PackageParser.Package> allPackages, PermissionCallback callback) {
+            PermissionManagerService.this.updateAllPermissions(
+                    volumeUuid, sdkUpdated, allPackages, callback);
+        }
+        @Override
+        public String[] getAppOpPermissionPackages(String permName) {
+            return PermissionManagerService.this.getAppOpPermissionPackages(permName);
+        }
+        @Override
+        public int getPermissionFlags(String permName, String packageName, int callingUid,
+                int userId) {
+            return PermissionManagerService.this.getPermissionFlags(permName, packageName,
+                    callingUid, userId);
+        }
+        @Override
+        public void updatePermissionFlags(String permName, String packageName, int flagMask,
+                int flagValues, int callingUid, int userId, PermissionCallback callback) {
+            PermissionManagerService.this.updatePermissionFlags(
+                    permName, packageName, flagMask, flagValues, callingUid, userId, callback);
+        }
+        @Override
+        public boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+                int userId, Collection<Package> packages, PermissionCallback callback) {
+            return PermissionManagerService.this.updatePermissionFlagsForAllApps(
+                    flagMask, flagValues, callingUid, userId, packages, callback);
+        }
+        @Override
+        public void enforceCrossUserPermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
+                    requireFullPermission, checkShell, message);
+        }
+        @Override
+        public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+            PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
+        }
+        @Override
+        public int checkPermission(String permName, String packageName, int callingUid,
+                int userId) {
+            return PermissionManagerService.this.checkPermission(
+                    permName, packageName, callingUid, userId);
+        }
+        @Override
+        public int checkUidPermission(String permName, int uid, int callingUid) {
+            return PermissionManagerService.this.checkUidPermission(permName, uid, callingUid);
+        }
+        @Override
+        public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionGroupInfo(
+                    groupName, flags, callingUid);
+        }
+        @Override
+        public List<PermissionGroupInfo> getAllPermissionGroups(int flags, int callingUid) {
+            return PermissionManagerService.this.getAllPermissionGroups(flags, callingUid);
+        }
+        @Override
+        public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionInfo(
+                    permName, packageName, flags, callingUid);
+        }
+        @Override
+        public List<PermissionInfo> getPermissionInfoByGroup(String group, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionInfoByGroup(group, flags, callingUid);
+        }
+        @Override
+        public PermissionSettings getPermissionSettings() {
+            return mSettings;
+        }
+        @Override
+        public DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy() {
+            return mDefaultPermissionGrantPolicy;
+        }
+        @Override
+        public BasePermission getPermissionTEMP(String permName) {
+            synchronized (PermissionManagerService.this.mLock) {
+                return mSettings.getPermissionLocked(permName);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionSettings.java b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
new file mode 100644
index 0000000..f6c4990
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionSettings.java
@@ -0,0 +1,262 @@
+/*
+ * 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.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageParser;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Permissions and other related data. This class is not meant for
+ * direct access outside of the permission package with the sole exception
+ * of package settings. Instead, it should be reference either from the
+ * permission manager or package settings.
+ */
+public class PermissionSettings {
+
+    public final boolean mPermissionReviewRequired;
+
+    /**
+     * All of the permissions known to the system. The mapping is from permission
+     * name to permission object.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, BasePermission> mPermissions =
+            new ArrayMap<String, BasePermission>();
+
+    /**
+     * All permission trees known to the system. The mapping is from permission tree
+     * name to permission object.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, BasePermission> mPermissionTrees =
+            new ArrayMap<String, BasePermission>();
+
+    /**
+     * All permisson groups know to the system. The mapping is from permission group
+     * name to permission group object.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
+            new ArrayMap<String, PackageParser.PermissionGroup>();
+
+    /**
+     * Set of packages that request a particular app op. The mapping is from permission
+     * name to package names.
+     */
+    @GuardedBy("mLock")
+    final ArrayMap<String, ArraySet<String>> mAppOpPermissionPackages = new ArrayMap<>();
+
+    private final Object mLock;
+
+    PermissionSettings(@NonNull Context context, @NonNull Object lock) {
+        mPermissionReviewRequired =
+                context.getResources().getBoolean(R.bool.config_permissionReviewRequired);
+        mLock = lock;
+    }
+
+    public @Nullable BasePermission getPermission(@NonNull String permName) {
+        synchronized (mLock) {
+            return getPermissionLocked(permName);
+        }
+    }
+
+    public void addAppOpPackage(String permName, String packageName) {
+        ArraySet<String> pkgs = mAppOpPermissionPackages.get(permName);
+        if (pkgs == null) {
+            pkgs = new ArraySet<>();
+            mAppOpPermissionPackages.put(permName, pkgs);
+        }
+        pkgs.add(packageName);
+    }
+
+    /**
+     * Transfers ownership of permissions from one package to another.
+     */
+    public void transferPermissions(String origPackageName, String newPackageName) {
+        synchronized (mLock) {
+            for (int i=0; i<2; i++) {
+                ArrayMap<String, BasePermission> permissions =
+                        i == 0 ? mPermissionTrees : mPermissions;
+                for (BasePermission bp : permissions.values()) {
+                    bp.transfer(origPackageName, newPackageName);
+                }
+            }
+        }
+    }
+
+    public boolean canPropagatePermissionToInstantApp(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant());
+        }
+    }
+
+    public void readPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            readPermissions(mPermissions, parser);
+        }
+    }
+
+    public void readPermissionTrees(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            readPermissions(mPermissionTrees, parser);
+        }
+    }
+
+    public void writePermissions(XmlSerializer serializer) throws IOException {
+        synchronized (mLock) {
+            for (BasePermission bp : mPermissions.values()) {
+                bp.writeLPr(serializer);
+            }
+        }
+    }
+
+    public void writePermissionTrees(XmlSerializer serializer) throws IOException {
+        synchronized (mLock) {
+            for (BasePermission bp : mPermissionTrees.values()) {
+                bp.writeLPr(serializer);
+            }
+        }
+    }
+
+    public static void readPermissions(ArrayMap<String, BasePermission> out, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (!BasePermission.readLPw(out, parser)) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element reading permissions: " + parser.getName() + " at "
+                                + parser.getPositionDescription());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    public void dumpPermissions(PrintWriter pw, String packageName,
+            ArraySet<String> permissionNames, boolean externalStorageEnforced,
+            DumpState dumpState) {
+        synchronized (mLock) {
+            boolean printedSomething = false;
+            for (BasePermission bp : mPermissions.values()) {
+                printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames,
+                        externalStorageEnforced, printedSomething, dumpState);
+            }
+            if (packageName == null && permissionNames == null) {
+                for (int iperm = 0; iperm<mAppOpPermissionPackages.size(); iperm++) {
+                    if (iperm == 0) {
+                        if (dumpState.onTitlePrinted())
+                            pw.println();
+                        pw.println("AppOp Permissions:");
+                    }
+                    pw.print("  AppOp Permission ");
+                    pw.print(mAppOpPermissionPackages.keyAt(iperm));
+                    pw.println(":");
+                    ArraySet<String> pkgs = mAppOpPermissionPackages.valueAt(iperm);
+                    for (int ipkg=0; ipkg<pkgs.size(); ipkg++) {
+                        pw.print("    "); pw.println(pkgs.valueAt(ipkg));
+                    }
+                }
+            }
+        }
+    }
+
+    @Nullable BasePermission getPermissionLocked(@NonNull String permName) {
+        return mPermissions.get(permName);
+    }
+
+    @Nullable BasePermission getPermissionTreeLocked(@NonNull String permName) {
+        return mPermissionTrees.get(permName);
+    }
+
+    void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
+        mPermissions.put(permName, permission);
+    }
+
+    void putPermissionTreeLocked(@NonNull String permName, @NonNull BasePermission permission) {
+        mPermissionTrees.put(permName, permission);
+    }
+
+    void removePermissionLocked(@NonNull String permName) {
+        mPermissions.remove(permName);
+    }
+
+    void removePermissionTreeLocked(@NonNull String permName) {
+        mPermissionTrees.remove(permName);
+    }
+
+    @NonNull Collection<BasePermission> getAllPermissionsLocked() {
+        return mPermissions.values();
+    }
+
+    @NonNull Collection<BasePermission> getAllPermissionTreesLocked() {
+        return mPermissionTrees.values();
+    }
+
+    /**
+     * Returns the permission tree for the given permission.
+     * @throws SecurityException If the calling UID is not allowed to add permissions to the
+     * found permission tree.
+     */
+    @Nullable BasePermission enforcePermissionTree(@NonNull String permName, int callingUid) {
+        synchronized (mLock) {
+            return BasePermission.enforcePermissionTree(
+                    mPermissionTrees.values(), permName, callingUid);
+        }
+    }
+
+    public boolean isPermissionInstant(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && bp.isInstant());
+        }
+    }
+
+    boolean isPermissionAppOp(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && bp.isAppOp());
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java
new file mode 100644
index 0000000..11df380
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2015 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.server.pm.permission;
+
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class encapsulates the permissions for a package or a shared user.
+ * <p>
+ * There are two types of permissions: install (granted at installation)
+ * and runtime (granted at runtime). Install permissions are granted to
+ * all device users while runtime permissions are granted explicitly to
+ * specific users.
+ * </p>
+ * <p>
+ * The permissions are kept on a per device user basis. For example, an
+ * application may have some runtime permissions granted under the device
+ * owner but not granted under the secondary user.
+ * <p>
+ * This class is also responsible for keeping track of the Linux gids per
+ * user for a package or a shared user. The gids are computed as a set of
+ * the gids for all granted permissions' gids on a per user basis.
+ * </p>
+ */
+public final class PermissionsState {
+
+    /** The permission operation failed. */
+    public static final int PERMISSION_OPERATION_FAILURE = -1;
+
+    /** The permission operation succeeded and no gids changed. */
+    public static final int PERMISSION_OPERATION_SUCCESS = 0;
+
+    /** The permission operation succeeded and gids changed. */
+    public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 1;
+
+    private static final int[] NO_GIDS = {};
+
+    private ArrayMap<String, PermissionData> mPermissions;
+
+    private int[] mGlobalGids = NO_GIDS;
+
+    private SparseBooleanArray mPermissionReviewRequired;
+
+    public PermissionsState() {
+        /* do nothing */
+    }
+
+    public PermissionsState(PermissionsState prototype) {
+        copyFrom(prototype);
+    }
+
+    /**
+     * Sets the global gids, applicable to all users.
+     *
+     * @param globalGids The global gids.
+     */
+    public void setGlobalGids(int[] globalGids) {
+        if (!ArrayUtils.isEmpty(globalGids)) {
+            mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
+        }
+    }
+
+    /**
+     * Initialized this instance from another one.
+     *
+     * @param other The other instance.
+     */
+    public void copyFrom(PermissionsState other) {
+        if (other == this) {
+            return;
+        }
+        if (mPermissions != null) {
+            if (other.mPermissions == null) {
+                mPermissions = null;
+            } else {
+                mPermissions.clear();
+            }
+        }
+        if (other.mPermissions != null) {
+            if (mPermissions == null) {
+                mPermissions = new ArrayMap<>();
+            }
+            final int permissionCount = other.mPermissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                String name = other.mPermissions.keyAt(i);
+                PermissionData permissionData = other.mPermissions.valueAt(i);
+                mPermissions.put(name, new PermissionData(permissionData));
+            }
+        }
+
+        mGlobalGids = NO_GIDS;
+        if (other.mGlobalGids != NO_GIDS) {
+            mGlobalGids = Arrays.copyOf(other.mGlobalGids,
+                    other.mGlobalGids.length);
+        }
+
+        if (mPermissionReviewRequired != null) {
+            if (other.mPermissionReviewRequired == null) {
+                mPermissionReviewRequired = null;
+            } else {
+                mPermissionReviewRequired.clear();
+            }
+        }
+        if (other.mPermissionReviewRequired != null) {
+            if (mPermissionReviewRequired == null) {
+                mPermissionReviewRequired = new SparseBooleanArray();
+            }
+            final int userCount = other.mPermissionReviewRequired.size();
+            for (int i = 0; i < userCount; i++) {
+                final boolean reviewRequired = other.mPermissionReviewRequired.valueAt(i);
+                mPermissionReviewRequired.put(i, reviewRequired);
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final PermissionsState other = (PermissionsState) obj;
+
+        if (mPermissions == null) {
+            if (other.mPermissions != null) {
+                return false;
+            }
+        } else if (!mPermissions.equals(other.mPermissions)) {
+            return false;
+        }
+        if (mPermissionReviewRequired == null) {
+            if (other.mPermissionReviewRequired != null) {
+                return false;
+            }
+        } else if (!mPermissionReviewRequired.equals(other.mPermissionReviewRequired)) {
+            return false;
+        }
+        return Arrays.equals(mGlobalGids, other.mGlobalGids);
+    }
+
+    public boolean isPermissionReviewRequired(int userId) {
+        return mPermissionReviewRequired != null && mPermissionReviewRequired.get(userId);
+    }
+
+    /**
+     * Grant an install permission.
+     *
+     * @param permission The permission to grant.
+     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+     *     #PERMISSION_OPERATION_FAILURE}.
+     */
+    public int grantInstallPermission(BasePermission permission) {
+        return grantPermission(permission, UserHandle.USER_ALL);
+    }
+
+    /**
+     * Revoke an install permission.
+     *
+     * @param permission The permission to revoke.
+     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+     *     #PERMISSION_OPERATION_FAILURE}.
+     */
+    public int revokeInstallPermission(BasePermission permission) {
+        return revokePermission(permission, UserHandle.USER_ALL);
+    }
+
+    /**
+     * Grant a runtime permission for a given device user.
+     *
+     * @param permission The permission to grant.
+     * @param userId The device user id.
+     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+     *     #PERMISSION_OPERATION_FAILURE}.
+     */
+    public int grantRuntimePermission(BasePermission permission, int userId) {
+        enforceValidUserId(userId);
+        if (userId == UserHandle.USER_ALL) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+        return grantPermission(permission, userId);
+    }
+
+    /**
+     *  Revoke a runtime permission for a given device user.
+     *
+     * @param permission The permission to revoke.
+     * @param userId The device user id.
+     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+     *     #PERMISSION_OPERATION_FAILURE}.
+     */
+    public int revokeRuntimePermission(BasePermission permission, int userId) {
+        enforceValidUserId(userId);
+        if (userId == UserHandle.USER_ALL) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+        return revokePermission(permission, userId);
+    }
+
+    /**
+     * Gets whether this state has a given runtime permission for a
+     * given device user id.
+     *
+     * @param name The permission name.
+     * @param userId The device user id.
+     * @return Whether this state has the permission.
+     */
+    public boolean hasRuntimePermission(String name, int userId) {
+        enforceValidUserId(userId);
+        return !hasInstallPermission(name) && hasPermission(name, userId);
+    }
+
+    /**
+     * Gets whether this state has a given install permission.
+     *
+     * @param name The permission name.
+     * @return Whether this state has the permission.
+     */
+    public boolean hasInstallPermission(String name) {
+        return hasPermission(name, UserHandle.USER_ALL);
+    }
+
+    /**
+     * Gets whether the state has a given permission for the specified
+     * user, regardless if this is an install or a runtime permission.
+     *
+     * @param name The permission name.
+     * @param userId The device user id.
+     * @return Whether the user has the permission.
+     */
+    public boolean hasPermission(String name, int userId) {
+        enforceValidUserId(userId);
+
+        if (mPermissions == null) {
+            return false;
+        }
+
+        PermissionData permissionData = mPermissions.get(name);
+        return permissionData != null && permissionData.isGranted(userId);
+    }
+
+    /**
+     * Returns whether the state has any known request for the given permission name,
+     * whether or not it has been granted.
+     */
+    public boolean hasRequestedPermission(ArraySet<String> names) {
+        if (mPermissions == null) {
+            return false;
+        }
+        for (int i=names.size()-1; i>=0; i--) {
+            if (mPermissions.get(names.valueAt(i)) != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets all permissions for a given device user id regardless if they
+     * are install time or runtime permissions.
+     *
+     * @param userId The device user id.
+     * @return The permissions or an empty set.
+     */
+    public Set<String> getPermissions(int userId) {
+        enforceValidUserId(userId);
+
+        if (mPermissions == null) {
+            return Collections.emptySet();
+        }
+
+        Set<String> permissions = new ArraySet<>(mPermissions.size());
+
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            String permission = mPermissions.keyAt(i);
+
+            if (hasInstallPermission(permission)) {
+                permissions.add(permission);
+                continue;
+            }
+
+            if (userId != UserHandle.USER_ALL) {
+                if (hasRuntimePermission(permission, userId)) {
+                    permissions.add(permission);
+                }
+            }
+        }
+
+        return permissions;
+    }
+
+    /**
+     * Gets the state for an install permission or null if no such.
+     *
+     * @param name The permission name.
+     * @return The permission state.
+     */
+    public PermissionState getInstallPermissionState(String name) {
+        return getPermissionState(name, UserHandle.USER_ALL);
+    }
+
+    /**
+     * Gets the state for a runtime permission or null if no such.
+     *
+     * @param name The permission name.
+     * @param userId The device user id.
+     * @return The permission state.
+     */
+    public PermissionState getRuntimePermissionState(String name, int userId) {
+        enforceValidUserId(userId);
+        return getPermissionState(name, userId);
+    }
+
+    /**
+     * Gets all install permission states.
+     *
+     * @return The permission states or an empty set.
+     */
+    public List<PermissionState> getInstallPermissionStates() {
+        return getPermissionStatesInternal(UserHandle.USER_ALL);
+    }
+
+    /**
+     * Gets all runtime permission states.
+     *
+     * @return The permission states or an empty set.
+     */
+    public List<PermissionState> getRuntimePermissionStates(int userId) {
+        enforceValidUserId(userId);
+        return getPermissionStatesInternal(userId);
+    }
+
+    /**
+     * Gets the flags for a permission regardless if it is install or
+     * runtime permission.
+     *
+     * @param name The permission name.
+     * @return The permission state or null if no such.
+     */
+    public int getPermissionFlags(String name, int userId) {
+        PermissionState installPermState = getInstallPermissionState(name);
+        if (installPermState != null) {
+            return installPermState.getFlags();
+        }
+        PermissionState runtimePermState = getRuntimePermissionState(name, userId);
+        if (runtimePermState != null) {
+            return runtimePermState.getFlags();
+        }
+        return 0;
+    }
+
+    /**
+     * Update the flags associated with a given permission.
+     * @param permission The permission whose flags to update.
+     * @param userId The user for which to update.
+     * @param flagMask Mask for which flags to change.
+     * @param flagValues New values for the mask flags.
+     * @return Whether the permission flags changed.
+     */
+    public boolean updatePermissionFlags(BasePermission permission, int userId,
+            int flagMask, int flagValues) {
+        enforceValidUserId(userId);
+
+        final boolean mayChangeFlags = flagValues != 0 || flagMask != 0;
+
+        if (mPermissions == null) {
+            if (!mayChangeFlags) {
+                return false;
+            }
+            ensurePermissionData(permission);
+        }
+
+        PermissionData permissionData = mPermissions.get(permission.getName());
+        if (permissionData == null) {
+            if (!mayChangeFlags) {
+                return false;
+            }
+            permissionData = ensurePermissionData(permission);
+        }
+
+        final int oldFlags = permissionData.getFlags(userId);
+
+        final boolean updated = permissionData.updateFlags(userId, flagMask, flagValues);
+        if (updated) {
+            final int newFlags = permissionData.getFlags(userId);
+            if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0
+                    && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                if (mPermissionReviewRequired == null) {
+                    mPermissionReviewRequired = new SparseBooleanArray();
+                }
+                mPermissionReviewRequired.put(userId, true);
+            } else if ((oldFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0
+                    && (newFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
+                if (mPermissionReviewRequired != null && !hasPermissionRequiringReview(userId)) {
+                    mPermissionReviewRequired.delete(userId);
+                    if (mPermissionReviewRequired.size() <= 0) {
+                        mPermissionReviewRequired = null;
+                    }
+                }
+            }
+        }
+        return updated;
+    }
+
+    private boolean hasPermissionRequiringReview(int userId) {
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            final PermissionData permission = mPermissions.valueAt(i);
+            if ((permission.getFlags(userId)
+                    & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean updatePermissionFlagsForAllPermissions(
+            int userId, int flagMask, int flagValues) {
+        enforceValidUserId(userId);
+
+        if (mPermissions == null) {
+            return false;
+        }
+        boolean changed = false;
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            PermissionData permissionData = mPermissions.valueAt(i);
+            changed |= permissionData.updateFlags(userId, flagMask, flagValues);
+        }
+        return changed;
+    }
+
+    /**
+     * Compute the Linux gids for a given device user from the permissions
+     * granted to this user. Note that these are computed to avoid additional
+     * state as they are rarely accessed.
+     *
+     * @param userId The device user id.
+     * @return The gids for the device user.
+     */
+    public int[] computeGids(int userId) {
+        enforceValidUserId(userId);
+
+        int[] gids = mGlobalGids;
+
+        if (mPermissions != null) {
+            final int permissionCount = mPermissions.size();
+            for (int i = 0; i < permissionCount; i++) {
+                String permission = mPermissions.keyAt(i);
+                if (!hasPermission(permission, userId)) {
+                    continue;
+                }
+                PermissionData permissionData = mPermissions.valueAt(i);
+                final int[] permGids = permissionData.computeGids(userId);
+                if (permGids != NO_GIDS) {
+                    gids = appendInts(gids, permGids);
+                }
+            }
+        }
+
+        return gids;
+    }
+
+    /**
+     * Compute the Linux gids for all device users from the permissions
+     * granted to these users.
+     *
+     * @return The gids for all device users.
+     */
+    public int[] computeGids(int[] userIds) {
+        int[] gids = mGlobalGids;
+
+        for (int userId : userIds) {
+            final int[] userGids = computeGids(userId);
+            gids = appendInts(gids, userGids);
+        }
+
+        return gids;
+    }
+
+    /**
+     * Resets the internal state of this object.
+     */
+    public void reset() {
+        mGlobalGids = NO_GIDS;
+        mPermissions = null;
+        mPermissionReviewRequired = null;
+    }
+
+    private PermissionState getPermissionState(String name, int userId) {
+        if (mPermissions == null) {
+            return null;
+        }
+        PermissionData permissionData = mPermissions.get(name);
+        if (permissionData == null) {
+            return null;
+        }
+        return permissionData.getPermissionState(userId);
+    }
+
+    private List<PermissionState> getPermissionStatesInternal(int userId) {
+        enforceValidUserId(userId);
+
+        if (mPermissions == null) {
+            return Collections.emptyList();
+        }
+
+        List<PermissionState> permissionStates = new ArrayList<>();
+
+        final int permissionCount = mPermissions.size();
+        for (int i = 0; i < permissionCount; i++) {
+            PermissionData permissionData = mPermissions.valueAt(i);
+
+            PermissionState permissionState = permissionData.getPermissionState(userId);
+            if (permissionState != null) {
+                permissionStates.add(permissionState);
+            }
+        }
+
+        return permissionStates;
+    }
+
+    private int grantPermission(BasePermission permission, int userId) {
+        if (hasPermission(permission.getName(), userId)) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+
+        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
+        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
+
+        PermissionData permissionData = ensurePermissionData(permission);
+
+        if (!permissionData.grant(userId)) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+
+        if (hasGids) {
+            final int[] newGids = computeGids(userId);
+            if (oldGids.length != newGids.length) {
+                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+            }
+        }
+
+        return PERMISSION_OPERATION_SUCCESS;
+    }
+
+    private int revokePermission(BasePermission permission, int userId) {
+        final String permName = permission.getName();
+        if (!hasPermission(permName, userId)) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+
+        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
+        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
+
+        PermissionData permissionData = mPermissions.get(permName);
+
+        if (!permissionData.revoke(userId)) {
+            return PERMISSION_OPERATION_FAILURE;
+        }
+
+        if (permissionData.isDefault()) {
+            ensureNoPermissionData(permName);
+        }
+
+        if (hasGids) {
+            final int[] newGids = computeGids(userId);
+            if (oldGids.length != newGids.length) {
+                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+            }
+        }
+
+        return PERMISSION_OPERATION_SUCCESS;
+    }
+
+    // TODO: fix this to use arraycopy and append all ints in one go
+    private static int[] appendInts(int[] current, int[] added) {
+        if (current != null && added != null) {
+            for (int guid : added) {
+                current = ArrayUtils.appendInt(current, guid);
+            }
+        }
+        return current;
+    }
+
+    private static void enforceValidUserId(int userId) {
+        if (userId != UserHandle.USER_ALL && userId < 0) {
+            throw new IllegalArgumentException("Invalid userId:" + userId);
+        }
+    }
+
+    private PermissionData ensurePermissionData(BasePermission permission) {
+        final String permName = permission.getName();
+        if (mPermissions == null) {
+            mPermissions = new ArrayMap<>();
+        }
+        PermissionData permissionData = mPermissions.get(permName);
+        if (permissionData == null) {
+            permissionData = new PermissionData(permission);
+            mPermissions.put(permName, permissionData);
+        }
+        return permissionData;
+    }
+
+    private void ensureNoPermissionData(String name) {
+        if (mPermissions == null) {
+            return;
+        }
+        mPermissions.remove(name);
+        if (mPermissions.isEmpty()) {
+            mPermissions = null;
+        }
+    }
+
+    private static final class PermissionData {
+        private final BasePermission mPerm;
+        private SparseArray<PermissionState> mUserStates = new SparseArray<>();
+
+        public PermissionData(BasePermission perm) {
+            mPerm = perm;
+        }
+
+        public PermissionData(PermissionData other) {
+            this(other.mPerm);
+            final int otherStateCount = other.mUserStates.size();
+            for (int i = 0; i < otherStateCount; i++) {
+                final int otherUserId = other.mUserStates.keyAt(i);
+                PermissionState otherState = other.mUserStates.valueAt(i);
+                mUserStates.put(otherUserId, new PermissionState(otherState));
+            }
+        }
+
+        public int[] computeGids(int userId) {
+            return mPerm.computeGids(userId);
+        }
+
+        public boolean isGranted(int userId) {
+            if (isInstallPermission()) {
+                userId = UserHandle.USER_ALL;
+            }
+
+            PermissionState userState = mUserStates.get(userId);
+            if (userState == null) {
+                return false;
+            }
+
+            return userState.mGranted;
+        }
+
+        public boolean grant(int userId) {
+            if (!isCompatibleUserId(userId)) {
+                return false;
+            }
+
+            if (isGranted(userId)) {
+                return false;
+            }
+
+            PermissionState userState = mUserStates.get(userId);
+            if (userState == null) {
+                userState = new PermissionState(mPerm.getName());
+                mUserStates.put(userId, userState);
+            }
+
+            userState.mGranted = true;
+
+            return true;
+        }
+
+        public boolean revoke(int userId) {
+            if (!isCompatibleUserId(userId)) {
+                return false;
+            }
+
+            if (!isGranted(userId)) {
+                return false;
+            }
+
+            PermissionState userState = mUserStates.get(userId);
+            userState.mGranted = false;
+
+            if (userState.isDefault()) {
+                mUserStates.remove(userId);
+            }
+
+            return true;
+        }
+
+        public PermissionState getPermissionState(int userId) {
+            return mUserStates.get(userId);
+        }
+
+        public int getFlags(int userId) {
+            PermissionState userState = mUserStates.get(userId);
+            if (userState != null) {
+                return userState.mFlags;
+            }
+            return 0;
+        }
+
+        public boolean isDefault() {
+            return mUserStates.size() <= 0;
+        }
+
+        public static boolean isInstallPermissionKey(int userId) {
+            return userId == UserHandle.USER_ALL;
+        }
+
+        public boolean updateFlags(int userId, int flagMask, int flagValues) {
+            if (isInstallPermission()) {
+                userId = UserHandle.USER_ALL;
+            }
+
+            if (!isCompatibleUserId(userId)) {
+                return false;
+            }
+
+            final int newFlags = flagValues & flagMask;
+
+            PermissionState userState = mUserStates.get(userId);
+            if (userState != null) {
+                final int oldFlags = userState.mFlags;
+                userState.mFlags = (userState.mFlags & ~flagMask) | newFlags;
+                if (userState.isDefault()) {
+                    mUserStates.remove(userId);
+                }
+                return userState.mFlags != oldFlags;
+            } else if (newFlags != 0) {
+                userState = new PermissionState(mPerm.getName());
+                userState.mFlags = newFlags;
+                mUserStates.put(userId, userState);
+                return true;
+            }
+
+            return false;
+        }
+
+        private boolean isCompatibleUserId(int userId) {
+            return isDefault() || !(isInstallPermission() ^ isInstallPermissionKey(userId));
+        }
+
+        private boolean isInstallPermission() {
+            return mUserStates.size() == 1
+                    && mUserStates.get(UserHandle.USER_ALL) != null;
+        }
+    }
+
+    public static final class PermissionState {
+        private final String mName;
+        private boolean mGranted;
+        private int mFlags;
+
+        public PermissionState(String name) {
+            mName = name;
+        }
+
+        public PermissionState(PermissionState other) {
+            mName = other.mName;
+            mGranted = other.mGranted;
+            mFlags = other.mFlags;
+        }
+
+        public boolean isDefault() {
+            return !mGranted && mFlags == 0;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public boolean isGranted() {
+            return mGranted;
+        }
+
+        public int getFlags() {
+            return mFlags;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 342ec4b..7a2e630 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -58,6 +58,9 @@
 
     public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
         if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
+        if (mStatusBarInternal.isGlobalActionsDisabled()) {
+            return;
+        }
         mKeyguardShowing = keyguardShowing;
         mDeviceProvisioned = deviceProvisioned;
         mShowing = true;
diff --git a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
index c6ec287..edd2fdb 100644
--- a/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/policy/ImmersiveModeConfirmation.java
@@ -52,7 +52,6 @@
 import android.widget.FrameLayout;
 
 import com.android.internal.R;
-import com.android.server.vr.VrManagerService;
 
 /**
  *  Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
@@ -148,6 +147,7 @@
                     && userSetupComplete
                     && !mVrModeEnabled
                     && !navBarEmpty
+                    && !isLockTaskModeLocked()
                     && !UserManager.isDeviceInDemoMode(mContext)) {
                 mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
             }
@@ -156,6 +156,20 @@
         }
     }
 
+    /**
+     * @return {@code true} if and only if the device is currently in LockTask mode managed by
+     * {@link android.app.admin.DevicePolicyManager}. Note that this differs from the screen pinning
+     * mode which is initiated by the user.
+     */
+    private boolean isLockTaskModeLocked() {
+        try {
+            return ActivityManager.getService().getLockTaskModeState()
+                    == ActivityManager.LOCK_TASK_MODE_LOCKED;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     public boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode,
             boolean navBarEmpty) {
         if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d19bf44..6520dc9 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -18,11 +18,15 @@
 
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.AppOpsManager.OP_TOAST_WINDOW;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.content.Context.DISPLAY_SERVICE;
 import static android.content.Context.WINDOW_SERVICE;
@@ -549,7 +553,6 @@
 
     int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE;
     int mUserRotation = Surface.ROTATION_0;
-    boolean mAccelerometerDefault;
 
     boolean mSupportAutoRotation;
     int mAllowAllRotations = -1;
@@ -704,7 +707,6 @@
     Intent mVrHeadsetHomeIntent;
     boolean mSearchKeyShortcutPending;
     boolean mConsumeSearchKeyUp;
-    boolean mAssistKeyLongPressed;
     boolean mPendingMetaAction;
     boolean mPendingCapsLockToggle;
     int mMetaState;
@@ -835,6 +837,8 @@
     private static final int MSG_DISPATCH_BACK_KEY_TO_AUTOFILL = 24;
     private static final int MSG_SYSTEM_KEY_PRESS = 25;
     private static final int MSG_HANDLE_ALL_APPS = 26;
+    private static final int MSG_LAUNCH_ASSIST = 27;
+    private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
 
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -876,8 +880,16 @@
                 case MSG_HIDE_BOOT_MESSAGE:
                     handleHideBootMessage();
                     break;
+                case MSG_LAUNCH_ASSIST:
+                    final int deviceId = msg.arg1;
+                    final String hint = (String) msg.obj;
+                    launchAssistAction(hint, deviceId);
+                    break;
+                case MSG_LAUNCH_ASSIST_LONG_PRESS:
+                    launchAssistLongPressAction();
+                    break;
                 case MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK:
-                    launchVoiceAssistWithWakeLock(msg.arg1 != 0);
+                    launchVoiceAssistWithWakeLock();
                     break;
                 case MSG_POWER_DELAYED_PRESS:
                     powerPress((Long)msg.obj, msg.arg1 != 0, msg.arg2);
@@ -907,7 +919,7 @@
                     disposeInputConsumer((InputConsumer) msg.obj);
                     break;
                 case MSG_BACK_DELAYED_PRESS:
-                    backMultiPressAction((Long) msg.obj, msg.arg1);
+                    backMultiPressAction(msg.arg1);
                     finishBackKeyPress();
                     break;
                 case MSG_ACCESSIBILITY_SHORTCUT:
@@ -1411,7 +1423,7 @@
         }
     }
 
-    private void backMultiPressAction(long eventTime, int count) {
+    private void backMultiPressAction(int count) {
         if (count >= PANIC_PRESS_BACK_COUNT) {
             switch (mPanicPressOnBackBehavior) {
                 case PANIC_PRESS_BACK_NOTHING:
@@ -1580,7 +1592,7 @@
         }
     }
 
-    private void sleepPress(long eventTime) {
+    private void sleepPress() {
         if (mShortPressOnSleepBehavior == SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME) {
             launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
         }
@@ -1642,11 +1654,23 @@
                 mScreenshotChordVolumeDownKeyConsumed = true;
                 mA11yShortcutChordVolumeUpKeyConsumed = true;
                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
-                        ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+                        getAccessibilityShortcutTimeout());
             }
         }
     }
 
+    private long getAccessibilityShortcutTimeout() {
+        ViewConfiguration config = ViewConfiguration.get(mContext);
+        try {
+            return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, mCurrentUserId) == 0
+                    ? config.getAccessibilityShortcutKeyTimeout()
+                    : config.getAccessibilityShortcutKeyTimeoutAfterConfirmation();
+        } catch (Settings.SettingNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     private long getScreenshotChordLongPressDelay() {
         if (mKeyguardDelegate.isShowing()) {
             // Double the time it takes to take a screenshot from the keyguard
@@ -2267,7 +2291,11 @@
 
         // Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per
         // http://developer.android.com/guide/practices/screens_support.html#range
-        mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 &&
+        // For car, ignore the dp limitation. It's physically impossible to rotate the car's screen
+        // so if the orientation is forced, we need to respect that no matter what.
+        boolean isCar = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+        mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar) &&
                 res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
                 // For debug purposes the next line turns this feature off with:
                 // $ adb shell setprop config.override_forced_orient true
@@ -2627,18 +2655,15 @@
             attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
 
-        if (ActivityManager.isHighEndGfx()) {
-            if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
-                attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-            }
-            final boolean forceWindowDrawsStatusBarBackground =
-                    (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND)
-                            != 0;
-            if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
-                    || forceWindowDrawsStatusBarBackground
-                            && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
-                attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-            }
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        }
+        final boolean forceWindowDrawsStatusBarBackground =
+                (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                || forceWindowDrawsStatusBarBackground
+                        && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
+            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         }
     }
 
@@ -2844,7 +2869,7 @@
 
         boolean keyguardLocked = isKeyguardLocked();
         boolean hideDockDivider = attrs.type == TYPE_DOCK_DIVIDER
-                && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+                && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         return (keyguardLocked && !allowWhenLocked && win.getDisplayId() == DEFAULT_DISPLAY)
                 || hideDockDivider;
     }
@@ -3537,44 +3562,11 @@
                 toggleKeyboardShortcutsMenu(event.getDeviceId());
             }
         } else if (keyCode == KeyEvent.KEYCODE_ASSIST) {
-            if (down) {
-                if (repeatCount == 0) {
-                    mAssistKeyLongPressed = false;
-                } else if (repeatCount == 1) {
-                    mAssistKeyLongPressed = true;
-                    if (!keyguardOn) {
-                         launchAssistLongPressAction();
-                    }
-                }
-            } else {
-                if (mAssistKeyLongPressed) {
-                    mAssistKeyLongPressed = false;
-                } else {
-                    if (!keyguardOn) {
-                        launchAssistAction(null, event.getDeviceId());
-                    }
-                }
-            }
+            Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
             return -1;
         } else if (keyCode == KeyEvent.KEYCODE_VOICE_ASSIST) {
-            if (!down) {
-                Intent voiceIntent;
-                if (!keyguardOn) {
-                    voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
-                } else {
-                    IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
-                            ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-                    if (dic != null) {
-                        try {
-                            dic.exitIdle("voice-search");
-                        } catch (RemoteException e) {
-                        }
-                    }
-                    voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-                    voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
-                }
-                startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
-            }
+            Slog.wtf(TAG, "KEYCODE_VOICE_ASSIST should be handled in interceptKeyBeforeQueueing");
+            return -1;
         } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
             if (down && repeatCount == 0) {
                 mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
@@ -3886,8 +3878,7 @@
                 mAccessibilityTvScheduled = true;
                 Message msg = Message.obtain(mHandler, MSG_ACCESSIBILITY_TV);
                 msg.setAsynchronous(true);
-                mHandler.sendMessageDelayed(msg,
-                        ViewConfiguration.get(mContext).getAccessibilityShortcutKeyTimeout());
+                mHandler.sendMessageDelayed(msg, getAccessibilityShortcutTimeout());
             }
         } else if (mAccessibilityTvScheduled) {
             mHandler.removeMessages(MSG_ACCESSIBILITY_TV);
@@ -5445,7 +5436,10 @@
 
         boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                 && attrs.type < FIRST_SYSTEM_WINDOW;
-        final int stackId = win.getStackId();
+        final int windowingMode = win.getWindowingMode();
+        final boolean inFullScreenOrSplitScreenSecondaryWindowingMode =
+                windowingMode == WINDOWING_MODE_FULLSCREEN
+                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
         if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) {
             if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
                 mForceStatusBar = true;
@@ -5464,7 +5458,7 @@
             // represent should be hidden or if we should hide the lockscreen. For attached app
             // windows we defer the decision to the window it is attached to.
             if (appWindow && attached == null) {
-                if (attrs.isFullscreen() && StackId.normallyFullscreenWindows(stackId)) {
+                if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
                     mTopFullscreenOpaqueWindowState = win;
                     if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
@@ -5495,7 +5489,7 @@
 
         // Keep track of the window if it's dimming but not necessarily fullscreen.
         if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi
-                && win.isDimming() && StackId.normallyFullscreenWindows(stackId)) {
+                && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
             mTopFullscreenOpaqueOrDimmingWindowState = win;
         }
 
@@ -5503,7 +5497,7 @@
         // separately, because both the "real fullscreen" opaque window and the one for the docked
         // stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
         if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null
-                && attrs.isFullscreen() && stackId == DOCKED_STACK_ID) {
+                && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mTopDockedOpaqueWindowState = win;
             if (mTopDockedOpaqueOrDimmingWindowState == null) {
                 mTopDockedOpaqueOrDimmingWindowState = win;
@@ -5513,7 +5507,7 @@
         // Also keep track of any windows that are dimming but not necessarily fullscreen in the
         // docked stack.
         if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming()
-                && stackId == DOCKED_STACK_ID) {
+                && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mTopDockedOpaqueOrDimmingWindowState = win;
         }
 
@@ -5608,8 +5602,9 @@
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
                     }
                 } else if (topIsFullscreen
-                        && !mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)
-                        && !mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID)) {
+                        && !mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM)
+                        && !mWindowManagerInternal.isStackVisible(
+                                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
                     if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
                     if (mStatusBarController.setBarShowingLw(false)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
@@ -6191,7 +6186,7 @@
                     useHapticFeedback = false; // suppress feedback if already non-interactive
                 }
                 if (down) {
-                    sleepPress(event.getEventTime());
+                    sleepPress();
                 } else {
                     sleepRelease(event.getEventTime());
                 }
@@ -6262,18 +6257,30 @@
                 }
                 break;
             }
-            case KeyEvent.KEYCODE_VOICE_ASSIST: {
-                // Only do this if we would otherwise not pass it to the user. In that case,
-                // interceptKeyBeforeDispatching would apply a similar but different policy in
-                // order to invoke voice assist actions. Note that we need to make a copy of the
-                // key event here because the original key event will be recycled when we return.
-                if ((result & ACTION_PASS_TO_USER) == 0 && !down) {
-                    mBroadcastWakeLock.acquire();
-                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK,
-                            keyguardActive ? 1 : 0, 0);
+            case KeyEvent.KEYCODE_ASSIST: {
+                final boolean longPressed = event.getRepeatCount() > 0;
+                if (down && longPressed) {
+                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST_LONG_PRESS);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
                 }
+                if (!down && !longPressed) {
+                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_ASSIST, event.getDeviceId(),
+                            0 /* unused */, null /* hint */);
+                    msg.setAsynchronous(true);
+                    msg.sendToTarget();
+                }
+                result &= ~ACTION_PASS_TO_USER;
+                break;
+            }
+            case KeyEvent.KEYCODE_VOICE_ASSIST: {
+                if (!down) {
+                    mBroadcastWakeLock.acquire();
+                    Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
+                    msg.setAsynchronous(true);
+                    msg.sendToTarget();
+                }
+                result &= ~ACTION_PASS_TO_USER;
                 break;
             }
             case KeyEvent.KEYCODE_WINDOW: {
@@ -6542,18 +6549,22 @@
         }
     }
 
-    void launchVoiceAssistWithWakeLock(boolean keyguardActive) {
-        IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-        if (dic != null) {
-            try {
-                dic.exitIdle("voice-search");
-            } catch (RemoteException e) {
+    void launchVoiceAssistWithWakeLock() {
+        final Intent voiceIntent;
+        if (!keyguardOn()) {
+            voiceIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+        } else {
+            IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
+                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+            if (dic != null) {
+                try {
+                    dic.exitIdle("voice-search");
+                } catch (RemoteException e) {
+                }
             }
+            voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
+            voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, true);
         }
-        Intent voiceIntent =
-            new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-        voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, keyguardActive);
         startActivityAsUser(voiceIntent, UserHandle.CURRENT_OR_SELF);
         mBroadcastWakeLock.release();
     }
@@ -7920,8 +7931,10 @@
                 mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
         final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
                 mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
-        mWindowManagerFuncs.getStackBounds(HOME_STACK_ID, mNonDockedStackBounds);
-        mWindowManagerFuncs.getStackBounds(DOCKED_STACK_ID, mDockedStackBounds);
+        mWindowManagerFuncs.getStackBounds(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
+        mWindowManagerFuncs.getStackBounds(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
         final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
         final int diff = visibility ^ mLastSystemUiFlags;
         final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
@@ -8007,9 +8020,10 @@
     }
 
     private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
-        final boolean dockedStackVisible = mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
+        final boolean dockedStackVisible =
+                mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final boolean freeformStackVisible =
-                mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID);
+                mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM);
         final boolean resizing = mWindowManagerInternal.isDockedDividerResizing();
 
         // We need to force system bars when the docked stack is visible, when the freeform stack
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 12ca89a..2494bde 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -58,10 +58,6 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
-import android.service.power.PowerServiceDumpProto;
-import android.service.power.PowerServiceSettingsAndConfigurationDumpProto;
-import android.service.power.SuspendBlockerProto;
-import android.service.power.WakeLockProto;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.util.EventLog;
@@ -120,7 +116,7 @@
         implements Watchdog.Monitor {
     private static final String TAG = "PowerManagerService";
 
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
     private static final boolean DEBUG_SPEW = DEBUG && true;
 
     // Message: Sent when a user activity timeout occurs to update the power state.
@@ -206,6 +202,8 @@
     private static final String REASON_REBOOT = "reboot";
     private static final String REASON_USERREQUESTED = "shutdown,userrequested";
     private static final String REASON_THERMAL_SHUTDOWN = "shutdown,thermal";
+    private static final String REASON_LOW_BATTERY = "shutdown,battery";
+    private static final String REASON_BATTERY_THERMAL_STATE = "shutdown,thermal,battery";
 
     private static final String TRACE_SCREEN_ON = "Screen turning on";
 
@@ -618,8 +616,8 @@
         }
 
         void dumpProto(ProtoOutputStream proto) {
-            final long constantsToken = proto.start(PowerServiceDumpProto.CONSTANTS);
-            proto.write(PowerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS,
+            final long constantsToken = proto.start(PowerManagerServiceDumpProto.CONSTANTS);
+            proto.write(PowerManagerServiceDumpProto.ConstantsProto.IS_NO_CACHED_WAKE_LOCKS,
                     NO_CACHED_WAKE_LOCKS);
             proto.end(constantsToken);
         }
@@ -3394,112 +3392,112 @@
 
         synchronized (mLock) {
             mConstants.dumpProto(proto);
-            proto.write(PowerServiceDumpProto.DIRTY, mDirty);
-            proto.write(PowerServiceDumpProto.WAKEFULNESS, mWakefulness);
-            proto.write(PowerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
-            proto.write(PowerServiceDumpProto.IS_POWERED, mIsPowered);
-            proto.write(PowerServiceDumpProto.PLUG_TYPE, mPlugType);
-            proto.write(PowerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
+            proto.write(PowerManagerServiceDumpProto.DIRTY, mDirty);
+            proto.write(PowerManagerServiceDumpProto.WAKEFULNESS, mWakefulness);
+            proto.write(PowerManagerServiceDumpProto.IS_WAKEFULNESS_CHANGING, mWakefulnessChanging);
+            proto.write(PowerManagerServiceDumpProto.IS_POWERED, mIsPowered);
+            proto.write(PowerManagerServiceDumpProto.PLUG_TYPE, mPlugType);
+            proto.write(PowerManagerServiceDumpProto.BATTERY_LEVEL, mBatteryLevel);
             proto.write(
-                    PowerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
+                    PowerManagerServiceDumpProto.BATTERY_LEVEL_WHEN_DREAM_STARTED,
                     mBatteryLevelWhenDreamStarted);
-            proto.write(PowerServiceDumpProto.DOCK_STATE, mDockState);
-            proto.write(PowerServiceDumpProto.IS_STAY_ON, mStayOn);
-            proto.write(PowerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
-            proto.write(PowerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
-            proto.write(PowerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
+            proto.write(PowerManagerServiceDumpProto.DOCK_STATE, mDockState);
+            proto.write(PowerManagerServiceDumpProto.IS_STAY_ON, mStayOn);
+            proto.write(PowerManagerServiceDumpProto.IS_PROXIMITY_POSITIVE, mProximityPositive);
+            proto.write(PowerManagerServiceDumpProto.IS_BOOT_COMPLETED, mBootCompleted);
+            proto.write(PowerManagerServiceDumpProto.IS_SYSTEM_READY, mSystemReady);
             proto.write(
-                    PowerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
+                    PowerManagerServiceDumpProto.IS_HAL_AUTO_SUSPEND_MODE_ENABLED,
                     mHalAutoSuspendModeEnabled);
             proto.write(
-                    PowerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
+                    PowerManagerServiceDumpProto.IS_HAL_AUTO_INTERACTIVE_MODE_ENABLED,
                     mHalInteractiveModeEnabled);
 
-            final long activeWakeLocksToken = proto.start(PowerServiceDumpProto.ACTIVE_WAKE_LOCKS);
+            final long activeWakeLocksToken = proto.start(PowerManagerServiceDumpProto.ACTIVE_WAKE_LOCKS);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_CPU,
                     (mWakeLockSummary & WAKE_LOCK_CPU) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_BRIGHT,
                     (mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_SCREEN_DIM,
                     (mWakeLockSummary & WAKE_LOCK_SCREEN_DIM) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_BUTTON_BRIGHT,
                     (mWakeLockSummary & WAKE_LOCK_BUTTON_BRIGHT) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_PROXIMITY_SCREEN_OFF,
                     (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_STAY_AWAKE,
                     (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DOZE,
                     (mWakeLockSummary & WAKE_LOCK_DOZE) != 0);
             proto.write(
-                    PowerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW,
+                    PowerManagerServiceDumpProto.ActiveWakeLocksProto.IS_DRAW,
                     (mWakeLockSummary & WAKE_LOCK_DRAW) != 0);
             proto.end(activeWakeLocksToken);
 
-            proto.write(PowerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled);
-            proto.write(PowerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
-            proto.write(PowerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
+            proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_SCHEDULED_MS, mNotifyLongScheduled);
+            proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
+            proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
 
-            final long userActivityToken = proto.start(PowerServiceDumpProto.USER_ACTIVITY);
+            final long userActivityToken = proto.start(PowerManagerServiceDumpProto.USER_ACTIVITY);
             proto.write(
-                    PowerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
+                    PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
                     (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0);
             proto.write(
-                    PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
+                    PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
                     (mUserActivitySummary & USER_ACTIVITY_SCREEN_DIM) != 0);
             proto.write(
-                    PowerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
+                    PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DREAM,
                     (mUserActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0);
             proto.end(userActivityToken);
 
             proto.write(
-                    PowerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
+                    PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
                     mRequestWaitForNegativeProximity);
-            proto.write(PowerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
-            proto.write(PowerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
-            proto.write(PowerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
-            proto.write(PowerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
-            proto.write(PowerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
-            proto.write(PowerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
+            proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
+            proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
+            proto.write(PowerManagerServiceDumpProto.IS_LOW_POWER_MODE_ENABLED, mLowPowerModeEnabled);
+            proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
+            proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
+            proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
 
             for (int id : mDeviceIdleWhitelist) {
-                proto.write(PowerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
+                proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_WHITELIST, id);
             }
             for (int id : mDeviceIdleTempWhitelist) {
-                proto.write(PowerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
+                proto.write(PowerManagerServiceDumpProto.DEVICE_IDLE_TEMP_WHITELIST, id);
             }
 
-            proto.write(PowerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
-            proto.write(PowerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
-            proto.write(PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_WAKE_TIME_MS, mLastWakeTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_SLEEP_TIME_MS, mLastSleepTime);
+            proto.write(PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_MS, mLastUserActivityTime);
             proto.write(
-                    PowerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
+                    PowerManagerServiceDumpProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
                     mLastUserActivityTimeNoChangeLights);
             proto.write(
-                    PowerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
+                    PowerManagerServiceDumpProto.LAST_INTERACTIVE_POWER_HINT_TIME_MS,
                     mLastInteractivePowerHintTime);
             proto.write(
-                    PowerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS,
+                    PowerManagerServiceDumpProto.LAST_SCREEN_BRIGHTNESS_BOOST_TIME_MS,
                     mLastScreenBrightnessBoostTime);
             proto.write(
-                    PowerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
+                    PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
                     mScreenBrightnessBoostInProgress);
-            proto.write(PowerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
+            proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
             proto.write(
-                    PowerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
+                    PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
                     mHoldingWakeLockSuspendBlocker);
             proto.write(
-                    PowerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER,
+                    PowerManagerServiceDumpProto.IS_HOLDING_DISPLAY_SUSPEND_BLOCKER,
                     mHoldingDisplaySuspendBlocker);
 
             final long settingsAndConfigurationToken =
-                    proto.start(PowerServiceDumpProto.SETTINGS_AND_CONFIGURATION);
+                    proto.start(PowerManagerServiceDumpProto.SETTINGS_AND_CONFIGURATION);
             proto.write(
                     PowerServiceSettingsAndConfigurationDumpProto
                             .IS_DECOUPLE_HAL_AUTO_SUSPEND_MODE_FROM_DISPLAY_CONFIG,
@@ -3696,42 +3694,43 @@
             final int sleepTimeout = getSleepTimeoutLocked();
             final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout);
             final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
-            proto.write(PowerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
-            proto.write(PowerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
-            proto.write(PowerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
-            proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging);
-            proto.write(PowerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged);
+            proto.write(PowerManagerServiceDumpProto.SLEEP_TIMEOUT_MS, sleepTimeout);
+            proto.write(PowerManagerServiceDumpProto.SCREEN_OFF_TIMEOUT_MS, screenOffTimeout);
+            proto.write(PowerManagerServiceDumpProto.SCREEN_DIM_DURATION_MS, screenDimDuration);
+            proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGING, mUidsChanging);
+            proto.write(PowerManagerServiceDumpProto.ARE_UIDS_CHANGED, mUidsChanged);
 
             for (int i = 0; i < mUidState.size(); i++) {
                 final UidState state = mUidState.valueAt(i);
-                final long uIDToken = proto.start(PowerServiceDumpProto.UIDS);
+                final long uIDToken = proto.start(PowerManagerServiceDumpProto.UID_STATES);
                 final int uid = mUidState.keyAt(i);
-                proto.write(PowerServiceDumpProto.UidProto.UID, uid);
-                proto.write(PowerServiceDumpProto.UidProto.UID_STRING, UserHandle.formatUid(uid));
-                proto.write(PowerServiceDumpProto.UidProto.IS_ACTIVE, state.mActive);
-                proto.write(PowerServiceDumpProto.UidProto.NUM_WAKE_LOCKS, state.mNumWakeLocks);
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.UID, uid);
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.UID_STRING, UserHandle.formatUid(uid));
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_ACTIVE, state.mActive);
+                proto.write(PowerManagerServiceDumpProto.UidStateProto.NUM_WAKE_LOCKS, state.mNumWakeLocks);
                 if (state.mProcState == ActivityManager.PROCESS_STATE_UNKNOWN) {
-                    proto.write(PowerServiceDumpProto.UidProto.IS_PROCESS_STATE_UNKNOWN, true);
+                    proto.write(PowerManagerServiceDumpProto.UidStateProto.IS_PROCESS_STATE_UNKNOWN, true);
                 } else {
-                    proto.write(PowerServiceDumpProto.UidProto.PROCESS_STATE, state.mProcState);
+                    proto.write(PowerManagerServiceDumpProto.UidStateProto.PROCESS_STATE,
+                            ActivityManager.processStateAmToProto(state.mProcState));
                 }
                 proto.end(uIDToken);
             }
 
-            mHandler.getLooper().writeToProto(proto, PowerServiceDumpProto.LOOPER);
+            mHandler.getLooper().writeToProto(proto, PowerManagerServiceDumpProto.LOOPER);
 
             for (WakeLock wl : mWakeLocks) {
-                wl.writeToProto(proto, PowerServiceDumpProto.WAKE_LOCKS);
+                wl.writeToProto(proto, PowerManagerServiceDumpProto.WAKE_LOCKS);
             }
 
             for (SuspendBlocker sb : mSuspendBlockers) {
-                sb.writeToProto(proto, PowerServiceDumpProto.SUSPEND_BLOCKERS);
+                sb.writeToProto(proto, PowerManagerServiceDumpProto.SUSPEND_BLOCKERS);
             }
             wcd = mWirelessChargerDetector;
         }
 
         if (wcd != null) {
-            wcd.writeToProto(proto, PowerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
+            wcd.writeToProto(proto, PowerManagerServiceDumpProto.WIRELESS_CHARGER_DETECTOR);
         }
         proto.flush();
     }
@@ -4651,6 +4650,10 @@
                 return PowerManager.SHUTDOWN_REASON_USER_REQUESTED;
             case REASON_THERMAL_SHUTDOWN:
                 return PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN;
+            case REASON_LOW_BATTERY:
+                return PowerManager.SHUTDOWN_REASON_LOW_BATTERY;
+            case REASON_BATTERY_THERMAL_STATE:
+                return PowerManager.SHUTDOWN_REASON_BATTERY_THERMAL;
             default:
                 return PowerManager.SHUTDOWN_REASON_UNKNOWN;
         }
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 853e1b2..755c5f0 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -34,8 +34,6 @@
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.media.AudioAttributes;
-import android.nfc.INfcAdapter;
-import android.nfc.NfcAdapter;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -124,7 +122,6 @@
     private static String METRIC_RADIOS = "shutdown_radios";
     private static String METRIC_BT = "shutdown_bt";
     private static String METRIC_RADIO = "shutdown_radio";
-    private static String METRIC_NFC = "shutdown_nfc";
     private static String METRIC_SM = "shutdown_storage_manager";
 
     private final Object mActionDoneSync = new Object();
@@ -457,8 +454,7 @@
         // First send the high-level shut down broadcast.
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendOrderedBroadcastAsUser(intent,
                 UserHandle.ALL, null, br, mHandler, 0, null, null);
 
@@ -630,29 +626,14 @@
         Thread t = new Thread() {
             public void run() {
                 TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
-                boolean nfcOff;
                 boolean bluetoothReadyForShutdown;
                 boolean radioOff;
 
-                final INfcAdapter nfc =
-                        INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
                 final ITelephony phone =
                         ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
                 final IBluetoothManager bluetooth =
                         IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
                                 BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
-                try {
-                    nfcOff = nfc == null ||
-                             nfc.getState() == NfcAdapter.STATE_OFF;
-                    if (!nfcOff) {
-                        Log.w(TAG, "Turning off NFC...");
-                        metricStarted(METRIC_NFC);
-                        nfc.disable(false); // Don't persist new state
-                    }
-                } catch (RemoteException ex) {
-                Log.e(TAG, "RemoteException during NFC shutdown", ex);
-                    nfcOff = true;
-                }
 
                 try {
                     bluetoothReadyForShutdown = bluetooth == null ||
@@ -679,7 +660,7 @@
                     radioOff = true;
                 }
 
-                Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
+                Log.i(TAG, "Waiting for Bluetooth and Radio...");
 
                 long delay = endTime - SystemClock.elapsedRealtime();
                 while (delay > 0) {
@@ -723,23 +704,9 @@
                                     .logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
                         }
                     }
-                    if (!nfcOff) {
-                        try {
-                            nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
-                        } catch (RemoteException ex) {
-                            Log.e(TAG, "RemoteException during NFC shutdown", ex);
-                            nfcOff = true;
-                        }
-                        if (nfcOff) {
-                            Log.i(TAG, "NFC turned off.");
-                            metricEnded(METRIC_NFC);
-                            shutdownTimingsTraceLog
-                                    .logDuration("ShutdownNfc", TRON_METRICS.get(METRIC_NFC));
-                        }
-                    }
 
-                    if (radioOff && bluetoothReadyForShutdown && nfcOff) {
-                        Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
+                    if (radioOff && bluetoothReadyForShutdown) {
+                        Log.i(TAG, "Radio and Bluetooth shutdown complete.");
                         done[0] = true;
                         break;
                     }
@@ -756,7 +723,7 @@
         } catch (InterruptedException ex) {
         }
         if (!done[0]) {
-            Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
+            Log.w(TAG, "Timed out waiting for Radio and Bluetooth shutdown.");
         }
     }
 
diff --git a/services/core/java/com/android/server/power/WirelessChargerDetector.java b/services/core/java/com/android/server/power/WirelessChargerDetector.java
index 6ee9dcd..54487e3 100644
--- a/services/core/java/com/android/server/power/WirelessChargerDetector.java
+++ b/services/core/java/com/android/server/power/WirelessChargerDetector.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
-import android.service.power.WirelessChargerDetectorProto;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 11ae212..22d2bcf 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -20,21 +20,39 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.IntentFilter;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IStatsCompanionService;
 import android.os.IStatsManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.StatsLogEventWrapper;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Slog;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
 import com.android.server.SystemService;
 
+import java.util.Map;
+
 /**
  * Helper service for statsd (the native stats management service in cmds/statsd/).
  * Used for registering and receiving alarms on behalf of statsd.
+ *
+ * @hide
  */
 public class StatsCompanionService extends IStatsCompanionService.Stub {
     static final String TAG = "StatsCompanionService";
@@ -48,6 +66,8 @@
 
     private final PendingIntent mAnomalyAlarmIntent;
     private final PendingIntent mPollingAlarmIntent;
+    private final BroadcastReceiver mAppUpdateReceiver;
+    private final BroadcastReceiver mUserUpdateReceiver;
 
     public StatsCompanionService(Context context) {
         super();
@@ -58,9 +78,114 @@
                 new Intent(mContext, AnomalyAlarmReceiver.class), 0);
         mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
                 new Intent(mContext, PollingAlarmReceiver.class), 0);
+        mAppUpdateReceiver = new AppUpdateReceiver();
+        mUserUpdateReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (sStatsdLock) {
+                    sStatsd = fetchStatsdService();
+                    if (sStatsd == null) {
+                        Slog.w(TAG, "Could not access statsd");
+                        return;
+                    }
+                    try {
+                        // Pull the latest state of UID->app name, version mapping.
+                        // Needed since the new user basically has a version of every app.
+                        informAllUidsLocked(context);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Failed to inform statsd latest update of all apps", e);
+                        forgetEverything();
+                    }
+                }
+            }
+        };
+        Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
     }
 
-    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
+    private final static int[] toIntArray(List<Integer> list) {
+        int[] ret = new int[list.size()];
+        for (int i = 0; i < ret.length; i++) {
+            ret[i] = list.get(i);
+        }
+        return ret;
+    }
+
+    // Assumes that sStatsdLock is held.
+    private final void informAllUidsLocked(Context context) throws RemoteException {
+        UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        PackageManager pm = context.getPackageManager();
+        final List<UserInfo> users = um.getUsers(true);
+        if (DEBUG) {
+            Slog.w(TAG, "Iterating over " + users.size() + " profiles.");
+        }
+
+        List<Integer> uids = new ArrayList();
+        List<Integer> versions = new ArrayList();
+        List<String> apps = new ArrayList();
+
+        // Add in all the apps for every user/profile.
+        for (UserInfo profile : users) {
+            List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
+            for (int j = 0; j < pi.size(); j++) {
+                if (pi.get(j).applicationInfo != null) {
+                    uids.add(pi.get(j).applicationInfo.uid);
+                    versions.add(pi.get(j).versionCode);
+                    apps.add(pi.get(j).packageName);
+                }
+            }
+        }
+        sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new
+                String[apps.size()]));
+        if (DEBUG) {
+            Slog.w(TAG, "Sent data for " + uids.size() + " apps");
+        }
+    }
+
+    public final static class AppUpdateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
+            /**
+             * App updates actually consist of REMOVE, ADD, and then REPLACE broadcasts. To avoid
+             * waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
+             */
+            if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
+                    intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                return; // Keep only replacing or normal add and remove.
+            }
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+                    return;
+                }
+                try {
+                    if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
+                        Bundle b = intent.getExtras();
+                        int uid = b.getInt(Intent.EXTRA_UID);
+                        boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                        if (!replacing) {
+                            // Don't bother sending an update if we're right about to get another
+                            // intent for the new version that's added.
+                            PackageManager pm = context.getPackageManager();
+                            String app = intent.getData().getSchemeSpecificPart();
+                            sStatsd.informOnePackageRemoved(app, uid);
+                        }
+                    } else {
+                        PackageManager pm = context.getPackageManager();
+                        Bundle b = intent.getExtras();
+                        int uid = b.getInt(Intent.EXTRA_UID);
+                        String app = intent.getData().getSchemeSpecificPart();
+                        PackageInfo pi = pm.getPackageInfo(app, PackageManager.MATCH_ANY_USER);
+                        sStatsd.informOnePackage(app, uid, pi.versionCode);
+                    }
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to inform statsd of an app update", e);
+                }
+            }
+        }
+    }
+
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
@@ -78,7 +203,7 @@
             }
             // AlarmManager releases its own wakelock here.
         }
-    };
+    }
 
     public final static class PollingAlarmReceiver extends BroadcastReceiver {
         @Override
@@ -98,7 +223,7 @@
             }
             // AlarmManager releases its own wakelock here.
         }
-    };
+    }
 
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
@@ -156,7 +281,45 @@
         }
     }
 
-    @Override
+    // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
+    // TODO: pull the constant from stats_events.proto instead
+    private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+    @Override // Binder call
+    public StatsLogEventWrapper[] pullData(int pullCode) {
+        enforceCallingPermission();
+        if (DEBUG) {
+            Slog.d(TAG, "Pulling " + pullCode);
+        }
+
+        List<StatsLogEventWrapper> ret = new ArrayList<>();
+        switch (pullCode) {
+            case PULL_CODE_KERNEL_WAKELOCKS: {
+                final KernelWakelockStats wakelockStats =
+                        mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+                for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+                    String name = ent.getKey();
+                    KernelWakelockStats.Entry kws = ent.getValue();
+                    StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+                    e.writeInt(kws.mCount);
+                    e.writeInt(kws.mVersion);
+                    e.writeLong(kws.mTotalTime);
+                    e.writeString(name);
+                    ret.add(e);
+                }
+                break;
+            }
+            default:
+                Slog.w(TAG, "No such pollable data as " + pullCode);
+                return null;
+        }
+        return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+    }
+
+    @Override // Binder call
     public void statsdReady() {
         enforceCallingPermission();
         if (DEBUG) Slog.d(TAG, "learned that statsdReady");
@@ -172,7 +335,9 @@
 
     // Lifecycle and related code
 
-    /** Fetches the statsd IBinder service */
+    /**
+     * Fetches the statsd IBinder service
+     */
     private static IStatsManager fetchStatsdService() {
         return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
     }
@@ -204,13 +369,17 @@
         }
     }
 
-    /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+    /**
+     * Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
+     */
     private void systemReady() {
         if (DEBUG) Slog.d(TAG, "Learned that systemReady");
         sayHiToStatsd();
     }
 
-    /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+    /**
+     * Tells statsd that statscompanion is ready. If the binder call returns, link to statsd.
+     */
     private void sayHiToStatsd() {
         synchronized (sStatsdLock) {
             if (sStatsd != null) {
@@ -233,6 +402,23 @@
                     Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
                     forgetEverything();
                 }
+                // Setup broadcast receiver for updates
+                IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
+                filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+                filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+                filter.addDataScheme("package");
+                mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null,
+                        null);
+
+                // Setup receiver for user initialize (which happens once for a new user) and
+                // if a user is removed.
+                filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
+                filter.addAction(Intent.ACTION_USER_REMOVED);
+                mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
+                        filter, null, null);
+
+                // Pull the latest state of UID->app name, version mapping when statsd starts.
+                informAllUidsLocked(mContext);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
                 forgetEverything();
@@ -251,6 +437,8 @@
     private void forgetEverything() {
         synchronized (sStatsdLock) {
             sStatsd = null;
+            mContext.unregisterReceiver(mAppUpdateReceiver);
+            mContext.unregisterReceiver(mUserUpdateReceiver);
             cancelAnomalyAlarm();
             cancelPollingAlarms();
         }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 0884678..b07fe98 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -77,6 +77,7 @@
 
     void setCurrentUser(int newUserId);
 
+    boolean isGlobalActionsDisabled();
     void setGlobalActionsListener(GlobalActionsListener listener);
     void showGlobalActions();
 
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index f10bf1a..c78a340 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.statusbar;
 
+import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
+
 import android.app.ActivityThread;
 import android.app.StatusBarManager;
 import android.content.ComponentName;
@@ -31,6 +33,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.service.notification.NotificationStats;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -362,6 +365,11 @@
         }
 
         @Override
+        public boolean isGlobalActionsDisabled() {
+            return (mDisabled2 & DISABLE2_GLOBAL_ACTIONS) != 0;
+        }
+
+        @Override
         public void setGlobalActionsListener(GlobalActionsListener listener) {
             mGlobalActionListener = listener;
             mGlobalActionListener.onStatusBarConnectedChanged(mBar != null);
@@ -529,7 +537,7 @@
      */
     @Override
     public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
-        enforceStatusBar();
+        enforceStatusBarService();
 
         synchronized (mLock) {
             disableLocked(userId, what, token, pkg, 2);
@@ -943,13 +951,15 @@
     }
 
     @Override
-    public void onNotificationClear(String pkg, String tag, int id, int userId) {
+    public void onNotificationClear(String pkg, String tag, int id, int userId, String key,
+            @NotificationStats.DismissalSurface int dismissalSurface) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
         long identity = Binder.clearCallingIdentity();
         try {
-            mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
+            mNotificationDelegate.onNotificationClear(
+                    callingUid, callingPid, pkg, tag, id, userId, key, dismissalSurface);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -983,6 +993,28 @@
     }
 
     @Override
+    public void onNotificationDirectReplied(String key) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationDirectReplied(key);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onNotificationSettingsViewed(String key) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationSettingsViewed(key);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void onClearAllNotifications(int userId) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/storage/AppCollector.java b/services/core/java/com/android/server/storage/AppCollector.java
index 03b754f..0b51f9c 100644
--- a/services/core/java/com/android/server/storage/AppCollector.java
+++ b/services/core/java/com/android/server/storage/AppCollector.java
@@ -135,7 +135,7 @@
                                 PackageStats packageStats = new PackageStats(app.packageName,
                                         user.id);
                                 packageStats.cacheSize = storageStats.getCacheBytes();
-                                packageStats.codeSize = storageStats.getCodeBytes();
+                                packageStats.codeSize = storageStats.getAppBytes();
                                 packageStats.dataSize = storageStats.getDataBytes();
                                 stats.add(packageStats);
                             } catch (NameNotFoundException | IOException e) {
diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
index 0094ab5..1db3ec4 100644
--- a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
+++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java
@@ -56,10 +56,12 @@
     public static final String SYSTEM_KEY = "systemSize";
     public static final String MISC_KEY = "otherSize";
     public static final String APP_SIZE_AGG_KEY = "appSize";
+    public static final String APP_DATA_SIZE_AGG_KEY = "appDataSize";
     public static final String APP_CACHE_AGG_KEY = "cacheSize";
     public static final String PACKAGE_NAMES_KEY = "packageNames";
     public static final String APP_SIZES_KEY = "appSizes";
     public static final String APP_CACHES_KEY = "cacheSizes";
+    public static final String APP_DATA_KEY = "appDataSizes";
     public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
 
     private MeasurementResult mResult;
@@ -114,31 +116,39 @@
     private void addAppsToJson(JSONObject json) throws JSONException {
         JSONArray names = new JSONArray();
         JSONArray appSizeList = new JSONArray();
+        JSONArray appDataSizeList = new JSONArray();
         JSONArray cacheSizeList = new JSONArray();
 
         long appSizeSum = 0L;
+        long appDataSizeSum = 0L;
         long cacheSizeSum = 0L;
         boolean isExternal = Environment.isExternalStorageEmulated();
         for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
             PackageStats stat = entry.getValue();
-            long appSize = stat.codeSize + stat.dataSize;
+            long appSize = stat.codeSize;
+            long appDataSize = stat.dataSize;
             long cacheSize = stat.cacheSize;
             if (isExternal) {
-                appSize += stat.externalCodeSize + stat.externalDataSize;
+                appSize += stat.externalCodeSize;
+                appDataSize += stat.externalDataSize;
                 cacheSize += stat.externalCacheSize;
             }
             appSizeSum += appSize;
+            appDataSizeSum += appDataSize;
             cacheSizeSum += cacheSize;
 
             names.put(stat.packageName);
             appSizeList.put(appSize);
+            appDataSizeList.put(appDataSize);
             cacheSizeList.put(cacheSize);
         }
         json.put(PACKAGE_NAMES_KEY, names);
         json.put(APP_SIZES_KEY, appSizeList);
         json.put(APP_CACHES_KEY, cacheSizeList);
+        json.put(APP_DATA_KEY, appDataSizeList);
         json.put(APP_SIZE_AGG_KEY, appSizeSum);
         json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
+        json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum);
     }
 
     /**
diff --git a/services/core/java/com/android/server/timezone/IntentHelperImpl.java b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
index bc0f6e4..6e6259d 100644
--- a/services/core/java/com/android/server/timezone/IntentHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/IntentHelperImpl.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.PatternMatcher;
+import android.os.UserHandle;
 import android.util.Slog;
 
 /**
@@ -76,7 +77,9 @@
         // not expected to need local data.
 
         Receiver packageUpdateReceiver = new Receiver(packageTracker);
-        mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
+        mContext.registerReceiverAsUser(
+                packageUpdateReceiver, UserHandle.SYSTEM, packageIntentFilter,
+                null /* broadcastPermission */, null /* default handler */);
     }
 
     /** Sends an intent to trigger an update check. */
diff --git a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
index 2e0c21b..b89dd38 100644
--- a/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
+++ b/services/core/java/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -26,6 +26,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import java.util.List;
@@ -114,8 +115,8 @@
     @Override
     public boolean contentProviderRegistered(String authority, String requiredPackageName) {
         int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-        ProviderInfo providerInfo =
-                mPackageManager.resolveContentProvider(authority, flags);
+        ProviderInfo providerInfo = mPackageManager.resolveContentProviderAsUser(
+                authority, flags, UserHandle.SYSTEM.getIdentifier());
         if (providerInfo == null) {
             Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
                     + authority);
@@ -136,7 +137,8 @@
             throws PackageManager.NameNotFoundException {
 
         int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceiversAsUser(
+                intent, flags, UserHandle.SYSTEM);
         if (resolveInfo.size() != 1) {
             Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
                     + " intent=" + intent + ", found=" + resolveInfo);
diff --git a/services/core/java/com/android/server/timezone/RulesManagerService.java b/services/core/java/com/android/server/timezone/RulesManagerService.java
index 6824a59..52b49ba 100644
--- a/services/core/java/com/android/server/timezone/RulesManagerService.java
+++ b/services/core/java/com/android/server/timezone/RulesManagerService.java
@@ -47,6 +47,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import libcore.icu.ICU;
+import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
 import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
@@ -479,9 +480,10 @@
                         case 'a': {
                             // Report the active rules version (i.e. the rules in use by the current
                             // process).
-                            pw.println("Active rules version (ICU, libcore): "
+                            pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
                                     + ICU.getTZDataVersion() + ","
-                                    + ZoneInfoDB.getInstance().getVersion());
+                                    + ZoneInfoDB.getInstance().getVersion() + ","
+                                    + TimeZoneFinder.getInstance().getIanaVersion());
                             break;
                         }
                         default: {
@@ -494,8 +496,10 @@
         }
 
         pw.println("RulesManagerService state: " + toString());
-        pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
-                + ZoneInfoDB.getInstance().getVersion());
+        pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
+                + ICU.getTZDataVersion() + ","
+                + ZoneInfoDB.getInstance().getVersion() + ","
+                + TimeZoneFinder.getInstance().getIanaVersion());
         pw.println("Distro state: " + rulesState.toString());
         mPackageTracker.dump(pw);
     }
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
index 6117da7..c1607e9 100644
--- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java
+++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
@@ -1022,20 +1022,6 @@
             }
         }
 
-        @Override
-        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
-            synchronized (mImplLock) {
-                if (mReleased) {
-                    throw new IllegalStateException("Device already released.");
-                }
-            }
-            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
-                return false;
-            }
-            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
-            return false;
-        }
-
         private boolean startCapture(Surface surface, TvStreamConfig config) {
             synchronized (mImplLock) {
                 if (mReleased) {
diff --git a/services/core/java/com/android/server/utils/ManagedApplicationService.java b/services/core/java/com/android/server/utils/ManagedApplicationService.java
index 0f251fd..c555388 100644
--- a/services/core/java/com/android/server/utils/ManagedApplicationService.java
+++ b/services/core/java/com/android/server/utils/ManagedApplicationService.java
@@ -16,19 +16,24 @@
 package com.android.server.utils;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
 
+import java.text.SimpleDateFormat;
 import java.util.Objects;
+import java.util.Date;
 
 /**
  * Manages the lifecycle of an application-provided service bound from system server.
@@ -38,39 +43,126 @@
 public class ManagedApplicationService {
     private final String TAG = getClass().getSimpleName();
 
+    /**
+     * Attempt to reconnect service forever if an onBindingDied or onServiceDisconnected event
+     * is received.
+     */
+    public static final int RETRY_FOREVER = 1;
+
+    /**
+     * Never attempt to reconnect the service - a single onBindingDied or onServiceDisconnected
+     * event will cause this to fully unbind the service and never attempt to reconnect.
+     */
+    public static final int RETRY_NEVER = 2;
+
+    /**
+     * Attempt to reconnect the service until the maximum number of retries is reached, then stop.
+     *
+     * The first retry will occur MIN_RETRY_DURATION_MS after the disconnection, and each
+     * subsequent retry will occur after 2x the duration used for the previous retry up to the
+     * MAX_RETRY_DURATION_MS duration.
+     *
+     * In this case, retries mean a full unbindService/bindService pair to handle cases when the
+     * usual service re-connection logic in ActiveServices has very high backoff times or when the
+     * serviceconnection has fully died due to a package update or similar.
+     */
+    public static final int RETRY_BEST_EFFORT = 3;
+
+    // Maximum number of retries before giving up (for RETRY_BEST_EFFORT).
+    private static final int MAX_RETRY_COUNT = 4;
+    // Max time between retry attempts.
+    private static final long MAX_RETRY_DURATION_MS = 16000;
+    // Min time between retry attempts.
+    private static final long MIN_RETRY_DURATION_MS = 2000;
+    // Time since the last retry attempt after which to clear the retry attempt counter.
+    private static final long RETRY_RESET_TIME_MS = MAX_RETRY_DURATION_MS * 4;
+
     private final Context mContext;
     private final int mUserId;
     private final ComponentName mComponent;
     private final int mClientLabel;
     private final String mSettingsAction;
     private final BinderChecker mChecker;
-
-    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
-        @Override
-        public void binderDied() {
-            synchronized (mLock) {
-                mBoundInterface = null;
-            }
-        }
-    };
+    private final boolean mIsImportant;
+    private final int mRetryType;
+    private final Handler mHandler;
+    private final Runnable mRetryRunnable = this::doRetry;
+    private final EventCallback mEventCb;
 
     private final Object mLock = new Object();
 
     // State protected by mLock
-    private ServiceConnection mPendingConnection;
     private ServiceConnection mConnection;
     private IInterface mBoundInterface;
     private PendingEvent mPendingEvent;
+    private int mRetryCount;
+    private long mLastRetryTimeMs;
+    private long mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+    private boolean mRetrying;
+
+    public static interface LogFormattable {
+       String toLogString(SimpleDateFormat dateFormat);
+    }
+
+    /**
+     * Lifecycle event of this managed service.
+     */
+    public static class LogEvent implements LogFormattable {
+        public static final int EVENT_CONNECTED = 1;
+        public static final int EVENT_DISCONNECTED = 2;
+        public static final int EVENT_BINDING_DIED = 3;
+        public static final int EVENT_STOPPED_PERMANENTLY = 4;
+
+        // Time of the events in "current time ms" timebase.
+        public final long timestamp;
+        // Name of the component for this system service.
+        public final ComponentName component;
+        // ID of the event that occurred.
+        public final int event;
+
+        public LogEvent(long timestamp, ComponentName component, int event) {
+            this.timestamp = timestamp;
+            this.component = component;
+            this.event = event;
+        }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            return dateFormat.format(new Date(timestamp)) + "   " + eventToString(event)
+                    + " Managed Service: "
+                    + ((component == null) ? "None" : component.flattenToString());
+        }
+
+        public static String eventToString(int event) {
+            switch (event) {
+                case EVENT_CONNECTED:
+                    return "Connected";
+                case EVENT_DISCONNECTED:
+                    return "Disconnected";
+                case EVENT_BINDING_DIED:
+                    return "Binding Died For";
+                case EVENT_STOPPED_PERMANENTLY:
+                    return "Permanently Stopped";
+                default:
+                    return "Unknown Event Occurred";
+            }
+        }
+    }
 
     private ManagedApplicationService(final Context context, final ComponentName component,
             final int userId, int clientLabel, String settingsAction,
-            BinderChecker binderChecker) {
+            BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler,
+            EventCallback eventCallback) {
         mContext = context;
         mComponent = component;
         mUserId = userId;
         mClientLabel = clientLabel;
         mSettingsAction = settingsAction;
         mChecker = binderChecker;
+        mIsImportant = isImportant;
+        mRetryType = retryType;
+        mHandler = handler;
+        mEventCb = eventCallback;
     }
 
     /**
@@ -85,7 +177,17 @@
      * Implement to call IInterface methods after service is connected.
      */
     public interface PendingEvent {
-         void runEvent(IInterface service) throws RemoteException;
+        void runEvent(IInterface service) throws RemoteException;
+    }
+
+    /**
+     * Implement to be notified about any problems with remote service.
+     */
+    public interface EventCallback {
+        /**
+         * Called when an sevice lifecycle event occurs.
+         */
+        void onServiceEvent(LogEvent event);
     }
 
     /**
@@ -95,19 +197,28 @@
      * @param component the {@link ComponentName} of the application service to bind.
      * @param userId the user ID of user to bind the application service as.
      * @param clientLabel the resource ID of a label displayed to the user indicating the
-     *      binding service.
+     *      binding service, or 0 if none is desired.
      * @param settingsAction an action that can be used to open the Settings UI to enable/disable
-     *      binding to these services.
-     * @param binderChecker an interface used to validate the returned binder object.
+     *      binding to these services, or null if none is desired.
+     * @param binderChecker an interface used to validate the returned binder object, or null if
+     *      this interface is unchecked.
+     * @param isImportant bind the user service with BIND_IMPORTANT.
+     * @param retryType reconnect behavior to have when bound service is disconnected.
+     * @param handler the Handler to use for retries and delivering EventCallbacks.
+     * @param eventCallback a callback used to deliver disconnection events, or null if you
+     *      don't care.
      * @return a ManagedApplicationService instance.
      */
     public static ManagedApplicationService build(@NonNull final Context context,
-        @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
-        @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
+            @NonNull final ComponentName component, final int userId, int clientLabel,
+            @Nullable String settingsAction, @Nullable BinderChecker binderChecker,
+            boolean isImportant, int retryType, @NonNull Handler handler,
+            @Nullable EventCallback eventCallback) {
         return new ManagedApplicationService(context, component, userId, clientLabel,
-            settingsAction, binderChecker);
+            settingsAction, binderChecker, isImportant, retryType, handler, eventCallback);
     }
 
+
     /**
      * @return the user ID of the user that owns the bound service.
      */
@@ -138,13 +249,12 @@
         return true;
     }
 
-
-  /**
-   * Send an event to run as soon as the binder interface is available.
-   *
-   * @param event a {@link PendingEvent} to send.
-   */
-  public void sendEvent(@NonNull PendingEvent event) {
+    /**
+     * Send an event to run as soon as the binder interface is available.
+     *
+     * @param event a {@link PendingEvent} to send.
+     */
+    public void sendEvent(@NonNull PendingEvent event) {
         IInterface iface;
         synchronized (mLock) {
             iface = mBoundInterface;
@@ -167,15 +277,13 @@
      */
     public void disconnect() {
         synchronized (mLock) {
-            // Wipe out pending connections
-            mPendingConnection = null;
-
             // Unbind existing connection, if it exists
-            if (mConnection != null) {
-                mContext.unbindService(mConnection);
-                mConnection = null;
+            if (mConnection == null) {
+                return;
             }
 
+            mContext.unbindService(mConnection);
+            mConnection = null;
             mBoundInterface = null;
         }
     }
@@ -185,48 +293,70 @@
      */
     public void connect() {
         synchronized (mLock) {
-            if (mConnection != null || mPendingConnection != null) {
+            if (mConnection != null) {
                 // We're already connected or are trying to connect
                 return;
             }
 
-            final PendingIntent pendingIntent = PendingIntent.getActivity(
-                    mContext, 0, new Intent(mSettingsAction), 0);
-            final Intent intent = new Intent().setComponent(mComponent).
-                    putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
-                    putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+            Intent intent  = new Intent().setComponent(mComponent);
+            if (mClientLabel != 0) {
+                intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel);
+            }
+            if (mSettingsAction != null) {
+                intent.putExtra(Intent.EXTRA_CLIENT_INTENT,
+                        PendingIntent.getActivity(mContext, 0, new Intent(mSettingsAction), 0));
+            }
 
-            final ServiceConnection serviceConnection = new ServiceConnection() {
+            mConnection = new ServiceConnection() {
+                @Override
+                public void onBindingDied(ComponentName componentName) {
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.w(TAG, "Service binding died: " + componentName);
+                    synchronized (mLock) {
+                        if (mConnection != this) {
+                            return;
+                        }
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_BINDING_DIED));
+                        });
+
+                        mBoundInterface = null;
+                        startRetriesLocked();
+                    }
+                }
+
                 @Override
                 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.i(TAG, "Service connected: " + componentName);
                     IInterface iface = null;
                     PendingEvent pendingEvent = null;
                     synchronized (mLock) {
-                        if (mPendingConnection == this) {
-                            // No longer pending, remove from pending connection
-                            mPendingConnection = null;
-                            mConnection = this;
-                        } else {
-                            // Service connection wasn't pending, must have been disconnected
-                            mContext.unbindService(this);
+                        if (mConnection != this) {
+                            // Must've been unbound.
                             return;
                         }
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_CONNECTED));
+                        });
 
-                        try {
-                            iBinder.linkToDeath(mDeathRecipient, 0);
+                        stopRetriesLocked();
+
+                        mBoundInterface = null;
+                        if (mChecker != null) {
                             mBoundInterface = mChecker.asInterface(iBinder);
                             if (!mChecker.checkType(mBoundInterface)) {
-                                // Received an invalid binder, disconnect
-                                mContext.unbindService(this);
+                                // Received an invalid binder, disconnect.
                                 mBoundInterface = null;
+                                Slog.w(TAG, "Invalid binder from " + componentName);
+                                startRetriesLocked();
+                                return;
                             }
                             iface = mBoundInterface;
                             pendingEvent = mPendingEvent;
                             mPendingEvent = null;
-                        } catch (RemoteException e) {
-                            // DOA
-                            Slog.w(TAG, "Unable to bind service: " + intent, e);
-                            mBoundInterface = null;
                         }
                     }
                     if (iface != null && pendingEvent != null) {
@@ -234,28 +364,44 @@
                             pendingEvent.runEvent(iface);
                         } catch (RuntimeException | RemoteException ex) {
                             Slog.e(TAG, "Received exception from user service: ", ex);
+                            startRetriesLocked();
                         }
                     }
                 }
 
                 @Override
                 public void onServiceDisconnected(ComponentName componentName) {
-                    Slog.w(TAG, "Service disconnected: " + intent);
-                    mConnection = null;
-                    mBoundInterface = null;
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.w(TAG, "Service disconnected: " + componentName);
+                    synchronized (mLock) {
+                        if (mConnection != this) {
+                            return;
+                        }
+
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_DISCONNECTED));
+                        });
+
+                        mBoundInterface = null;
+                        startRetriesLocked();
+                    }
                 }
             };
 
-            mPendingConnection = serviceConnection;
-
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            if (mIsImportant) {
+                flags |= Context.BIND_IMPORTANT;
+            }
             try {
-                if (!mContext.bindServiceAsUser(intent, serviceConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                if (!mContext.bindServiceAsUser(intent, mConnection, flags,
                         new UserHandle(mUserId))) {
                     Slog.w(TAG, "Unable to bind service: " + intent);
+                    startRetriesLocked();
                 }
             } catch (SecurityException e) {
                 Slog.w(TAG, "Unable to bind service: " + intent, e);
+                startRetriesLocked();
             }
         }
     }
@@ -263,4 +409,81 @@
     private boolean matches(final ComponentName component, final int userId) {
         return Objects.equals(mComponent, component) && mUserId == userId;
     }
+
+    private void startRetriesLocked() {
+        if (checkAndDeliverServiceDiedCbLocked()) {
+            // If we delivered the service callback, disconnect and stop retrying.
+            disconnect();
+            return;
+        }
+
+        if (mRetrying) {
+            // Retry already queued, don't queue a new one.
+            return;
+        }
+        mRetrying = true;
+        queueRetryLocked();
+    }
+
+    private void stopRetriesLocked() {
+        mRetrying = false;
+        mHandler.removeCallbacks(mRetryRunnable);
+    }
+
+    private void queueRetryLocked() {
+        long now = SystemClock.uptimeMillis();
+        if ((now - mLastRetryTimeMs) > RETRY_RESET_TIME_MS) {
+            // It's been longer than the reset time since we last had to retry.  Re-initialize.
+            mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+            mRetryCount = 0;
+        }
+        mLastRetryTimeMs = now;
+        mHandler.postDelayed(mRetryRunnable, mNextRetryDurationMs);
+        mNextRetryDurationMs = Math.min(2 * mNextRetryDurationMs, MAX_RETRY_DURATION_MS);
+        mRetryCount++;
+    }
+
+    private boolean checkAndDeliverServiceDiedCbLocked() {
+
+       if (mRetryType == RETRY_NEVER || (mRetryType == RETRY_BEST_EFFORT
+                && mRetryCount >= MAX_RETRY_COUNT)) {
+            // If we never retry, or we've exhausted our retries, post the onServiceDied callback.
+            Slog.e(TAG, "Service " + mComponent + " has died too much, not retrying.");
+            if (mEventCb != null) {
+                final long timestamp = System.currentTimeMillis();
+                mHandler.post(() -> {
+                  mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                        LogEvent.EVENT_STOPPED_PERMANENTLY));
+                });
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void doRetry() {
+        synchronized (mLock) {
+            if (mConnection == null) {
+                // We disconnected for good.  Don't attempt to retry.
+                return;
+            }
+            if (!mRetrying) {
+                // We successfully connected.  Don't attempt to retry.
+                return;
+            }
+            Slog.i(TAG, "Attempting to reconnect " + mComponent + "...");
+            // While frameworks may restart the remote Service if we stay bound, we have little
+            // control of the backoff timing for reconnecting the service.  In the event of a
+            // process crash, the backoff time can be very large (1-30 min), which is not
+            // acceptable for the types of services this is used for.  Instead force an unbind/bind
+            // sequence to cause a more immediate retry.
+            disconnect();
+            if (checkAndDeliverServiceDiedCbLocked()) {
+                // No more retries.
+                return;
+            }
+            queueRetryLocked();
+            connect();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/utils/PriorityDump.java b/services/core/java/com/android/server/utils/PriorityDump.java
index 054f156..fb92c2b 100644
--- a/services/core/java/com/android/server/utils/PriorityDump.java
+++ b/services/core/java/com/android/server/utils/PriorityDump.java
@@ -16,12 +16,19 @@
 
 package com.android.server.utils;
 
+import android.annotation.IntDef;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 
 /**
  * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
- * {@link #PRIORITY_ARG} argument.
+ * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
  * <p>
  * Typical usage:
  *
@@ -31,13 +38,25 @@
  private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
 
      @Override
-     public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
-       pw.println("Donuts in the box: 1");
+     public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+       if (asProto) {
+         ProtoOutputStream proto = new ProtoOutputStream(fd);
+         proto.write(SpringfieldProto.DONUTS, 1);
+         proto.flush();
+       } else {
+         pw.println("Donuts in the box: 1");
+       }
      }
 
      @Override
      public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
-       pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+        if (asProto) {
+          ProtoOutputStream proto = new ProtoOutputStream(fd);
+          proto.write(SpringfieldProto.REACTOR_STATUS, DANGER_MELTDOWN_IMMINENT);
+          proto.flush();
+        } else {
+          pw.println("Nuclear reactor status: DANGER - MELTDOWN IMMINENT");
+        }
      }
   };
 
@@ -65,6 +84,9 @@
     $ adb shell dumpsys snpp --dump-priority NORMAL
     Nuclear reactor status: DANGER - MELTDOWN IMMINENT
 
+    $ adb shell dumpsys snpp --dump-priority CRITICAL --proto
+    //binary output
+
  * </code></pre>
  *
  *
@@ -85,95 +107,146 @@
 public final class PriorityDump {
 
     public static final String PRIORITY_ARG = "--dump-priority";
+    public static final String PROTO_ARG = "--proto";
 
     private PriorityDump() {
         throw new UnsupportedOperationException();
     }
 
+    /** Enum to switch through supported priority types */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({PRIORITY_TYPE_INVALID, PRIORITY_TYPE_CRITICAL, PRIORITY_TYPE_HIGH,
+            PRIORITY_TYPE_NORMAL})
+    private @interface PriorityType { }
+    private static final int PRIORITY_TYPE_INVALID = 0;
+    private static final int PRIORITY_TYPE_CRITICAL = 1;
+    private static final int PRIORITY_TYPE_HIGH = 2;
+    private static final int PRIORITY_TYPE_NORMAL = 3;
+
     /**
-     * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
-     * {@code --dump-priority}, stripping the priority and its type.
+     * Parses {@code args} matching {@code --dump-priority} and/or {@code --proto}. The matching
+     * arguments are stripped.
+     * <p>
+     * If priority args are passed as an argument, it will call the appropriate method and if proto
+     * args are passed then the {@code asProto} flag is set.
      * <p>
      * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
-     * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
+     * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}, false) </code>
      * <p>
      * If the {@code --dump-priority} is not set, it calls
-     * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
+     * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[], boolean)} passing the whole
      * {@code args} instead.
      */
     public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw,
             String[] args) {
-        if (args != null && args.length >= 2 && args[0].equals(PRIORITY_ARG)) {
-            final String priority = args[1];
-            switch (priority) {
-                case "CRITICAL": {
-                    dumper.dumpCritical(fd, pw, getStrippedArgs(args));
-                    return;
+        boolean asProto = false;
+        @PriorityType int priority = PRIORITY_TYPE_INVALID;
+
+        if (args == null) {
+            dumper.dump(fd, pw, args, asProto);
+            return;
+        }
+
+        String[] strippedArgs = new String[args.length];
+        int strippedCount = 0;
+        for (int argIndex = 0; argIndex < args.length; argIndex++) {
+            if (args[argIndex].equals(PROTO_ARG)) {
+                asProto = true;
+            } else if (args[argIndex].equals(PRIORITY_ARG)) {
+                if (argIndex + 1 < args.length) {
+                    argIndex++;
+                    priority = getPriorityType(args[argIndex]);
                 }
-                case "HIGH": {
-                    dumper.dumpHigh(fd, pw, getStrippedArgs(args));
-                    return;
-                }
-                case "NORMAL": {
-                    dumper.dumpNormal(fd, pw, getStrippedArgs(args));
-                    return;
-                }
+            } else {
+                strippedArgs[strippedCount++] = args[argIndex];
             }
         }
-        dumper.dump(fd, pw, args);
+
+        if (strippedCount < args.length) {
+            strippedArgs = Arrays.copyOf(strippedArgs, strippedCount);
+        }
+
+        switch (priority) {
+            case PRIORITY_TYPE_CRITICAL: {
+                dumper.dumpCritical(fd, pw, strippedArgs, asProto);
+                return;
+            }
+            case PRIORITY_TYPE_HIGH: {
+                dumper.dumpHigh(fd, pw, strippedArgs, asProto);
+                return;
+            }
+            case PRIORITY_TYPE_NORMAL: {
+                dumper.dumpNormal(fd, pw, strippedArgs, asProto);
+                return;
+            }
+            default: {
+                dumper.dump(fd, pw, strippedArgs, asProto);
+                return;
+            }
+        }
     }
 
     /**
-     * Gets an array without the {@code --dump-priority PRIORITY} prefix.
+     * Converts priority argument type to enum.
      */
-    private static String[] getStrippedArgs(String[] args) {
-        final String[] stripped = new String[args.length - 2];
-        System.arraycopy(args, 2, stripped, 0, stripped.length);
-        return stripped;
+    private static @PriorityType int getPriorityType(String arg) {
+        switch (arg) {
+            case "CRITICAL": {
+                return PRIORITY_TYPE_CRITICAL;
+            }
+            case "HIGH": {
+                return PRIORITY_TYPE_HIGH;
+            }
+            case "NORMAL": {
+                return PRIORITY_TYPE_NORMAL;
+            }
+        }
+        return PRIORITY_TYPE_INVALID;
     }
 
     /**
      * Helper for {@link android.os.Binder#dump(java.io.FileDescriptor, String[])} that supports the
-     * {@link #PRIORITY_ARG} argument.
+     * {@link #PRIORITY_ARG} and {@link #PROTO_ARG} arguments.
      *
      * @hide
      */
-    public static interface PriorityDumper {
+    public interface PriorityDumper {
 
         /**
          * Dumps only the critical section.
          */
         @SuppressWarnings("unused")
-        default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+        default void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
         }
 
         /**
          * Dumps only the high-priority section.
          */
         @SuppressWarnings("unused")
-        default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+        default void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
         }
 
         /**
          * Dumps only the normal section.
          */
         @SuppressWarnings("unused")
-        default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+        default void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
         }
 
         /**
          * Dumps all sections.
          * <p>
          * This method is called when
-         * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[])} is
-         * called without priority arguments. By default, it calls the 3 {@code dumpTYPE} methods,
-         * so sub-classes just need to implement the priority types they support.
+         * {@link PriorityDump#dump(PriorityDumper, FileDescriptor, PrintWriter, String[], boolean)}
+         * is called without priority arguments. By default, it calls the 3 {@code dumpTYPE}
+         * methods, so sub-classes just need to implement the priority types they support.
          */
         @SuppressWarnings("unused")
-        default void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            dumpCritical(fd, pw, args);
-            dumpHigh(fd, pw, args);
-            dumpNormal(fd, pw, args);
+        default void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            dumpCritical(fd, pw, args, asProto);
+            dumpHigh(fd, pw, args, asProto);
+            dumpNormal(fd, pw, args, asProto);
         }
     }
 }
diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java
index 8f50a39..95d03d4 100644
--- a/services/core/java/com/android/server/vr/Vr2dDisplay.java
+++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java
@@ -294,6 +294,9 @@
 
             int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
             mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
                     DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
                     null /* surface */, flags, null /* callback */, null /* handler */,
diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java
index bdd9de0..7b1e12e 100644
--- a/services/core/java/com/android/server/vr/VrManagerInternal.java
+++ b/services/core/java/com/android/server/vr/VrManagerInternal.java
@@ -74,6 +74,13 @@
     public abstract void onScreenStateChanged(boolean isScreenOn);
 
     /**
+     * Set whether the keyguard is currently active/showing.
+     *
+     * @param isShowing is {@code true} if the keyguard is active/showing.
+     */
+    public abstract void onKeyguardStateChanged(boolean isShowing);
+
+    /**
      * Return NO_ERROR if the given package is installed on the device and enabled as a
      * VrListenerService for the given current user, or a negative error code indicating a failure.
      *
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 1f0b2f0..e7e4efc 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -66,6 +66,8 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.utils.ManagedApplicationService.PendingEvent;
+import com.android.server.utils.ManagedApplicationService.LogEvent;
+import com.android.server.utils.ManagedApplicationService.LogFormattable;
 import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeListener;
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
@@ -108,16 +110,18 @@
     static final boolean DBG = false;
 
     private static final int PENDING_STATE_DELAY_MS = 300;
-    private static final int EVENT_LOG_SIZE = 32;
+    private static final int EVENT_LOG_SIZE = 64;
     private static final int INVALID_APPOPS_MODE = -1;
     /** Null set of sleep sleep flags. */
     private static final int FLAG_NONE = 0;
     /** Flag set when the device is not sleeping. */
-    private static final int FLAG_AWAKE = 1;
+    private static final int FLAG_AWAKE = 1 << 0;
     /** Flag set when the screen has been turned on. */
-    private static final int FLAG_SCREEN_ON = 2;
+    private static final int FLAG_SCREEN_ON = 1 << 1;
+    /** Flag set when the keyguard is not active. */
+    private static final int FLAG_KEYGUARD_UNLOCKED = 1 << 2;
     /** Flag indicating that all system sleep flags have been set.*/
-    private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON;
+    private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON | FLAG_KEYGUARD_UNLOCKED;
 
     private static native void initializeNative();
     private static native void setVrModeNative(boolean enabled);
@@ -134,6 +138,7 @@
     private int mVrAppProcessId;
     private EnabledComponentsObserver mComponentObserver;
     private ManagedApplicationService mCurrentVrService;
+    private ManagedApplicationService mCurrentVrCompositorService;
     private ComponentName mDefaultVrService;
     private Context mContext;
     private ComponentName mCurrentVrModeComponent;
@@ -147,19 +152,45 @@
     private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
     private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
     private VrState mPendingState;
-    private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
+    private boolean mLogLimitHit;
+    private final ArrayDeque<LogFormattable> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
     private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
     private INotificationManager mNotificationManager;
     /** Tracks the state of the screen and keyguard UI.*/
-    private int mSystemSleepFlags = FLAG_AWAKE;
+    private int mSystemSleepFlags = FLAG_AWAKE | FLAG_KEYGUARD_UNLOCKED;
     /**
      * Set when ACTION_USER_UNLOCKED is fired. We shouldn't try to bind to the
-     * vr service before then.
+     * vr service before then. This gets set only once the first time the user unlocks the device
+     * and stays true thereafter.
      */
     private boolean mUserUnlocked;
     private Vr2dDisplay mVr2dDisplay;
     private boolean mBootsToVr;
 
+    // Handles events from the managed services (e.g. VrListenerService and any bound VR compositor
+    // service).
+    private final ManagedApplicationService.EventCallback mEventCallback
+                = new ManagedApplicationService.EventCallback() {
+        @Override
+        public void onServiceEvent(LogEvent event) {
+            logEvent(event);
+
+            ComponentName component = null;
+            synchronized (mLock) {
+                component = ((mCurrentVrService == null) ? null : mCurrentVrService.getComponent());
+            }
+
+            // If not on an AIO device and we permanently stopped trying to connect to the
+            // VrListenerService (or don't have one bound), leave persistent VR mode and VR mode.
+            if (!mBootsToVr && event.event == LogEvent.EVENT_STOPPED_PERMANENTLY &&
+                    (component == null || component.equals(event.component))) {
+                Slog.e(TAG, "VrListenerSevice has died permanently, leaving system VR mode.");
+                // We're not a native VR device.  Leave VR + persistent mode.
+                setPersistentVrModeEnabled(false);
+            }
+        }
+    };
+
     private static final int MSG_VR_STATE_CHANGE = 0;
     private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
     private static final int MSG_PERSISTENT_VR_MODE_STATE_CHANGE = 2;
@@ -180,7 +211,6 @@
                 if (mBootsToVr) {
                     setPersistentVrModeEnabled(true);
                 }
-                consumeAndApplyPendingStateLocked();
                 if (mBootsToVr && !mVrModeEnabled) {
                   setVrMode(true, mDefaultVrService, 0, -1, null);
                 }
@@ -202,29 +232,40 @@
     }
 
     private void setSleepState(boolean isAsleep) {
-        synchronized(mLock) {
-
-            if (!isAsleep) {
-                mSystemSleepFlags |= FLAG_AWAKE;
-            } else {
-                mSystemSleepFlags &= ~FLAG_AWAKE;
-            }
-
-            updateVrModeAllowedLocked();
-        }
+        setSystemState(FLAG_AWAKE, !isAsleep);
     }
 
     private void setScreenOn(boolean isScreenOn) {
+        setSystemState(FLAG_SCREEN_ON, isScreenOn);
+    }
+
+    private void setKeyguardShowing(boolean isShowing) {
+        setSystemState(FLAG_KEYGUARD_UNLOCKED, !isShowing);
+    }
+
+    private void setSystemState(int flags, boolean isOn) {
         synchronized(mLock) {
-            if (isScreenOn) {
-                mSystemSleepFlags |= FLAG_SCREEN_ON;
+            int oldState = mSystemSleepFlags;
+            if (isOn) {
+                mSystemSleepFlags |= flags;
             } else {
-                mSystemSleepFlags &= ~FLAG_SCREEN_ON;
+                mSystemSleepFlags &= ~flags;
             }
-            updateVrModeAllowedLocked();
+            if (oldState != mSystemSleepFlags) {
+                if (DBG) Slog.d(TAG, "System state: " + getStateAsString());
+                updateVrModeAllowedLocked();
+            }
         }
     }
 
+    private String getStateAsString() {
+        return new StringBuilder()
+                .append((mSystemSleepFlags & FLAG_AWAKE) != 0 ? "awake, " : "")
+                .append((mSystemSleepFlags & FLAG_SCREEN_ON) != 0 ? "screen_on, " : "")
+                .append((mSystemSleepFlags & FLAG_KEYGUARD_UNLOCKED) != 0 ? "keyguard_off" : "")
+                .toString();
+    }
+
     private void setUserUnlocked() {
         synchronized(mLock) {
             mUserUnlocked = true;
@@ -276,7 +317,24 @@
         }
     };
 
-    private static class VrState {
+    // Event used to log when settings are changed for dumpsys logs.
+    private static class SettingEvent implements LogFormattable {
+        public final long timestamp;
+        public final String what;
+
+        SettingEvent(String what) {
+            this.timestamp = System.currentTimeMillis();
+            this.what = what;
+        }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            return dateFormat.format(new Date(timestamp)) + "   " + what;
+        }
+    }
+
+    // Event used to track changes of the primary on-screen VR activity.
+    private static class VrState implements LogFormattable {
         final boolean enabled;
         final boolean running2dInVr;
         final int userId;
@@ -286,7 +344,6 @@
         final long timestamp;
         final boolean defaultPermissionsGranted;
 
-
         VrState(boolean enabled, boolean running2dInVr, ComponentName targetPackageName, int userId,
                 int processId, ComponentName callingPackage) {
             this.enabled = enabled;
@@ -310,6 +367,39 @@
             this.defaultPermissionsGranted = defaultPermissionsGranted;
             this.timestamp = System.currentTimeMillis();
         }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            String tab = "  ";
+            String newLine = "\n";
+            StringBuilder sb = new StringBuilder(dateFormat.format(new Date(timestamp)));
+            sb.append(tab);
+            sb.append("State changed to:");
+            sb.append(tab);
+            sb.append((enabled) ? "ENABLED" : "DISABLED");
+            sb.append(newLine);
+            if (enabled) {
+                sb.append(tab);
+                sb.append("User=");
+                sb.append(userId);
+                sb.append(newLine);
+                sb.append(tab);
+                sb.append("Current VR Activity=");
+                sb.append((callingPackage == null) ? "None" : callingPackage.flattenToString());
+                sb.append(newLine);
+                sb.append(tab);
+                sb.append("Bound VrListenerService=");
+                sb.append((targetPackageName == null) ? "None"
+                        : targetPackageName.flattenToString());
+                sb.append(newLine);
+                if (defaultPermissionsGranted) {
+                    sb.append(tab);
+                    sb.append("Default permissions granted to the bound VrListenerService.");
+                    sb.append(newLine);
+                }
+            }
+            return sb.toString();
+        }
     }
 
     private static final BinderChecker sBinderChecker = new BinderChecker() {
@@ -490,6 +580,13 @@
         }
 
         @Override
+        public void setAndBindCompositor(String componentName) {
+            enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
+            VrManagerService.this.setAndBindCompositor(
+                (componentName == null) ? null : ComponentName.unflattenFromString(componentName));
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
@@ -497,6 +594,12 @@
             pw.println("VR mode is currently: " + ((mVrModeAllowed) ? "allowed" : "disallowed"));
             pw.println("Persistent VR mode is currently: " +
                     ((mPersistentVrModeEnabled) ? "enabled" : "disabled"));
+            pw.println("Currently bound VR listener service: "
+                    + ((mCurrentVrService == null)
+                    ? "None" : mCurrentVrService.getComponent().flattenToString()));
+            pw.println("Currently bound VR compositor service: "
+                    + ((mCurrentVrCompositorService == null)
+                    ? "None" : mCurrentVrCompositorService.getComponent().flattenToString()));
             pw.println("Previous state transitions:\n");
             String tab = "  ";
             dumpStateTransitions(pw);
@@ -582,6 +685,11 @@
         }
 
         @Override
+        public void onKeyguardStateChanged(boolean isShowing) {
+            VrManagerService.this.setKeyguardShowing(isShowing);
+        }
+
+        @Override
         public boolean isCurrentVrListener(String packageName, int userId) {
             return VrManagerService.this.isCurrentVrListener(packageName, userId);
         }
@@ -785,6 +893,7 @@
                         + mCurrentVrService.getComponent() + " for user "
                         + mCurrentVrService.getUserId());
                     mCurrentVrService.disconnect();
+                    updateCompositorServiceLocked(UserHandle.USER_NULL, null);
                     mCurrentVrService = null;
                 } else {
                     nothingChanged = true;
@@ -798,6 +907,7 @@
                         Slog.i(TAG, "VR mode component changed to " + component
                             + ", disconnecting " + mCurrentVrService.getComponent()
                             + " for user " + mCurrentVrService.getUserId());
+                        updateCompositorServiceLocked(UserHandle.USER_NULL, null);
                         createAndConnectService(component, userId);
                         sendUpdatedCaller = true;
                     } else {
@@ -985,7 +1095,7 @@
 
 
     private void createAndConnectService(@NonNull ComponentName component, int userId) {
-        mCurrentVrService = VrManagerService.create(mContext, component, userId);
+        mCurrentVrService = createVrListenerService(component, userId);
         mCurrentVrService.connect();
         Slog.i(TAG, "Connecting " + component + " for user " + userId);
     }
@@ -1020,13 +1130,27 @@
     }
 
     /**
-     * Helper function for making ManagedApplicationService instances.
+     * Helper function for making ManagedApplicationService for VrListenerService instances.
      */
-    private static ManagedApplicationService create(@NonNull Context context,
-            @NonNull ComponentName component, int userId) {
-        return ManagedApplicationService.build(context, component, userId,
+    private ManagedApplicationService createVrListenerService(@NonNull ComponentName component,
+            int userId) {
+        int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+                : ManagedApplicationService.RETRY_NEVER;
+        return ManagedApplicationService.build(mContext, component, userId,
                 R.string.vr_listener_binding_label, Settings.ACTION_VR_LISTENER_SETTINGS,
-                sBinderChecker);
+                sBinderChecker, /*isImportant*/true, retryType, mHandler, mEventCallback);
+    }
+
+    /**
+     * Helper function for making ManagedApplicationService for VR Compositor instances.
+     */
+    private ManagedApplicationService createVrCompositorService(@NonNull ComponentName component,
+            int userId) {
+        int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+                : ManagedApplicationService.RETRY_BEST_EFFORT;
+        return ManagedApplicationService.build(mContext, component, userId, /*clientLabel*/0,
+                /*settingsAction*/null, /*binderChecker*/null, /*isImportant*/true, retryType,
+                mHandler, /*disconnectCallback*/mEventCallback);
     }
 
     /**
@@ -1057,44 +1181,35 @@
 
     private void logStateLocked() {
         ComponentName currentBoundService = (mCurrentVrService == null) ? null :
-            mCurrentVrService.getComponent();
-        VrState current = new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
-            mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted);
-        if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
-            mLoggingDeque.removeFirst();
+                mCurrentVrService.getComponent();
+        logEvent(new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
+                mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted));
+    }
+
+    private void logEvent(LogFormattable event) {
+        synchronized (mLoggingDeque) {
+            if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
+                mLoggingDeque.removeFirst();
+                mLogLimitHit = true;
+            }
+            mLoggingDeque.add(event);
         }
-        mLoggingDeque.add(current);
     }
 
     private void dumpStateTransitions(PrintWriter pw) {
         SimpleDateFormat d = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-        String tab = "  ";
-        if (mLoggingDeque.size() == 0) {
-            pw.print(tab);
-            pw.println("None");
-        }
-        for (VrState state : mLoggingDeque) {
-            pw.print(d.format(new Date(state.timestamp)));
-            pw.print(tab);
-            pw.print("State changed to:");
-            pw.print(tab);
-            pw.println((state.enabled) ? "ENABLED" : "DISABLED");
-            if (state.enabled) {
-                pw.print(tab);
-                pw.print("User=");
-                pw.println(state.userId);
-                pw.print(tab);
-                pw.print("Current VR Activity=");
-                pw.println((state.callingPackage == null) ?
-                    "None" : state.callingPackage.flattenToString());
-                pw.print(tab);
-                pw.print("Bound VrListenerService=");
-                pw.println((state.targetPackageName == null) ?
-                    "None" : state.targetPackageName.flattenToString());
-                if (state.defaultPermissionsGranted) {
-                    pw.print(tab);
-                    pw.println("Default permissions granted to the bound VrListenerService.");
-                }
+        synchronized (mLoggingDeque) {
+            if (mLoggingDeque.size() == 0) {
+                pw.print("  ");
+                pw.println("None");
+            }
+
+            if (mLogLimitHit) {
+                pw.println("..."); // Indicates log overflow
+            }
+
+            for (LogFormattable event : mLoggingDeque) {
+                pw.println(event.toLogString(d));
             }
         }
     }
@@ -1177,10 +1292,41 @@
         return INVALID_DISPLAY;
     }
 
+    private void setAndBindCompositor(ComponentName componentName) {
+        final int userId = UserHandle.getCallingUserId();
+        final long token = Binder.clearCallingIdentity();
+        synchronized (mLock) {
+            updateCompositorServiceLocked(userId, componentName);
+        }
+        Binder.restoreCallingIdentity(token);
+    }
+
+    private void updateCompositorServiceLocked(int userId, ComponentName componentName) {
+        if (mCurrentVrCompositorService != null
+                && mCurrentVrCompositorService.disconnectIfNotMatching(componentName, userId)) {
+            Slog.i(TAG, "Disconnecting compositor service: "
+                    + mCurrentVrCompositorService.getComponent());
+            // Check if existing service matches the requested one, if not (or if the requested
+            // component is null) disconnect it.
+            mCurrentVrCompositorService = null;
+        }
+
+        if (componentName != null && mCurrentVrCompositorService == null) {
+            // We don't have an existing service matching the requested component, so attempt to
+            // connect one.
+            Slog.i(TAG, "Connecting compositor service: " + componentName);
+            mCurrentVrCompositorService = createVrCompositorService(componentName, userId);
+            mCurrentVrCompositorService.connect();
+        }
+    }
+
     private void setPersistentModeAndNotifyListenersLocked(boolean enabled) {
         if (mPersistentVrModeEnabled == enabled) {
             return;
         }
+        String eventName = "Persistent VR mode " + ((enabled) ? "enabled" : "disabled");
+        Slog.i(TAG, eventName);
+        logEvent(new SettingEvent(eventName));
         mPersistentVrModeEnabled = enabled;
 
         mHandler.sendMessage(mHandler.obtainMessage(MSG_PERSISTENT_VR_MODE_STATE_CHANGE,
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 6484a13..de4fd7cd 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -74,12 +74,12 @@
  */
 final class AccessibilityController {
 
-    private final WindowManagerService mWindowManagerService;
+    private final WindowManagerService mService;
 
     private static final float[] sTempFloats = new float[9];
 
     public AccessibilityController(WindowManagerService service) {
-        mWindowManagerService = service;
+        mService = service;
     }
 
     private DisplayMagnifier mDisplayMagnifier;
@@ -91,7 +91,7 @@
             if (mDisplayMagnifier != null) {
                 throw new IllegalStateException("Magnification callbacks already set!");
             }
-            mDisplayMagnifier = new DisplayMagnifier(mWindowManagerService, callbacks);
+            mDisplayMagnifier = new DisplayMagnifier(mService, callbacks);
         } else {
             if  (mDisplayMagnifier == null) {
                 throw new IllegalStateException("Magnification callbacks already cleared!");
@@ -108,7 +108,7 @@
                         "Windows for accessibility callback already set!");
             }
             mWindowsForAccessibilityObserver = new WindowsForAccessibilityObserver(
-                    mWindowManagerService, callback);
+                    mService, callback);
         } else {
             if (mWindowsForAccessibilityObserver == null) {
                 throw new IllegalStateException(
@@ -120,7 +120,7 @@
 
     public void performComputeChangedWindowsNotLocked() {
         WindowsForAccessibilityObserver observer = null;
-        synchronized (mWindowManagerService) {
+        synchronized (mService) {
             observer = mWindowsForAccessibilityObserver;
         }
         if (observer != null) {
@@ -188,7 +188,7 @@
         // Not relevant for the display magnifier.
 
         WindowsForAccessibilityObserver observer = null;
-        synchronized (mWindowManagerService) {
+        synchronized (mService) {
             observer = mWindowsForAccessibilityObserver;
         }
         if (observer != null) {
@@ -268,7 +268,7 @@
         private final Region mTempRegion4 = new Region();
 
         private final Context mContext;
-        private final WindowManagerService mWindowManagerService;
+        private final WindowManagerService mService;
         private final MagnifiedViewport mMagnifedViewport;
         private final Handler mHandler;
 
@@ -281,9 +281,9 @@
         public DisplayMagnifier(WindowManagerService windowManagerService,
                 MagnificationCallbacks callbacks) {
             mContext = windowManagerService.mContext;
-            mWindowManagerService = windowManagerService;
+            mService = windowManagerService;
             mCallbacks = callbacks;
-            mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mHandler = new MyHandler(mService.mH.getLooper());
             mMagnifedViewport = new MagnifiedViewport();
             mLongAnimationDuration = mContext.getResources().getInteger(
                     com.android.internal.R.integer.config_longAnimTime);
@@ -292,7 +292,7 @@
         public void setMagnificationSpecLocked(MagnificationSpec spec) {
             mMagnifedViewport.updateMagnificationSpecLocked(spec);
             mMagnifedViewport.recomputeBoundsLocked();
-            mWindowManagerService.scheduleAnimationLocked();
+            mService.scheduleAnimationLocked();
         }
 
         public void setForceShowMagnifiableBoundsLocked(boolean show) {
@@ -330,7 +330,7 @@
                 Slog.i(LOG_TAG, "Layers changed.");
             }
             mMagnifedViewport.recomputeBoundsLocked();
-            mWindowManagerService.scheduleAnimationLocked();
+            mService.scheduleAnimationLocked();
         }
 
         public void onRotationChangedLocked(DisplayContent displayContent) {
@@ -421,7 +421,7 @@
         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
             if (spec != null && !spec.isNop()) {
-                if (!mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
                     return null;
                 }
             }
@@ -565,7 +565,7 @@
                     portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
                     windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
 
-                    if (mWindowManagerService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                    if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
                         mMagnificationRegion.op(windowBounds, Region.Op.UNION);
                         mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
                     } else {
@@ -632,7 +632,7 @@
                 if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
                     setMagnifiedRegionBorderShownLocked(false, false);
                     final long delay = (long) (mLongAnimationDuration
-                            * mWindowManagerService.getWindowAnimationScaleLocked());
+                            * mService.getWindowAnimationScaleLocked());
                     Message message = mHandler.obtainMessage(
                             MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
                     mHandler.sendMessageDelayed(message, delay);
@@ -675,7 +675,7 @@
             }
 
             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
-                final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+                final DisplayContent dc = mService.getDefaultDisplayContentLocked();
                 dc.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisibleLw()
                             && !w.mWinAnimator.mEnterAnimationPending) {
@@ -705,23 +705,25 @@
                     SurfaceControl surfaceControl = null;
                     try {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
-                        surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession,
-                                SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT,
-                                SurfaceControl.HIDDEN);
+                        surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                                .setName(SURFACE_TITLE)
+                                .setSize(mTempPoint.x, mTempPoint.y) // not a typo
+                                .setFormat(PixelFormat.TRANSLUCENT)
+                                .build();
                     } catch (OutOfResourcesException oore) {
                         /* ignore */
                     }
                     mSurfaceControl = surfaceControl;
                     mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
                             .getLayerStack());
-                    mSurfaceControl.setLayer(mWindowManagerService.mPolicy.getWindowLayerFromTypeLw(
+                    mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
                             TYPE_MAGNIFICATION_OVERLAY)
                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
                     mSurfaceControl.setPosition(0, 0);
                     mSurface.copyFrom(mSurfaceControl);
 
                     mAnimationController = new AnimationController(context,
-                            mWindowManagerService.mH.getLooper());
+                            mService.mH.getLooper());
 
                     TypedValue typedValue = new TypedValue();
                     context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
@@ -736,7 +738,7 @@
                 }
 
                 public void setShown(boolean shown, boolean animate) {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (mShown == shown) {
                             return;
                         }
@@ -751,13 +753,13 @@
                 @SuppressWarnings("unused")
                 // Called reflectively from an animator.
                 public int getAlpha() {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         return mAlpha;
                     }
                 }
 
                 public void setAlpha(int alpha) {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (mAlpha == alpha) {
                             return;
                         }
@@ -770,7 +772,7 @@
                 }
 
                 public void setBounds(Region bounds) {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (mBounds.equals(bounds)) {
                             return;
                         }
@@ -783,7 +785,7 @@
                 }
 
                 public void updateSize() {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
                         mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
                         invalidate(mDirtyRect);
@@ -797,12 +799,12 @@
                         mDirtyRect.setEmpty();
                     }
                     mInvalidated = true;
-                    mWindowManagerService.scheduleAnimationLocked();
+                    mService.scheduleAnimationLocked();
                 }
 
                 /** NOTE: This has to be called within a surface transaction. */
                 public void drawIfNeeded() {
-                    synchronized (mWindowManagerService.mWindowMap) {
+                    synchronized (mService.mWindowMap) {
                         if (!mInvalidated) {
                             return;
                         }
@@ -950,11 +952,11 @@
                     } break;
 
                     case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
-                        synchronized (mWindowManagerService.mWindowMap) {
+                        synchronized (mService.mWindowMap) {
                             if (mMagnifedViewport.isMagnifyingLocked()
                                     || isForceShowingMagnifiableBoundsLocked()) {
                                 mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
-                                mWindowManagerService.scheduleAnimationLocked();
+                                mService.scheduleAnimationLocked();
                             }
                         }
                     } break;
@@ -995,7 +997,7 @@
 
         private final Context mContext;
 
-        private final WindowManagerService mWindowManagerService;
+        private final WindowManagerService mService;
 
         private final Handler mHandler;
 
@@ -1006,9 +1008,9 @@
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 WindowsForAccessibilityCallback callback) {
             mContext = windowManagerService.mContext;
-            mWindowManagerService = windowManagerService;
+            mService = windowManagerService;
             mCallback = callback;
-            mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+            mHandler = new MyHandler(mService.mH.getLooper());
             mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
                     .getSendRecurringAccessibilityEventsInterval();
             computeChangedWindows();
@@ -1034,11 +1036,11 @@
             boolean windowsChanged = false;
             List<WindowInfo> windows = new ArrayList<WindowInfo>();
 
-            synchronized (mWindowManagerService.mWindowMap) {
+            synchronized (mService.mWindowMap) {
                 // Do not send the windows if there is no current focus as
                 // the window manager is still looking for where to put it.
                 // We will do the work when we get a focus change callback.
-                if (mWindowManagerService.mCurrentFocus == null) {
+                if (mService.mCurrentFocus == null) {
                     return;
                 }
 
@@ -1320,7 +1322,7 @@
         }
 
         private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
-            final DisplayContent dc = mWindowManagerService.getDefaultDisplayContentLocked();
+            final DisplayContent dc = mService.getDefaultDisplayContentLocked();
             dc.forAllWindows((w) -> {
                 if (w.isVisibleLw()) {
                     outWindows.put(w.mLayer, w);
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index c76b905..2ef7f25 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -16,10 +16,10 @@
 
 package com.android.server.wm;
 
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -78,11 +78,8 @@
     // requires that the duration of the two animations are the same.
     SurfaceControl thumbnail;
     int thumbnailTransactionSeq;
-    // TODO(b/62029108): combine both members into a private one. Create a member function to set
-    // the thumbnail layer to +1 to the highest layer position and replace all setter instances
-    // with this function. Remove all unnecessary calls to both variables in other classes.
-    int thumbnailLayer;
-    int thumbnailForceAboveLayer;
+    private int mThumbnailLayer;
+
     Animation thumbnailAnimation;
     final Transformation thumbnailTransformation = new Transformation();
     // This flag indicates that the destruction of the thumbnail surface is synchronized with
@@ -256,7 +253,7 @@
 
     private void updateLayers() {
         mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-        thumbnailLayer = mAppToken.getHighestAnimLayer();
+        updateThumbnailLayer();
     }
 
     private void stepThumbnailAnimation(long currentTime) {
@@ -280,26 +277,33 @@
         thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
                 "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
-                + " layer=" + thumbnailLayer
+                + " layer=" + mThumbnailLayer
                 + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
                 + "," + tmpFloats[Matrix.MSKEW_Y]
                 + "][" + tmpFloats[Matrix.MSKEW_X]
                 + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
         thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        if (thumbnailForceAboveLayer > 0) {
-            thumbnail.setLayer(thumbnailForceAboveLayer + 1);
-        } else {
-            // The thumbnail is layered below the window immediately above this
-            // token's anim layer.
-            thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-        }
+        updateThumbnailLayer();
         thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
         thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
     }
 
     /**
+     * Updates the thumbnail layer z order to just above the highest animation layer if changed
+     */
+    void updateThumbnailLayer() {
+        if (thumbnail != null) {
+            final int layer = mAppToken.getHighestAnimLayer();
+            if (DEBUG_LAYERS) Slog.v(TAG,
+                    "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
+            thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+            mThumbnailLayer = layer;
+        }
+    }
+
+    /**
      * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
      * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
      * and keep producing the first frame of the animation.
@@ -473,7 +477,7 @@
         }
         if (thumbnail != null) {
             pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
-                    pw.print(" layer="); pw.println(thumbnailLayer);
+                    pw.print(" layer="); pw.println(mThumbnailLayer);
             pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
             pw.print(prefix); pw.print("thumbnailTransformation=");
                     pw.println(thumbnailTransformation.toShortString());
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 2d1fc91..e873d32 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -16,8 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -824,7 +823,7 @@
 
         // For freeform windows, we can't freeze the bounds at the moment because this would make
         // the resizing unresponsive.
-        if (task == null || task.inFreeformWorkspace()) {
+        if (task == null || task.inFreeformWindowingMode()) {
             return false;
         }
 
@@ -1171,7 +1170,6 @@
                 wAppAnimator.thumbnail.destroy();
             }
             wAppAnimator.thumbnail = tAppAnimator.thumbnail;
-            wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
             wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
             tAppAnimator.thumbnail = null;
         }
@@ -1312,7 +1310,7 @@
 
                 // Notify the pinned stack upon all windows drawn. If there was an animation in
                 // progress then this signal will resume that animation.
-                final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
                 if (pinnedStack != null) {
                     pinnedStack.onAllWindowsDrawn();
                 }
@@ -1623,10 +1621,10 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
         writeNameToProto(proto, NAME);
-        super.writeToProto(proto, WINDOW_TOKEN);
+        super.writeToProto(proto, WINDOW_TOKEN, trim);
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
index 5c29a0a..9729e50 100644
--- a/services/core/java/com/android/server/wm/BlackFrame.java
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -18,7 +18,6 @@
 
 import static android.graphics.PixelFormat.OPAQUE;
 import static android.view.SurfaceControl.FX_SURFACE_DIM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -51,14 +50,11 @@
             int w = r-l;
             int h = b-t;
 
-            if (DEBUG_SURFACE_TRACE) {
-                surface = new WindowSurfaceController.SurfaceTrace(session, "BlackSurface("
-                        + l + ", " + t + ")",
-                        w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            } else {
-                surface = new SurfaceControl(session, "BlackSurface",
-                        w, h, OPAQUE, FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            }
+            surface = new SurfaceControl.Builder(session)
+                    .setName("BlackSurface")
+                    .setSize(w, h)
+                    .setColorLayer(true)
+                    .build();
 
             surface.setAlpha(1);
             surface.setLayerStack(layerStack);
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index ae41541..2d5d1b2 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -67,14 +66,12 @@
 
         SurfaceControl ctrl = null;
         try {
-            if (DEBUG_SURFACE_TRACE) {
-                ctrl = new WindowSurfaceController.SurfaceTrace(session, "CircularDisplayMask",
-                        mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
-                        SurfaceControl.HIDDEN);
-            } else {
-                ctrl = new SurfaceControl(session, "CircularDisplayMask", mScreenSize.x,
-                        mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-            }
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("CircularDisplayMask")
+                    .setSize(mScreenSize.x, mScreenSize.y) // not a typo
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
+
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 1da94da..cc94807 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -21,9 +21,14 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static com.android.server.wm.proto.ConfigurationContainerProto.FULL_CONFIGURATION;
 import static com.android.server.wm.proto.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
 import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
@@ -33,6 +38,7 @@
 import android.content.res.Configuration;
 import android.util.proto.ProtoOutputStream;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -44,6 +50,9 @@
     /** Contains override configuration settings applied to this configuration container. */
     private Configuration mOverrideConfiguration = new Configuration();
 
+    /** True if mOverrideConfiguration is not empty */
+    private boolean mHasOverrideConfiguration;
+
     /**
      * Contains full configuration applied to this configuration container. Corresponds to full
      * parent's config with applied {@link #mOverrideConfiguration}.
@@ -95,6 +104,9 @@
      * @see #mFullConfiguration
      */
     public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        // Pre-compute this here, so we don't need to go through the entire Configuration when
+        // writing to proto (which has significant cost if we write a lot of empty configurations).
+        mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
         mOverrideConfiguration.setTo(overrideConfiguration);
         // Update full configuration of this container and all its children.
         final ConfigurationContainer parent = getParent();
@@ -152,6 +164,17 @@
         onOverrideConfigurationChanged(mTmpConfig);
     }
 
+    /**
+     * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
+     * with another activity.
+     */
+    public boolean inMultiWindowMode() {
+        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
+                mFullConfiguration.windowConfiguration.getWindowingMode();
+        return windowingMode != WINDOWING_MODE_FULLSCREEN
+                && windowingMode != WINDOWING_MODE_UNDEFINED;
+    }
+
     /** Returns true if this container is currently in split-screen windowing mode. */
     public boolean inSplitScreenWindowingMode() {
         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
@@ -169,16 +192,29 @@
         return windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
     }
 
+    public boolean inSplitScreenPrimaryWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode()
+                == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+    }
+
     /**
      * Returns true if this container can be put in either
      * {@link WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
      * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
      * its current state.
      */
-    public boolean supportSplitScreenWindowingMode() {
+    public boolean supportsSplitScreenWindowingMode() {
         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
     }
 
+    public boolean inPinnedWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
+    }
+
+    public boolean inFreeformWindowingMode() {
+        return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+    }
+
     /** Returns the activity type associated with the the configuration container. */
     /*@WindowConfiguration.ActivityType*/
     public int getActivityType() {
@@ -225,9 +261,48 @@
         /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
         /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
 
-        return thisType == otherType
-                || thisType == ACTIVITY_TYPE_UNDEFINED
-                || otherType == ACTIVITY_TYPE_UNDEFINED;
+        if (thisType == otherType) {
+            return true;
+        }
+        if (thisType == ACTIVITY_TYPE_ASSISTANT) {
+            // Assistant activities are only compatible with themselves...
+            return false;
+        }
+        // Otherwise we are compatible if us or other is not currently defined.
+        return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Returns true if this container is compatible with the input windowing mode and activity type.
+     * The container is compatible:
+     * - If {@param activityType} and {@param windowingMode} match this container activity type and
+     * windowing mode.
+     * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
+     * standard or undefined and its windowing mode matches {@param windowingMode}.
+     * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
+     * also standard or undefined and its activity type matches {@param activityType} regardless of
+     * if {@param windowingMode} matches the containers windowing mode.
+     */
+    public boolean isCompatible(int windowingMode, int activityType) {
+        final int thisActivityType = getActivityType();
+        final int thisWindowingMode = getWindowingMode();
+        final boolean sameActivityType = thisActivityType == activityType;
+        final boolean sameWindowingMode = thisWindowingMode == windowingMode;
+
+        if (sameActivityType && sameWindowingMode) {
+            return true;
+        }
+
+        if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
+                || !isActivityTypeStandardOrUndefined()) {
+            // Only activity type need to match for non-standard activity types that are defined.
+            return sameActivityType;
+        }
+
+        // Otherwise we are compatible if the windowing mode is the same.
+        return sameWindowingMode;
     }
 
     public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
@@ -261,18 +336,43 @@
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.ConfigurationContainerProto}.
      *
-     * @param protoOutputStream Stream to write the ConfigurationContainer object to.
-     * @param fieldId           Field Id of the ConfigurationContainer as defined in the parent
-     *                          message.
+     * @param proto    Stream to write the ConfigurationContainer object to.
+     * @param fieldId  Field Id of the ConfigurationContainer as defined in the parent
+     *                 message.
+     * @param trim     If true, reduce amount of data written.
      * @hide
      */
     @CallSuper
-    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
-        final long token = protoOutputStream.start(fieldId);
-        mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION);
-        mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION);
-        mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION);
-        protoOutputStream.end(token);
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
+        final long token = proto.start(fieldId);
+        if (!trim || mHasOverrideConfiguration) {
+            mOverrideConfiguration.writeToProto(proto, OVERRIDE_CONFIGURATION);
+        }
+        if (!trim) {
+            mFullConfiguration.writeToProto(proto, FULL_CONFIGURATION);
+            mMergedOverrideConfiguration.writeToProto(proto, MERGED_OVERRIDE_CONFIGURATION);
+        }
+        proto.end(token);
+    }
+
+    /**
+     * Dumps the names of this container children in the input print writer indenting each
+     * level with the input prefix.
+     */
+    public void dumpChildrenNames(PrintWriter pw, String prefix) {
+        final String childPrefix = prefix + " ";
+        pw.println(getName()
+                + " type=" + activityTypeToString(getActivityType())
+                + " mode=" + windowingModeToString(getWindowingMode()));
+        for (int i = getChildCount() - 1; i >= 0; --i) {
+            final E cc = getChildAt(i);
+            pw.print(childPrefix + "#" + i + " ");
+            cc.dumpChildrenNames(pw, childPrefix);
+        }
+    }
+
+    String getName() {
+        return toString();
     }
 
     abstract protected int getChildCount();
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 708973d..401547e 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -105,16 +104,12 @@
     private void constructSurface(WindowManagerService service) {
         service.openSurfaceTransaction();
         try {
-            if (DEBUG_SURFACE_TRACE) {
-                mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession,
-                    "DimSurface",
-                    16, 16, PixelFormat.OPAQUE,
-                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            } else {
-                mDimSurface = new SurfaceControl(service.mFxSession, mName,
-                    16, 16, PixelFormat.OPAQUE,
-                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
-            }
+            mDimSurface = new SurfaceControl.Builder(service.mFxSession)
+                    .setName(mName)
+                    .setSize(16, 16)
+                    .setColorLayer(true)
+                    .build();
+
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
                     "  DIM " + mDimSurface + ": CREATE");
             mDimSurface.setLayerStack(mDisplayId);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 98a1bd3..4c6ab3f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -16,11 +16,12 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -116,8 +117,7 @@
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
-import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
+import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -296,10 +296,6 @@
     /** Window tokens that are in the process of exiting, but still on screen for animations. */
     final ArrayList<WindowToken> mExitingTokens = new ArrayList<>();
 
-    /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack
-     * (except a future lockscreen TaskStack) moves to the top. */
-    private TaskStack mHomeStack = null;
-
     /** Detect user tapping outside of current focused task bounds .*/
     TaskTapPointerEventListener mTapDetector;
 
@@ -391,10 +387,6 @@
                     != mTmpWindowAnimator.mAnimTransactionSequence) {
                 appAnimator.thumbnailTransactionSeq =
                         mTmpWindowAnimator.mAnimTransactionSequence;
-                appAnimator.thumbnailLayer = 0;
-            }
-            if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
-                appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
             }
         }
     };
@@ -968,10 +960,10 @@
         final int lastOrientation = mLastOrientation;
         final boolean oldAltOrientation = mAltOrientation;
         int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
-        final boolean rotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
+        boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
                 rotation);
 
-        if (rotateSeamlessly) {
+        if (mayRotateSeamlessly) {
             final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
             if (seamlessRotated != null) {
                 // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
@@ -980,7 +972,20 @@
                 // window-removal.
                 return false;
             }
+
+            // In the presence of the PINNED stack or System Alert
+            // windows we unfortunately can not seamlessly rotate.
+            if (hasPinnedStack()) {
+                mayRotateSeamlessly = false;
+            }
+            for (int i = 0; i < mService.mSessions.size(); i++) {
+                if (mService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
+                    mayRotateSeamlessly = false;
+                    break;
+                }
+            }
         }
+        final boolean rotateSeamlessly = mayRotateSeamlessly;
 
         // TODO: Implement forced rotation changes.
         //       Set mAltOrientation to indicate that the application is receiving
@@ -1441,36 +1446,56 @@
     }
 
     TaskStack getHomeStack() {
-        if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
-            Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
-        }
-        return mHomeStack;
+        return mTaskStackContainers.getHomeStack();
     }
 
-    TaskStack getStackById(int stackId) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.mStackId == stackId) {
-                return stack;
-            }
-        }
-        return null;
+    /**
+     * @return The primary split-screen stack, but only if it is visible, and {@code null} otherwise.
+     */
+    TaskStack getSplitScreenPrimaryStackStack() {
+        TaskStack stack = mTaskStackContainers.getSplitScreenPrimaryStackStack();
+        return (stack != null && stack.isVisible()) ? stack : null;
+    }
+
+    /**
+     * Like {@link #getSplitScreenPrimaryStackStack}, but also returns the stack if it's currently
+     * not visible.
+     */
+    TaskStack getSplitScreenPrimaryStackStackIgnoringVisibility() {
+        return mTaskStackContainers.getSplitScreenPrimaryStackStack();
+    }
+
+    TaskStack getPinnedStack() {
+        return mTaskStackContainers.getPinnedStack();
+    }
+
+    private boolean hasPinnedStack() {
+        return mTaskStackContainers.getPinnedStack() != null;
+    }
+
+    /**
+     * Returns the topmost stack on the display that is compatible with the input windowing mode.
+     * Null is no compatible stack on the display.
+     */
+    TaskStack getStack(int windowingMode) {
+        return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    /**
+     * Returns the topmost stack on the display that is compatible with the input windowing mode and
+     * activity type. Null is no compatible stack on the display.
+     */
+    TaskStack getStack(int windowingMode, int activityType) {
+        return mTaskStackContainers.getStack(windowingMode, activityType);
     }
 
     @VisibleForTesting
-    int getStackCount() {
-        return mTaskStackContainers.size();
+    TaskStack getTopStack() {
+        return mTaskStackContainers.getTopStack();
     }
 
-    @VisibleForTesting
-    int getStackPosById(int stackId) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.mStackId == stackId) {
-                return i;
-            }
-        }
-        return -1;
+    void onStackWindowingModeChanged(TaskStack stack) {
+        mTaskStackContainers.onStackWindowingModeChanged(stack);
     }
 
     @Override
@@ -1491,8 +1516,8 @@
      * bounds were updated.
      */
     void updateStackBoundsAfterConfigChange(@NonNull List<Integer> changedStackList) {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack.updateBoundsAfterConfigChange()) {
                 changedStackList.add(stack.mStackId);
             }
@@ -1501,7 +1526,7 @@
         // If there was no pinned stack, we still need to notify the controller of the display info
         // update as a result of the config change.  We do this here to consolidate the flow between
         // changes when there is and is not a stack.
-        if (getStackById(PINNED_STACK_ID) == null) {
+        if (!hasPinnedStack()) {
             mPinnedStackControllerLocked.onDisplayInfoChanged();
         }
     }
@@ -1600,8 +1625,8 @@
         mDisplay.getDisplayInfo(mDisplayInfo);
         mDisplay.getMetrics(mDisplayMetrics);
 
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            mTaskStackContainers.get(i).updateDisplayInfo(null);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            mTaskStackContainers.getChildAt(i).updateDisplayInfo(null);
         }
     }
 
@@ -1722,26 +1747,14 @@
         out.set(mContentRect);
     }
 
-    TaskStack addStackToDisplay(int stackId, boolean onTop, StackWindowController controller) {
+    TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) {
         if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
                 + mDisplayId);
 
-        TaskStack stack = getStackById(stackId);
-        if (stack != null) {
-            // It's already attached to the display...clear mDeferRemoval, set controller, and move
-            // stack to appropriate z-order on display as needed.
-            stack.mDeferRemoval = false;
-            stack.setController(controller);
-            // We're not moving the display to front when we're adding stacks, only when
-            // requested to change the position of stack explicitly.
-            mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
-                    false /* includingParents */);
-        } else {
-            stack = new TaskStack(mService, stackId, controller);
-            mTaskStackContainers.addStackToDisplay(stack, onTop);
-        }
+        final TaskStack stack = new TaskStack(mService, stackId, controller);
+        mTaskStackContainers.addStackToDisplay(stack, onTop);
 
-        if (stackId == DOCKED_STACK_ID) {
+        if (stack.inSplitScreenPrimaryWindowingMode()) {
             mDividerControllerLocked.notifyDockedStackExistsChanged(true);
         }
         return stack;
@@ -1758,7 +1771,7 @@
                     + " to its current displayId=" + mDisplayId);
         }
 
-        prevDc.mTaskStackContainers.removeStackFromDisplay(stack);
+        prevDc.mTaskStackContainers.removeChild(stack);
         mTaskStackContainers.addStackToDisplay(stack, onTop);
     }
 
@@ -1792,8 +1805,8 @@
     }
 
     int taskIdFromPoint(int x, int y) {
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             final int taskId = stack.taskIdFromPoint(x, y);
             if (taskId != -1) {
                 return taskId;
@@ -1809,8 +1822,8 @@
     Task findTaskForResizePoint(int x, int y) {
         final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
         mTmpTaskForResizePointSearchResult.reset();
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             if (!stack.getWindowConfiguration().canResizeTask()) {
                 return null;
             }
@@ -1832,8 +1845,8 @@
             mTouchExcludeRegion.set(mBaseDisplayRect);
             final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
             mTmpRect2.setEmpty();
-            for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-                final TaskStack stack = mTaskStackContainers.get(stackNdx);
+            for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
                 stack.setTouchExcludeRegion(
                         focusedTask, delta, mTouchExcludeRegion, mContentRect, mTmpRect2);
             }
@@ -1864,7 +1877,7 @@
             mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION);
         }
         // TODO(multi-display): Support docked stacks on secondary displays.
-        if (mDisplayId == DEFAULT_DISPLAY && getDockedStackLocked() != null) {
+        if (mDisplayId == DEFAULT_DISPLAY && getSplitScreenPrimaryStackStack() != null) {
             mDividerControllerLocked.getTouchRegion(mTmpRect);
             mTmpRegion.set(mTmpRect);
             mTouchExcludeRegion.op(mTmpRegion, Op.UNION);
@@ -1881,8 +1894,8 @@
     }
 
     private void resetAnimationBackgroundAnimator() {
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            mTaskStackContainers.get(stackNdx).resetAnimationBackgroundAnimator();
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            mTaskStackContainers.getChildAt(stackNdx).resetAnimationBackgroundAnimator();
         }
     }
 
@@ -1953,8 +1966,8 @@
             float dividerAnimationTarget) {
         boolean updated = false;
 
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack == null || !stack.isAdjustedForIme()) {
                 continue;
             }
@@ -1982,8 +1995,8 @@
 
     boolean clearImeAdjustAnimation() {
         boolean changed = false;
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack != null && stack.isAdjustedForIme()) {
                 stack.resetAdjustedForIme(true /* adjustBoundsNow */);
                 changed  = true;
@@ -1993,8 +2006,8 @@
     }
 
     void beginImeAdjustAnimation() {
-        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-            final TaskStack stack = mTaskStackContainers.get(i);
+        for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(i);
             if (stack.isVisible() && stack.isAdjustedForIme()) {
                 stack.beginImeAdjustAnimation();
             }
@@ -2005,7 +2018,7 @@
         final WindowState imeWin = mService.mInputMethodWindow;
         final boolean imeVisible = imeWin != null && imeWin.isVisibleLw() && imeWin.isDisplayedLw()
                 && !mDividerControllerLocked.isImeHideRequested();
-        final boolean dockVisible = isStackVisible(DOCKED_STACK_ID);
+        final boolean dockVisible = isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final TaskStack imeTargetStack = mService.getImeFocusStackLocked();
         final int imeDockSide = (dockVisible && imeTargetStack != null) ?
                 imeTargetStack.getDockSide() : DOCKED_INVALID;
@@ -2023,11 +2036,11 @@
         // - If IME is not visible, divider is not moved and is normal width.
 
         if (imeVisible && dockVisible && (imeOnTop || imeOnBottom) && !dockMinimized) {
-            for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-                final TaskStack stack = mTaskStackContainers.get(i);
+            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(i);
                 final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
-                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) &&
-                        StackId.isStackAffectedByDragResizing(stack.mStackId)) {
+                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
+                        && stack.inSplitScreenWindowingMode()) {
                     stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
                 } else {
                     stack.resetAdjustedForIme(false);
@@ -2036,8 +2049,8 @@
             mDividerControllerLocked.setAdjustedForIme(
                     imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
         } else {
-            for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
-                final TaskStack stack = mTaskStackContainers.get(i);
+            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(i);
                 stack.resetAdjustedForIme(!dockVisible);
             }
             mDividerControllerLocked.setAdjustedForIme(
@@ -2068,8 +2081,8 @@
     }
 
     void prepareFreezingTaskBounds() {
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             stack.prepareFreezingTaskBounds();
         }
     }
@@ -2124,27 +2137,27 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(ID, mDisplayId);
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
-            stack.writeToProto(proto, STACKS);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
+            stack.writeToProto(proto, STACKS, trim);
         }
         mDividerControllerLocked.writeToProto(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
         mPinnedStackControllerLocked.writeToProto(proto, PINNED_STACK_CONTROLLER);
-        for (int i = mAboveAppWindowsContainers.size() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mAboveAppWindowsContainers.get(i);
-            windowToken.writeToProto(proto, ABOVE_APP_WINDOWS);
+        for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
+            windowToken.writeToProto(proto, ABOVE_APP_WINDOWS, trim);
         }
-        for (int i = mBelowAppWindowsContainers.size() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mBelowAppWindowsContainers.get(i);
-            windowToken.writeToProto(proto, BELOW_APP_WINDOWS);
+        for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
+            windowToken.writeToProto(proto, BELOW_APP_WINDOWS, trim);
         }
-        for (int i = mImeWindowsContainers.size() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mImeWindowsContainers.get(i);
-            windowToken.writeToProto(proto, IME_WINDOWS);
+        for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
+            final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
+            windowToken.writeToProto(proto, IME_WINDOWS, trim);
         }
         proto.write(DPI, mBaseDisplayDensity);
         mDisplayInfo.writeToProto(proto, DISPLAY_INFO);
@@ -2189,8 +2202,8 @@
 
         pw.println();
         pw.println(prefix + "Application tokens in top down Z order:");
-        for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
-            final TaskStack stack = mTaskStackContainers.get(stackNdx);
+        for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mTaskStackContainers.getChildAt(stackNdx);
             stack.dump(prefix + "  ", pw);
         }
 
@@ -2209,6 +2222,22 @@
         pw.println();
         mDimLayerController.dump(prefix, pw);
         pw.println();
+
+        // Dump stack references
+        final TaskStack homeStack = getHomeStack();
+        if (homeStack != null) {
+            pw.println(prefix + "homeStack=" + homeStack.getName());
+        }
+        final TaskStack pinnedStack = getPinnedStack();
+        if (pinnedStack != null) {
+            pw.println(prefix + "pinnedStack=" + pinnedStack.getName());
+        }
+        final TaskStack splitScreenPrimaryStack = getSplitScreenPrimaryStackStack();
+        if (splitScreenPrimaryStack != null) {
+            pw.println(prefix + "splitScreenPrimaryStack=" + splitScreenPrimaryStack.getName());
+        }
+
+        pw.println();
         mDividerControllerLocked.dump(prefix, pw);
         pw.println();
         mPinnedStackControllerLocked.dump(prefix, pw);
@@ -2228,26 +2257,10 @@
         return "Display " + mDisplayId + " name=\"" + mDisplayInfo.name + "\"";
     }
 
-    /** Checks if stack with provided id is visible on this display. */
-    boolean isStackVisible(int stackId) {
-        final TaskStack stack = getStackById(stackId);
-        return (stack != null && stack.isVisible());
-    }
-
-    /**
-     * @return The docked stack, but only if it is visible, and {@code null} otherwise.
-     */
-    TaskStack getDockedStackLocked() {
-        final TaskStack stack = getStackById(DOCKED_STACK_ID);
-        return (stack != null && stack.isVisible()) ? stack : null;
-    }
-
-    /**
-     * Like {@link #getDockedStackLocked}, but also returns the docked stack if it's currently not
-     * visible.
-     */
-    TaskStack getDockedStackIgnoringVisibility() {
-        return getStackById(DOCKED_STACK_ID);
+    /** Returns true if the stack in the windowing mode is visible. */
+    boolean isStackVisible(int windowingMode) {
+        final TaskStack stack = getStack(windowingMode);
+        return stack != null && stack.isVisible();
     }
 
     /** Find the visible, touch-deliverable window under the given point */
@@ -3326,14 +3339,6 @@
      */
     static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {
 
-        int size() {
-            return mChildren.size();
-        }
-
-        E get(int index) {
-            return mChildren.get(index);
-        }
-
         @Override
         boolean fillsParent() {
             return true;
@@ -3351,25 +3356,108 @@
      */
     private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
 
+        // Cached reference to some special stacks we tend to get a lot so we don't need to loop
+        // through the list to find them.
+        private TaskStack mHomeStack = null;
+        private TaskStack mPinnedStack = null;
+        private TaskStack mSplitScreenPrimaryStack = null;
+
+        /**
+         * Returns the topmost stack on the display that is compatible with the input windowing mode
+         * and activity type. Null is no compatible stack on the display.
+         */
+        TaskStack getStack(int windowingMode, int activityType) {
+            if (activityType == ACTIVITY_TYPE_HOME) {
+                return mHomeStack;
+            }
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                return mPinnedStack;
+            } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                return mSplitScreenPrimaryStack;
+            }
+            for (int i = mTaskStackContainers.getChildCount() - 1; i >= 0; --i) {
+                final TaskStack stack = mTaskStackContainers.getChildAt(i);
+                if (stack.isCompatible(windowingMode, activityType)) {
+                    return stack;
+                }
+            }
+            return null;
+        }
+
+        @VisibleForTesting
+        TaskStack getTopStack() {
+            return mTaskStackContainers.getChildCount() > 0
+                    ? mTaskStackContainers.getChildAt(mTaskStackContainers.getChildCount() - 1) : null;
+        }
+
+        TaskStack getHomeStack() {
+            if (mHomeStack == null && mDisplayId == DEFAULT_DISPLAY) {
+                Slog.e(TAG_WM, "getHomeStack: Returning null from this=" + this);
+            }
+            return mHomeStack;
+        }
+
+        TaskStack getPinnedStack() {
+            return mPinnedStack;
+        }
+
+        TaskStack getSplitScreenPrimaryStackStack() {
+            return mSplitScreenPrimaryStack;
+        }
+
         /**
          * Adds the stack to this container.
-         * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+         * @see DisplayContent#createStack(int, boolean, StackWindowController)
          */
         void addStackToDisplay(TaskStack stack, boolean onTop) {
-            if (stack.mStackId == HOME_STACK_ID) {
-                if (mHomeStack != null) {
-                    throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
-                }
-                mHomeStack = stack;
-            }
+            addStackReferenceIfNeeded(stack);
             addChild(stack, onTop);
             stack.onDisplayChanged(DisplayContent.this);
         }
 
-        /** Removes the stack from its container and prepare for changing the parent. */
-        void removeStackFromDisplay(TaskStack stack) {
-            removeChild(stack);
-            stack.onRemovedFromDisplay();
+        void onStackWindowingModeChanged(TaskStack stack) {
+            removeStackReferenceIfNeeded(stack);
+            addStackReferenceIfNeeded(stack);
+            if (stack == mPinnedStack && getTopStack() != stack) {
+                // Looks like this stack changed windowing mode to pinned. Move it to the top.
+                positionChildAt(POSITION_TOP, stack, false /* includingParents */);
+            }
+        }
+
+        private void addStackReferenceIfNeeded(TaskStack stack) {
+            if (stack.isActivityTypeHome()) {
+                if (mHomeStack != null) {
+                    throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+                            + mHomeStack + " already exist on display=" + this + " stack=" + stack);
+                }
+                mHomeStack = stack;
+            }
+            final int windowingMode = stack.getWindowingMode();
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                if (mPinnedStack != null) {
+                    throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
+                            + mPinnedStack + " already exist on display=" + this
+                            + " stack=" + stack);
+                }
+                mPinnedStack = stack;
+            } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                if (mSplitScreenPrimaryStack != null) {
+                    throw new IllegalArgumentException("addStackReferenceIfNeeded:"
+                            + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
+                            + " already exist on display=" + this + " stack=" + stack);
+                }
+                mSplitScreenPrimaryStack = stack;
+            }
+        }
+
+        private void removeStackReferenceIfNeeded(TaskStack stack) {
+            if (stack == mHomeStack) {
+                mHomeStack = null;
+            } else if (stack == mPinnedStack) {
+                mPinnedStack = null;
+            } else if (stack == mSplitScreenPrimaryStack) {
+                mSplitScreenPrimaryStack = null;
+            }
         }
 
         private void addChild(TaskStack stack, boolean toTop) {
@@ -3379,6 +3467,11 @@
             setLayoutNeeded();
         }
 
+        @Override
+        protected void removeChild(TaskStack stack) {
+            super.removeChild(stack);
+            removeStackReferenceIfNeeded(stack);
+        }
 
         @Override
         boolean isOnTop() {
@@ -3421,11 +3514,10 @@
                     : requestedPosition >= topChildPosition;
             int targetPosition = requestedPosition;
 
-            if (toTop && stack.mStackId != PINNED_STACK_ID
-                    && getStackById(PINNED_STACK_ID) != null) {
+            if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED && hasPinnedStack()) {
                 // The pinned stack is always the top most stack (always-on-top) when it is present.
                 TaskStack topStack = mChildren.get(topChildPosition);
-                if (topStack.mStackId != PINNED_STACK_ID) {
+                if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
                     throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
                 }
 
@@ -3524,7 +3616,8 @@
 
         @Override
         int getOrientation() {
-            if (isStackVisible(DOCKED_STACK_ID) || isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+            if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
+                    || isStackVisible(WINDOWING_MODE_FREEFORM)) {
                 // Apps and their containers are not allowed to specify an orientation while the
                 // docked or freeform stack is visible...except for the home stack/task if the
                 // docked stack is minimized and it actually set something.
@@ -3539,6 +3632,16 @@
             }
 
             final int orientation = super.getOrientation();
+            boolean isCar = mService.mContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_AUTOMOTIVE);
+            if (isCar) {
+                // In a car, you cannot physically rotate the screen, so it doesn't make sense to
+                // allow anything but the default orientation.
+                if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
+                        "Forcing UNSPECIFIED orientation in car. Ignoring " + orientation);
+                return SCREEN_ORIENTATION_UNSPECIFIED;
+            }
+
             if (orientation != SCREEN_ORIENTATION_UNSET
                     && orientation != SCREEN_ORIENTATION_BEHIND) {
                 if (DEBUG_ORIENTATION) Slog.v(TAG_WM,
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 030b986..5ce4d46 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -16,9 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Surface.ROTATION_270;
@@ -138,6 +137,8 @@
     float mLastDividerProgress;
     private final DividerSnapAlgorithm[] mSnapAlgorithmForRotation = new DividerSnapAlgorithm[4];
     private boolean mImeHideRequested;
+    private final Rect mLastDimLayerRect = new Rect();
+    private float mLastDimLayerAlpha;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
@@ -320,7 +321,7 @@
         if (mWindow == null) {
             return;
         }
-        TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
 
         // If the stack is invisible, we policy force hide it in WindowAnimator.shouldForceHide
         final boolean visible = stack != null;
@@ -330,7 +331,7 @@
         mLastVisibility = visible;
         notifyDockedDividerVisibilityChanged(visible);
         if (!visible) {
-            setResizeDimLayer(false, INVALID_STACK_ID, 0f);
+            setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
         }
     }
 
@@ -360,7 +361,7 @@
     }
 
     void positionDockedStackedDivider(Rect frame) {
-        TaskStack stack = mDisplayContent.getDockedStackLocked();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStack();
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
@@ -456,7 +457,8 @@
             boolean isHomeStackResizable) {
         long animDuration = 0;
         if (animate) {
-            final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+            final TaskStack stack =
+                    mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
             final long transitionDuration = isAnimationMaximizing()
                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -510,31 +512,57 @@
     void registerDockedStackListener(IDockedStackListener listener) {
         mDockedStackListeners.register(listener);
         notifyDockedDividerVisibilityChanged(wasVisible());
-        notifyDockedStackExistsChanged(mDisplayContent.getDockedStackIgnoringVisibility() != null);
+        notifyDockedStackExistsChanged(
+                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null);
         notifyDockedStackMinimizedChanged(mMinimizedDock, false /* animate */,
                 isHomeStackResizable());
         notifyAdjustedForImeChanged(mAdjustedForIme, 0 /* animDuration */);
 
     }
 
-    void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
-        mService.openSurfaceTransaction();
-        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
-        final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
+    /**
+     * Shows a dim layer with {@param alpha} if {@param visible} is true and
+     * {@param targetWindowingMode} isn't
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the
+     * display in that windowing mode.
+     */
+    void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
+        // TODO: Maybe only allow split-screen windowing modes?
+        final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
+                ? mDisplayContent.getStack(targetWindowingMode)
+                : null;
+        final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStackStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
             stack.getDimBounds(mTmpRect);
             if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
-                mDimLayer.setBounds(mTmpRect);
-                mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+                if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
+                    try {
+                        // TODO: This should use the regular animation transaction - here and below
+                        mService.openSurfaceTransaction();
+                        mDimLayer.setBounds(mTmpRect);
+                        mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
+                    } finally {
+                        mService.closeSurfaceTransaction();
+                    }
+                }
+                mLastDimLayerRect.set(mTmpRect);
+                mLastDimLayerAlpha = alpha;
             } else {
                 visibleAndValid = false;
             }
         }
         if (!visibleAndValid) {
-            mDimLayer.hide();
+            if (mLastDimLayerAlpha != 0f) {
+                try {
+                    mService.openSurfaceTransaction();
+                    mDimLayer.hide();
+                } finally {
+                    mService.closeSurfaceTransaction();
+                }
+            }
+            mLastDimLayerAlpha = 0f;
         }
-        mService.closeSurfaceTransaction();
     }
 
     /**
@@ -576,7 +604,7 @@
     private boolean containsAppInDockedStack(ArraySet<AppWindowToken> apps) {
         for (int i = apps.size() - 1; i >= 0; i--) {
             final AppWindowToken token = apps.valueAt(i);
-            if (token.getTask() != null && token.getTask().mStack.mStackId == DOCKED_STACK_ID) {
+            if (token.getTask() != null && token.inSplitScreenPrimaryWindowingMode()) {
                 return true;
             }
         }
@@ -588,7 +616,7 @@
     }
 
     private void checkMinimizeChanged(boolean animate) {
-        if (mDisplayContent.getDockedStackIgnoringVisibility() == null) {
+        if (mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() == null) {
             return;
         }
         final TaskStack homeStack = mDisplayContent.getHomeStack();
@@ -605,8 +633,8 @@
         if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
             return;
         }
-        final TaskStack fullscreenStack =
-                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
+        final TaskStack fullscreenStack = mDisplayContent.getStack(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
         final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
         setMinimizedDockedStack(homeVisible && !homeBehind, animate);
@@ -750,7 +778,7 @@
     }
 
     private boolean setMinimizedDockedStack(boolean minimized) {
-        final TaskStack stack = mDisplayContent.getDockedStackIgnoringVisibility();
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
         notifyDockedStackMinimizedChanged(minimized, false /* animate */, isHomeStackResizable());
         return stack != null && stack.setAdjustedForMinimizedDock(minimized ? 1f : 0f);
     }
@@ -801,7 +829,7 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+        final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
new file mode 100644
index 0000000..860ff38
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -0,0 +1,356 @@
+/*
+ * 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.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.content.ClipData;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Slog;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+
+/**
+ * Managing drag and drop operations initiated by View#startDragAndDrop.
+ */
+class DragDropController {
+    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+    private static final long DRAG_TIMEOUT_MS = 5000;
+
+    // Messages for Handler.
+    private static final int MSG_DRAG_START_TIMEOUT = 0;
+    static final int MSG_DRAG_END_TIMEOUT = 1;
+    static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
+    static final int MSG_ANIMATION_END = 3;
+
+    DragState mDragState;
+
+    private WindowManagerService mService;
+    private final Handler mHandler;
+
+    boolean dragDropActiveLocked() {
+        return mDragState != null;
+    }
+
+    DragDropController(WindowManagerService service, Looper looper) {
+        mService = service;
+        mHandler = new DragHandler(service, looper);
+    }
+
+    IBinder prepareDrag(SurfaceSession session, int callerPid,
+            int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
+                    + " flags=" + Integer.toHexString(flags) + " win=" + window
+                    + " asbinder=" + window.asBinder());
+        }
+
+        IBinder token = null;
+
+        synchronized (mService.mWindowMap) {
+            if (dragDropActiveLocked()) {
+                Slog.w(TAG_WM, "Drag already in progress");
+                return null;
+            }
+
+            // TODO(multi-display): support other displays
+            final DisplayContent displayContent =
+                    mService.getDefaultDisplayContentLocked();
+            final Display display = displayContent.getDisplay();
+
+            final SurfaceControl surface = new SurfaceControl.Builder(session)
+                    .setName("drag surface")
+                    .setSize(width, height)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
+            surface.setLayerStack(display.getLayerStack());
+            float alpha = 1;
+            if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
+                alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
+            }
+            surface.setAlpha(alpha);
+
+            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG " + surface + ": CREATE");
+            outSurface.copyFrom(surface);
+            final IBinder winBinder = window.asBinder();
+            token = new Binder();
+            mDragState = new DragState(mService, token, surface, flags, winBinder);
+            mDragState.mPid = callerPid;
+            mDragState.mUid = callerUid;
+            mDragState.mOriginalAlpha = alpha;
+            token = mDragState.mToken = new Binder();
+
+            // 5 second timeout for this window to actually begin the drag
+            sendTimeoutMessage(MSG_DRAG_START_TIMEOUT, winBinder);
+        }
+
+        return token;
+    }
+
+    boolean performDrag(IWindow window, IBinder dragToken,
+            int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+            ClipData data) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
+        }
+
+        synchronized (mService.mWindowMap) {
+            if (mDragState == null) {
+                Slog.w(TAG_WM, "No drag prepared");
+                throw new IllegalStateException("performDrag() without prepareDrag()");
+            }
+
+            if (dragToken != mDragState.mToken) {
+                Slog.w(TAG_WM, "Performing mismatched drag");
+                throw new IllegalStateException("performDrag() does not match prepareDrag()");
+            }
+
+            final WindowState callingWin = mService.windowForClientLocked(null, window, false);
+            if (callingWin == null) {
+                Slog.w(TAG_WM, "Bad requesting window " + window);
+                return false;  // !!! TODO: throw here?
+            }
+
+            // !!! TODO: if input is not still focused on the initiating window, fail
+            // the drag initiation (e.g. an alarm window popped up just as the application
+            // called performDrag()
+
+            mHandler.removeMessages(MSG_DRAG_START_TIMEOUT, window.asBinder());
+
+            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
+            // will let us eliminate the (touchX,touchY) parameters from the API.
+
+            // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as
+            // the actual drag event dispatch stuff in the dragstate
+
+            final DisplayContent displayContent = callingWin.getDisplayContent();
+            if (displayContent == null) {
+                return false;
+            }
+            Display display = displayContent.getDisplay();
+            mDragState.register(display);
+            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                    mDragState.getInputChannel())) {
+                Slog.e(TAG_WM, "Unable to transfer touch focus");
+                mDragState.unregister();
+                mDragState.reset();
+                mDragState = null;
+                return false;
+            }
+
+            mDragState.mDisplayContent = displayContent;
+            mDragState.mData = data;
+            mDragState.broadcastDragStartedLocked(touchX, touchY);
+            mDragState.overridePointerIconLocked(touchSource);
+
+            // remember the thumb offsets for later
+            mDragState.mThumbOffsetX = thumbCenterX;
+            mDragState.mThumbOffsetY = thumbCenterY;
+
+            // Make the surface visible at the proper location
+            final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                    TAG_WM, ">>> OPEN TRANSACTION performDrag");
+            mService.openSurfaceTransaction();
+            try {
+                surfaceControl.setPosition(touchX - thumbCenterX,
+                        touchY - thumbCenterY);
+                surfaceControl.setLayer(mDragState.getDragLayerLocked());
+                surfaceControl.setLayerStack(display.getLayerStack());
+                surfaceControl.show();
+            } finally {
+                mService.closeSurfaceTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                        TAG_WM, "<<< CLOSE TRANSACTION performDrag");
+            }
+
+            mDragState.notifyLocationLocked(touchX, touchY);
+        }
+
+        return true;    // success!
+    }
+
+    void reportDropResult(IWindow window, boolean consumed) {
+        IBinder token = window.asBinder();
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
+        }
+
+        synchronized (mService.mWindowMap) {
+            if (mDragState == null) {
+                // Most likely the drop recipient ANRed and we ended the drag
+                // out from under it.  Log the issue and move on.
+                Slog.w(TAG_WM, "Drop result given but no drag in progress");
+                return;
+            }
+
+            if (mDragState.mToken != token) {
+                // We're in a drag, but the wrong window has responded.
+                Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
+                throw new IllegalStateException("reportDropResult() by non-recipient");
+            }
+
+            // The right window has responded, even if it's no longer around,
+            // so be sure to halt the timeout even if the later WindowState
+            // lookup fails.
+            mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+            WindowState callingWin = mService.windowForClientLocked(null, window, false);
+            if (callingWin == null) {
+                Slog.w(TAG_WM, "Bad result-reporting window " + window);
+                return;  // !!! TODO: throw here?
+            }
+
+            mDragState.mDragResult = consumed;
+            mDragState.endDragLocked();
+        }
+    }
+
+    void cancelDragAndDrop(IBinder dragToken) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "cancelDragAndDrop");
+        }
+
+        synchronized (mService.mWindowMap) {
+            if (mDragState == null) {
+                Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
+                throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+            }
+
+            if (mDragState.mToken != dragToken) {
+                Slog.w(TAG_WM,
+                        "cancelDragAndDrop() does not match prepareDrag()");
+                throw new IllegalStateException(
+                        "cancelDragAndDrop() does not match prepareDrag()");
+            }
+
+            mDragState.mDragResult = false;
+            mDragState.cancelDragLocked();
+        }
+    }
+
+    void dragRecipientEntered(IWindow window) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
+        }
+    }
+
+    void dragRecipientExited(IWindow window) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
+        }
+    }
+
+    /**
+     * Sends a message to the Handler managed by DragDropController.
+     */
+    void sendHandlerMessage(int what, Object arg) {
+        mHandler.obtainMessage(what, arg).sendToTarget();
+    }
+
+    /**
+     * Sends a timeout message to the Handler managed by DragDropController.
+     */
+    void sendTimeoutMessage(int what, Object arg) {
+        mHandler.removeMessages(what, arg);
+        final Message msg = mHandler.obtainMessage(what, arg);
+        mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
+    }
+
+    private class DragHandler extends Handler {
+        /**
+         * Lock for window manager.
+         */
+        private final WindowManagerService mService;
+
+        DragHandler(WindowManagerService service, Looper looper) {
+            super(looper);
+            mService = service;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DRAG_START_TIMEOUT: {
+                    IBinder win = (IBinder) msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG_WM, "Timeout starting drag by win " + win);
+                    }
+                    synchronized (mService.mWindowMap) {
+                        // !!! TODO: ANR the app that has failed to start the drag in time
+                        if (mDragState != null) {
+                            mDragState.unregister();
+                            mDragState.reset();
+                            mDragState = null;
+                        }
+                    }
+                    break;
+                }
+
+                case MSG_DRAG_END_TIMEOUT: {
+                    IBinder win = (IBinder) msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG_WM, "Timeout ending drag to win " + win);
+                    }
+                    synchronized (mService.mWindowMap) {
+                        // !!! TODO: ANR the drag-receiving app
+                        if (mDragState != null) {
+                            mDragState.mDragResult = false;
+                            mDragState.endDragLocked();
+                        }
+                    }
+                    break;
+                }
+
+                case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: {
+                    if (DEBUG_DRAG)
+                        Slog.d(TAG_WM, "Drag ending; tearing down input channel");
+                    DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
+                    if (interceptor != null) {
+                        synchronized (mService.mWindowMap) {
+                            interceptor.tearDown();
+                        }
+                    }
+                    break;
+                }
+
+                case MSG_ANIMATION_END: {
+                    synchronized (mService.mWindowMap) {
+                        if (mDragState == null) {
+                            Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " +
+                                    "plyaing animation");
+                            return;
+                        }
+                        mDragState.onAnimationEndLocked();
+                    }
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DragInputEventReceiver.java b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
new file mode 100644
index 0000000..b4bbc90
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
@@ -0,0 +1,151 @@
+/*
+ * 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.server.wm;
+
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.MotionEvent.BUTTON_STYLUS_PRIMARY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.os.Looper;
+import android.util.Slog;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+
+/**
+ * Input receiver for drag and drop
+ */
+class DragInputEventReceiver extends InputEventReceiver {
+    private final WindowManagerService mService;
+    private final DragDropController mDragDropController;
+
+    // Set, if stylus button was down at the start of the drag.
+    private boolean mStylusButtonDownAtStart;
+    // Indicates the first event to check for button state.
+    private boolean mIsStartEvent = true;
+    // Set to true to ignore input events after the drag gesture is complete but the drag events
+    // are still being dispatched.
+    private boolean mMuteInput = false;
+
+    public DragInputEventReceiver(InputChannel inputChannel, Looper looper,
+            DragDropController controller, WindowManagerService service) {
+        super(inputChannel, looper);
+        mDragDropController = controller;
+        mService = service;
+    }
+
+    @Override
+    public void onInputEvent(InputEvent event, int displayId) {
+        boolean handled = false;
+        try {
+            synchronized (mService.mWindowMap) {
+                if (!mDragDropController.dragDropActiveLocked()) {
+                    // The drag has ended but the clean-up message has not been processed by
+                    // window manager. Drop events that occur after this until window manager
+                    // has a chance to clean-up the input handle.
+                    handled = true;
+                    return;
+                }
+                if (!(event instanceof MotionEvent)
+                        || (event.getSource() & SOURCE_CLASS_POINTER) == 0
+                        || mMuteInput) {
+                    return;
+                }
+                final MotionEvent motionEvent = (MotionEvent) event;
+                boolean endDrag = false;
+                final float newX = motionEvent.getRawX();
+                final float newY = motionEvent.getRawY();
+                final boolean isStylusButtonDown =
+                        (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
+
+                if (mIsStartEvent) {
+                    if (isStylusButtonDown) {
+                        // First event and the button was down, check for the button being
+                        // lifted in the future, if that happens we'll drop the item.
+                        mStylusButtonDownAtStart = true;
+                    }
+                    mIsStartEvent = false;
+                }
+
+                switch (motionEvent.getAction()) {
+                    case ACTION_DOWN: {
+                        if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+                    }
+                    break;
+
+                    case ACTION_MOVE: {
+                        if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+                            if (DEBUG_DRAG) {
+                                Slog.d(TAG_WM, "Button no longer pressed; dropping at "
+                                        + newX + "," + newY);
+                            }
+                            mMuteInput = true;
+                            endDrag = mDragDropController.mDragState
+                                    .notifyDropLocked(newX, newY);
+                        } else {
+                            // move the surface and tell the involved window(s) where we are
+                            mDragDropController.mDragState.notifyMoveLocked(newX, newY);
+                        }
+                    }
+                    break;
+
+                    case ACTION_UP: {
+                        if (DEBUG_DRAG) {
+                            Slog.d(TAG_WM, "Got UP on move channel; dropping at "
+                                    + newX + "," + newY);
+                        }
+                        mMuteInput = true;
+                        endDrag = mDragDropController.mDragState
+                                .notifyDropLocked(newX, newY);
+                    }
+                    break;
+
+                    case ACTION_CANCEL: {
+                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+                        mMuteInput = true;
+                        endDrag = true;
+                    }
+                    break;
+                }
+
+                if (endDrag) {
+                    if (DEBUG_DRAG)
+                        Slog.d(TAG_WM, "Drag ended; tearing down state");
+                    // tell all the windows that the drag has ended
+                    // endDragLocked will post back to looper to dispose the receiver
+                    // since we still need the receiver for the last finishInputEvent.
+                    mDragDropController.mDragState.endDragLocked();
+                    mStylusButtonDownAtStart = false;
+                    mIsStartEvent = true;
+                }
+
+                handled = true;
+            }
+        } catch (Exception e) {
+            Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
+        } finally {
+            finishInputEvent(event, handled);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
index 8ab0406..c0bf1e8 100644
--- a/services/core/java/com/android/server/wm/DragResizeMode.java
+++ b/services/core/java/com/android/server/wm/DragResizeMode.java
@@ -16,11 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
 /**
  * Describes the mode in which a window is drag resizing.
@@ -39,15 +35,12 @@
      */
     static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
 
-    static boolean isModeAllowedForStack(int stackId, int mode) {
+    static boolean isModeAllowedForStack(TaskStack stack, int mode) {
         switch (mode) {
             case DRAG_RESIZE_MODE_FREEFORM:
-                return stackId == FREEFORM_WORKSPACE_STACK_ID;
+                return stack.getWindowingMode() == WINDOWING_MODE_FREEFORM;
             case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
-                return stackId == DOCKED_STACK_ID
-                        || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                        || stackId == HOME_STACK_ID
-                        || stackId == RECENTS_STACK_ID;
+                return stack.inSplitScreenWindowingMode();
             default:
                 return false;
         }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 3fdafc7..9a955de 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,21 +16,26 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
+import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT;
+import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
+import android.animation.Animator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.hardware.input.InputManager;
 import android.os.Build;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -46,21 +51,12 @@
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.Transformation;
-import android.view.animation.TranslateAnimation;
-
-import com.android.server.input.InputApplicationHandle;
-import com.android.server.input.InputWindowHandle;
-import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
-import com.android.server.wm.WindowManagerService.H;
 
 import com.android.internal.view.IDragAndDropPermissions;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
 
 import java.util.ArrayList;
 
@@ -78,8 +74,18 @@
             View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
             View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
 
+    // Property names for animations
+    private static final String ANIMATED_PROPERTY_X = "x";
+    private static final String ANIMATED_PROPERTY_Y = "y";
+    private static final String ANIMATED_PROPERTY_ALPHA = "alpha";
+    private static final String ANIMATED_PROPERTY_SCALE = "scale";
+
     final WindowManagerService mService;
+    final DragDropController mDragDropController;
     IBinder mToken;
+    /**
+     * Do not use the variable from the out of animation thread while mAnimator is not null.
+     */
     SurfaceControl mSurfaceControl;
     int mFlags;
     IBinder mLocalWin;
@@ -101,14 +107,14 @@
     boolean mDragInProgress;
     DisplayContent mDisplayContent;
 
-    private Animation mAnimation;
-    final Transformation mTransformation = new Transformation();
+    @Nullable private ValueAnimator mAnimator;
     private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
     private Point mDisplaySize = new Point();
 
     DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
             int flags, IBinder localWin) {
         mService = service;
+        mDragDropController = service.mDragDropController;
         mToken = token;
         mSurfaceControl = surface;
         mFlags = flags;
@@ -117,6 +123,10 @@
     }
 
     void reset() {
+        if (mAnimator != null) {
+            Slog.wtf(TAG_WM,
+                    "Unexpectedly destroying mSurfaceControl while animation is running");
+        }
         if (mSurfaceControl != null) {
             mSurfaceControl.destroy();
         }
@@ -140,8 +150,8 @@
             mServerChannel = channels[0];
             mClientChannel = channels[1];
             mService.mInputManager.registerInputChannel(mServerChannel, null);
-            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
-                    mService.mH.getLooper());
+            mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
+                    mService.mH.getLooper(), mDragDropController, mService);
 
             mDragApplicationHandle = new InputApplicationHandle(null);
             mDragApplicationHandle.name = "drag";
@@ -152,7 +162,7 @@
                     display.getDisplayId());
             mDragWindowHandle.name = "drag";
             mDragWindowHandle.inputChannel = mServerChannel;
-            mDragWindowHandle.layer = getDragLayerLw();
+            mDragWindowHandle.layer = getDragLayerLocked();
             mDragWindowHandle.layoutParamsFlags = 0;
             mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
             mDragWindowHandle.dispatchingTimeoutNanos =
@@ -231,14 +241,14 @@
             Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
         } else {
             // Input channel should be disposed on the thread where the input is being handled.
-            mService.mH.obtainMessage(
-                    H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
+            mDragDropController.sendHandlerMessage(
+                    MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
             mInputInterceptor = null;
             mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
         }
     }
 
-    int getDragLayerLw() {
+    int getDragLayerLocked() {
         return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
                 * WindowManagerService.TYPE_LAYER_MULTIPLIER
                 + WindowManagerService.TYPE_LAYER_OFFSET;
@@ -246,7 +256,7 @@
 
     /* call out to each visible window/session informing it about the drag
      */
-    void broadcastDragStartedLw(final float touchX, final float touchY) {
+    void broadcastDragStartedLocked(final float touchX, final float touchY) {
         mOriginalX = mCurrentX = touchX;
         mOriginalY = mCurrentY = touchY;
 
@@ -273,7 +283,7 @@
         }
 
         mDisplayContent.forAllWindows(w -> {
-            sendDragStartedLw(w, touchX, touchY, mDataDescription);
+            sendDragStartedLocked(w, touchX, touchY, mDataDescription);
         }, false /* traverseTopToBottom */ );
     }
 
@@ -285,7 +295,7 @@
      * This method clones the 'event' parameter if it's being delivered to the same
      * process, so it's safe for the caller to call recycle() on the event afterwards.
      */
-    private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+    private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY,
             ClipDescription desc) {
         if (mDragInProgress && isValidDropTarget(newWin)) {
             DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
@@ -334,7 +344,7 @@
      * previously been notified, i.e. it became visible after the drag operation
      * was begun.  This is a rare case.
      */
-    void sendDragStartedIfNeededLw(WindowState newWin) {
+    void sendDragStartedIfNeededLocked(WindowState newWin) {
         if (mDragInProgress) {
             // If we have sent the drag-started, we needn't do so again
             if (isWindowNotified(newWin)) {
@@ -343,7 +353,7 @@
             if (DEBUG_DRAG) {
                 Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
             }
-            sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+            sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription);
         }
     }
 
@@ -356,7 +366,7 @@
         return false;
     }
 
-    private void broadcastDragEndedLw() {
+    private void broadcastDragEndedLocked() {
         final int myPid = Process.myPid();
 
         if (DEBUG_DRAG) {
@@ -387,28 +397,38 @@
         mDragInProgress = false;
     }
 
-    void endDragLw() {
-        if (mAnimation != null) {
+    void endDragLocked() {
+        if (mAnimator != null) {
             return;
         }
         if (!mDragResult) {
-            mAnimation = createReturnAnimationLocked();
-            mService.scheduleAnimationLocked();
+            mAnimator = createReturnAnimationLocked();
             return;  // Will call cleanUpDragLw when the animation is done.
         }
-        cleanUpDragLw();
+        cleanUpDragLocked();
     }
 
-    void cancelDragLw() {
-        if (mAnimation != null) {
+    void cancelDragLocked() {
+        if (mAnimator != null) {
             return;
         }
-        mAnimation = createCancelAnimationLocked();
-        mService.scheduleAnimationLocked();
+        if (!mDragInProgress) {
+            // This can happen if an app invokes Session#cancelDragAndDrop before
+            // Session#performDrag. Reset the drag state:
+            // 1. without sending the end broadcast because the start broadcast has not been sent,
+            // and
+            // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
+            //    WindowManagerService, which will cause DragState#reset() while playing the
+            //    cancel animation.
+            reset();
+            mDragDropController.mDragState = null;
+            return;
+        }
+        mAnimator = createCancelAnimationLocked();
     }
 
-    private void cleanUpDragLw() {
-        broadcastDragEndedLw();
+    private void cleanUpDragLocked() {
+        broadcastDragEndedLocked();
         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
             mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
         }
@@ -418,11 +438,11 @@
 
         // free our resources and drop all the object references
         reset();
-        mService.mDragState = null;
+        mDragDropController.mDragState = null;
     }
 
-    void notifyMoveLw(float x, float y) {
-        if (mAnimation != null) {
+    void notifyMoveLocked(float x, float y) {
+        if (mAnimator != null) {
             return;
         }
         mCurrentX = x;
@@ -430,7 +450,7 @@
 
         // Move the surface to the given touch
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
+                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked");
         mService.openSurfaceTransaction();
         try {
             mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
@@ -440,12 +460,12 @@
         } finally {
             mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
+                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked");
         }
-        notifyLocationLw(x, y);
+        notifyLocationLocked(x, y);
     }
 
-    void notifyLocationLw(float x, float y) {
+    void notifyLocationLocked(float x, float y) {
         // Tell the affected window
         WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
         if (touchedWin != null && !isWindowNotified(touchedWin)) {
@@ -490,8 +510,8 @@
     // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
     // dispatch the global drag-ended message, 'false' if we need to wait for a
     // result from the recipient.
-    boolean notifyDropLw(float x, float y) {
-        if (mAnimation != null) {
+    boolean notifyDropLocked(float x, float y) {
+        if (mAnimator != null) {
             return false;
         }
         mCurrentX = x;
@@ -534,9 +554,7 @@
             touchedWin.mClient.dispatchDragEvent(evt);
 
             // 5 second timeout for this window to respond to the drop
-            mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
-            Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
-            mService.mH.sendMessageDelayed(msg, 5000);
+            mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
         } catch (RemoteException e) {
             Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
             return true;
@@ -549,6 +567,15 @@
         return false;
     }
 
+    void onAnimationEndLocked() {
+        if (mAnimator == null) {
+            Slog.wtf(TAG_WM, "Unexpected null mAnimator");
+            return;
+        }
+        mAnimator = null;
+        cleanUpDragLocked();
+    }
+
     private static DragEvent obtainDragEvent(WindowState win, int action,
             float x, float y, Object localState,
             ClipDescription description, ClipData data,
@@ -560,66 +587,99 @@
                 dragAndDropPermissions, result);
     }
 
-    boolean stepAnimationLocked(long currentTimeMs) {
-        if (mAnimation == null) {
-            return false;
-        }
+    private ValueAnimator createReturnAnimationLocked() {
+        final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX,
+                        mOriginalX - mThumbOffsetX),
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY,
+                        mOriginalY - mThumbOffsetY),
+                PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1),
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2));
 
-        mTransformation.clear();
-        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
-            cleanUpDragLw();
-            return false;
-        }
-
-        mTransformation.getMatrix().postTranslate(
-                mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
-        final float tmpFloats[] = mService.mTmpFloats;
-        mTransformation.getMatrix().getValues(tmpFloats);
-        mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
-        mSurfaceControl.setAlpha(mTransformation.getAlpha());
-        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
-                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
-        return true;
-    }
-
-    private Animation createReturnAnimationLocked() {
-        final AnimationSet set = new AnimationSet(false);
         final float translateX = mOriginalX - mCurrentX;
         final float translateY = mOriginalY - mCurrentY;
-        set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY));
-        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
         // Adjust the duration to the travel distance.
         final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
         final double displayDiagonal =
                 Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
         final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
                 * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
-        set.setDuration(duration);
-        set.setInterpolator(mCubicEaseOutInterpolator);
-        set.initialize(0, 0, 0, 0);
-        set.start();  // Will start on the first call to getTransformation.
-        return set;
+        final AnimationListener listener = new AnimationListener();
+        animator.setDuration(duration);
+        animator.setInterpolator(mCubicEaseOutInterpolator);
+        animator.addListener(listener);
+        animator.addUpdateListener(listener);
+
+        mService.mAnimationHandler.post(() -> animator.start());
+        return animator;
     }
 
-    private Animation createCancelAnimationLocked() {
-        final AnimationSet set = new AnimationSet(false);
-        set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
-        set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
-        set.setDuration(MIN_ANIMATION_DURATION_MS);
-        set.setInterpolator(mCubicEaseOutInterpolator);
-        set.initialize(0, 0, 0, 0);
-        set.start();  // Will start on the first call to getTransformation.
-        return set;
+    private ValueAnimator createCancelAnimationLocked() {
+        final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX),
+                PropertyValuesHolder.ofFloat(
+                        ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY),
+                PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0),
+                PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+        final AnimationListener listener = new AnimationListener();
+        animator.setDuration(MIN_ANIMATION_DURATION_MS);
+        animator.setInterpolator(mCubicEaseOutInterpolator);
+        animator.addListener(listener);
+        animator.addUpdateListener(listener);
+
+        mService.mAnimationHandler.post(() -> animator.start());
+        return animator;
     }
 
     private boolean isFromSource(int source) {
         return (mTouchSource & source) == source;
     }
 
-    void overridePointerIconLw(int touchSource) {
+    void overridePointerIconLocked(int touchSource) {
         mTouchSource = touchSource;
         if (isFromSource(InputDevice.SOURCE_MOUSE)) {
             InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
         }
     }
+
+    private class AnimationListener
+            implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
+                transaction.setPosition(
+                        mSurfaceControl,
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+                transaction.setAlpha(
+                        mSurfaceControl,
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+                transaction.setMatrix(
+                        mSurfaceControl,
+                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+                        0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+                transaction.apply();
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animator animator) {}
+
+        @Override
+        public void onAnimationCancel(Animator animator) {}
+
+        @Override
+        public void onAnimationRepeat(Animator animator) {}
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            // Updating mDragState requires the WM lock so continues it on the out of
+            // AnimationThread.
+            mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index 3186d3d..8bec8d7 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -57,14 +56,11 @@
 
         SurfaceControl ctrl = null;
         try {
-            if (DEBUG_SURFACE_TRACE) {
-                ctrl = new WindowSurfaceController.SurfaceTrace(session, "EmulatorDisplayOverlay",
-                        mScreenSize.x, mScreenSize.y, PixelFormat.TRANSLUCENT,
-                        SurfaceControl.HIDDEN);
-            } else {
-                ctrl = new SurfaceControl(session, "EmulatorDisplayOverlay", mScreenSize.x,
-                        mScreenSize.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-            }
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("EmulatorDisplayOverlay")
+                    .setSize(mScreenSize.x, mScreenSize.y)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 36753b7..d40db8c 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -16,21 +16,36 @@
 
 package com.android.server.wm;
 
+import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.view.Display;
 import android.view.InputChannel;
 import android.view.WindowManager;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 
-class InputConsumerImpl {
+import java.io.PrintWriter;
+
+class InputConsumerImpl implements IBinder.DeathRecipient {
     final WindowManagerService mService;
     final InputChannel mServerChannel, mClientChannel;
     final InputApplicationHandle mApplicationHandle;
     final InputWindowHandle mWindowHandle;
 
-    InputConsumerImpl(WindowManagerService service, String name, InputChannel inputChannel) {
+    final IBinder mToken;
+    final String mName;
+    final int mClientPid;
+    final UserHandle mClientUser;
+
+    InputConsumerImpl(WindowManagerService service, IBinder token, String name,
+            InputChannel inputChannel, int clientPid, UserHandle clientUser) {
         mService = service;
+        mToken = token;
+        mName = name;
+        mClientPid = clientPid;
+        mClientUser = clientUser;
 
         InputChannel[] channels = InputChannel.openInputChannelPair(name);
         mServerChannel = channels[0];
@@ -68,6 +83,26 @@
         mWindowHandle.scaleFactor = 1.0f;
     }
 
+    void linkToDeathRecipient() {
+        if (mToken == null) {
+            return;
+        }
+
+        try {
+            mToken.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            // Client died, do nothing
+        }
+    }
+
+    void unlinkFromDeathRecipient() {
+        if (mToken == null) {
+            return;
+        }
+
+        mToken.unlinkToDeath(this, 0);
+    }
+
     void layout(int dw, int dh) {
         mWindowHandle.touchableRegion.set(0, 0, dw, dh);
         mWindowHandle.frameLeft = 0;
@@ -86,5 +121,19 @@
         mService.mInputManager.unregisterInputChannel(mServerChannel);
         mClientChannel.dispose();
         mServerChannel.dispose();
+        unlinkFromDeathRecipient();
+    }
+
+    @Override
+    public void binderDied() {
+        synchronized (mService.getWindowManagerLock()) {
+            // Clean up the input consumer
+            mService.mInputMonitor.destroyInputConsumer(mName);
+            unlinkFromDeathRecipient();
+        }
+    }
+
+    void dump(PrintWriter pw, String name, String prefix) {
+        pw.println(prefix + "  name=" + name + " pid=" + mClientPid + " user=" + mClientUser);
     }
 }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 5057f63..40eab45 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
@@ -26,6 +25,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
@@ -35,8 +35,11 @@
 import android.app.ActivityManager;
 import android.graphics.Rect;
 import android.os.Debug;
+import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -44,7 +47,6 @@
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
 import android.view.WindowManager;
-
 import android.view.WindowManagerPolicy;
 
 import com.android.server.input.InputApplicationHandle;
@@ -107,8 +109,9 @@
 
         EventReceiverInputConsumer(WindowManagerService service, InputMonitor monitor,
                                    Looper looper, String name,
-                                   InputEventReceiver.Factory inputEventReceiverFactory) {
-            super(service, name, null);
+                                   InputEventReceiver.Factory inputEventReceiverFactory,
+                                   int clientPid, UserHandle clientUser) {
+            super(service, null /* token */, name, null /* inputChannel */, clientPid, clientUser);
             mInputMonitor = monitor;
             mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
                     mClientChannel, looper);
@@ -130,6 +133,7 @@
 
     private void addInputConsumer(String name, InputConsumerImpl consumer) {
         mInputConsumers.put(name, consumer);
+        consumer.linkToDeathRecipient();
         updateInputWindowsLw(true /* force */);
     }
 
@@ -167,17 +171,20 @@
         }
 
         final EventReceiverInputConsumer consumer = new EventReceiverInputConsumer(mService,
-                this, looper, name, inputEventReceiverFactory);
+                this, looper, name, inputEventReceiverFactory, Process.myPid(),
+                UserHandle.SYSTEM);
         addInputConsumer(name, consumer);
         return consumer;
     }
 
-    void createInputConsumer(String name, InputChannel inputChannel) {
+    void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid,
+            UserHandle clientUser) {
         if (mInputConsumers.containsKey(name)) {
             throw new IllegalStateException("Existing input consumer found with name: " + name);
         }
 
-        final InputConsumerImpl consumer = new InputConsumerImpl(mService, name, inputChannel);
+        final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name,
+                inputChannel, clientPid, clientUser);
         switch (name) {
             case INPUT_CONSUMER_WALLPAPER:
                 consumer.mWindowHandle.hasWallpaper = true;
@@ -368,12 +375,13 @@
         // currently has touch focus.
 
         // If there's a drag in flight, provide a pseudo-window to catch drag input
-        final boolean inDrag = (mService.mDragState != null);
+        final boolean inDrag = mService.mDragDropController.dragDropActiveLocked();
         if (inDrag) {
             if (DEBUG_DRAG) {
                 Log.d(TAG_WM, "Inserting drag window");
             }
-            final InputWindowHandle dragWindowHandle = mService.mDragState.getInputWindowHandle();
+            final InputWindowHandle dragWindowHandle =
+                    mService.mDragDropController.mDragState.getInputWindowHandle();
             if (dragWindowHandle != null) {
                 addInputWindowHandle(dragWindowHandle);
             } else {
@@ -593,7 +601,7 @@
         if (!inputConsumerKeys.isEmpty()) {
             pw.println(prefix + "InputConsumers:");
             for (String key : inputConsumerKeys) {
-                pw.println(prefix + "  name=" + key);
+                mInputConsumers.get(key).dump(pw, key, prefix);
             }
         }
     }
@@ -650,7 +658,7 @@
             final boolean hasFocus = w == mInputFocus;
             final boolean isVisible = w.isVisibleLw();
 
-            if (w.getStackId() == PINNED_STACK_ID) {
+            if (w.inPinnedWindowingMode()) {
                 if (mAddPipInputConsumerHandle
                         && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
                     // Update the bounds of the Pip input consumer to match the Pinned stack
@@ -690,7 +698,7 @@
             // If there's a drag in progress and 'child' is a potential drop target,
             // make sure it's been told about the drag
             if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
-                mService.mDragState.sendDragStartedIfNeededLw(w);
+                mService.mDragDropController.mDragState.sendDragStartedIfNeededLocked(w);
             }
 
             addInputWindowHandle(
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 1e7140a..365366a 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -16,7 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -404,27 +405,28 @@
      */
     private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
         synchronized (mService.mWindowMap) {
-            if (mPinnedStackListener != null) {
-                try {
-                    final Rect insetBounds = new Rect();
-                    getInsetBounds(insetBounds);
-                    final Rect normalBounds = getDefaultBounds();
-                    if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                        transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                                false /* useCurrentMinEdgeSize */);
-                    }
-                    final Rect animatingBounds = mTmpAnimatingBoundsRect;
-                    final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
-                    if (pinnedStack != null) {
-                        pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                    } else {
-                        animatingBounds.set(normalBounds);
-                    }
-                    mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                            animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
-                } catch (RemoteException e) {
-                    Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+            if (mPinnedStackListener == null) {
+                return;
+            }
+            try {
+                final Rect insetBounds = new Rect();
+                getInsetBounds(insetBounds);
+                final Rect normalBounds = getDefaultBounds();
+                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+                            false /* useCurrentMinEdgeSize */);
                 }
+                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final TaskStack pinnedStack = mDisplayContent.getPinnedStack();
+                if (pinnedStack != null) {
+                    pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
+                } else {
+                    animatingBounds.set(normalBounds);
+                }
+                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+                        animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
         }
     }
@@ -491,7 +493,7 @@
         pw.println(prefix + "PinnedStackController");
         pw.print(prefix + "  defaultBounds="); getDefaultBounds().printShortString(pw);
         pw.println();
-        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
         pw.println();
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
@@ -512,7 +514,7 @@
     void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS);
-        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS);
         proto.end(token);
     }
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index 590ac6e..41f076d 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -16,19 +16,16 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
 import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
 
 import android.app.RemoteAction;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.server.UiThread;
-
 import java.util.List;
 
 /**
@@ -40,8 +37,8 @@
     private Rect mTmpToBounds = new Rect();
 
     public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
-            int displayId, boolean onTop, Rect outBounds) {
-        super(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+        super(stackId, listener, displayId, onTop, outBounds, service);
     }
 
     /**
@@ -101,7 +98,8 @@
                 }
                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
 
-                mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpToBounds);
+                mService.getStackBounds(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds);
                 if (!mTmpToBounds.isEmpty()) {
                     // If there is a fullscreen bounds, use that
                     toBounds = new Rect(mTmpToBounds);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 227e4b2..a710441 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -411,10 +411,10 @@
         }
     }
 
-    TaskStack getStackById(int stackId) {
+    TaskStack getStack(int windowingMode, int activityType) {
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final DisplayContent dc = mChildren.get(i);
-            final TaskStack stack = dc.getStackById(stackId);
+            final TaskStack stack = dc.getStack(windowingMode, activityType);
             if (stack != null) {
                 return stack;
             }
@@ -1081,19 +1081,21 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         if (mService.mDisplayReady) {
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
                 final DisplayContent displayContent = mChildren.get(i);
-                displayContent.writeToProto(proto, DISPLAYS);
+                displayContent.writeToProto(proto, DISPLAYS, trim);
             }
         }
-        forAllWindows((w) -> {
-            w.writeIdentifierToProto(proto, WINDOWS);
-        }, true);
+        if (!trim) {
+            forAllWindows((w) -> {
+                w.writeIdentifierToProto(proto, WINDOWS);
+            }, true);
+        }
         proto.end(token);
     }
 
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d5b6d24..c25b19c 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
@@ -24,7 +23,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
-import static com.android.server.wm.WindowSurfaceController.SurfaceTrace;
 import static com.android.server.wm.proto.ScreenRotationAnimationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.proto.ScreenRotationAnimationProto.STARTED;
 
@@ -271,22 +269,12 @@
 
         try {
             try {
-                int flags = SurfaceControl.HIDDEN;
-                if (isSecure) {
-                    flags |= SurfaceControl.SECURE;
-                }
+                mSurfaceControl = new SurfaceControl.Builder(session)
+                        .setName("ScreenshotSurface")
+                        .setSize(mWidth, mHeight)
+                        .setSecure(isSecure)
+                        .build();
 
-                if (DEBUG_SURFACE_TRACE) {
-                    mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
-                            mWidth, mHeight,
-                            PixelFormat.OPAQUE, flags);
-                    Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset="
-                            + mOriginalDisplayRect.toShortString());
-                } else {
-                    mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
-                            mWidth, mHeight,
-                            PixelFormat.OPAQUE, flags);
-                }
                 // capture a screenshot into the surface we just created
                 Surface sur = new Surface();
                 sur.copyFrom(mSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1781247..ad7c300 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -68,9 +68,7 @@
  * This class represents an active client session.  There is generally one
  * Session object per process that is interacting with the window manager.
  */
-// Needs to be public and not final so we can mock during tests...sucks I know :(
-public class Session extends IWindowSession.Stub
-        implements IBinder.DeathRecipient {
+class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
     final WindowManagerService mService;
     final IWindowSessionCallback mCallback;
     final IInputMethodClient mClient;
@@ -83,6 +81,7 @@
     private final Set<WindowSurfaceController> mAppOverlaySurfaces = new HashSet<>();
     // Set of visible alert window surfaces connected to this session.
     private final Set<WindowSurfaceController> mAlertWindowSurfaces = new HashSet<>();
+    private final DragDropController mDragDropController;
     final boolean mCanAddInternalSystemWindow;
     final boolean mCanHideNonSystemOverlayWindows;
     final boolean mCanAcquireSleepToken;
@@ -108,6 +107,7 @@
         mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER)
                 == PERMISSION_GRANTED;
         mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
+        mDragDropController = mService.mDragDropController;
         StringBuilder sb = new StringBuilder();
         sb.append("Session{");
         sb.append(Integer.toHexString(System.identityHashCode(this)));
@@ -306,94 +306,56 @@
 
     /* Drag/drop */
     @Override
-    public IBinder prepareDrag(IWindow window, int flags,
-            int width, int height, Surface outSurface) {
-        return mService.prepareDragSurface(window, mSurfaceSession, flags,
-                width, height, outSurface);
+    public IBinder prepareDrag(IWindow window, int flags, int width, int height,
+            Surface outSurface) {
+        final int callerPid = Binder.getCallingPid();
+        final int callerUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mDragDropController.prepareDrag(
+                    mSurfaceSession, callerPid, callerUid, window, flags, width, height,
+                    outSurface);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     @Override
     public boolean performDrag(IWindow window, IBinder dragToken,
             int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
             ClipData data) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "perform drag: win=" + window + " data=" + data);
+        return mDragDropController.performDrag(window, dragToken, touchSource,
+                touchX, touchY, thumbCenterX, thumbCenterY, data);
+    }
+
+    @Override
+    public void reportDropResult(IWindow window, boolean consumed) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDragDropController.reportDropResult(window, consumed);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
+    }
 
-        synchronized (mService.mWindowMap) {
-            if (mService.mDragState == null) {
-                Slog.w(TAG_WM, "No drag prepared");
-                throw new IllegalStateException("performDrag() without prepareDrag()");
-            }
-
-            if (dragToken != mService.mDragState.mToken) {
-                Slog.w(TAG_WM, "Performing mismatched drag");
-                throw new IllegalStateException("performDrag() does not match prepareDrag()");
-            }
-
-            WindowState callingWin = mService.windowForClientLocked(null, window, false);
-            if (callingWin == null) {
-                Slog.w(TAG_WM, "Bad requesting window " + window);
-                return false;  // !!! TODO: throw here?
-            }
-
-            // !!! TODO: if input is not still focused on the initiating window, fail
-            // the drag initiation (e.g. an alarm window popped up just as the application
-            // called performDrag()
-
-            mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
-
-            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
-            // will let us eliminate the (touchX,touchY) parameters from the API.
-
-            // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
-            // the actual drag event dispatch stuff in the dragstate
-
-            final DisplayContent displayContent = callingWin.getDisplayContent();
-            if (displayContent == null) {
-               return false;
-            }
-            Display display = displayContent.getDisplay();
-            mService.mDragState.register(display);
-            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
-                    mService.mDragState.getInputChannel())) {
-                Slog.e(TAG_WM, "Unable to transfer touch focus");
-                mService.mDragState.unregister();
-                mService.mDragState.reset();
-                mService.mDragState = null;
-                return false;
-            }
-
-            mService.mDragState.mDisplayContent = displayContent;
-            mService.mDragState.mData = data;
-            mService.mDragState.broadcastDragStartedLw(touchX, touchY);
-            mService.mDragState.overridePointerIconLw(touchSource);
-
-            // remember the thumb offsets for later
-            mService.mDragState.mThumbOffsetX = thumbCenterX;
-            mService.mDragState.mThumbOffsetY = thumbCenterY;
-
-            // Make the surface visible at the proper location
-            final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
-            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                    TAG_WM, ">>> OPEN TRANSACTION performDrag");
-            mService.openSurfaceTransaction();
-            try {
-                surfaceControl.setPosition(touchX - thumbCenterX,
-                        touchY - thumbCenterY);
-                surfaceControl.setLayer(mService.mDragState.getDragLayerLw());
-                surfaceControl.setLayerStack(display.getLayerStack());
-                surfaceControl.show();
-            } finally {
-                mService.closeSurfaceTransaction();
-                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
-                        TAG_WM, "<<< CLOSE TRANSACTION performDrag");
-            }
-
-            mService.mDragState.notifyLocationLw(touchX, touchY);
+    @Override
+    public void cancelDragAndDrop(IBinder dragToken) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mDragDropController.cancelDragAndDrop(dragToken);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
+    }
 
-        return true;    // success!
+    @Override
+    public void dragRecipientEntered(IWindow window) {
+        mDragDropController.dragRecipientEntered(window);
+    }
+
+    @Override
+    public void dragRecipientExited(IWindow window) {
+        mDragDropController.dragRecipientExited(window);
     }
 
     @Override
@@ -410,90 +372,6 @@
     }
 
     @Override
-    public void reportDropResult(IWindow window, boolean consumed) {
-        IBinder token = window.asBinder();
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token);
-        }
-
-        synchronized (mService.mWindowMap) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                if (mService.mDragState == null) {
-                    // Most likely the drop recipient ANRed and we ended the drag
-                    // out from under it.  Log the issue and move on.
-                    Slog.w(TAG_WM, "Drop result given but no drag in progress");
-                    return;
-                }
-
-                if (mService.mDragState.mToken != token) {
-                    // We're in a drag, but the wrong window has responded.
-                    Slog.w(TAG_WM, "Invalid drop-result claim by " + window);
-                    throw new IllegalStateException("reportDropResult() by non-recipient");
-                }
-
-                // The right window has responded, even if it's no longer around,
-                // so be sure to halt the timeout even if the later WindowState
-                // lookup fails.
-                mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
-                WindowState callingWin = mService.windowForClientLocked(null, window, false);
-                if (callingWin == null) {
-                    Slog.w(TAG_WM, "Bad result-reporting window " + window);
-                    return;  // !!! TODO: throw here?
-                }
-
-                mService.mDragState.mDragResult = consumed;
-                mService.mDragState.endDragLw();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
-    public void cancelDragAndDrop(IBinder dragToken) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "cancelDragAndDrop");
-        }
-
-        synchronized (mService.mWindowMap) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                if (mService.mDragState == null) {
-                    Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
-                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
-                }
-
-                if (mService.mDragState.mToken != dragToken) {
-                    Slog.w(TAG_WM,
-                            "cancelDragAndDrop() does not match prepareDrag()");
-                    throw new IllegalStateException(
-                            "cancelDragAndDrop() does not match prepareDrag()");
-                }
-
-                mService.mDragState.mDragResult = false;
-                mService.mDragState.cancelDragLw();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
-    @Override
-    public void dragRecipientEntered(IWindow window) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
-        }
-    }
-
-    @Override
-    public void dragRecipientExited(IWindow window) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder());
-        }
-    }
-
-    @Override
     public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
         synchronized(mService.mWindowMap) {
             long ident = Binder.clearCallingIdentity();
@@ -719,4 +597,8 @@
     public String toString() {
         return mStringName;
     }
+
+    boolean hasAlertWindowSurfaces() {
+        return !mAlertWindowSurfaces.isEmpty();
+    }
 }
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index c0a4cb7..aff1bc6 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -45,7 +43,7 @@
 public class StackWindowController
         extends WindowContainerController<TaskStack, StackWindowListener> {
 
-    final int mStackId;
+    private final int mStackId;
 
     private final H mHandler;
 
@@ -74,7 +72,7 @@
                         + " to unknown displayId=" + displayId);
             }
 
-            dc.addStackToDisplay(stackId, onTop, this);
+            dc.createStack(stackId, onTop, this);
             getRawBounds(outBounds);
         }
     }
@@ -154,7 +152,8 @@
         }
     }
 
-    public void positionChildAtBottom(TaskWindowContainerController child) {
+    public void positionChildAtBottom(TaskWindowContainerController child,
+            boolean includingParents) {
         if (child == null) {
             // TODO: Fix the call-points that cause this to happen.
             return;
@@ -166,7 +165,7 @@
                 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
                 return;
             }
-            mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
+            mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
 
             if (mService.mAppTransition.isTransitionSet()) {
                 childTask.setSendingToBottom(true);
@@ -280,8 +279,9 @@
             if (stack.getWindowConfiguration().tasksAreFloating()) {
                 // Floating tasks should not be resized to the screen's bounds.
 
-                if (mStackId == PINNED_STACK_ID && bounds.width() == mTmpDisplayBounds.width() &&
-                        bounds.height() == mTmpDisplayBounds.height()) {
+                if (stack.inPinnedWindowingMode()
+                        && bounds.width() == mTmpDisplayBounds.width()
+                        && bounds.height() == mTmpDisplayBounds.height()) {
                     // If the bounds we are animating is the same as the fullscreen stack
                     // dimensions, then apply the same inset calculations that we normally do for
                     // the fullscreen stack, without intersecting it with the display bounds
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index d1547eb..eb8ee69 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -44,8 +44,11 @@
     public StrictModeFlash(Display display, SurfaceSession session) {
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl(session, "StrictModeFlash",
-                1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("StrictModeFlash")
+                    .setSize(1, 1)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
             ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
             ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
index b0eaf14..a5080d5 100644
--- a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
+++ b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
@@ -66,10 +66,10 @@
         mWindowSurfaceController = other.mWindowSurfaceController;
     }
 
-    public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format,
-            int flags, int windowType, int ownerUid,
+    public SurfaceControlWithBackground(String name, SurfaceControl.Builder b,
+            int windowType, int w, int h,
             WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
-        super(s, name, w, h, format, flags, windowType, ownerUid);
+        super(b.build());
 
         // We should only show background behind app windows that are letterboxed in a task.
         if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
@@ -80,9 +80,11 @@
         mLastWidth = w;
         mLastHeight = h;
         mWindowSurfaceController.getContainerRect(mTmpContainerRect);
-        mBackgroundControl = new SurfaceControl(s, "Background for - " + name,
-                mTmpContainerRect.width(), mTmpContainerRect.height(), PixelFormat.OPAQUE,
-                flags | SurfaceControl.FX_SURFACE_DIM);
+        mBackgroundControl = b.setName("Background for - " + name)
+                .setSize(mTmpContainerRect.width(), mTmpContainerRect.height())
+                .setFormat(OPAQUE)
+                .setColorLayer(true)
+                .build();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7464a9b..7620cb0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -17,12 +17,11 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.res.Configuration.EMPTY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 
 import static com.android.server.EventLogTags.WM_TASK_REMOVED;
@@ -37,7 +36,6 @@
 import static com.android.server.wm.proto.TaskProto.WINDOW_CONTAINER;
 
 import android.annotation.CallSuper;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -213,7 +211,7 @@
         // then we want to preserve our insets so that there will not
         // be a jump in the area covered by system decorations. We rely
         // on the pinned animation to later unset this value.
-        if (stack.mStackId == PINNED_STACK_ID) {
+        if (stack.inPinnedWindowingMode()) {
             mPreserveNonFloatingState = true;
         } else {
             mPreserveNonFloatingState = false;
@@ -280,15 +278,9 @@
     // WindowConfiguration long term.
     private int setBounds(Rect bounds, Configuration overrideConfig) {
         if (overrideConfig == null) {
-            overrideConfig = Configuration.EMPTY;
+            overrideConfig = EMPTY;
         }
-        if (bounds == null && !Configuration.EMPTY.equals(overrideConfig)) {
-            throw new IllegalArgumentException("null bounds but non empty configuration: "
-                    + overrideConfig);
-        }
-        if (bounds != null && Configuration.EMPTY.equals(overrideConfig)) {
-            throw new IllegalArgumentException("non null bounds, but empty configuration");
-        }
+
         boolean oldFullscreen = mFillsParent;
         int rotation = Surface.ROTATION_0;
         final DisplayContent displayContent = mStack.getDisplayContent();
@@ -323,7 +315,7 @@
         if (displayContent != null) {
             displayContent.mDimLayerController.updateDimLayer(this);
         }
-        onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
+        onOverrideConfigurationChanged(overrideConfig);
         return boundsChange;
     }
 
@@ -406,7 +398,7 @@
      *                    the adjusted bounds's top.
      */
     void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
-        if (!isResizeable() || Configuration.EMPTY.equals(getOverrideConfiguration())) {
+        if (!isResizeable() || EMPTY.equals(getOverrideConfiguration())) {
             return;
         }
 
@@ -427,7 +419,7 @@
         return mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || displayContent == null
-                || displayContent.getDockedStackIgnoringVisibility() != null;
+                || displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility() != null;
     }
 
     /** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -498,7 +490,7 @@
         final boolean dockedResizing = displayContent != null
                 && displayContent.mDividerControllerLocked.isResizing();
         if (useCurrentBounds()) {
-            if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
+            if (inFreeformWindowingMode() && getMaxVisibleBounds(out)) {
                 return;
             }
 
@@ -531,7 +523,7 @@
 
     void setDragResizing(boolean dragResizing, int dragResizeMode) {
         if (mDragResizing != dragResizing) {
-            if (!DragResizeMode.isModeAllowedForStack(mStack.mStackId, dragResizeMode)) {
+            if (!DragResizeMode.isModeAllowedForStack(mStack, dragResizeMode)) {
                 throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
                         + mStack.mStackId + " dragResizeMode=" + dragResizeMode);
             }
@@ -554,7 +546,9 @@
             return;
         }
         if (mFillsParent) {
-            setBounds(null, Configuration.EMPTY);
+            // TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
+            // problem once we move mBounds into WindowConfiguration.
+            setBounds(null, getOverrideConfiguration());
             return;
         }
         final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -602,14 +596,6 @@
         return (tokensCount != 0) && mChildren.get(tokensCount - 1).mShowForAllUsers;
     }
 
-    boolean inFreeformWorkspace() {
-        return mStack != null && mStack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    boolean inPinnedWorkspace() {
-        return mStack != null && mStack.mStackId == PINNED_STACK_ID;
-    }
-
     /**
      * When we are in a floating stack (Freeform, Pinned, ...) we calculate
      * insets differently. However if we are animating to the fullscreen stack
@@ -739,13 +725,13 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(ID, mTaskId);
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken appWindowToken = mChildren.get(i);
-            appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS);
+            appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim);
         }
         proto.write(FILLS_PARENT, mFillsParent);
         mBounds.writeToProto(proto, BOUNDS);
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index c58212c..a11b333 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -16,11 +16,10 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
@@ -211,9 +210,9 @@
 
                         if (mCurrentDimSide != CTRL_NONE) {
                             final int createMode = mCurrentDimSide == CTRL_LEFT
-                                    ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                                    : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-                            mService.mActivityManager.moveTaskToDockedStack(
+                                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+                            mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
                                     mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
                                     null /* initialBounds */);
                         }
@@ -649,7 +648,7 @@
      * shouldn't be shown.
      */
     private int getDimSide(int x) {
-        if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
+        if (!mTask.mStack.inFreeformWindowingMode()
                 || !mTask.mStack.fillsParent()
                 || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
             return CTRL_NONE;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 751769a..54ef065 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -254,7 +254,7 @@
     @VisibleForTesting
     int getSnapshotMode(Task task) {
         final AppWindowToken topChild = task.getTopChild();
-        if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
+        if (!task.isActivityTypeStandardOrUndefined()) {
             return SNAPSHOT_MODE_NONE;
         } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
             return SNAPSHOT_MODE_APP_THEME;
@@ -294,7 +294,9 @@
         decorPainter.drawDecors(c, null /* statusBarExcludeFrame */);
         node.end(c);
         final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
-
+        if (hwBitmap == null) {
+            return null;
+        }
         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
                 ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 4698d72..3ce090a 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -304,9 +304,11 @@
         final SurfaceSession session = new SurfaceSession(mSurface);
 
         // Keep a reference to it such that it doesn't get destroyed when finalized.
-        mChildSurfaceControl = new SurfaceControl(session,
-                mTitle + " - task-snapshot-surface",
-                buffer.getWidth(), buffer.getHeight(), buffer.getFormat(), HIDDEN);
+        mChildSurfaceControl = new SurfaceControl.Builder(session)
+                .setName(mTitle + " - task-snapshot-surface")
+                .setSize(buffer.getWidth(), buffer.getHeight())
+                .setFormat(buffer.getFormat())
+                .build();
         Surface surface = new Surface();
         surface.copyFrom(mChildSurfaceControl);
 
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7cb90de..7cee66b 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -16,11 +16,12 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -44,7 +45,6 @@
 import static com.android.server.wm.proto.StackProto.WINDOW_CONTAINER;
 
 import android.annotation.CallSuper;
-import android.app.ActivityManager.StackId;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -294,7 +294,7 @@
         if (mFillsParent
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
-                || mDisplayContent.getDockedStackLocked() != null) {
+                || mDisplayContent.getSplitScreenPrimaryStackStack() != null) {
             return true;
         }
         return false;
@@ -407,7 +407,7 @@
             return false;
         }
 
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             getAnimationOrCurrentBounds(mTmpRect2);
             boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
                     mTmpRect2, mTmpRect3);
@@ -441,21 +441,19 @@
 
         mTmpRect2.set(mBounds);
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
-        switch (mStackId) {
-            case DOCKED_STACK_ID:
-                repositionDockedStackAfterRotation(mTmpRect2);
-                snapDockedStackAfterRotation(mTmpRect2);
-                final int newDockSide = getDockSide(mTmpRect2);
+        if (inSplitScreenPrimaryWindowingMode()) {
+            repositionDockedStackAfterRotation(mTmpRect2);
+            snapDockedStackAfterRotation(mTmpRect2);
+            final int newDockSide = getDockSide(mTmpRect2);
 
-                // Update the dock create mode and clear the dock create bounds, these
-                // might change after a rotation and the original values will be invalid.
-                mService.setDockedStackCreateStateLocked(
-                        (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
-                                ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
-                                : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
-                        null);
-                mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
-                break;
+            // Update the dock create mode and clear the dock create bounds, these
+            // might change after a rotation and the original values will be invalid.
+            mService.setDockedStackCreateStateLocked(
+                    (newDockSide == DOCKED_LEFT || newDockSide == DOCKED_TOP)
+                            ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                            : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT,
+                    null);
+            mDisplayContent.getDockedDividerController().notifyDockSideChanged(newDockSide);
         }
 
         mBoundsAfterRotation.set(mTmpRect2);
@@ -675,6 +673,16 @@
         }
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final int prevWindowingMode = getWindowingMode();
+        super.onConfigurationChanged(newParentConfig);
+        if (mDisplayContent != null && prevWindowingMode != getWindowingMode()) {
+            mDisplayContent.onStackWindowingModeChanged(this);
+        }
+    }
+
+    @Override
     void onDisplayChanged(DisplayContent dc) {
         if (mDisplayContent != null) {
             throw new IllegalStateException("onDisplayChanged: Already attached");
@@ -685,8 +693,8 @@
                 "animation background stackId=" + mStackId);
 
         Rect bounds = null;
-        final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
-        if (mStackId == DOCKED_STACK_ID
+        final TaskStack dockedStack = dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
+        if (inSplitScreenPrimaryWindowingMode()
                 || (dockedStack != null && inSplitScreenSecondaryWindowingMode()
                         && !dockedStack.fillsParent())) {
             // The existence of a docked stack affects the size of other static stack created since
@@ -700,11 +708,11 @@
                 dockedStack.getRawBounds(mTmpRect2);
             }
             final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
-                    == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-            getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
+                    == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+            getStackDockedModeBounds(mTmpRect, bounds, mTmpRect2,
                     mDisplayContent.mDividerControllerLocked.getContentWidth(),
                     dockedOnTopOrLeft);
-        } else if (mStackId == PINNED_STACK_ID) {
+        } else if (inPinnedWindowingMode()) {
             // Update the bounds based on any changes to the display info
             getAnimationOrCurrentBounds(mTmpRect2);
             boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
@@ -736,7 +744,7 @@
         outTempTaskBounds.setEmpty();
 
         // When the home stack is resizable, should always have the same stack and task bounds
-        if (mStackId == HOME_STACK_ID) {
+        if (isActivityTypeHome()) {
             final Task homeTask = findHomeTask();
             if (homeTask != null && homeTask.isResizeable()) {
                 // Calculate the home stack bounds when in docked mode and the home stack is
@@ -764,7 +772,8 @@
             return;
         }
 
-        final TaskStack dockedStack = mDisplayContent.getDockedStackIgnoringVisibility();
+        final TaskStack dockedStack =
+                mDisplayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
         if (dockedStack == null) {
             // Not sure why you are calling this method when there is no docked stack...
             throw new IllegalStateException(
@@ -789,7 +798,7 @@
         mDisplayContent.getLogicalDisplayRect(mTmpRect);
         dockedStack.getRawBounds(mTmpRect2);
         final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
-        getStackDockedModeBounds(mTmpRect, outStackBounds, mStackId, mTmpRect2,
+        getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2,
                 mDisplayContent.mDividerControllerLocked.getContentWidth(), dockedOnTopOrLeft);
 
     }
@@ -798,16 +807,15 @@
      * Outputs the bounds a stack should be given the presence of a docked stack on the display.
      * @param displayRect The bounds of the display the docked stack is on.
      * @param outBounds Output bounds that should be used for the stack.
-     * @param stackId Id of stack we are calculating the bounds for.
      * @param dockedBounds Bounds of the docked stack.
      * @param dockDividerWidth We need to know the width of the divider make to the output bounds
      *                         close to the side of the dock.
      * @param dockOnTopOrLeft If the docked stack is on the top or left side of the screen.
      */
     private void getStackDockedModeBounds(
-            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int dockDividerWidth,
+            Rect displayRect, Rect outBounds, Rect dockedBounds, int dockDividerWidth,
             boolean dockOnTopOrLeft) {
-        final boolean dockedStack = stackId == DOCKED_STACK_ID;
+        final boolean dockedStack = inSplitScreenPrimaryWindowingMode();
         final boolean splitHorizontally = displayRect.width() > displayRect.height();
 
         outBounds.set(displayRect);
@@ -864,7 +872,7 @@
     }
 
     void resetDockedStackToMiddle() {
-        if (mStackId != DOCKED_STACK_ID) {
+        if (inSplitScreenPrimaryWindowingMode()) {
             throw new IllegalStateException("Not a docked stack=" + this);
         }
 
@@ -892,17 +900,12 @@
     }
 
     @Override
-    void removeImmediately() {
-        super.removeImmediately();
+    void onParentSet() {
+        if (getParent() != null || mDisplayContent == null) {
+            return;
+        }
 
-        onRemovedFromDisplay();
-    }
-
-    /**
-     * Removes the stack it from its current parent, so it can be either destroyed completely or
-     * re-parented.
-     */
-    void onRemovedFromDisplay() {
+        // Looks like the stack was removed from the display. Go ahead and clean things up.
         mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
@@ -911,7 +914,7 @@
             mAnimationBackgroundSurface = null;
         }
 
-        if (mStackId == DOCKED_STACK_ID) {
+        if (inSplitScreenPrimaryWindowingMode()) {
             mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
         }
 
@@ -921,7 +924,9 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        mAnimationBackgroundSurface.hide();
+        if (mAnimationBackgroundSurface != null) {
+            mAnimationBackgroundSurface.hide();
+        }
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -1003,12 +1008,15 @@
     void resetAdjustedForIme(boolean adjustBoundsNow) {
         if (adjustBoundsNow) {
             mImeWin = null;
-            mAdjustedForIme = false;
             mImeGoingAway = false;
             mAdjustImeAmount = 0f;
             mAdjustDividerAmount = 0f;
+            if (!mAdjustedForIme) {
+                return;
+            }
+            mAdjustedForIme = false;
             updateAdjustedBounds();
-            mService.setResizeDimLayer(false, mStackId, 1.0f);
+            mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
         } else {
             mImeGoingAway |= mAdjustedForIme;
         }
@@ -1031,8 +1039,8 @@
     }
 
     boolean shouldIgnoreInput() {
-        return isAdjustedForMinimizedDockedStack() || mStackId == DOCKED_STACK_ID &&
-                isMinimizedDockAndHomeStackResizable();
+        return isAdjustedForMinimizedDockedStack() ||
+                (inSplitScreenPrimaryWindowingMode() && isMinimizedDockAndHomeStackResizable());
     }
 
     /**
@@ -1204,7 +1212,7 @@
         if (mAdjustedForIme && adjust && !isImeTarget) {
             final float alpha = Math.max(mAdjustImeAmount, mAdjustDividerAmount)
                     * IME_ADJUST_DIM_AMOUNT;
-            mService.setResizeDimLayer(true, mStackId, alpha);
+            mService.setResizeDimLayer(true, getWindowingMode(), alpha);
         }
     }
 
@@ -1224,12 +1232,12 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(ID, mStackId);
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
-            mChildren.get(taskNdx).writeToProto(proto, TASKS);
+            mChildren.get(taskNdx).writeToProto(proto, TASKS, trim);
         }
         proto.write(FILLS_PARENT, mFillsParent);
         mBounds.writeToProto(proto, BOUNDS);
@@ -1283,7 +1291,7 @@
 
     @Override
     public boolean dimFullscreen() {
-        return StackId.isHomeOrRecentsStack(mStackId) || fillsParent();
+        return !isActivityTypeStandard() || fillsParent();
     }
 
     @Override
@@ -1467,7 +1475,7 @@
                 postExclude.set(mTmpRect);
             }
 
-            final boolean isFreeformed = task.inFreeformWorkspace();
+            final boolean isFreeformed = task.inFreeformWindowingMode();
             if (task != focusedTask || isFreeformed) {
                 if (isFreeformed) {
                     // If the task is freeformed, enlarge the area to account for outside
@@ -1525,7 +1533,7 @@
             }
         }
 
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             try {
                 mService.mActivityManager.notifyPinnedStackAnimationStarted();
             } catch (RemoteException e) {
@@ -1557,7 +1565,7 @@
             mService.requestTraversal();
         }
 
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             // Update to the final bounds if requested. This is done here instead of in the bounds
             // animator to allow us to coordinate this after we notify the PiP mode changed
 
@@ -1591,7 +1599,7 @@
      *         bounds and we have a deferred PiP mode changed callback set with the animation.
      */
     public boolean deferScheduleMultiWindowModeChanged() {
-        if (mStackId == PINNED_STACK_ID) {
+        if (inPinnedWindowingMode()) {
             return (mBoundsAnimatingRequested || mBoundsAnimating);
         }
         return false;
@@ -1663,7 +1671,15 @@
 
     @Override
     int getOrientation() {
-        return (StackId.canSpecifyOrientation(mStackId))
-                ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+        return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+    }
+
+    private boolean canSpecifyOrientation() {
+        final int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || activityType == ACTIVITY_TYPE_HOME
+                || activityType == ACTIVITY_TYPE_RECENTS
+                || activityType == ACTIVITY_TYPE_ASSISTANT;
     }
 }
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 7213c95..629cc86 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -18,7 +18,7 @@
 
 import com.android.internal.util.ToBooleanFunction;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -447,7 +447,7 @@
 
     private void findWallpaperTarget(DisplayContent dc) {
         mFindResults.reset();
-        if (dc.isStackVisible(FREEFORM_WORKSPACE_STACK_ID)) {
+        if (dc.isStackVisible(WINDOWING_MODE_FREEFORM)) {
             // In freeform mode we set the wallpaper as its own target, so we don't need an
             // additional window to make it visible.
             mFindResults.setUseTopWallpaperAsTarget(true);
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index 171e575..d97aaac 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -114,8 +114,11 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl(session, "WatermarkSurface",
-                    1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl = new SurfaceControl.Builder(session)
+                    .setName("WatermarkSurface")
+                    .setSize(1, 1)
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .build();
             ctrl.setLayerStack(mDisplay.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
             ctrl.setPosition(0, 0);
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c01ee31..e409a68 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -223,10 +223,6 @@
                     }
                 }
 
-                if (mService.mDragState != null) {
-                    mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
-                }
-
                 if (!mAnimating) {
                     cancelAnimation();
                 }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 40923c8..8f4b897 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -73,12 +73,12 @@
 
 
     @Override
-    final protected int getChildCount() {
+    protected int getChildCount() {
         return mChildren.size();
     }
 
     @Override
-    final protected E getChildAt(int index) {
+    protected E getChildAt(int index) {
         return mChildren.get(index);
     }
 
@@ -674,38 +674,21 @@
     }
 
     /**
-     * Dumps the names of this container children in the input print writer indenting each
-     * level with the input prefix.
-     */
-    void dumpChildrenNames(StringBuilder out, String prefix) {
-        final String childPrefix = prefix + " ";
-        out.append(getName() + "\n");
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer wc = mChildren.get(i);
-            out.append(childPrefix + "#" + i + " ");
-            wc.dumpChildrenNames(out, childPrefix);
-        }
-    }
-
-    /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
      *
-     * @param protoOutputStream Stream to write the WindowContainer object to.
-     * @param fieldId           Field Id of the WindowContainer as defined in the parent message.
+     * @param proto     Stream to write the WindowContainer object to.
+     * @param fieldId   Field Id of the WindowContainer as defined in the parent message.
+     * @param trim      If true, reduce the amount of data written.
      * @hide
      */
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
-        final long token = protoOutputStream.start(fieldId);
-        super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER);
-        protoOutputStream.write(ORIENTATION, mOrientation);
-        protoOutputStream.end(token);
-    }
-
-    String getName() {
-        return toString();
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER, trim);
+        proto.write(ORIENTATION, mOrientation);
+        proto.end(token);
     }
 
     private ForAllWindowsConsumerWrapper obtainConsumerWrapper(Consumer<WindowState> consumer) {
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 857b13d..7caf2fe 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -21,11 +21,8 @@
 import java.util.ArrayDeque;
 import java.util.function.Consumer;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -187,12 +184,13 @@
             }
         }
 
-        final int stackId = w.getAppToken() != null ? w.getStackId() : INVALID_STACK_ID;
-        if (stackId == PINNED_STACK_ID) {
+        final int windowingMode = w.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_PINNED) {
             mPinnedWindows.add(w);
-        } else if (stackId == DOCKED_STACK_ID) {
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mDockedWindows.add(w);
-        } else if (stackId == ASSISTANT_STACK_ID) {
+        }
+        if (w.isActivityTypeAssistant()) {
             mAssistantWindows.add(w);
         }
     }
@@ -268,20 +266,8 @@
         w.mLayer = layer;
         w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
                 + w.getSpecialWindowAnimLayerAdjustment();
-        if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0) {
-            if (w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
-                w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
-            }
-            // TODO(b/62029108): the entire contents of the if statement should call the refactored
-            // function to set the thumbnail layer for w.AppToken
-            int highestLayer = w.mAppToken.getHighestAnimLayer();
-            if (highestLayer > 0) {
-                if (w.mAppToken.mAppAnimator.thumbnail != null
-                        && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer != highestLayer) {
-                    w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = highestLayer;
-                    w.mAppToken.mAppAnimator.thumbnail.setLayer(highestLayer + 1);
-                }
-            }
+        if (w.mAppToken != null) {
+            w.mAppToken.mAppAnimator.updateThumbnailLayer();
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 6d5673e..9d9805a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -60,7 +60,6 @@
     static final boolean DEBUG_SCREENSHOT = false;
     static final boolean DEBUG_BOOT = false;
     static final boolean DEBUG_LAYOUT_REPEATS = false;
-    static final boolean DEBUG_SURFACE_TRACE = false;
     static final boolean DEBUG_WINDOW_TRACE = false;
     static final boolean DEBUG_TASK_MOVEMENT = false;
     static final boolean DEBUG_TASK_POSITIONING = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6a5f6fa..391f36e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -20,7 +20,7 @@
 import static android.Manifest.permission.READ_FRAME_BUFFER;
 import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS;
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
-import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
@@ -137,7 +137,6 @@
 import android.graphics.Bitmap;
 import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -210,7 +209,6 @@
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
@@ -226,7 +224,7 @@
 import android.view.inputmethod.InputMethodManagerInternal;
 
 import com.android.internal.R;
-import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.app.IAssistDataReceiver;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -357,8 +355,6 @@
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
-    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
-
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
@@ -394,13 +390,14 @@
 
     private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
         @Override
-        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, new String[] {"-a"});
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
+            doDump(fd, pw, new String[] {"-a"}, asProto);
         }
 
         @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            doDump(fd, pw, args);
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+            doDump(fd, pw, args, asProto);
         }
     };
 
@@ -561,7 +558,7 @@
     // The root of the device window hierarchy.
     RootWindowContainer mRoot;
 
-    int mDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+    int mDockedStackCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
     Rect mDockedStackCreateBounds;
 
     private final SparseIntArray mTmpTaskIds = new SparseIntArray();
@@ -752,7 +749,7 @@
     boolean mAllowTheaterModeWakeFromLayout;
 
     TaskPositioner mTaskPositioner;
-    DragState mDragState = null;
+    final DragDropController mDragDropController;
 
     // For frozen screen animations.
     private int mExitAnimId, mEnterAnimId;
@@ -772,110 +769,6 @@
 
     private WindowContentFrameStats mTempWindowRenderStats;
 
-    final class DragInputEventReceiver extends InputEventReceiver {
-        // Set, if stylus button was down at the start of the drag.
-        private boolean mStylusButtonDownAtStart;
-        // Indicates the first event to check for button state.
-        private boolean mIsStartEvent = true;
-        // Set to true to ignore input events after the drag gesture is complete but the drag events
-        // are still being dispatched.
-        private boolean mMuteInput = false;
-
-        public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
-            super(inputChannel, looper);
-        }
-
-        @Override
-        public void onInputEvent(InputEvent event, int displayId) {
-            boolean handled = false;
-            try {
-                if (mDragState == null) {
-                    // The drag has ended but the clean-up message has not been processed by
-                    // window manager. Drop events that occur after this until window manager
-                    // has a chance to clean-up the input handle.
-                    handled = true;
-                    return;
-                }
-                if (event instanceof MotionEvent
-                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
-                        && !mMuteInput) {
-                    final MotionEvent motionEvent = (MotionEvent)event;
-                    boolean endDrag = false;
-                    final float newX = motionEvent.getRawX();
-                    final float newY = motionEvent.getRawY();
-                    final boolean isStylusButtonDown =
-                            (motionEvent.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
-
-                    if (mIsStartEvent) {
-                        if (isStylusButtonDown) {
-                            // First event and the button was down, check for the button being
-                            // lifted in the future, if that happens we'll drop the item.
-                            mStylusButtonDownAtStart = true;
-                        }
-                        mIsStartEvent = false;
-                    }
-
-                    switch (motionEvent.getAction()) {
-                    case MotionEvent.ACTION_DOWN: {
-                        if (DEBUG_DRAG) {
-                            Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
-                        }
-                    } break;
-
-                    case MotionEvent.ACTION_MOVE: {
-                        if (mStylusButtonDownAtStart && !isStylusButtonDown) {
-                            if (DEBUG_DRAG) Slog.d(TAG_WM, "Button no longer pressed; dropping at "
-                                    + newX + "," + newY);
-                            mMuteInput = true;
-                            synchronized (mWindowMap) {
-                                endDrag = mDragState.notifyDropLw(newX, newY);
-                            }
-                        } else {
-                            synchronized (mWindowMap) {
-                                // move the surface and tell the involved window(s) where we are
-                                mDragState.notifyMoveLw(newX, newY);
-                            }
-                        }
-                    } break;
-
-                    case MotionEvent.ACTION_UP: {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Got UP on move channel; dropping at "
-                                + newX + "," + newY);
-                        mMuteInput = true;
-                        synchronized (mWindowMap) {
-                            endDrag = mDragState.notifyDropLw(newX, newY);
-                        }
-                    } break;
-
-                    case MotionEvent.ACTION_CANCEL: {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
-                        mMuteInput = true;
-                        endDrag = true;
-                    } break;
-                    }
-
-                    if (endDrag) {
-                        if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ended; tearing down state");
-                        // tell all the windows that the drag has ended
-                        synchronized (mWindowMap) {
-                            // endDragLw will post back to looper to dispose the receiver
-                            // since we still need the receiver for the last finishInputEvent.
-                            mDragState.endDragLw();
-                        }
-                        mStylusButtonDownAtStart = false;
-                        mIsStartEvent = true;
-                    }
-
-                    handled = true;
-                }
-            } catch (Exception e) {
-                Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
-            } finally {
-                finishInputEvent(event, handled);
-            }
-        }
-    }
-
     /**
      * Whether the UI is currently running in touch mode (not showing
      * navigational focus because the user is directly pressing the screen).
@@ -1165,6 +1058,7 @@
         mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
 
+        mDragDropController = new DragDropController(this, mH.getLooper());
 
         LocalServices.addService(WindowManagerInternal.class, new LocalService());
         initPolicy();
@@ -2043,10 +1937,14 @@
                 Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
                         + " newVis=" + viewVisibility, stack);
             }
-            if (viewVisibility == View.VISIBLE &&
-                    (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
-                            || !win.mAppToken.isClientHidden())) {
 
+            // We should only relayout if the view is visible, it is a starting window, or the
+            // associated appToken is not hidden.
+            final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
+                (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
+                    || !win.mAppToken.isClientHidden());
+
+            if (shouldRelayout) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
 
                 // We are about to create a surface, but we didn't run a layout yet. So better run
@@ -2193,8 +2091,18 @@
             // and needs process it before handling the corresponding window frame. the variable
             // {@code mergedConfiguration} is an out parameter that will be passed back to the
             // client over IPC and checked there.
-            win.getMergedConfiguration(mergedConfiguration);
-            win.setReportedConfiguration(mergedConfiguration);
+            // Note: in the cases where the window is tied to an activity, we should not send a
+            // configuration update when the window has requested to be hidden. Doing so can lead
+            // to the client erroneously accepting a configuration that would have otherwise caused
+            // an activity restart. We instead hand back the last reported
+            // {@link MergedConfiguration}.
+            if (shouldRelayout) {
+                win.getMergedConfiguration(mergedConfiguration);
+            } else {
+                win.getLastReportedMergedConfiguration(mergedConfiguration);
+            }
+
+            win.setLastReportedMergedConfiguration(mergedConfiguration);
 
             outFrame.set(win.mCompatFrame);
             outOverscanInsets.set(win.mOverscanInsets);
@@ -2370,7 +2278,7 @@
             final Rect insets = new Rect();
             final Rect stableInsets = new Rect();
             Rect surfaceInsets = null;
-            final boolean freeform = win != null && win.inFreeformWorkspace();
+            final boolean freeform = win != null && win.inFreeformWindowingMode();
             if (win != null) {
                 // Containing frame will usually cover the whole screen, including dialog windows.
                 // For freeform workspace windows it will not cover the whole screen and it also
@@ -2780,7 +2688,7 @@
         for (final WindowState win : mWindowMap.values()) {
             final Task task = win.getTask();
             if (task != null && mTmpTaskIds.get(task.mTaskId, -1) != -1
-                    && task.inFreeformWorkspace()) {
+                    && task.inFreeformWindowingMode()) {
                 final AppWindowToken appToken = win.mAppToken;
                 if (appToken != null && appToken.mAppAnimator != null) {
                     appToken.mAppAnimator.startProlongAnimation(scaleUp ?
@@ -2895,9 +2803,9 @@
     }
 
     @Override
-    public void getStackBounds(int stackId, Rect bounds) {
+    public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
         synchronized (mWindowMap) {
-            final TaskStack stack = mRoot.getStackById(stackId);
+            final TaskStack stack = mRoot.getStack(windowingMode, activityType);
             if (stack != null) {
                 stack.getBounds(bounds);
                 return;
@@ -3377,7 +3285,8 @@
 
             // Notify whether the docked stack exists for the current user
             final DisplayContent displayContent = getDefaultDisplayContentLocked();
-            final TaskStack stack = displayContent.getDockedStackIgnoringVisibility();
+            final TaskStack stack =
+                    displayContent.getSplitScreenPrimaryStackStackIgnoringVisibility();
             displayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(
                     stack != null && stack.hasTaskForUser(newUserId));
 
@@ -3769,7 +3678,7 @@
      * of the target image.
      */
     @Override
-    public boolean requestAssistScreenshot(final IAssistScreenshotReceiver receiver) {
+    public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
         if (!checkCallingPermission(READ_FRAME_BUFFER,
                 "requestAssistScreenshot()")) {
             throw new SecurityException("Requires READ_FRAME_BUFFER permission");
@@ -3781,7 +3690,7 @@
                     1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
                     false /* includeDecor */);
             try {
-                receiver.send(bm);
+                receiver.onHandleAssistScreenshot(bm);
             } catch (RemoteException e) {
             }
         });
@@ -4622,73 +4531,6 @@
     }
 
     // -------------------------------------------------------------
-    // Drag and drop
-    // -------------------------------------------------------------
-
-    IBinder prepareDragSurface(IWindow window, SurfaceSession session,
-            int flags, int width, int height, Surface outSurface) {
-        if (DEBUG_DRAG) {
-            Slog.d(TAG_WM, "prepare drag surface: w=" + width + " h=" + height
-                    + " flags=" + Integer.toHexString(flags) + " win=" + window
-                    + " asbinder=" + window.asBinder());
-        }
-
-        final int callerPid = Binder.getCallingPid();
-        final int callerUid = Binder.getCallingUid();
-        final long origId = Binder.clearCallingIdentity();
-        IBinder token = null;
-
-        try {
-            synchronized (mWindowMap) {
-                try {
-                    if (mDragState == null) {
-                        // TODO(multi-display): support other displays
-                        final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                        final Display display = displayContent.getDisplay();
-
-                        SurfaceControl surface = new SurfaceControl(session, "drag surface",
-                                width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
-                        surface.setLayerStack(display.getLayerStack());
-                        float alpha = 1;
-                        if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
-                            alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
-                        }
-                        surface.setAlpha(alpha);
-
-                        if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
-                                + surface + ": CREATE");
-                        outSurface.copyFrom(surface);
-                        final IBinder winBinder = window.asBinder();
-                        token = new Binder();
-                        mDragState = new DragState(this, token, surface, flags, winBinder);
-                        mDragState.mPid = callerPid;
-                        mDragState.mUid = callerUid;
-                        mDragState.mOriginalAlpha = alpha;
-                        token = mDragState.mToken = new Binder();
-
-                        // 5 second timeout for this window to actually begin the drag
-                        mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
-                        Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
-                        mH.sendMessageDelayed(msg, 5000);
-                    } else {
-                        Slog.w(TAG_WM, "Drag already in progress");
-                    }
-                } catch (OutOfResourcesException e) {
-                    Slog.e(TAG_WM, "Can't allocate drag surface w=" + width + " h=" + height, e);
-                    if (mDragState != null) {
-                        mDragState.reset();
-                        mDragState = null;
-                    }
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-
-        return token;
-    }
-
-    // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
 
@@ -4849,8 +4691,7 @@
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int SEND_NEW_CONFIGURATION = 18;
         public static final int REPORT_WINDOWS_CHANGE = 19;
-        public static final int DRAG_START_TIMEOUT = 20;
-        public static final int DRAG_END_TIMEOUT = 21;
+
         public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
         public static final int BOOT_TIMEOUT = 23;
         public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
@@ -4876,8 +4717,6 @@
 
         public static final int UPDATE_DOCKED_STACK_DIVIDER = 41;
 
-        public static final int TEAR_DOWN_DRAG_AND_DROP_INPUT = 44;
-
         public static final int WINDOW_REPLACEMENT_TIMEOUT = 46;
 
         public static final int NOTIFY_APP_TRANSITION_STARTING = 47;
@@ -5105,48 +4944,6 @@
                     break;
                 }
 
-                case DRAG_START_TIMEOUT: {
-                    IBinder win = (IBinder)msg.obj;
-                    if (DEBUG_DRAG) {
-                        Slog.w(TAG_WM, "Timeout starting drag by win " + win);
-                    }
-                    synchronized (mWindowMap) {
-                        // !!! TODO: ANR the app that has failed to start the drag in time
-                        if (mDragState != null) {
-                            mDragState.unregister();
-                            mDragState.reset();
-                            mDragState = null;
-                        }
-                    }
-                    break;
-                }
-
-                case DRAG_END_TIMEOUT: {
-                    IBinder win = (IBinder)msg.obj;
-                    if (DEBUG_DRAG) {
-                        Slog.w(TAG_WM, "Timeout ending drag to win " + win);
-                    }
-                    synchronized (mWindowMap) {
-                        // !!! TODO: ANR the drag-receiving app
-                        if (mDragState != null) {
-                            mDragState.mDragResult = false;
-                            mDragState.endDragLw();
-                        }
-                    }
-                    break;
-                }
-
-                case TEAR_DOWN_DRAG_AND_DROP_INPUT: {
-                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag ending; tearing down input channel");
-                    DragState.InputInterceptor interceptor = (DragState.InputInterceptor) msg.obj;
-                    if (interceptor != null) {
-                        synchronized (mWindowMap) {
-                            interceptor.tearDown();
-                        }
-                    }
-                }
-                break;
-
                 case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
                     notifyHardKeyboardStatusChange();
                     break;
@@ -6340,9 +6137,10 @@
     }
 
     @Override
-    public void createInputConsumer(String name, InputChannel inputChannel) {
+    public void createInputConsumer(IBinder token, String name, InputChannel inputChannel) {
         synchronized (mWindowMap) {
-            mInputMonitor.createInputConsumer(name, inputChannel);
+            mInputMonitor.createInputConsumer(token, name, inputChannel, Binder.getCallingPid(),
+                    Binder.getCallingUserHandle());
         }
     }
 
@@ -6517,9 +6315,16 @@
         }
     }
 
-    private void writeToProtoLocked(ProtoOutputStream proto) {
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at
+     * {@link com.android.server.wm.proto.WindowManagerServiceProto}.
+     *
+     * @param proto     Stream to write the WindowContainer object to.
+     * @param trim      If true, reduce the amount of data written.
+     */
+    private void writeToProtoLocked(ProtoOutputStream proto, boolean trim) {
         mPolicy.writeToProto(proto, POLICY);
-        mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER);
+        mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER, trim);
         if (mCurrentFocus != null) {
             mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
         }
@@ -6810,10 +6615,9 @@
         PriorityDump.dump(mPriorityDumper, fd, pw, args);
     }
 
-    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
         boolean dumpAll = false;
-        boolean useProto = false;
 
         int opti = 0;
         while (opti < args.length) {
@@ -6824,8 +6628,6 @@
             opti++;
             if ("-a".equals(opt)) {
                 dumpAll = true;
-            } else if ("--proto".equals(opt)) {
-                useProto = true;
             } else if ("-h".equals(opt)) {
                 pw.println("Window manager dump options:");
                 pw.println("  [-a] [-h] [cmd] ...");
@@ -6855,7 +6657,7 @@
         if (useProto) {
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
             synchronized (mWindowMap) {
-                writeToProtoLocked(proto);
+                writeToProtoLocked(proto, false /* trim */);
             }
             proto.flush();
             return;
@@ -6884,11 +6686,6 @@
                     dumpSessionsLocked(pw, true);
                 }
                 return;
-            } else if ("surfaces".equals(cmd)) {
-                synchronized(mWindowMap) {
-                    WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, null);
-                }
-                return;
             } else if ("displays".equals(cmd) || "d".equals(cmd)) {
                 synchronized(mWindowMap) {
                     mRoot.dumpDisplayContents(pw);
@@ -6911,9 +6708,7 @@
                 return;
             } else if ("containers".equals(cmd)) {
                 synchronized(mWindowMap) {
-                    StringBuilder output = new StringBuilder();
-                    mRoot.dumpChildrenNames(output, " ");
-                    pw.println(output.toString());
+                    mRoot.dumpChildrenNames(pw, " ");
                     pw.println(" ");
                     mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */);
                 }
@@ -6953,10 +6748,6 @@
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            WindowSurfaceController.SurfaceTrace.dumpAllSurfaces(pw, dumpAll ?
-                    "-------------------------------------------------------------------------------"
-                    : null);
-            pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
@@ -7118,7 +6909,7 @@
     public int getDockedStackSide() {
         synchronized (mWindowMap) {
             final TaskStack dockedStack = getDefaultDisplayContentLocked()
-                    .getDockedStackIgnoringVisibility();
+                    .getSplitScreenPrimaryStackStackIgnoringVisibility();
             return dockedStack == null ? DOCKED_INVALID : dockedStack.getDockSide();
         }
     }
@@ -7141,10 +6932,10 @@
     }
 
     @Override
-    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         synchronized (mWindowMap) {
             getDefaultDisplayContentLocked().getDockedDividerController().setResizeDimLayer(
-                    visible, targetStackId, alpha);
+                    visible, targetWindowingMode, alpha);
         }
     }
 
@@ -7264,7 +7055,7 @@
         }
 
         synchronized (mWindowMap) {
-            if (mDragState != null) {
+            if (mDragDropController.mDragState != null) {
                 // Drag cursor overrides the app cursor.
                 return;
             }
@@ -7590,10 +7381,10 @@
         }
 
         @Override
-        public boolean isStackVisible(int stackId) {
+        public boolean isStackVisible(int windowingMode) {
             synchronized (mWindowMap) {
                 final DisplayContent dc = getDefaultDisplayContentLocked();
-                return dc.isStackVisible(stackId);
+                return dc.isStackVisible(windowingMode);
             }
         }
 
@@ -7698,7 +7489,8 @@
     }
 
     boolean hasWideColorGamutSupport() {
-        return mHasWideColorGamutSupport;
+        return mHasWideColorGamutSupport &&
+                !SystemProperties.getBoolean("persist.sys.sf.native_mode", false);
     }
 
     void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8a2cb5a..4370a76 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -16,10 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
@@ -83,7 +80,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -257,7 +253,7 @@
      * We'll send configuration to client only if it is different from the last applied one and
      * client won't perform unnecessary updates.
      */
-    private final Configuration mLastReportedConfiguration = new Configuration();
+    private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
 
     /**
      * Actual position of the surface shown on-screen (may be modified by animation). These are
@@ -815,13 +811,12 @@
             final WindowState imeWin = mService.mInputMethodWindow;
             // IME is up and obscuring this window. Adjust the window position so it is visible.
             if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this) {
-                final int stackId = getStackId();
-                if (stackId == FREEFORM_WORKSPACE_STACK_ID
+                if (inFreeformWindowingMode()
                         && mContainingFrame.bottom > contentFrame.bottom) {
                     // In freeform we want to move the top up directly.
                     // TODO: Investigate why this is contentFrame not parentFrame.
                     mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
-                } else if (stackId != PINNED_STACK_ID
+                } else if (!inPinnedWindowingMode()
                         && mContainingFrame.bottom > parentFrame.bottom) {
                     // But in docked we want to behave like fullscreen and behave as if the task
                     // were given smaller bounds for the purposes of layout. Skip adjustments for
@@ -898,7 +893,7 @@
             // For pinned workspace the frame isn't limited in any particular
             // way since SystemUI controls the bounds. For freeform however
             // we want to keep things inside the content frame.
-            final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame;
+            final Rect limitFrame = task.inPinnedWindowingMode() ? mFrame : mContentFrame;
             // Keep the frame out of the blocked system area, limit it in size to the content area
             // and make sure that there is always a minimum visible so that the user can drag it
             // into a usable area..
@@ -1210,7 +1205,7 @@
             // application when it has finished drawing.
             if (getOrientationChanging() || dragResizingChanged
                     || isResizedWhileNotDragResizing()) {
-                if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
+                if (DEBUG_ANIM || DEBUG_ORIENTATION || DEBUG_RESIZE) {
                     Slog.v(TAG_WM, "Orientation or resize start waiting for draw"
                             + ", mDrawState=DRAW_PENDING in " + this
                             + ", surfaceController " + winAnimator.mSurfaceController);
@@ -1244,7 +1239,7 @@
         //                   this is not necessarily what the client has processed yet. Find a
         //                   better indicator consistent with the client.
         return (mOrientationChanging || (isVisible()
-                && getConfiguration().orientation != mLastReportedConfiguration.orientation))
+                && getConfiguration().orientation != getLastReportedConfiguration().orientation))
                 && !mSeamlesslyRotated
                 && !mOrientationChangeTimedOut;
     }
@@ -1662,9 +1657,9 @@
             //
             // Anyway we don't need to synchronize position and content updates for these
             // windows since they aren't at the base layer and could be moved around anyway.
-            if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION &&
-                    !mWinAnimator.isForceScaled() && !isGoneForLayoutLw() &&
-                    !getTask().inPinnedWorkspace()) {
+            if (!computeDragResizing() && mAttrs.type == TYPE_BASE_APPLICATION
+                    && !mWinAnimator.isForceScaled() && !isGoneForLayoutLw()
+                    && !getTask().inPinnedWindowingMode()) {
                 setResizedWhileNotDragResizing(true);
             }
         }
@@ -1758,7 +1753,7 @@
 
     /** Returns true if last applied config was not yet requested by client. */
     boolean isConfigChanged() {
-        return !mLastReportedConfiguration.equals(getConfiguration());
+        return !getLastReportedConfiguration().equals(getConfiguration());
     }
 
     void onWindowReplacementTimeout() {
@@ -2196,12 +2191,6 @@
         }
     }
 
-    // TODO: Strange usage of word workspace here and above.
-    boolean inPinnedWorkspace() {
-        final Task task = getTask();
-        return task != null && task.inPinnedWorkspace();
-    }
-
     void applyAdjustForImeIfNeeded() {
         final Task task = getTask();
         if (task != null && task.mStack != null && task.mStack.isAdjustedForIme()) {
@@ -2235,7 +2224,7 @@
             } else {
                 getVisibleBounds(mTmpRect);
             }
-            if (inFreeformWorkspace()) {
+            if (inFreeformWindowingMode()) {
                 // For freeform windows we the touch region to include the whole surface for the
                 // shadows.
                 final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
@@ -2312,8 +2301,16 @@
         outConfiguration.setConfiguration(globalConfig, overrideConfig);
     }
 
-    void setReportedConfiguration(MergedConfiguration config) {
-        mLastReportedConfiguration.setTo(config.getMergedConfiguration());
+    void setLastReportedMergedConfiguration(MergedConfiguration config) {
+        mLastReportedConfiguration.setTo(config);
+    }
+
+    void getLastReportedMergedConfiguration(MergedConfiguration config) {
+        config.setTo(mLastReportedConfiguration);
+    }
+
+    private Configuration getLastReportedConfiguration() {
+        return mLastReportedConfiguration.getMergedConfiguration();
     }
 
     void adjustStartingWindowFlags() {
@@ -2363,7 +2360,8 @@
                             // just in case they have the divider at an unstable position. Better
                             // also reset drag resizing state, because the owner can't do it
                             // anymore.
-                            final TaskStack stack = dc.getDockedStackIgnoringVisibility();
+                            final TaskStack stack =
+                                    dc.getSplitScreenPrimaryStackStackIgnoringVisibility();
                             if (stack != null) {
                                 stack.resetDockedStackToMiddle();
                             }
@@ -2853,7 +2851,7 @@
                     new MergedConfiguration(mService.mRoot.getConfiguration(),
                     getMergedOverrideConfiguration());
 
-            setReportedConfiguration(mergedConfiguration);
+            setLastReportedMergedConfiguration(mergedConfiguration);
 
             if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
                 Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
@@ -2930,8 +2928,7 @@
         return mTmpRect;
     }
 
-    @Override
-    public int getStackId() {
+    private int getStackId() {
         final TaskStack stack = getStack();
         if (stack == null) {
             return INVALID_STACK_ID;
@@ -2975,11 +2972,6 @@
         }
     }
 
-    boolean inFreeformWorkspace() {
-        final Task task = getTask();
-        return task != null && task.inFreeformWorkspace();
-    }
-
     @Override
     public boolean isInMultiWindowMode() {
         final Task task = getTask();
@@ -3080,7 +3072,7 @@
         if (task == null) {
             return false;
         }
-        if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+        if (!inSplitScreenWindowingMode()) {
             return false;
         }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3097,7 +3089,7 @@
         // background.
         return (getDisplayContent().mDividerControllerLocked.isResizing()
                         || mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) &&
-                !task.inFreeformWorkspace() && !isGoneForLayoutLw();
+                !task.inFreeformWindowingMode() && !isGoneForLayoutLw();
 
     }
 
@@ -3128,9 +3120,9 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(DISPLAY_ID, getDisplayId());
         proto.write(STACK_ID, getStackId());
@@ -3145,7 +3137,7 @@
         mWinAnimator.writeToProto(proto, ANIMATOR);
         proto.write(ANIMATING_EXIT, mAnimatingExit);
         for (int i = 0; i < mChildren.size(); i++) {
-            mChildren.get(i).writeToProto(proto, CHILD_WINDOWS);
+            mChildren.get(i).writeToProto(proto, CHILD_WINDOWS, trim);
         }
         proto.end(token);
     }
@@ -3173,7 +3165,7 @@
                 pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
                 pw.print(" package="); pw.print(mAttrs.packageName);
                 pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
-        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
+        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs.toString(prefix));
         pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
                 pw.print(" h="); pw.print(mRequestedHeight);
                 pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
@@ -3254,7 +3246,7 @@
             }
             pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration());
             pw.print(prefix); pw.print("mLastReportedConfiguration=");
-                    pw.println(mLastReportedConfiguration);
+                    pw.println(getLastReportedConfiguration());
         }
         pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
                 pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
@@ -3314,7 +3306,7 @@
             pw.print(prefix); pw.print("mOrientationChanging=");
                     pw.print(mOrientationChanging);
                     pw.print(" configOrientationChanging=");
-                    pw.print(mLastReportedConfiguration.orientation
+                    pw.print(getLastReportedConfiguration().orientation
                             != getConfiguration().orientation);
                     pw.print(" mAppFreezing="); pw.print(mAppFreezing);
                     pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
@@ -3687,7 +3679,7 @@
 
         // Force the show in the next prepareSurfaceLocked() call.
         mWinAnimator.mLastAlpha = -1;
-        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) Slog.v(TAG,
+        if (DEBUG_ANIM) Slog.v(TAG,
                 "performShowLocked: mDrawState=HAS_DRAWN in " + this);
         mWinAnimator.mDrawState = HAS_DRAWN;
         mService.scheduleAnimationLocked();
@@ -3748,7 +3740,7 @@
         windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
         windowInfo.focused = isFocused();
         Task task = getTask();
-        windowInfo.inPictureInPicture = (task != null) && task.inPinnedWorkspace();
+        windowInfo.inPictureInPicture = (task != null) && task.inPinnedWindowingMode();
 
         if (mIsChildWindow) {
             windowInfo.parentToken = getParentWindow().mClient.asBinder();
@@ -4211,7 +4203,7 @@
         // If a freeform window is animating from a position where it would be cutoff, it would be
         // cutoff during the animation. We don't want that, so for the duration of the animation
         // we ignore the decor cropping and depend on layering to position windows correctly.
-        final boolean cropToDecor = !(inFreeformWorkspace() && isAnimatingLw());
+        final boolean cropToDecor = !(inFreeformWindowingMode() && isAnimatingLw());
         if (cropToDecor) {
             // Intersect with the decor rect, offsetted by window position.
             systemDecorRect.intersect(decorRect.left - left, decorRect.top - top,
@@ -4295,7 +4287,7 @@
         // scale for the animation using the source hint rect
         // (see WindowStateAnimator#setSurfaceBoundariesLocked()).
         if (isDragResizeChanged() || isResizedWhileNotDragResizing()
-                || (surfaceInsetsChanging() && !inPinnedWorkspace())) {
+                || (surfaceInsetsChanging() && !inPinnedWindowingMode())) {
             mLastSurfaceInsets.set(mAttrs.surfaceInsets);
 
             setDragResizing();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1b7e527..5266903 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -31,7 +31,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW_VERBOSE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_CROP;
@@ -509,7 +508,7 @@
         boolean layoutNeeded = false;
 
         if (mDrawState == DRAW_PENDING) {
-            if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
+            if (DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
                 Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + mWin + " in "
                         + mSurfaceController);
             if (DEBUG_STARTING_WINDOW && startingWindow) {
@@ -532,7 +531,7 @@
         if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
             return false;
         }
-        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
+        if (DEBUG_ANIM) {
             Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceController);
         }
         mDrawState = READY_TO_SHOW;
@@ -1033,7 +1032,7 @@
                 //Slog.i(TAG_WM, "Not applying alpha transform");
             }
 
-            if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
+            if ((DEBUG_ANIM || WindowManagerService.localLOGV)
                     && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
                     TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
                     + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
@@ -1112,7 +1111,7 @@
      */
     private boolean useFinalClipRect() {
         return (isAnimationSet() && resolveStackClip() == STACK_CLIP_AFTER_ANIM)
-                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWorkspace();
+                || mDestroyPreservedSurfaceUponRedraw || mWin.inPinnedWindowingMode();
     }
 
     /**
@@ -1177,7 +1176,7 @@
             return false;
         }
 
-        if (w.inPinnedWorkspace()) {
+        if (w.inPinnedWindowingMode()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 2e1e3f7..edd650a 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -22,7 +22,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -102,8 +101,14 @@
         mWindowSession = win.mSession;
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
+        final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+                .setName(name)
+                .setSize(w, h)
+                .setFormat(format)
+                .setFlags(flags)
+                .setMetadata(windowType, ownerUid);
         mSurfaceControl = new SurfaceControlWithBackground(
-                s, name, w, h, format, flags, windowType, ownerUid, this);
+                name, b, windowType, w, h, this);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
         if (mService.mRoot.mSurfaceTraceEnabled) {
@@ -247,7 +252,7 @@
                 if (mAnimator.mWin.usesRelativeZOrdering()) {
                     mSurfaceControl.setRelativeLayer(
                             mAnimator.mWin.getParentWindow()
-                            .mWinAnimator.mSurfaceController.mSurfaceControl.getHandle(),
+                            .mWinAnimator.mSurfaceController.mSurfaceControl,
                             -1);
                 } else {
                     mSurfaceLayer = layer;
@@ -565,262 +570,4 @@
     public String toString() {
         return mSurfaceControl.toString();
     }
-
-    static class SurfaceTrace extends SurfaceControl {
-        private final static String SURFACE_TAG = TAG_WITH_CLASS_NAME ? "SurfaceTrace" : TAG_WM;
-        private final static boolean LOG_SURFACE_TRACE = DEBUG_SURFACE_TRACE;
-        final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
-
-        private float mSurfaceTraceAlpha = 0;
-        private int mLayer;
-        private final PointF mPosition = new PointF();
-        private final Point mSize = new Point();
-        private final Rect mWindowCrop = new Rect();
-        private final Rect mFinalCrop = new Rect();
-        private boolean mShown = false;
-        private int mLayerStack;
-        private boolean mIsOpaque;
-        private float mDsdx, mDtdx, mDsdy, mDtdy;
-        private final String mName;
-
-        public SurfaceTrace(SurfaceSession s, String name, int w, int h, int format, int flags,
-                        int windowType, int ownerUid)
-                    throws OutOfResourcesException {
-            super(s, name, w, h, format, flags, windowType, ownerUid);
-            mName = name != null ? name : "Not named";
-            mSize.set(w, h);
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.add(0, this);
-            }
-        }
-
-        public SurfaceTrace(SurfaceSession s,
-                        String name, int w, int h, int format, int flags) {
-            super(s, name, w, h, format, flags);
-            mName = name != null ? name : "Not named";
-            mSize.set(w, h);
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.add(0, this);
-            }
-        }
-
-        @Override
-        public void setAlpha(float alpha) {
-            if (mSurfaceTraceAlpha != alpha) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this +
-                        ". Called by " + Debug.getCallers(3));
-                mSurfaceTraceAlpha = alpha;
-            }
-            super.setAlpha(alpha);
-        }
-
-        @Override
-        public void setLayer(int zorder) {
-            if (zorder != mLayer) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this
-                        + ". Called by " + Debug.getCallers(3));
-                mLayer = zorder;
-            }
-            super.setLayer(zorder);
-
-            synchronized (sSurfaces) {
-                sSurfaces.remove(this);
-                int i;
-                for (i = sSurfaces.size() - 1; i >= 0; i--) {
-                    SurfaceTrace s = sSurfaces.get(i);
-                    if (s.mLayer < zorder) {
-                        break;
-                    }
-                }
-                sSurfaces.add(i + 1, this);
-            }
-        }
-
-        @Override
-        public void setPosition(float x, float y) {
-            if (x != mPosition.x || y != mPosition.y) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mPosition.set(x, y);
-            }
-            super.setPosition(x, y);
-        }
-
-        @Override
-        public void setGeometryAppliesWithResize() {
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setGeometryAppliesWithResize(): OLD: "
-                    + this + ". Called by" + Debug.getCallers(3));
-            super.setGeometryAppliesWithResize();
-        }
-
-        @Override
-        public void setSize(int w, int h) {
-            if (w != mSize.x || h != mSize.y) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mSize.set(w, h);
-            }
-            super.setSize(w, h);
-        }
-
-        @Override
-        public void setWindowCrop(Rect crop) {
-            if (crop != null) {
-                if (!crop.equals(mWindowCrop)) {
-                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setWindowCrop("
-                            + crop.toShortString() + "): OLD:" + this + ". Called by "
-                            + Debug.getCallers(3));
-                    mWindowCrop.set(crop);
-                }
-            }
-            super.setWindowCrop(crop);
-        }
-
-        @Override
-        public void setFinalCrop(Rect crop) {
-            if (crop != null) {
-                if (!crop.equals(mFinalCrop)) {
-                    if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setFinalCrop("
-                            + crop.toShortString() + "): OLD:" + this + ". Called by "
-                            + Debug.getCallers(3));
-                    mFinalCrop.set(crop);
-                }
-            }
-            super.setFinalCrop(crop);
-        }
-
-        @Override
-        public void setLayerStack(int layerStack) {
-            if (layerStack != mLayerStack) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mLayerStack = layerStack;
-            }
-            super.setLayerStack(layerStack);
-        }
-
-        @Override
-        public void setOpaque(boolean isOpaque) {
-            if (isOpaque != mIsOpaque) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setOpaque(" + isOpaque + "): OLD:"
-                        + this + ". Called by " + Debug.getCallers(3));
-                mIsOpaque = isOpaque;
-            }
-            super.setOpaque(isOpaque);
-        }
-
-        @Override
-        public void setSecure(boolean isSecure) {
-            super.setSecure(isSecure);
-        }
-
-        @Override
-        public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
-            if (dsdx != mDsdx || dtdx != mDtdx || dsdy != mDsdy || dtdy != mDtdy) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setMatrix(" + dsdx + "," + dtdx + ","
-                        + dsdy + "," + dtdy + "): OLD:" + this + ". Called by "
-                        + Debug.getCallers(3));
-                mDsdx = dsdx;
-                mDtdx = dtdx;
-                mDsdy = dsdy;
-                mDtdy = dtdy;
-            }
-            super.setMatrix(dsdx, dtdx, dsdy, dtdy);
-        }
-
-        @Override
-        public void hide() {
-            if (mShown) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by "
-                        + Debug.getCallers(3));
-                mShown = false;
-            }
-            super.hide();
-        }
-
-        @Override
-        public void show() {
-            if (!mShown) {
-                if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by "
-                        + Debug.getCallers(3));
-                mShown = true;
-            }
-            super.show();
-        }
-
-        @Override
-        public void destroy() {
-            super.destroy();
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.remove(this);
-            }
-        }
-
-        @Override
-        public void release() {
-            super.release();
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "release: " + this + ". Called by "
-                    + Debug.getCallers(3));
-            synchronized (sSurfaces) {
-                sSurfaces.remove(this);
-            }
-        }
-
-        @Override
-        public void setTransparentRegionHint(Region region) {
-            if (LOG_SURFACE_TRACE) Slog.v(SURFACE_TAG, "setTransparentRegionHint(" + region
-                    + "): OLD: " + this + " . Called by " + Debug.getCallers(3));
-            super.setTransparentRegionHint(region);
-        }
-
-        static void dumpAllSurfaces(PrintWriter pw, String header) {
-            synchronized (sSurfaces) {
-                final int N = sSurfaces.size();
-                if (N <= 0) {
-                    return;
-                }
-                if (header != null) {
-                    pw.println(header);
-                }
-                pw.println("WINDOW MANAGER SURFACES (dumpsys window surfaces)");
-                for (int i = 0; i < N; i++) {
-                    SurfaceTrace s = sSurfaces.get(i);
-                    pw.print("  Surface #"); pw.print(i); pw.print(": #");
-                            pw.print(Integer.toHexString(System.identityHashCode(s)));
-                            pw.print(" "); pw.println(s.mName);
-                    pw.print("    mLayerStack="); pw.print(s.mLayerStack);
-                            pw.print(" mLayer="); pw.println(s.mLayer);
-                    pw.print("    mShown="); pw.print(s.mShown); pw.print(" mAlpha=");
-                            pw.print(s.mSurfaceTraceAlpha); pw.print(" mIsOpaque=");
-                            pw.println(s.mIsOpaque);
-                    pw.print("    mPosition="); pw.print(s.mPosition.x); pw.print(",");
-                            pw.print(s.mPosition.y);
-                            pw.print(" mSize="); pw.print(s.mSize.x); pw.print("x");
-                            pw.println(s.mSize.y);
-                    pw.print("    mCrop="); s.mWindowCrop.printShortString(pw); pw.println();
-                    pw.print("    mFinalCrop="); s.mFinalCrop.printShortString(pw); pw.println();
-                    pw.print("    Transform: ("); pw.print(s.mDsdx); pw.print(", ");
-                            pw.print(s.mDtdx); pw.print(", "); pw.print(s.mDsdy);
-                            pw.print(", "); pw.print(s.mDtdy); pw.println(")");
-                }
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " "
-                    + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer
-                    + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y
-                    + " " + mSize.x + "x" + mSize.y
-                    + " crop=" + mWindowCrop.toShortString()
-                    + " opaque=" + mIsOpaque
-                    + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")";
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 88625d3..d57fdd2 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1,3 +1,18 @@
+/*
+ * 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.server.wm;
 
@@ -342,10 +357,7 @@
         mTmpLayerAndToken.token = null;
         handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
         final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
-        final int topClosingLayer = mTmpLayerAndToken.layer;
-
-        final AppWindowToken topOpeningApp = handleOpeningApps(transit,
-                animLp, voiceInteraction, topClosingLayer);
+        final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction);
 
         mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
 
@@ -387,8 +399,9 @@
     }
 
     private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp,
-            boolean voiceInteraction, int topClosingLayer) {
+            boolean voiceInteraction) {
         AppWindowToken topOpeningApp = null;
+        int topOpeningLayer = Integer.MIN_VALUE;
         final int appsCount = mService.mOpeningApps.size();
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
@@ -422,7 +435,6 @@
             }
             mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
-            int topOpeningLayer = 0;
             if (animLp != null) {
                 final int layer = wtoken.getHighestAnimLayer();
                 if (topOpeningApp == null || layer > topOpeningLayer) {
@@ -431,7 +443,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+                createThumbnailAppAnimator(transit, wtoken);
             }
         }
         return topOpeningApp;
@@ -452,6 +464,9 @@
             //       animating?
             wtoken.setVisibility(animLp, false, transit, false, voiceInteraction);
             wtoken.updateReportedVisibilityLocked();
+            // setAllAppWinAnimators so the windows get onExitAnimationDone once the animation is
+            // done.
+            wtoken.setAllAppWinAnimators();
             // Force the allDrawn flag, because we want to start
             // this guy's animations regardless of whether it's
             // gotten drawn.
@@ -473,7 +488,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                createThumbnailAppAnimator(transit, wtoken, 0, layerAndToken.layer);
+                createThumbnailAppAnimator(transit, wtoken);
             }
         }
     }
@@ -666,8 +681,7 @@
         }
     }
 
-    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
-            int openingLayer, int closingLayer) {
+    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
         AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
         if (openingAppAnimator == null || openingAppAnimator.animation == null) {
             return;
@@ -690,11 +704,14 @@
 
             // Create a new surface for the thumbnail
             WindowState window = appToken.findMainWindow();
-            SurfaceControl surfaceControl = new SurfaceControl(mService.mFxSession,
-                    "thumbnail anim", dirty.width(), dirty.height(),
-                    PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN,
-                    appToken.windowType,
-                    window != null ? window.mOwnerUid : Binder.getCallingUid());
+            final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                    .setName("thumbnail anim")
+                    .setSize(dirty.width(), dirty.height())
+                    .setFormat(PixelFormat.TRANSLUCENT)
+                    .setMetadata(appToken.windowType,
+                            window != null ? window.mOwnerUid : Binder.getCallingUid())
+                    .build();
+
             surfaceControl.setLayerStack(display.getLayerStack());
             if (SHOW_TRANSACTIONS) {
                 Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
@@ -724,7 +741,6 @@
                 anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
                         insets, thumbnailHeader, taskId, displayConfig.uiMode,
                         displayConfig.orientation);
-                openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
                 openingAppAnimator.deferThumbnailDestruction =
                         !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
             } else {
@@ -734,8 +750,8 @@
             anim.restrictDuration(MAX_ANIMATION_DURATION);
             anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
 
+            openingAppAnimator.updateThumbnailLayer();
             openingAppAnimator.thumbnail = surfaceControl;
-            openingAppAnimator.thumbnailLayer = openingLayer;
             openingAppAnimator.thumbnailAnimation = anim;
             mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
         } catch (Surface.OutOfResourcesException e) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 943448e..62a2abb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -267,13 +267,13 @@
 
     @CallSuper
     @Override
-    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId, boolean trim) {
         final long token = proto.start(fieldId);
-        super.writeToProto(proto, WINDOW_CONTAINER);
+        super.writeToProto(proto, WINDOW_CONTAINER, trim);
         proto.write(HASH_CODE, System.identityHashCode(this));
         for (int i = 0; i < mChildren.size(); i++) {
             final WindowState w = mChildren.get(i);
-            w.writeToProto(proto, WINDOWS);
+            w.writeToProto(proto, WINDOWS, trim);
         }
         proto.end(token);
     }
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index bf9f941..36e9e7f 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -78,7 +78,6 @@
     libschedulerservicehidl \
     libsensorservice \
     libsensorservicehidl \
-    libskia \
     libgui \
     libusbhost \
     libsuspend \
@@ -93,6 +92,7 @@
     android.hardware.audio.common@2.0 \
     android.hardware.broadcastradio@1.0 \
     android.hardware.broadcastradio@1.1 \
+    android.hardware.broadcastradio@1.2 \
     android.hardware.contexthub@1.0 \
     android.hardware.gnss@1.0 \
     android.hardware.ir@1.0 \
@@ -110,5 +110,5 @@
     android.frameworks.sensorservice@1.0 \
 
 LOCAL_STATIC_LIBRARIES += \
-    android.hardware.broadcastradio@1.1-utils-lib \
+    android.hardware.broadcastradio@common-utils-lib \
     libscrypt_static \
diff --git a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
index b3817db..e1dbdeb 100644
--- a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
+++ b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
@@ -23,8 +23,9 @@
 #include "convert.h"
 
 #include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
-#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <android/hardware/broadcastradio/1.2/IBroadcastRadioFactory.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
+#include <broadcastradio-utils/Utils.h>
 #include <core_jni_helpers.h>
 #include <hidl/ServiceManagement.h>
 #include <nativehelper/JNIHelp.h>
@@ -44,14 +45,16 @@
 
 namespace V1_0 = hardware::broadcastradio::V1_0;
 namespace V1_1 = hardware::broadcastradio::V1_1;
-
-using V1_0::Class;
-using V1_0::Result;
+namespace V1_2 = hardware::broadcastradio::V1_2;
+namespace utils = hardware::broadcastradio::utils;
 
 using V1_0::BandConfig;
-using V1_0::ProgramInfo;
-using V1_0::MetaData;
+using V1_0::Class;
 using V1_0::ITuner;
+using V1_0::MetaData;
+using V1_0::ProgramInfo;
+using V1_0::Result;
+using utils::HalRevision;
 
 static mutex gContextMutex;
 
@@ -67,10 +70,15 @@
     } Tuner;
 } gjni;
 
+struct Module {
+    sp<V1_0::IBroadcastRadio> radioModule;
+    HalRevision halRev;
+};
+
 struct ServiceContext {
     ServiceContext() {}
 
-    std::vector<sp<V1_0::IBroadcastRadio>> mModules;
+    std::vector<Module> mModules;
 
 private:
     DISALLOW_COPY_AND_ASSIGN(ServiceContext);
@@ -139,6 +147,17 @@
             continue;
         }
 
+        auto halRev = HalRevision::V1_0;
+        auto halMinor = 0;
+        if (V1_2::IBroadcastRadioFactory::castFrom(factory).withDefault(nullptr) != nullptr) {
+            halRev = HalRevision::V1_2;
+            halMinor = 2;
+        } else if (V1_1::IBroadcastRadioFactory::castFrom(factory).withDefault(nullptr)
+                != nullptr) {
+            halRev = HalRevision::V1_1;
+            halMinor = 1;
+        }
+
         // Second level of scanning - that's unfortunate.
         for (auto&& clazz : gAllClasses) {
             sp<V1_0::IBroadcastRadio> module10 = nullptr;
@@ -155,9 +174,9 @@
             if (module10 == nullptr) continue;
 
             auto idx = ctx.mModules.size();
-            ctx.mModules.push_back(module10);
-            ALOGI("loaded broadcast radio module %zu: %s:%s",
-                    idx, serviceName.c_str(), V1_0::toString(clazz).c_str());
+            ctx.mModules.push_back({module10, halRev});
+            ALOGI("loaded broadcast radio module %zu: %s:%s (HAL 1.%d)",
+                    idx, serviceName.c_str(), V1_0::toString(clazz).c_str(), halMinor);
 
             JavaRef<jobject> jModule = nullptr;
             Result halResult = Result::OK;
@@ -198,22 +217,15 @@
         ALOGE("Invalid module ID: %d", moduleId);
         return nullptr;
     }
-    auto module = ctx.mModules[moduleId];
 
-    HalRevision halRev;
-    if (V1_1::IBroadcastRadio::castFrom(module).withDefault(nullptr) != nullptr) {
-        ALOGI("Opening tuner %d with broadcast radio HAL 1.1", moduleId);
-        halRev = HalRevision::V1_1;
-    } else {
-        ALOGI("Opening tuner %d with broadcast radio HAL 1.0", moduleId);
-        halRev = HalRevision::V1_0;
-    }
+    ALOGI("Opening tuner %d", moduleId);
+    auto module = ctx.mModules[moduleId];
 
     Region region;
     BandConfig bandConfigHal = convert::BandConfigToHal(env, bandConfig, region);
 
     auto tuner = make_javaref(env, env->NewObject(gjni.Tuner.clazz, gjni.Tuner.cstor,
-            callback, halRev, region, withAudio, bandConfigHal.type));
+            callback, module.halRev, region, withAudio, bandConfigHal.type));
     if (tuner == nullptr) {
         ALOGE("Unable to create new tuner object.");
         return nullptr;
@@ -223,7 +235,7 @@
     Result halResult;
     sp<ITuner> halTuner = nullptr;
 
-    auto hidlResult = module->openTuner(bandConfigHal, withAudio, tunerCb,
+    auto hidlResult = module.radioModule->openTuner(bandConfigHal, withAudio, tunerCb,
             [&](Result result, const sp<ITuner>& tuner) {
                 halResult = result;
                 halTuner = tuner;
@@ -235,7 +247,7 @@
         return nullptr;
     }
 
-    Tuner::assignHalInterfaces(env, tuner, module, halTuner);
+    Tuner::assignHalInterfaces(env, tuner, module.radioModule, halTuner);
     ALOGD("Opened tuner %p", halTuner.get());
     return tuner.release();
 }
diff --git a/services/core/jni/BroadcastRadio/Tuner.cpp b/services/core/jni/BroadcastRadio/Tuner.cpp
index e1ade4d..6403a5a 100644
--- a/services/core/jni/BroadcastRadio/Tuner.cpp
+++ b/services/core/jni/BroadcastRadio/Tuner.cpp
@@ -22,7 +22,7 @@
 #include "convert.h"
 #include "TunerCallback.h"
 
-#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <android/hardware/broadcastradio/1.2/IBroadcastRadioFactory.h>
 #include <binder/IPCThreadState.h>
 #include <broadcastradio-utils/Utils.h>
 #include <core_jni_helpers.h>
@@ -44,13 +44,16 @@
 
 namespace V1_0 = hardware::broadcastradio::V1_0;
 namespace V1_1 = hardware::broadcastradio::V1_1;
+namespace V1_2 = hardware::broadcastradio::V1_2;
+namespace utils = hardware::broadcastradio::utils;
 
 using V1_0::Band;
 using V1_0::BandConfig;
 using V1_0::MetaData;
 using V1_0::Result;
-using V1_1::ITunerCallback;
+using V1_2::ITunerCallback;
 using V1_1::ProgramListResult;
+using utils::HalRevision;
 
 static mutex gContextMutex;
 
@@ -310,7 +313,7 @@
         convert::ThrowIfFailed(env, halTuner11->tuneByProgramSelector(selector));
     } else {
         uint32_t channel, subChannel;
-        if (!V1_1::utils::getLegacyChannel(selector, &channel, &subChannel)) {
+        if (!utils::getLegacyChannel(selector, &channel, &subChannel)) {
             jniThrowException(env, "java/lang/IllegalArgumentException",
                     "Can't tune to non-AM/FM channel with HAL<1.1");
             return;
diff --git a/services/core/jni/BroadcastRadio/Tuner.h b/services/core/jni/BroadcastRadio/Tuner.h
index 818597b..48c3bc7 100644
--- a/services/core/jni/BroadcastRadio/Tuner.h
+++ b/services/core/jni/BroadcastRadio/Tuner.h
@@ -22,8 +22,8 @@
 #include "JavaRef.h"
 
 #include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
-#include <android/hardware/broadcastradio/1.1/ITuner.h>
-#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
+#include <android/hardware/broadcastradio/1.2/ITuner.h>
+#include <android/hardware/broadcastradio/1.2/ITunerCallback.h>
 #include <jni.h>
 #include <utils/StrongPointer.h>
 
@@ -39,7 +39,7 @@
         sp<hardware::broadcastradio::V1_0::IBroadcastRadio> halModule,
         sp<hardware::broadcastradio::V1_0::ITuner> halTuner);
 
-sp<hardware::broadcastradio::V1_1::ITunerCallback>
+sp<hardware::broadcastradio::V1_2::ITunerCallback>
 getNativeCallback(JNIEnv *env, JavaRef<jobject> const &tuner);
 
 Region getRegion(JNIEnv *env, jobject obj);
diff --git a/services/core/jni/BroadcastRadio/TunerCallback.cpp b/services/core/jni/BroadcastRadio/TunerCallback.cpp
index d53721f..ed7c9c4 100644
--- a/services/core/jni/BroadcastRadio/TunerCallback.cpp
+++ b/services/core/jni/BroadcastRadio/TunerCallback.cpp
@@ -40,15 +40,19 @@
 
 namespace V1_0 = hardware::broadcastradio::V1_0;
 namespace V1_1 = hardware::broadcastradio::V1_1;
+namespace V1_2 = hardware::broadcastradio::V1_2;
+namespace utils = hardware::broadcastradio::utils;
 
 using V1_0::Band;
 using V1_0::BandConfig;
 using V1_0::MetaData;
 using V1_0::Result;
-using V1_1::ITunerCallback;
 using V1_1::ProgramInfo;
 using V1_1::ProgramListResult;
 using V1_1::ProgramSelector;
+using V1_1::VendorKeyValue;
+using V1_2::ITunerCallback;
+using utils::HalRevision;
 
 static JavaVM *gvm = nullptr;
 
@@ -117,6 +121,7 @@
     virtual Return<void> backgroundScanComplete(ProgramListResult result);
     virtual Return<void> programListChanged();
     virtual Return<void> currentProgramInfoChanged(const ProgramInfo& info);
+    virtual Return<void> parametersUpdated(const hidl_vec<VendorKeyValue>& parameters);
 };
 
 struct TunerCallbackContext {
@@ -203,7 +208,7 @@
         return {};
     }
 
-    auto selector = V1_1::utils::make_selector(mBand, info.channel, info.subChannel);
+    auto selector = utils::make_selector(mBand, info.channel, info.subChannel);
     return tuneComplete_1_1(result, selector);
 }
 
@@ -338,6 +343,14 @@
     return Return<void>();
 }
 
+Return<void> NativeCallback::parametersUpdated(const hidl_vec<VendorKeyValue>& parameters) {
+    ALOGV("%s", __func__);
+
+    // TODO(b/65862441): pass this callback to the front-end
+
+    return {};
+}
+
 static TunerCallbackContext& getNativeContext(jlong nativeContextHandle) {
     auto nativeContext = reinterpret_cast<TunerCallbackContext*>(nativeContextHandle);
     LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
diff --git a/services/core/jni/BroadcastRadio/TunerCallback.h b/services/core/jni/BroadcastRadio/TunerCallback.h
index af12d21..7e776c2 100644
--- a/services/core/jni/BroadcastRadio/TunerCallback.h
+++ b/services/core/jni/BroadcastRadio/TunerCallback.h
@@ -21,7 +21,7 @@
 #include "NativeCallbackThread.h"
 #include "types.h"
 
-#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
+#include <android/hardware/broadcastradio/1.2/ITunerCallback.h>
 #include <jni.h>
 
 namespace android {
@@ -32,7 +32,7 @@
 namespace BroadcastRadio {
 namespace TunerCallback {
 
-sp<hardware::broadcastradio::V1_1::ITunerCallback>
+sp<hardware::broadcastradio::V1_2::ITunerCallback>
 getNativeCallback(JNIEnv *env, jobject jTunerCallback);
 
 } // namespace TunerCallback
diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp
index ae278de..8dfa14f 100644
--- a/services/core/jni/BroadcastRadio/convert.cpp
+++ b/services/core/jni/BroadcastRadio/convert.cpp
@@ -31,7 +31,7 @@
 namespace BroadcastRadio {
 namespace convert {
 
-namespace utils = V1_1::utils;
+namespace utils = hardware::broadcastradio::utils;
 
 using hardware::Return;
 using hardware::hidl_vec;
diff --git a/services/core/jni/BroadcastRadio/regions.cpp b/services/core/jni/BroadcastRadio/regions.cpp
index b856419..b7fd0f3 100644
--- a/services/core/jni/BroadcastRadio/regions.cpp
+++ b/services/core/jni/BroadcastRadio/regions.cpp
@@ -27,7 +27,7 @@
 namespace BroadcastRadio {
 namespace regions {
 
-namespace utils = hardware::broadcastradio::V1_1::utils;
+namespace utils = hardware::broadcastradio::utils;
 
 using hardware::hidl_vec;
 
diff --git a/services/core/jni/BroadcastRadio/types.h b/services/core/jni/BroadcastRadio/types.h
index f726af3..64a4f63 100644
--- a/services/core/jni/BroadcastRadio/types.h
+++ b/services/core/jni/BroadcastRadio/types.h
@@ -27,11 +27,6 @@
  * frameworks/base/core/java/android/hardware/radio/RadioManager.java.
  */
 
-enum class HalRevision : jint {
-    V1_0,
-    V1_1,
-};
-
 // Keep in sync with STATUS_* constants from RadioManager.java.
 enum class Status : jint {
     OK = 0,
diff --git a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
index 5f67ac1..ed79352 100644
--- a/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
+++ b/services/core/jni/com_android_server_HardwarePropertiesManagerService.cpp
@@ -30,6 +30,8 @@
 
 namespace android {
 
+using android::hidl::base::V1_0::IBase;
+using hardware::hidl_death_recipient;
 using hardware::hidl_vec;
 using hardware::thermal::V1_0::CoolingDevice;
 using hardware::thermal::V1_0::CpuUsage;
@@ -58,7 +60,22 @@
 
 jfloat gUndefinedTemperature;
 
-static sp<IThermal> gThermalModule;
+static void getThermalHalLocked();
+static std::mutex gThermalHalMutex;
+static sp<IThermal> gThermalHal = nullptr;
+
+// struct ThermalHalDeathRecipient;
+struct ThermalHalDeathRecipient : virtual public hidl_death_recipient {
+      // hidl_death_recipient interface
+      virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
+          std::lock_guard<std::mutex> lock(gThermalHalMutex);
+          ALOGE("ThermalHAL just died");
+          gThermalHal = nullptr;
+          getThermalHalLocked();
+      }
+};
+
+sp<ThermalHalDeathRecipient> gThermalHalDeathRecipient = nullptr;
 
 // ----------------------------------------------------------------------------
 
@@ -66,25 +83,50 @@
     return isnan(temperature) ? gUndefinedTemperature : temperature;
 }
 
-static void nativeInit(JNIEnv* env, jobject obj) {
-    // TODO(b/31632518)
-    if (gThermalModule == nullptr) {
-        gThermalModule = IThermal::getService();
+// The caller must be holding gThermalHalMutex.
+static void getThermalHalLocked() {
+    if (gThermalHal != nullptr) {
+        return;
     }
 
-    if (gThermalModule == nullptr) {
+    gThermalHal = IThermal::getService();
+
+    if (gThermalHal == nullptr) {
         ALOGE("Unable to get Thermal service.");
+    } else {
+        if (gThermalHalDeathRecipient == nullptr) {
+            gThermalHalDeathRecipient = new ThermalHalDeathRecipient();
+        }
+        hardware::Return<bool> linked = gThermalHal->linkToDeath(
+            gThermalHalDeathRecipient, 0x451F /* cookie */);
+        if (!linked.isOk()) {
+            ALOGE("Transaction error in linking to ThermalHAL death: %s",
+            linked.description().c_str());
+            gThermalHal = nullptr;
+        } else if (!linked) {
+            ALOGW("Unable to link to ThermalHal death notifications");
+            gThermalHal = nullptr;
+        } else {
+            ALOGD("Link to death notification successful");
+        }
     }
 }
 
+static void nativeInit(JNIEnv* env, jobject obj) {
+    std::lock_guard<std::mutex> lock(gThermalHalMutex);
+    getThermalHalLocked();
+}
+
 static jfloatArray nativeGetFanSpeeds(JNIEnv *env, jclass /* clazz */) {
-    if (gThermalModule == nullptr) {
+    std::lock_guard<std::mutex> lock(gThermalHalMutex);
+    getThermalHalLocked();
+    if (gThermalHal == nullptr) {
         ALOGE("Couldn't get fan speeds because of HAL error.");
         return env->NewFloatArray(0);
     }
 
     hidl_vec<CoolingDevice> list;
-    Return<void> ret = gThermalModule->getCoolingDevices(
+    Return<void> ret = gThermalHal->getCoolingDevices(
             [&list](ThermalStatus status, hidl_vec<CoolingDevice> devices) {
                 if (status.code == ThermalStatusCode::SUCCESS) {
                     list = std::move(devices);
@@ -109,12 +151,14 @@
 
 static jfloatArray nativeGetDeviceTemperatures(JNIEnv *env, jclass /* clazz */, int type,
                                                int source) {
-    if (gThermalModule == nullptr) {
+    std::lock_guard<std::mutex> lock(gThermalHalMutex);
+    getThermalHalLocked();
+    if (gThermalHal == nullptr) {
         ALOGE("Couldn't get device temperatures because of HAL error.");
         return env->NewFloatArray(0);
     }
     hidl_vec<Temperature> list;
-    Return<void> ret = gThermalModule->getTemperatures(
+    Return<void> ret = gThermalHal->getTemperatures(
             [&list](ThermalStatus status, hidl_vec<Temperature> temperatures) {
                 if (status.code == ThermalStatusCode::SUCCESS) {
                     list = std::move(temperatures);
@@ -154,12 +198,14 @@
 }
 
 static jobjectArray nativeGetCpuUsages(JNIEnv *env, jclass /* clazz */) {
-    if (gThermalModule == nullptr || !gCpuUsageInfoClassInfo.initMethod) {
+    std::lock_guard<std::mutex> lock(gThermalHalMutex);
+    getThermalHalLocked();
+    if (gThermalHal == nullptr || !gCpuUsageInfoClassInfo.initMethod) {
         ALOGE("Couldn't get CPU usages because of HAL error.");
         return env->NewObjectArray(0, gCpuUsageInfoClassInfo.clazz, nullptr);
     }
     hidl_vec<CpuUsage> list;
-    Return<void> ret = gThermalModule->getCpuUsages(
+    Return<void> ret = gThermalHal->getCpuUsages(
             [&list](ThermalStatus status, hidl_vec<CpuUsage> cpuUsages) {
                 if (status.code == ThermalStatusCode::SUCCESS) {
                     list = std::move(cpuUsages);
@@ -202,7 +248,6 @@
 };
 
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env) {
-    gThermalModule = nullptr;
     int res = jniRegisterNativeMethods(env, "com/android/server/HardwarePropertiesManagerService",
                                        gHardwarePropertiesManagerServiceMethods,
                                        NELEM(gHardwarePropertiesManagerServiceMethods));
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 98c5ec1..df85e31 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -18,7 +18,7 @@
 #include "utils/Log.h"
 
 #include "jni.h"
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 
 #include <usbhost/usbhost.h>
 
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 9a17635..3eaf488 100644
--- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -103,8 +103,8 @@
     // fd2   A file descriptor bound to the following netlink groups
     //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
     base::unique_fd
-            fd1(conntrackSocket(NFNLGRP_CONNTRACK_NEW | NFNLGRP_CONNTRACK_DESTROY)),
-            fd2(conntrackSocket(NFNLGRP_CONNTRACK_UPDATE | NFNLGRP_CONNTRACK_DESTROY));
+            fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
+            fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
     if (fd1.get() < 0 || fd2.get() < 0) {
         ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
         return false;
diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp
index d90b011..ad372de 100644
--- a/services/core/jni/com_android_server_location_ContextHubService.cpp
+++ b/services/core/jni/com_android_server_location_ContextHubService.cpp
@@ -1162,7 +1162,7 @@
     }
 
     if (result != Result::OK) {
-        ALOGD("Send Message failure - %d", retVal);
+        ALOGD("Send Message failure - %d", result);
         if (msgType == CONTEXT_HUB_LOAD_APP) {
             jint ignored;
             closeLoadTxn(false, &ignored);
diff --git a/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp b/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp
index 248dedb..bc13fde 100644
--- a/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp
+++ b/services/core/jni/com_android_server_locksettings_SyntheticPasswordManager.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "SyntheticPasswordManager"
 
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 #include <android_runtime/Log.h>
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 39f90ca..b6c3df7 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -19,7 +19,7 @@
 //#define LOG_NDEBUG 0
 
 #include <android/hardware/power/1.1/IPower.h>
-#include "JNIHelp.h"
+#include <nativehelper/JNIHelp.h>
 #include "jni.h"
 
 #include <nativehelper/ScopedUtfChars.h>
diff --git a/services/coverage/java/com/android/server/coverage/CoverageService.java b/services/coverage/java/com/android/server/coverage/CoverageService.java
index d600aa8..ed8d24a 100644
--- a/services/coverage/java/com/android/server/coverage/CoverageService.java
+++ b/services/coverage/java/com/android/server/coverage/CoverageService.java
@@ -112,7 +112,7 @@
             }
 
             // Try to open the destination file
-            ParcelFileDescriptor fd = openOutputFileForSystem(dest);
+            ParcelFileDescriptor fd = openFileForSystem(dest, "w");
             if (fd == null) {
                 return -1;
             }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6c859f7..2d8a0ee 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
 import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
 import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
@@ -97,8 +98,8 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
@@ -175,6 +176,7 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -224,6 +226,8 @@
 
     private static final String TAG_LOCK_TASK_COMPONENTS = "lock-task-component";
 
+    private static final String TAG_LOCK_TASK_FEATURES = "lock-task-features";
+
     private static final String TAG_STATUS_BAR = "statusbar";
 
     private static final String ATTR_DISABLED = "disabled";
@@ -506,6 +510,9 @@
         // This is the list of component allowed to start lock task mode.
         List<String> mLockTaskPackages = new ArrayList<>();
 
+        // Bitfield of feature flags to be enabled during LockTask mode.
+        int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+
         boolean mStatusBarDisabled = false;
 
         ComponentName mRestrictionsProvider;
@@ -1682,6 +1689,10 @@
             return getCallingUid() == Process.myUid();
         }
 
+        void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) {
+             Binder.withCleanCallingIdentity(action);
+        }
+
         final int userHandleGetCallingUserId() {
             return UserHandle.getUserId(binderGetCallingUid());
         }
@@ -2623,6 +2634,12 @@
                 out.endTag(null, TAG_LOCK_TASK_COMPONENTS);
             }
 
+            if (policy.mLockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE) {
+                out.startTag(null, TAG_LOCK_TASK_FEATURES);
+                out.attribute(null, ATTR_VALUE, Integer.toString(policy.mLockTaskFeatures));
+                out.endTag(null, TAG_LOCK_TASK_FEATURES);
+            }
+
             if (policy.mStatusBarDisabled) {
                 out.startTag(null, TAG_STATUS_BAR);
                 out.attribute(null, ATTR_DISABLED, Boolean.toString(policy.mStatusBarDisabled));
@@ -2863,6 +2880,9 @@
                     policy.mAcceptedCaCertificates.add(parser.getAttributeValue(null, ATTR_NAME));
                 } else if (TAG_LOCK_TASK_COMPONENTS.equals(tag)) {
                     policy.mLockTaskPackages.add(parser.getAttributeValue(null, "name"));
+                } else if (TAG_LOCK_TASK_FEATURES.equals(tag)) {
+                    policy.mLockTaskFeatures = Integer.parseInt(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_STATUS_BAR.equals(tag)) {
                     policy.mStatusBarDisabled = Boolean.parseBoolean(
                             parser.getAttributeValue(null, ATTR_DISABLED));
@@ -2931,6 +2951,7 @@
         validatePasswordOwnerLocked(policy);
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
+        updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
@@ -2948,6 +2969,18 @@
         }
     }
 
+    private void updateLockTaskFeaturesLocked(int flags, int userId) {
+        long ident = mInjector.binderClearCallingIdentity();
+        try {
+            mInjector.getIActivityManager()
+                    .updateLockTaskFeatures(userId, flags);
+        } catch (RemoteException e) {
+            // Not gonna happen.
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
     private void updateDeviceOwnerLocked() {
         long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -5350,7 +5383,7 @@
         }
     }
 
-    private void forceWipeUser(int userId) {
+    private void forceWipeUser(int userId, String wipeReasonForUser) {
         try {
             IActivityManager am = mInjector.getIActivityManager();
             if (am.getCurrentUser().id == userId) {
@@ -5361,7 +5394,7 @@
             if (!userRemoved) {
                 Slog.w(LOG_TAG, "Couldn't remove user " + userId);
             } else if (isManagedProfile(userId)) {
-                sendWipeProfileNotification();
+                sendWipeProfileNotification(wipeReasonForUser);
             }
         } catch (RemoteException re) {
             // Shouldn't happen
@@ -5369,23 +5402,26 @@
     }
 
     @Override
-    public void wipeData(int flags) {
+    public void wipeDataWithReason(int flags, String wipeReasonForUser) {
         if (!mHasFeature) {
             return;
         }
+        Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
         enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
 
         final ActiveAdmin admin;
         synchronized (this) {
             admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
         }
-        String reason = "DevicePolicyManager.wipeData() from "
+        String internalReason = "DevicePolicyManager.wipeDataWithReason() from "
                 + admin.info.getComponent().flattenToShortString();
         wipeDataNoLock(
-                admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
+                admin.info.getComponent(), flags, internalReason, wipeReasonForUser,
+                admin.getUserHandle().getIdentifier());
     }
 
-    private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
+    private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
+                                String wipeReasonForUser, int userId) {
         wtfIfInLock();
 
         long ident = mInjector.binderClearCallingIdentity();
@@ -5420,25 +5456,26 @@
             // (rather than system), we should probably trigger factory reset. Current code just
             // removes that user (but still clears FRP...)
             if (userId == UserHandle.USER_SYSTEM) {
-                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
-                        reason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
+                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (
+                        flags & WIPE_EXTERNAL_STORAGE) != 0,
+                        internalReason,
+                        /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
             } else {
-                forceWipeUser(userId);
+                forceWipeUser(userId, wipeReasonForUser);
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
     }
 
-    private void sendWipeProfileNotification() {
-        String contentText = mContext.getString(R.string.work_profile_deleted_description_dpm_wipe);
+    private void sendWipeProfileNotification(String wipeReasonForUser) {
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
                         .setContentTitle(mContext.getString(R.string.work_profile_deleted))
-                        .setContentText(contentText)
+                        .setContentText(wipeReasonForUser)
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
-                        .setStyle(new Notification.BigTextStyle().bigText(contentText))
+                        .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
                         .build();
         mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
     }
@@ -5610,9 +5647,12 @@
             // able to do so).
             // IMPORTANT: Call without holding the lock to prevent deadlock.
             try {
+                String wipeReasonForUser = mContext.getString(
+                        R.string.work_profile_deleted_reason_maximum_password_failure);
                 wipeDataNoLock(strictestAdmin.info.getComponent(),
                         /*flags=*/ 0,
                         /*reason=*/ "reportFailedPasswordAttempt()",
+                        wipeReasonForUser,
                         userId);
             } catch (SecurityException e) {
                 Slog.w(LOG_TAG, "Failed to wipe user " + userId
@@ -5621,7 +5661,8 @@
         }
 
         if (mInjector.securityLogIsLoggingEnabled()) {
-            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
+            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
+                    /*result*/ 0,
                     /*method strength*/ 1);
         }
     }
@@ -6926,6 +6967,7 @@
         policy.mUserProvisioningState = DevicePolicyManager.STATE_USER_UNMANAGED;
         policy.mAffiliationIds.clear();
         policy.mLockTaskPackages.clear();
+        policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
         saveSettingsLocked(userId);
 
         try {
@@ -8908,25 +8950,6 @@
         updateLockTaskPackagesLocked(packages, userHandle);
     }
 
-    private void maybeClearLockTaskPackagesLocked() {
-        final long ident = mInjector.binderClearCallingIdentity();
-        try {
-            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
-            for (int i = 0; i < userInfos.size(); i++) {
-                int userId = userInfos.get(i).id;
-                final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
-                if (!lockTaskPackages.isEmpty() &&
-                        !isUserAffiliatedWithDeviceLocked(userId)) {
-                    Slog.d(LOG_TAG,
-                            "User id " + userId + " not affiliated. Clearing lock task packages");
-                    setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
-                }
-            }
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
-    }
-
     @Override
     public String[] getLockTaskPackages(ComponentName who) {
         Preconditions.checkNotNull(who, "ComponentName is null");
@@ -8953,12 +8976,82 @@
     }
 
     @Override
+    public void setLockTaskFeatures(ComponentName who, int flags) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
+                throw new SecurityException("Admin " + who +
+                        " is neither the device owner or affiliated user's profile owner.");
+            }
+            setLockTaskFeaturesLocked(userHandle, flags);
+        }
+    }
+
+    private void setLockTaskFeaturesLocked(int userHandle, int flags) {
+        DevicePolicyData policy = getUserData(userHandle);
+        policy.mLockTaskFeatures = flags;
+        saveSettingsLocked(userHandle);
+        updateLockTaskFeaturesLocked(flags, userHandle);
+    }
+
+    @Override
+    public int getLockTaskFeatures(ComponentName who) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        final int userHandle = mInjector.userHandleGetCallingUserId();
+        synchronized (this) {
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            if (!isUserAffiliatedWithDeviceLocked(userHandle)) {
+                throw new SecurityException("Admin " + who +
+                        " is neither the device owner or affiliated user's profile owner.");
+            }
+            return getUserData(userHandle).mLockTaskFeatures;
+        }
+    }
+
+    private void maybeClearLockTaskPolicyLocked() {
+        final long ident = mInjector.binderClearCallingIdentity();
+        try {
+            final List<UserInfo> userInfos = mUserManager.getUsers(/*excludeDying=*/ true);
+            for (int i = userInfos.size() - 1; i >= 0; i--) {
+                int userId = userInfos.get(i).id;
+                if (isUserAffiliatedWithDeviceLocked(userId)) {
+                    continue;
+                }
+
+                final List<String> lockTaskPackages = getUserData(userId).mLockTaskPackages;
+                if (!lockTaskPackages.isEmpty()) {
+                    Slog.d(LOG_TAG,
+                            "User id " + userId + " not affiliated. Clearing lock task packages");
+                    setLockTaskPackagesLocked(userId, Collections.<String>emptyList());
+                }
+                final int lockTaskFeatures = getUserData(userId).mLockTaskFeatures;
+                if (lockTaskFeatures != DevicePolicyManager.LOCK_TASK_FEATURE_NONE){
+                    Slog.d(LOG_TAG,
+                            "User id " + userId + " not affiliated. Clearing lock task features");
+                    setLockTaskFeaturesLocked(userId, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+                }
+            }
+        } finally {
+            mInjector.binderRestoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void notifyLockTaskModeChanged(boolean isEnabled, String pkg, int userHandle) {
         if (!isCallerWithSystemUid()) {
             throw new SecurityException("notifyLockTaskModeChanged can only be called by system");
         }
         synchronized (this) {
             final DevicePolicyData policy = getUserData(userHandle);
+
+            if (policy.mStatusBarDisabled) {
+                // Status bar is managed by LockTaskController during LockTask, so we cancel this
+                // policy when LockTask starts, and reapply it when LockTask ends
+                setStatusBarDisabledInternal(!isEnabled, userHandle);
+            }
+
             Bundle adminExtras = new Bundle();
             adminExtras.putString(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE, pkg);
             for (ActiveAdmin admin : policy.mAdminList) {
@@ -9015,6 +9108,31 @@
     }
 
     @Override
+    public boolean setTime(ComponentName who, long millis) {
+        Preconditions.checkNotNull(who, "ComponentName is null in setTime");
+        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        // Don't allow set time when auto time is on.
+        if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
+            return false;
+        }
+        mInjector.binderWithCleanCallingIdentity(() -> mInjector.getAlarmManager().setTime(millis));
+        return true;
+    }
+
+    @Override
+    public boolean setTimeZone(ComponentName who, String timeZone) {
+        Preconditions.checkNotNull(who, "ComponentName is null in setTimeZone");
+        getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+        // Don't allow set timezone when auto timezone is on.
+        if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
+            return false;
+        }
+        mInjector.binderWithCleanCallingIdentity(() ->
+            mInjector.getAlarmManager().setTimeZone(timeZone));
+        return true;
+    }
+
+    @Override
     public void setSecureSetting(ComponentName who, String setting, String value) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         int callingUserId = mInjector.userHandleGetCallingUserId();
@@ -9144,8 +9262,17 @@
             getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
             DevicePolicyData policy = getUserData(userId);
             if (policy.mStatusBarDisabled != disabled) {
-                if (!setStatusBarDisabledInternal(disabled, userId)) {
-                    return false;
+                boolean isLockTaskMode = false;
+                try {
+                    isLockTaskMode = mInjector.getIActivityManager().getLockTaskModeState()
+                            != LOCK_TASK_MODE_NONE;
+                } catch (RemoteException e) {
+                    Slog.e(LOG_TAG, "Failed to get LockTask mode");
+                }
+                if (!isLockTaskMode) {
+                    if (!setStatusBarDisabledInternal(disabled, userId)) {
+                        return false;
+                    }
                 }
                 policy.mStatusBarDisabled = disabled;
                 saveSettingsLocked(userId);
@@ -10245,7 +10372,7 @@
             // but as a result of that other users might become affiliated or un-affiliated.
             maybePauseDeviceWideLoggingLocked();
             maybeResumeDeviceWideLoggingLocked();
-            maybeClearLockTaskPackagesLocked();
+            maybeClearLockTaskPolicyLocked();
         }
     }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 948c028..de5d879 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,10 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 public final class SystemServer {
@@ -152,7 +156,7 @@
      * them from the build system somehow.
      */
     private static final String BACKUP_MANAGER_SERVICE_CLASS =
-            "com.android.server.backup.BackupManagerService$Lifecycle";
+            "com.android.server.backup.RefactoredBackupManagerService$Lifecycle";
     private static final String APPWIDGET_SERVICE_CLASS =
             "com.android.server.appwidget.AppWidgetService";
     private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
@@ -205,7 +209,8 @@
             "com.android.server.autofill.AutofillManagerService";
     private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
             "com.android.server.timezone.RulesManagerService$Lifecycle";
-
+    private static final String IOT_SERVICE_CLASS =
+            "com.google.android.things.services.IoTSystemService";
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
 
     private static final String UNCRYPT_PACKAGE_FILE = "/cache/recovery/uncrypt_file";
@@ -668,9 +673,11 @@
         traceEnd();
 
         // Tracks whether the updatable WebView is in a ready state and watches for update installs.
-        traceBeginAndSlog("StartWebViewUpdateService");
-        mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
-        traceEnd();
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            traceBeginAndSlog("StartWebViewUpdateService");
+            mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
+            traceEnd();
+        }
     }
 
     /**
@@ -682,6 +689,7 @@
         VibratorService vibrator = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
+        IpSecService ipSecService = null;
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
@@ -821,7 +829,8 @@
             wm = WindowManagerService.main(context, inputManager,
                     mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                     !mFirstBoot, mOnlyCore, new PhoneWindowManager());
-            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
+            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
+                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
             ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
             traceEnd();
 
@@ -1031,6 +1040,15 @@
                     reportWtf("starting NetworkManagement Service", e);
                 }
                 traceEnd();
+
+                traceBeginAndSlog("StartIpSecService");
+                try {
+                    ipSecService = IpSecService.create(context);
+                    ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+                } catch (Throwable e) {
+                    reportWtf("starting IpSec Service", e);
+                }
+                traceEnd();
             }
 
             if (!disableNonCoreServices && !disableTextServices) {
@@ -1081,6 +1099,14 @@
                     traceBeginAndSlog("StartWifiRtt");
                     mSystemServiceManager.startService("com.android.server.wifi.RttService");
                     traceEnd();
+
+                    if (context.getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_WIFI_RTT)) {
+                        traceBeginAndSlog("StartRttService");
+                        mSystemServiceManager.startService(
+                                "com.android.server.wifi.rtt.RttService");
+                        traceEnd();
+                    }
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1088,8 +1114,6 @@
                     traceBeginAndSlog("StartWifiAware");
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
                     traceEnd();
-                } else {
-                    Slog.i(TAG, "No Wi-Fi Aware Service (Aware support Not Present)");
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1117,7 +1141,9 @@
                 try {
                     connectivity = new ConnectivityService(
                             context, networkManagement, networkStats, networkPolicy);
-                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
+                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity,
+                            /* allowIsolated= */ false,
+                            DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
                     networkStats.bindConnectivityManager(connectivity);
                     networkPolicy.bindConnectivityManager(connectivity);
                 } catch (Throwable e) {
@@ -1524,6 +1550,12 @@
             traceEnd();
         }
 
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) {
+            traceBeginAndSlog("StartIoTSystemService");
+            mSystemServiceManager.startService(IOT_SERVICE_CLASS);
+            traceEnd();
+        }
+
         // Statsd helper
         traceBeginAndSlog("StartStatsCompanionService");
         mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
@@ -1622,11 +1654,7 @@
         traceEnd();
 
         traceBeginAndSlog("MakePackageManagerServiceReady");
-        try {
-            mPackageManagerService.systemReady();
-        } catch (Throwable e) {
-            reportWtf("making Package Manager Service ready", e);
-        }
+        mPackageManagerService.systemReady();
         traceEnd();
 
         traceBeginAndSlog("MakeDisplayManagerServiceReady");
@@ -1640,6 +1668,25 @@
 
         mSystemServiceManager.setSafeMode(safeMode);
 
+        // Start device specific services
+        traceBeginAndSlog("StartDeviceSpecificServices");
+        final String[] classes = mSystemContext.getResources().getStringArray(
+                R.array.config_deviceSpecificSystemServices);
+        for (final String className : classes) {
+            traceBeginAndSlog("StartDeviceSpecificServices " + className);
+            try {
+                mSystemServiceManager.startService(className);
+            } catch (Throwable e) {
+                reportWtf("starting " + className, e);
+            }
+            traceEnd();
+        }
+        traceEnd();
+
+        traceBeginAndSlog("StartBootPhaseDeviceSpecificServicesReady");
+        mSystemServiceManager.startBootPhase(SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY);
+        traceEnd();
+
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkStatsService networkStatsF = networkStats;
@@ -1654,6 +1701,7 @@
         final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
         final MediaRouterService mediaRouterF = mediaRouter;
         final MmsServiceBroker mmsServiceF = mmsService;
+        final IpSecService ipSecServiceF = ipSecService;
         final WindowManagerService windowManagerF = wm;
 
         // We now tell the activity manager it is okay to run third party
@@ -1676,10 +1724,10 @@
             traceEnd();
 
             // No dependency on Webview preparation in system server. But this should
-            // be completed before allowring 3rd party
+            // be completed before allowing 3rd party
             final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
             Future<?> webviewPrep = null;
-            if (!mOnlyCore) {
+            if (!mOnlyCore && mWebViewUpdateService != null) {
                 webviewPrep = SystemServerInitThreadPool.get().submit(() -> {
                     Slog.i(TAG, WEBVIEW_PREPARATION);
                     TimingsTraceLog traceLog = new TimingsTraceLog(
@@ -1724,6 +1772,13 @@
                         .networkScoreAndNetworkManagementServiceReady();
             }
             traceEnd();
+            traceBeginAndSlog("MakeIpSecServiceReady");
+            try {
+                if (ipSecServiceF != null) ipSecServiceF.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making IpSec Service ready", e);
+            }
+            traceEnd();
             traceBeginAndSlog("MakeNetworkStatsServiceReady");
             try {
                 if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 190b3a6..5c2b66f 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -33,7 +33,7 @@
 import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
@@ -238,7 +238,7 @@
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
     private final ApfCapabilities mApfCapabilities;
-    private final IpManager.Callback mIpManagerCallback;
+    private final IpClient.Callback mIpClientCallback;
     private final NetworkInterface mNetworkInterface;
     private final IpConnectivityLog mMetricsLog;
 
@@ -262,10 +262,10 @@
 
     @VisibleForTesting
     ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
-            IpManager.Callback ipManagerCallback, boolean multicastFilter,
+            IpClient.Callback ipClientCallback, boolean multicastFilter,
             boolean ieee802_3Filter, int[] ethTypeBlackList, IpConnectivityLog log) {
         mApfCapabilities = apfCapabilities;
-        mIpManagerCallback = ipManagerCallback;
+        mIpClientCallback = ipClientCallback;
         mNetworkInterface = networkInterface;
         mMulticastFilter = multicastFilter;
         mDrop802_3Frames = ieee802_3Filter;
@@ -275,7 +275,7 @@
 
         mMetricsLog = log;
 
-        // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
+        // TODO: ApfFilter should not generate programs until IpClient sends provisioning success.
         maybeStartFilter();
     }
 
@@ -1051,7 +1051,7 @@
         if (VDBG) {
             hexDump("Installing filter: ", program, program.length);
         }
-        mIpManagerCallback.installPacketFilter(program);
+        mIpClientCallback.installPacketFilter(program);
         logApfProgramEventLocked(now);
         mLastInstallEvent = new ApfProgramEvent();
         mLastInstallEvent.lifetime = programMinLifetime;
@@ -1161,7 +1161,7 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
-            NetworkInterface networkInterface, IpManager.Callback ipManagerCallback,
+            NetworkInterface networkInterface, IpClient.Callback ipClientCallback,
             boolean multicastFilter, boolean ieee802_3Filter, int[] ethTypeBlackList) {
         if (apfCapabilities == null || networkInterface == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
@@ -1178,7 +1178,7 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback,
+        return new ApfFilter(apfCapabilities, networkInterface, ipClientCallback,
                 multicastFilter, ieee802_3Filter, ethTypeBlackList, new IpConnectivityLog());
     }
 
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
index 884a8a7..1925c39 100644
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
@@ -25,6 +25,7 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.PacketSocketAddress;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.LocalLog;
 
@@ -59,13 +60,16 @@
     private static final boolean DBG = false;
     private static final String MARK_START = "--- START ---";
     private static final String MARK_STOP = "--- STOP ---";
+    private static final String MARK_NAMED_START = "--- START (%s) ---";
+    private static final String MARK_NAMED_STOP = "--- STOP (%s) ---";
 
     private final String mTag;
-    private final Handler mHandler;
     private final LocalLog mLog;
     private final BlockingSocketReader mPacketListener;
+    private boolean mRunning;
+    private String mDisplayName;
 
-    public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) {
+    public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
         final String ifname;
         final int ifindex;
         final byte[] hwaddr;
@@ -81,44 +85,42 @@
         }
 
         mTag = TAG + "." + ifname;
-        mHandler = new Handler();
         mLog = log;
-        mPacketListener = new PacketListener(ifindex, hwaddr, mtu);
+        mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
     }
 
-    public void start() {
-        mLog.log(MARK_START);
+    public void start(String displayName) {
+        mRunning = true;
+        mDisplayName = displayName;
         mPacketListener.start();
     }
 
     public void stop() {
         mPacketListener.stop();
-        mLog.log(MARK_STOP);
+        mRunning = false;
+        mDisplayName = null;
     }
 
     private final class PacketListener extends BlockingSocketReader {
         private final int mIfIndex;
         private final byte mHwAddr[];
 
-        PacketListener(int ifindex, byte[] hwaddr, int mtu) {
-            super(mtu);
+        PacketListener(Handler h, int ifindex, byte[] hwaddr, int mtu) {
+            super(h, mtu);
             mIfIndex = ifindex;
             mHwAddr = hwaddr;
         }
 
         @Override
-        protected FileDescriptor createSocket() {
+        protected FileDescriptor createFd() {
             FileDescriptor s = null;
             try {
-                // TODO: Evaluate switching to SOCK_DGRAM and changing the
-                // BlockingSocketReader's read() to recvfrom(), so that this
-                // might work on non-ethernet-like links (via SLL).
                 s = Os.socket(AF_PACKET, SOCK_RAW, 0);
                 NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
                 Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
             } catch (ErrnoException | IOException e) {
                 logError("Failed to create packet tracking socket: ", e);
-                closeSocket(s);
+                closeFd(s);
                 return null;
             }
             return s;
@@ -136,13 +138,30 @@
         }
 
         @Override
+        protected void onStart() {
+            final String msg = TextUtils.isEmpty(mDisplayName)
+                    ? MARK_START
+                    : String.format(MARK_NAMED_START, mDisplayName);
+            mLog.log(msg);
+        }
+
+        @Override
+        protected void onStop() {
+            String msg = TextUtils.isEmpty(mDisplayName)
+                    ? MARK_STOP
+                    : String.format(MARK_NAMED_STOP, mDisplayName);
+            if (!mRunning) msg += " (packet listener stopped unexpectedly)";
+            mLog.log(msg);
+        }
+
+        @Override
         protected void logError(String msg, Exception e) {
             Log.e(mTag, msg, e);
             addLogEntry(msg + e);
         }
 
         private void addLogEntry(String entry) {
-            mHandler.post(() -> mLog.log(entry));
+            mLog.log(entry);
         }
     }
 }
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
new file mode 100644
index 0000000..2359fab
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -0,0 +1,1712 @@
+/*
+ * 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.net.ip;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.WakeupMessage;
+
+import android.content.Context;
+import android.net.DhcpResults;
+import android.net.INetd;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties.ProvisioningChange;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ProxyInfo;
+import android.net.RouteInfo;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.dhcp.DhcpClient;
+import android.net.metrics.IpConnectivityLog;
+import android.net.metrics.IpManagerEvent;
+import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
+import android.net.util.NetworkConstants;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.IState;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.net.NetlinkTracker;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+
+/**
+ * IpClient
+ *
+ * This class provides the interface to IP-layer provisioning and maintenance
+ * functionality that can be used by transport layers like Wi-Fi, Ethernet,
+ * et cetera.
+ *
+ * [ Lifetime ]
+ * IpClient is designed to be instantiated as soon as the interface name is
+ * known and can be as long-lived as the class containing it (i.e. declaring
+ * it "private final" is okay).
+ *
+ * @hide
+ */
+public class IpClient extends StateMachine {
+    private static final boolean DBG = false;
+
+    // For message logging.
+    private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class };
+    private static final SparseArray<String> sWhatToString =
+            MessageUtils.findMessageNames(sMessageClasses);
+
+    /**
+     * Callbacks for handling IpClient events.
+     */
+    public static class Callback {
+        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
+        // when constructing a ProvisioningConfiguration.
+        //
+        // Implementations of onPreDhcpAction() must call
+        // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
+        // to proceed.
+        public void onPreDhcpAction() {}
+        public void onPostDhcpAction() {}
+
+        // This is purely advisory and not an indication of provisioning
+        // success or failure.  This is only here for callers that want to
+        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+        // DHCPv4 or static IPv4 configuration failure or success can be
+        // determined by whether or not the passed-in DhcpResults object is
+        // null or not.
+        public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+        public void onProvisioningSuccess(LinkProperties newLp) {}
+        public void onProvisioningFailure(LinkProperties newLp) {}
+
+        // Invoked on LinkProperties changes.
+        public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+        // Called when the internal IpReachabilityMonitor (if enabled) has
+        // detected the loss of a critical number of required neighbors.
+        public void onReachabilityLost(String logMsg) {}
+
+        // Called when the IpClient state machine terminates.
+        public void onQuit() {}
+
+        // Install an APF program to filter incoming packets.
+        public void installPacketFilter(byte[] filter) {}
+
+        // If multicast filtering cannot be accomplished with APF, this function will be called to
+        // actuate multicast filtering using another means.
+        public void setFallbackMulticastFilter(boolean enabled) {}
+
+        // Enabled/disable Neighbor Discover offload functionality. This is
+        // called, for example, whenever 464xlat is being started or stopped.
+        public void setNeighborDiscoveryOffload(boolean enable) {}
+    }
+
+    // Use a wrapper class to log in order to ensure complete and detailed
+    // logging. This method is lighter weight than annotations/reflection
+    // and has the following benefits:
+    //
+    //     - No invoked method can be forgotten.
+    //       Any new method added to IpClient.Callback must be overridden
+    //       here or it will never be called.
+    //
+    //     - No invoking call site can be forgotten.
+    //       Centralized logging in this way means call sites don't need to
+    //       remember to log, and therefore no call site can be forgotten.
+    //
+    //     - No variation in log format among call sites.
+    //       Encourages logging of any available arguments, and all call sites
+    //       are necessarily logged identically.
+    //
+    // TODO: Find an lighter weight approach.
+    private class LoggingCallbackWrapper extends Callback {
+        private static final String PREFIX = "INVOKE ";
+        private Callback mCallback;
+
+        public LoggingCallbackWrapper(Callback callback) {
+            mCallback = callback;
+        }
+
+        private void log(String msg) {
+            mLog.log(PREFIX + msg);
+        }
+
+        @Override
+        public void onPreDhcpAction() {
+            mCallback.onPreDhcpAction();
+            log("onPreDhcpAction()");
+        }
+        @Override
+        public void onPostDhcpAction() {
+            mCallback.onPostDhcpAction();
+            log("onPostDhcpAction()");
+        }
+        @Override
+        public void onNewDhcpResults(DhcpResults dhcpResults) {
+            mCallback.onNewDhcpResults(dhcpResults);
+            log("onNewDhcpResults({" + dhcpResults + "})");
+        }
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCallback.onProvisioningSuccess(newLp);
+            log("onProvisioningSuccess({" + newLp + "})");
+        }
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCallback.onProvisioningFailure(newLp);
+            log("onProvisioningFailure({" + newLp + "})");
+        }
+        @Override
+        public void onLinkPropertiesChange(LinkProperties newLp) {
+            mCallback.onLinkPropertiesChange(newLp);
+            log("onLinkPropertiesChange({" + newLp + "})");
+        }
+        @Override
+        public void onReachabilityLost(String logMsg) {
+            mCallback.onReachabilityLost(logMsg);
+            log("onReachabilityLost(" + logMsg + ")");
+        }
+        @Override
+        public void onQuit() {
+            mCallback.onQuit();
+            log("onQuit()");
+        }
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mCallback.installPacketFilter(filter);
+            log("installPacketFilter(byte[" + filter.length + "])");
+        }
+        @Override
+        public void setFallbackMulticastFilter(boolean enabled) {
+            mCallback.setFallbackMulticastFilter(enabled);
+            log("setFallbackMulticastFilter(" + enabled + ")");
+        }
+        @Override
+        public void setNeighborDiscoveryOffload(boolean enable) {
+            mCallback.setNeighborDiscoveryOffload(enable);
+            log("setNeighborDiscoveryOffload(" + enable + ")");
+        }
+    }
+
+    /**
+     * This class encapsulates parameters to be passed to
+     * IpClient#startProvisioning(). A defensive copy is made by IpClient
+     * and the values specified herein are in force until IpClient#stop()
+     * is called.
+     *
+     * Example use:
+     *
+     *     final ProvisioningConfiguration config =
+     *             mIpClient.buildProvisioningConfiguration()
+     *                     .withPreDhcpAction()
+     *                     .withProvisioningTimeoutMs(36 * 1000)
+     *                     .build();
+     *     mIpClient.startProvisioning(config);
+     *     ...
+     *     mIpClient.stop();
+     *
+     * The specified provisioning configuration will only be active until
+     * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
+     * must specify the configuration again.
+     */
+    public static class ProvisioningConfiguration {
+        // TODO: Delete this default timeout once those callers that care are
+        // fixed to pass in their preferred timeout.
+        //
+        // We pick 36 seconds so we can send DHCP requests at
+        //
+        //     t=0, t=2, t=6, t=14, t=30
+        //
+        // allowing for 10% jitter.
+        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+        public static class Builder {
+            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+            public Builder withoutIPv4() {
+                mConfig.mEnableIPv4 = false;
+                return this;
+            }
+
+            public Builder withoutIPv6() {
+                mConfig.mEnableIPv6 = false;
+                return this;
+            }
+
+            public Builder withoutIpReachabilityMonitor() {
+                mConfig.mUsingIpReachabilityMonitor = false;
+                return this;
+            }
+
+            public Builder withPreDhcpAction() {
+                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+                return this;
+            }
+
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+                return this;
+            }
+
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                mConfig.mInitialConfig = initialConfig;
+                return this;
+            }
+
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                mConfig.mStaticIpConfig = staticConfig;
+                return this;
+            }
+
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                mConfig.mApfCapabilities = apfCapabilities;
+                return this;
+            }
+
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                mConfig.mProvisioningTimeoutMs = timeoutMs;
+                return this;
+            }
+
+            public Builder withIPv6AddrGenModeEUI64() {
+                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+                return this;
+            }
+
+            public Builder withIPv6AddrGenModeStablePrivacy() {
+                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+                return this;
+            }
+
+            public Builder withNetwork(Network network) {
+                mConfig.mNetwork = network;
+                return this;
+            }
+
+            public Builder withDisplayName(String displayName) {
+                mConfig.mDisplayName = displayName;
+                return this;
+            }
+
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(mConfig);
+            }
+        }
+
+        /* package */ boolean mEnableIPv4 = true;
+        /* package */ boolean mEnableIPv6 = true;
+        /* package */ boolean mUsingIpReachabilityMonitor = true;
+        /* package */ int mRequestedPreDhcpActionMs;
+        /* package */ InitialConfiguration mInitialConfig;
+        /* package */ StaticIpConfiguration mStaticIpConfig;
+        /* package */ ApfCapabilities mApfCapabilities;
+        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+        /* package */ Network mNetwork = null;
+        /* package */ String mDisplayName = null;
+
+        public ProvisioningConfiguration() {} // used by Builder
+
+        public ProvisioningConfiguration(ProvisioningConfiguration other) {
+            mEnableIPv4 = other.mEnableIPv4;
+            mEnableIPv6 = other.mEnableIPv6;
+            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+            mStaticIpConfig = other.mStaticIpConfig;
+            mApfCapabilities = other.mApfCapabilities;
+            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+            mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+            mNetwork = other.mNetwork;
+            mDisplayName = other.mDisplayName;
+        }
+
+        @Override
+        public String toString() {
+            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+                    .add("mEnableIPv4: " + mEnableIPv4)
+                    .add("mEnableIPv6: " + mEnableIPv6)
+                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+                    .add("mInitialConfig: " + mInitialConfig)
+                    .add("mStaticIpConfig: " + mStaticIpConfig)
+                    .add("mApfCapabilities: " + mApfCapabilities)
+                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+                    .add("mNetwork: " + mNetwork)
+                    .add("mDisplayName: " + mDisplayName)
+                    .toString();
+        }
+
+        public boolean isValid() {
+            return (mInitialConfig == null) || mInitialConfig.isValid();
+        }
+    }
+
+    public static class InitialConfiguration {
+        public final Set<LinkAddress> ipAddresses = new HashSet<>();
+        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+        public final Set<InetAddress> dnsServers = new HashSet<>();
+        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
+
+        public static InitialConfiguration copy(InitialConfiguration config) {
+            if (config == null) {
+                return null;
+            }
+            InitialConfiguration configCopy = new InitialConfiguration();
+            configCopy.ipAddresses.addAll(config.ipAddresses);
+            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+            configCopy.dnsServers.addAll(config.dnsServers);
+            return configCopy;
+        }
+
+        @Override
+        public String toString() {
+            return String.format(
+                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
+                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+                    join(", ", dnsServers), gateway);
+        }
+
+        public boolean isValid() {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            // For every IP address, there must be at least one prefix containing that address.
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+                    return false;
+                }
+            }
+            // For every dns server, there must be at least one prefix containing that address.
+            for (InetAddress addr : dnsServers) {
+                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+                    return false;
+                }
+            }
+            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+            // (read: compliant with RFC4291#section2.5.4).
+            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // If directlyConnectedRoutes contains an IPv6 default route
+            // then ipAddresses MUST contain at least one non-ULA GUA.
+            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+                return false;
+            }
+            // The prefix length of routes in directlyConnectedRoutes be within reasonable
+            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+                return false;
+            }
+            // There no more than one IPv4 address
+            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * @return true if the given list of addressess and routes satisfies provisioning for this
+         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+         * because addresses and routes seen by Netlink will contain additional fields like flags,
+         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+         * provisioning check always fails.
+         *
+         * If the given list of routes is null, only addresses are taken into considerations.
+         */
+        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+                    return false;
+                }
+            }
+
+            if (routes != null) {
+                for (IpPrefix prefix : directlyConnectedRoutes) {
+                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+            return !route.hasGateway() && prefix.equals(route.getDestination());
+        }
+
+        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+        }
+
+        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+        }
+
+        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
+        }
+
+        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+            return prefix.getAddress().equals(Inet6Address.ANY);
+        }
+
+        private static boolean isIPv6GUA(LinkAddress addr) {
+            return addr.isIPv6() && addr.isGlobalPreferred();
+        }
+    }
+
+    public static final String DUMP_ARG = "ipclient";
+    public static final String DUMP_ARG_CONFIRM = "confirm";
+
+    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
+    private static final int CMD_STOP                             = 2;
+    private static final int CMD_START                            = 3;
+    private static final int CMD_CONFIRM                          = 4;
+    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
+    // Sent by NetlinkTracker to communicate netlink events.
+    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
+    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
+    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
+    private static final int CMD_SET_MULTICAST_FILTER             = 9;
+    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
+    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
+
+    private static final int MAX_LOG_RECORDS = 500;
+    private static final int MAX_PACKET_RECORDS = 100;
+
+    private static final boolean NO_CALLBACKS = false;
+    private static final boolean SEND_CALLBACKS = true;
+
+    // This must match the interface prefix in clatd.c.
+    // TODO: Revert this hack once IpClient and Nat464Xlat work in concert.
+    private static final String CLAT_PREFIX = "v4-";
+
+    private final State mStoppedState = new StoppedState();
+    private final State mStoppingState = new StoppingState();
+    private final State mStartedState = new StartedState();
+    private final State mRunningState = new RunningState();
+
+    private final String mTag;
+    private final Context mContext;
+    private final String mInterfaceName;
+    private final String mClatInterfaceName;
+    @VisibleForTesting
+    protected final Callback mCallback;
+    private final INetworkManagementService mNwService;
+    private final NetlinkTracker mNetlinkTracker;
+    private final WakeupMessage mProvisioningTimeoutAlarm;
+    private final WakeupMessage mDhcpActionTimeoutAlarm;
+    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+    private final SharedLog mLog;
+    private final LocalLog mConnectivityPacketLog;
+    private final MessageHandlingLogger mMsgStateLogger;
+    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+    private final InterfaceController mInterfaceCtrl;
+
+    private NetworkInterface mNetworkInterface;
+
+    /**
+     * Non-final member variables accessed only from within our StateMachine.
+     */
+    private LinkProperties mLinkProperties;
+    private ProvisioningConfiguration mConfiguration;
+    private IpReachabilityMonitor mIpReachabilityMonitor;
+    private DhcpClient mDhcpClient;
+    private DhcpResults mDhcpResults;
+    private String mTcpBufferSizes;
+    private ProxyInfo mHttpProxy;
+    private ApfFilter mApfFilter;
+    private boolean mMulticastFiltering;
+    private long mStartTimeMillis;
+
+    public IpClient(Context context, String ifName, Callback callback) {
+        this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
+                NetdService.getInstance());
+    }
+
+    /**
+     * An expanded constructor, useful for dependency injection.
+     * TODO: migrate all test users to mock IpClient directly and remove this ctor.
+     */
+    public IpClient(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService) {
+        this(context, ifName, callback, nwService, NetdService.getInstance());
+    }
+
+    @VisibleForTesting
+    IpClient(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService, INetd netd) {
+        super(IpClient.class.getSimpleName() + "." + ifName);
+        mTag = getName();
+
+        mContext = context;
+        mInterfaceName = ifName;
+        mClatInterfaceName = CLAT_PREFIX + ifName;
+        mCallback = new LoggingCallbackWrapper(callback);
+        mNwService = nwService;
+
+        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
+        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
+        mMsgStateLogger = new MessageHandlingLogger();
+
+        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
+
+        mNetlinkTracker = new NetlinkTracker(
+                mInterfaceName,
+                new NetlinkTracker.Callback() {
+                    @Override
+                    public void update() {
+                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+                    }
+                }) {
+            @Override
+            public void interfaceAdded(String iface) {
+                super.interfaceAdded(iface);
+                if (mClatInterfaceName.equals(iface)) {
+                    mCallback.setNeighborDiscoveryOffload(false);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceAdded(" + iface +")";
+                logMsg(msg);
+            }
+
+            @Override
+            public void interfaceRemoved(String iface) {
+                super.interfaceRemoved(iface);
+                // TODO: Also observe mInterfaceName going down and take some
+                // kind of appropriate action.
+                if (mClatInterfaceName.equals(iface)) {
+                    // TODO: consider sending a message to the IpClient main
+                    // StateMachine thread, in case "NDO enabled" state becomes
+                    // tied to more things that 464xlat operation.
+                    mCallback.setNeighborDiscoveryOffload(true);
+                } else if (!mInterfaceName.equals(iface)) {
+                    return;
+                }
+
+                final String msg = "interfaceRemoved(" + iface +")";
+                logMsg(msg);
+            }
+
+            private void logMsg(String msg) {
+                Log.d(mTag, msg);
+                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
+            }
+        };
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+
+        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
+                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
+
+        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
+        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
+                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
+
+        // Anything the StateMachine may access must have been instantiated
+        // before this point.
+        configureAndStartStateMachine();
+
+        // Anything that may send messages to the StateMachine must only be
+        // configured to do so after the StateMachine has started (above).
+        startStateMachineUpdaters();
+    }
+
+    private void configureAndStartStateMachine() {
+        addState(mStoppedState);
+        addState(mStartedState);
+            addState(mRunningState, mStartedState);
+        addState(mStoppingState);
+
+        setInitialState(mStoppedState);
+
+        super.start();
+    }
+
+    private void startStateMachineUpdaters() {
+        try {
+            mNwService.registerObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't register NetlinkTracker: %s", e);
+        }
+
+        mMultinetworkPolicyTracker.start();
+    }
+
+    private void stopStateMachineUpdaters() {
+        try {
+            mNwService.unregisterObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            logError("Couldn't unregister NetlinkTracker: %s", e);
+        }
+
+        mMultinetworkPolicyTracker.shutdown();
+    }
+
+    @Override
+    protected void onQuitting() {
+        mCallback.onQuit();
+    }
+
+    // Shut down this IpClient instance altogether.
+    public void shutdown() {
+        stop();
+        sendMessage(CMD_TERMINATE_AFTER_STOP);
+    }
+
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
+    }
+
+    public void startProvisioning(ProvisioningConfiguration req) {
+        if (!req.isValid()) {
+            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+            return;
+        }
+
+        getNetworkInterface();
+
+        mCallback.setNeighborDiscoveryOffload(true);
+        sendMessage(CMD_START, new ProvisioningConfiguration(req));
+    }
+
+    // TODO: Delete this.
+    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
+        startProvisioning(buildProvisioningConfiguration()
+                .withStaticConfiguration(staticIpConfig)
+                .build());
+    }
+
+    public void startProvisioning() {
+        startProvisioning(new ProvisioningConfiguration());
+    }
+
+    public void stop() {
+        sendMessage(CMD_STOP);
+    }
+
+    public void confirmConfiguration() {
+        sendMessage(CMD_CONFIRM);
+    }
+
+    public void completedPreDhcpAction() {
+        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setTcpBufferSizes(String tcpBufferSizes) {
+        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setHttpProxy(ProxyInfo proxyInfo) {
+        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public void setMulticastFilter(boolean enabled) {
+        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
+            // Execute confirmConfiguration() and take no further action.
+            confirmConfiguration();
+            return;
+        }
+
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        final ApfFilter apfFilter = mApfFilter;
+        final ProvisioningConfiguration provisioningConfig = mConfiguration;
+        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
+                ? provisioningConfig.mApfCapabilities : null;
+
+        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println(mTag + " APF dump:");
+        pw.increaseIndent();
+        if (apfFilter != null) {
+            apfFilter.dump(pw);
+        } else {
+            pw.print("No active ApfFilter; ");
+            if (provisioningConfig == null) {
+                pw.println("IpClient not yet started.");
+            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
+                pw.println("Hardware does not support APF.");
+            } else {
+                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
+            }
+        }
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " current ProvisioningConfiguration:");
+        pw.increaseIndent();
+        pw.println(Objects.toString(provisioningConfig, "N/A"));
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " StateMachine dump:");
+        pw.increaseIndent();
+        mLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+
+        pw.println();
+        pw.println(mTag + " connectivity packet log:");
+        pw.println();
+        pw.println("Debug with python and scapy via:");
+        pw.println("shell$ python");
+        pw.println(">>> from scapy import all as scapy");
+        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
+        pw.println();
+
+        pw.increaseIndent();
+        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
+
+
+    /**
+     * Internals.
+     */
+
+    @Override
+    protected String getWhatToString(int what) {
+        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
+    }
+
+    @Override
+    protected String getLogRecString(Message msg) {
+        final String logLine = String.format(
+                "%s/%d %d %d %s [%s]",
+                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
+                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
+
+        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
+        mLog.log(richerLogLine);
+        if (DBG) {
+            Log.d(mTag, richerLogLine);
+        }
+
+        mMsgStateLogger.reset();
+        return logLine;
+    }
+
+    @Override
+    protected boolean recordLogRec(Message msg) {
+        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
+        // and we already log any LinkProperties change that results in an
+        // invocation of IpClient.Callback#onLinkPropertiesChange().
+        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
+        if (!shouldLog) {
+            mMsgStateLogger.reset();
+        }
+        return shouldLog;
+    }
+
+    private void logError(String fmt, Object... args) {
+        final String msg = "ERROR " + String.format(fmt, args);
+        Log.e(mTag, msg);
+        mLog.log(msg);
+    }
+
+    private void getNetworkInterface() {
+        try {
+            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
+        } catch (SocketException | NullPointerException e) {
+            // TODO: throw new IllegalStateException.
+            logError("Failed to get interface object: %s", e);
+        }
+    }
+
+    // This needs to be called with care to ensure that our LinkProperties
+    // are in sync with the actual LinkProperties of the interface. For example,
+    // we should only call this if we know for sure that there are no IP addresses
+    // assigned to the interface, etc.
+    private void resetLinkProperties() {
+        mNetlinkTracker.clearLinkProperties();
+        mConfiguration = null;
+        mDhcpResults = null;
+        mTcpBufferSizes = "";
+        mHttpProxy = null;
+
+        mLinkProperties = new LinkProperties();
+        mLinkProperties.setInterfaceName(mInterfaceName);
+    }
+
+    private void recordMetric(final int type) {
+        if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
+        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
+        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
+    }
+
+    // For now: use WifiStateMachine's historical notion of provisioned.
+    @VisibleForTesting
+    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
+        // For historical reasons, we should connect even if all we have is
+        // an IPv4 address and nothing else.
+        if (lp.hasIPv4Address() || lp.isProvisioned()) {
+            return true;
+        }
+        if (config == null) {
+            return false;
+        }
+
+        // When an InitialConfiguration is specified, ignore any difference with previous
+        // properties and instead check if properties observed match the desired properties.
+        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
+    }
+
+    // TODO: Investigate folding all this into the existing static function
+    // LinkProperties.compareProvisioning() or some other single function that
+    // takes two LinkProperties objects and returns a ProvisioningChange
+    // object that is a correct and complete assessment of what changed, taking
+    // account of the asymmetries described in the comments in this function.
+    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
+    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
+        ProvisioningChange delta;
+        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+        final boolean wasProvisioned = isProvisioned(oldLp, config);
+        final boolean isProvisioned = isProvisioned(newLp, config);
+
+        if (!wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.GAINED_PROVISIONING;
+        } else if (wasProvisioned && isProvisioned) {
+            delta = ProvisioningChange.STILL_PROVISIONED;
+        } else if (!wasProvisioned && !isProvisioned) {
+            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
+        } else {
+            // (wasProvisioned && !isProvisioned)
+            //
+            // Note that this is true even if we lose a configuration element
+            // (e.g., a default gateway) that would not be required to advance
+            // into provisioned state. This is intended: if we have a default
+            // router and we lose it, that's a sure sign of a problem, but if
+            // we connect to a network with no IPv4 DNS servers, we consider
+            // that to be a network without DNS servers and connect anyway.
+            //
+            // See the comment below.
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
+        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
+        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
+
+        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
+        // provisioning. Otherwise, when a hotspot that loses Internet
+        // access sends out a 0-lifetime RA to its clients, the clients
+        // will disconnect and then reconnect, avoiding the bad hotspot,
+        // instead of getting stuck on the bad hotspot. http://b/31827713 .
+        //
+        // This is incorrect because if the hotspot then regains Internet
+        // access with a different prefix, TCP connections on the
+        // deprecated addresses will remain stuck.
+        //
+        // Note that we can still be disconnected by IpReachabilityMonitor
+        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
+        // accompanying code in IpReachabilityMonitor) is unreachable.
+        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
+
+        // Additionally:
+        //
+        // Partial configurations (e.g., only an IPv4 address with no DNS
+        // servers and no default route) are accepted as long as DHCPv4
+        // succeeds. On such a network, isProvisioned() will always return
+        // false, because the configuration is not complete, but we want to
+        // connect anyway. It might be a disconnected network such as a
+        // Chromecast or a wireless printer, for example.
+        //
+        // Because on such a network isProvisioned() will always return false,
+        // delta will never be LOST_PROVISIONING. So check for loss of
+        // provisioning here too.
+        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        // Additionally:
+        //
+        // If the previous link properties had a global IPv6 address and an
+        // IPv6 default route then also consider the loss of that default route
+        // to be a loss of provisioning. See b/27962810.
+        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        return delta;
+    }
+
+    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
+        switch (delta) {
+            case GAINED_PROVISIONING:
+                if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_OK);
+                mCallback.onProvisioningSuccess(newLp);
+                break;
+
+            case LOST_PROVISIONING:
+                if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+                mCallback.onProvisioningFailure(newLp);
+                break;
+
+            default:
+                if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
+                mCallback.onLinkPropertiesChange(newLp);
+                break;
+        }
+    }
+
+    // Updates all IpClient-related state concerned with LinkProperties.
+    // Returns a ProvisioningChange for possibly notifying other interested
+    // parties that are not fronted by IpClient.
+    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
+        if (mApfFilter != null) {
+            mApfFilter.setLinkProperties(newLp);
+        }
+        if (mIpReachabilityMonitor != null) {
+            mIpReachabilityMonitor.updateLinkProperties(newLp);
+        }
+
+        ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
+        mLinkProperties = new LinkProperties(newLp);
+
+        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
+            // TODO: Add a proper ProvisionedState and cancel the alarm in
+            // its enter() method.
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        return delta;
+    }
+
+    private LinkProperties assembleLinkProperties() {
+        // [1] Create a new LinkProperties object to populate.
+        LinkProperties newLp = new LinkProperties();
+        newLp.setInterfaceName(mInterfaceName);
+
+        // [2] Pull in data from netlink:
+        //         - IPv4 addresses
+        //         - IPv6 addresses
+        //         - IPv6 routes
+        //         - IPv6 DNS servers
+        //
+        // N.B.: this is fundamentally race-prone and should be fixed by
+        // changing NetlinkTracker from a hybrid edge/level model to an
+        // edge-only model, or by giving IpClient its own netlink socket(s)
+        // so as to track all required information directly.
+        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
+        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
+        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
+            newLp.addRoute(route);
+        }
+        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
+
+        // [3] Add in data from DHCPv4, if available.
+        //
+        // mDhcpResults is never shared with any other owner so we don't have
+        // to worry about concurrent modification.
+        if (mDhcpResults != null) {
+            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
+                newLp.addRoute(route);
+            }
+            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
+            newLp.setDomains(mDhcpResults.domains);
+
+            if (mDhcpResults.mtu != 0) {
+                newLp.setMtu(mDhcpResults.mtu);
+            }
+        }
+
+        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+            newLp.setTcpBufferSizes(mTcpBufferSizes);
+        }
+        if (mHttpProxy != null) {
+            newLp.setHttpProxy(mHttpProxy);
+        }
+
+        // [5] Add data from InitialConfiguration
+        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+            InitialConfiguration config = mConfiguration.mInitialConfig;
+            // Add InitialConfiguration routes and dns server addresses once all addresses
+            // specified in the InitialConfiguration have been observed with Netlink.
+            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+                for (IpPrefix prefix : config.directlyConnectedRoutes) {
+                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+                }
+            }
+            addAllReachableDnsServers(newLp, config.dnsServers);
+        }
+        final LinkProperties oldLp = mLinkProperties;
+        if (DBG) {
+            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+                    netlinkLinkProperties, newLp, oldLp));
+        }
+
+        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
+        return newLp;
+    }
+
+    private static void addAllReachableDnsServers(
+            LinkProperties lp, Iterable<InetAddress> dnses) {
+        // TODO: Investigate deleting this reachability check.  We should be
+        // able to pass everything down to netd and let netd do evaluation
+        // and RFC6724-style sorting.
+        for (InetAddress dns : dnses) {
+            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
+                lp.addDnsServer(dns);
+            }
+        }
+    }
+
+    // Returns false if we have lost provisioning, true otherwise.
+    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+        final LinkProperties newLp = assembleLinkProperties();
+        if (Objects.equals(newLp, mLinkProperties)) {
+            return true;
+        }
+        final ProvisioningChange delta = setLinkProperties(newLp);
+        if (sendCallbacks) {
+            dispatchCallback(delta, newLp);
+        }
+        return (delta != ProvisioningChange.LOST_PROVISIONING);
+    }
+
+    private void handleIPv4Success(DhcpResults dhcpResults) {
+        mDhcpResults = new DhcpResults(dhcpResults);
+        final LinkProperties newLp = assembleLinkProperties();
+        final ProvisioningChange delta = setLinkProperties(newLp);
+
+        if (DBG) {
+            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
+        }
+        mCallback.onNewDhcpResults(dhcpResults);
+        dispatchCallback(delta, newLp);
+    }
+
+    private void handleIPv4Failure() {
+        // TODO: Investigate deleting this clearIPv4Address() call.
+        //
+        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
+        // that could trigger a call to this function. If we missed handling
+        // that message in StartedState for some reason we would still clear
+        // any addresses upon entry to StoppedState.
+        mInterfaceCtrl.clearIPv4Address();
+        mDhcpResults = null;
+        if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
+        mCallback.onNewDhcpResults(null);
+
+        handleProvisioningFailure();
+    }
+
+    private void handleProvisioningFailure() {
+        final LinkProperties newLp = assembleLinkProperties();
+        ProvisioningChange delta = setLinkProperties(newLp);
+        // If we've gotten here and we're still not provisioned treat that as
+        // a total loss of provisioning.
+        //
+        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
+        // there was no usable IPv6 obtained before a non-zero provisioning
+        // timeout expired.
+        //
+        // Regardless: GAME OVER.
+        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
+            delta = ProvisioningChange.LOST_PROVISIONING;
+        }
+
+        dispatchCallback(delta, newLp);
+        if (delta == ProvisioningChange.LOST_PROVISIONING) {
+            transitionTo(mStoppingState);
+        }
+    }
+
+    private void doImmediateProvisioningFailure(int failureType) {
+        logError("onProvisioningFailure(): %s", failureType);
+        recordMetric(failureType);
+        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+    }
+
+    private boolean startIPv4() {
+        // If we have a StaticIpConfiguration attempt to apply it and
+        // handle the result accordingly.
+        if (mConfiguration.mStaticIpConfig != null) {
+            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+            } else {
+                return false;
+            }
+        } else {
+            // Start DHCPv4.
+            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName);
+            mDhcpClient.registerForPreDhcpNotification();
+            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+        }
+
+        return true;
+    }
+
+    private boolean startIPv6() {
+        return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
+               mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
+               mInterfaceCtrl.enableIPv6();
+    }
+
+    private boolean applyInitialConfig(InitialConfiguration config) {
+        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+            if (!mInterfaceCtrl.addAddress(addr)) return false;
+        }
+
+        return true;
+    }
+
+    private boolean startIpReachabilityMonitor() {
+        try {
+            mIpReachabilityMonitor = new IpReachabilityMonitor(
+                    mContext,
+                    mInterfaceName,
+                    mLog,
+                    new IpReachabilityMonitor.Callback() {
+                        @Override
+                        public void notifyLost(InetAddress ip, String logMsg) {
+                            mCallback.onReachabilityLost(logMsg);
+                        }
+                    },
+                    mMultinetworkPolicyTracker);
+        } catch (IllegalArgumentException iae) {
+            // Failed to start IpReachabilityMonitor. Log it and call
+            // onProvisioningFailure() immediately.
+            //
+            // See http://b/31038971.
+            logError("IpReachabilityMonitor failure: %s", iae);
+            mIpReachabilityMonitor = null;
+        }
+
+        return (mIpReachabilityMonitor != null);
+    }
+
+    private void stopAllIP() {
+        // We don't need to worry about routes, just addresses, because:
+        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
+        //     - we don't get IPv4 routes from netlink
+        // so we neither react to nor need to wait for changes in either.
+
+        mInterfaceCtrl.disableIPv6();
+        mInterfaceCtrl.clearAllAddresses();
+    }
+
+    class StoppedState extends State {
+        @Override
+        public void enter() {
+            stopAllIP();
+
+            resetLinkProperties();
+            if (mStartTimeMillis > 0) {
+                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
+                mStartTimeMillis = 0;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_TERMINATE_AFTER_STOP:
+                    stopStateMachineUpdaters();
+                    quit();
+                    break;
+
+                case CMD_STOP:
+                    break;
+
+                case CMD_START:
+                    mConfiguration = (ProvisioningConfiguration) msg.obj;
+                    transitionTo(mStartedState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER:
+                    mMulticastFiltering = (boolean) msg.obj;
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // Everything is already stopped.
+                    logError("Unexpected CMD_ON_QUIT (already stopped).");
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StoppingState extends State {
+        @Override
+        public void enter() {
+            if (mDhcpClient == null) {
+                // There's no DHCPv4 for which to wait; proceed to stopped.
+                transitionTo(mStoppedState);
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    mDhcpClient = null;
+                    transitionTo(mStoppedState);
+                    break;
+
+                default:
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    class StartedState extends State {
+        @Override
+        public void enter() {
+            mStartTimeMillis = SystemClock.elapsedRealtime();
+
+            if (mConfiguration.mProvisioningTimeoutMs > 0) {
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mProvisioningTimeoutMs;
+                mProvisioningTimeoutAlarm.schedule(alarmTime);
+            }
+
+            if (readyToProceed()) {
+                transitionTo(mRunningState);
+            } else {
+                // Clear all IPv4 and IPv6 before proceeding to RunningState.
+                // Clean up any leftover state from an abnormal exit from
+                // tethering or during an IpClient restart.
+                stopAllIP();
+            }
+        }
+
+        @Override
+        public void exit() {
+            mProvisioningTimeoutAlarm.cancel();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    if (readyToProceed()) {
+                        transitionTo(mRunningState);
+                    }
+                    break;
+
+                case EVENT_PROVISIONING_TIMEOUT:
+                    handleProvisioningFailure();
+                    break;
+
+                default:
+                    // It's safe to process messages out of order because the
+                    // only message that can both
+                    //     a) be received at this time and
+                    //     b) affect provisioning state
+                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
+                    deferMessage(msg);
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+
+        boolean readyToProceed() {
+            return (!mLinkProperties.hasIPv4Address() &&
+                    !mLinkProperties.hasGlobalIPv6Address());
+        }
+    }
+
+    class RunningState extends State {
+        private ConnectivityPacketTracker mPacketTracker;
+        private boolean mDhcpActionInFlight;
+
+        @Override
+        public void enter() {
+            // Get the Configuration for ApfFilter from Context
+            final boolean filter802_3Frames =
+                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
+
+            final int[] ethTypeBlackList = mContext.getResources().getIntArray(
+                    R.array.config_apfEthTypeBlackList);
+
+            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
+                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
+            // TODO: investigate the effects of any multicast filtering racing/interfering with the
+            // rest of this IP configuration startup.
+            if (mApfFilter == null) {
+                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+            }
+
+            mPacketTracker = createPacketTracker();
+            if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName);
+
+            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            final InitialConfiguration config = mConfiguration.mInitialConfig;
+            if ((config != null) && !applyInitialConfig(config)) {
+                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+                transitionTo(mStoppingState);
+                return;
+            }
+
+            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
+                doImmediateProvisioningFailure(
+                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
+                transitionTo(mStoppingState);
+                return;
+            }
+        }
+
+        @Override
+        public void exit() {
+            stopDhcpAction();
+
+            if (mIpReachabilityMonitor != null) {
+                mIpReachabilityMonitor.stop();
+                mIpReachabilityMonitor = null;
+            }
+
+            if (mDhcpClient != null) {
+                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
+                mDhcpClient.doQuit();
+            }
+
+            if (mPacketTracker != null) {
+                mPacketTracker.stop();
+                mPacketTracker = null;
+            }
+
+            if (mApfFilter != null) {
+                mApfFilter.shutdown();
+                mApfFilter = null;
+            }
+
+            resetLinkProperties();
+        }
+
+        private ConnectivityPacketTracker createPacketTracker() {
+            try {
+                return new ConnectivityPacketTracker(
+                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
+            } catch (IllegalArgumentException e) {
+                return null;
+            }
+        }
+
+        private void ensureDhcpAction() {
+            if (!mDhcpActionInFlight) {
+                mCallback.onPreDhcpAction();
+                mDhcpActionInFlight = true;
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mRequestedPreDhcpActionMs;
+                mDhcpActionTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+
+        private void stopDhcpAction() {
+            mDhcpActionTimeoutAlarm.cancel();
+            if (mDhcpActionInFlight) {
+                mCallback.onPostDhcpAction();
+                mDhcpActionInFlight = false;
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_STOP:
+                    transitionTo(mStoppingState);
+                    break;
+
+                case CMD_START:
+                    logError("ALERT: START received in StartedState. Please fix caller.");
+                    break;
+
+                case CMD_CONFIRM:
+                    // TODO: Possibly introduce a second type of confirmation
+                    // that both probes (a) on-link neighbors and (b) does
+                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
+                    // roams.
+                    if (mIpReachabilityMonitor != null) {
+                        mIpReachabilityMonitor.probeAll();
+                    }
+                    break;
+
+                case EVENT_PRE_DHCP_ACTION_COMPLETE:
+                    // It's possible to reach here if, for example, someone
+                    // calls completedPreDhcpAction() after provisioning with
+                    // a static IP configuration.
+                    if (mDhcpClient != null) {
+                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_SET_MULTICAST_FILTER: {
+                    mMulticastFiltering = (boolean) msg.obj;
+                    if (mApfFilter != null) {
+                        mApfFilter.setMulticastFilter(mMulticastFiltering);
+                    } else {
+                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
+                    }
+                    break;
+                }
+
+                case EVENT_DHCPACTION_TIMEOUT:
+                    stopDhcpAction();
+                    break;
+
+                case DhcpClient.CMD_PRE_DHCP_ACTION:
+                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
+                        ensureDhcpAction();
+                    } else {
+                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
+                    }
+                    break;
+
+                case DhcpClient.CMD_CLEAR_LINKADDRESS:
+                    mInterfaceCtrl.clearIPv4Address();
+                    break;
+
+                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
+                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
+                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
+                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
+                    } else {
+                        logError("Failed to set IPv4 address.");
+                        dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
+                                new LinkProperties(mLinkProperties));
+                        transitionTo(mStoppingState);
+                    }
+                    break;
+                }
+
+                // This message is only received when:
+                //
+                //     a) initial address acquisition succeeds,
+                //     b) renew succeeds or is NAK'd,
+                //     c) rebind succeeds or is NAK'd, or
+                //     c) the lease expires,
+                //
+                // but never when initial address acquisition fails. The latter
+                // condition is now governed by the provisioning timeout.
+                case DhcpClient.CMD_POST_DHCP_ACTION:
+                    stopDhcpAction();
+
+                    switch (msg.arg1) {
+                        case DhcpClient.DHCP_SUCCESS:
+                            handleIPv4Success((DhcpResults) msg.obj);
+                            break;
+                        case DhcpClient.DHCP_FAILURE:
+                            handleIPv4Failure();
+                            break;
+                        default:
+                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
+                    }
+                    break;
+
+                case DhcpClient.CMD_ON_QUIT:
+                    // DHCPv4 quit early for some reason.
+                    logError("Unexpected CMD_ON_QUIT.");
+                    mDhcpClient = null;
+                    break;
+
+                default:
+                    return NOT_HANDLED;
+            }
+
+            mMsgStateLogger.handled(this, getCurrentState());
+            return HANDLED;
+        }
+    }
+
+    private static class MessageHandlingLogger {
+        public String processedInState;
+        public String receivedInState;
+
+        public void reset() {
+            processedInState = null;
+            receivedInState = null;
+        }
+
+        public void handled(State processedIn, IState receivedIn) {
+            processedInState = processedIn.getClass().getSimpleName();
+            receivedInState = receivedIn.getName();
+        }
+
+        public String toString() {
+            return String.format("rcvd_in=%s, proc_in=%s",
+                                 receivedInState, processedInState);
+        }
+    }
+
+    // TODO: extract out into CollectionUtils.
+    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+        for (T t : coll) {
+            if (fn.test(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+        return !any(coll, not(fn));
+    }
+
+    static <T> Predicate<T> not(Predicate<T> fn) {
+        return (t) -> !fn.test(t);
+    }
+
+    static <T> String join(String delimiter, Collection<T> coll) {
+        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+    }
+
+    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+        for (T t: coll) {
+            if (fn.test(t)) {
+              return t;
+            }
+        }
+        return null;
+    }
+
+    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+        return coll.stream().filter(fn).collect(Collectors.toList());
+    }
+}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index b1eb085..b12cb32 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -16,131 +16,112 @@
 
 package android.net.ip;
 
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
-
 import android.content.Context;
-import android.net.DhcpResults;
 import android.net.INetd;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
 import android.net.LinkProperties;
-import android.net.ProxyInfo;
-import android.net.RouteInfo;
+import android.net.Network;
 import android.net.StaticIpConfiguration;
 import android.net.apf.ApfCapabilities;
-import android.net.apf.ApfFilter;
-import android.net.dhcp.DhcpClient;
-import android.net.metrics.IpConnectivityLog;
-import android.net.metrics.IpManagerEvent;
-import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
-import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
-import android.os.Message;
-import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.Log;
-import android.util.SparseArray;
+import android.net.apf.ApfCapabilities;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.IState;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-import com.android.server.net.NetlinkTracker;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 
-/**
- * IpManager
- *
- * This class provides the interface to IP-layer provisioning and maintenance
- * functionality that can be used by transport layers like Wi-Fi, Ethernet,
- * et cetera.
- *
- * [ Lifetime ]
- * IpManager is designed to be instantiated as soon as the interface name is
- * known and can be as long-lived as the class containing it (i.e. declaring
- * it "private final" is okay).
+/*
+ * TODO: Delete this altogether in favor of its renamed successor: IpClient.
  *
  * @hide
  */
-public class IpManager extends StateMachine {
-    private static final boolean DBG = false;
+public class IpManager extends IpClient {
+    public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
+        public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
+            super(ipcConfig);
+        }
 
-    // For message logging.
-    private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
-    private static final SparseArray<String> sWhatToString =
-            MessageUtils.findMessageNames(sMessageClasses);
+        public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
+            @Override
+            public Builder withoutIPv4() {
+                super.withoutIPv4();
+                return this;
+            }
+            @Override
+            public Builder withoutIPv6() {
+                super.withoutIPv6();
+                return this;
+            }
+            @Override
+            public Builder withoutIpReachabilityMonitor() {
+                super.withoutIpReachabilityMonitor();
+                return this;
+            }
+            @Override
+            public Builder withPreDhcpAction() {
+                super.withPreDhcpAction();
+                return this;
+            }
+            @Override
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                super.withPreDhcpAction(dhcpActionTimeoutMs);
+                return this;
+            }
+            // No Override; locally defined type.
+            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+                super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
+                return this;
+            }
+            @Override
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                super.withStaticConfiguration(staticConfig);
+                return this;
+            }
+            @Override
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                super.withApfCapabilities(apfCapabilities);
+                return this;
+            }
+            @Override
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                super.withProvisioningTimeoutMs(timeoutMs);
+                return this;
+            }
+            @Override
+            public Builder withIPv6AddrGenModeEUI64() {
+                super.withIPv6AddrGenModeEUI64();
+                return this;
+            }
+            @Override
+            public Builder withIPv6AddrGenModeStablePrivacy() {
+                super.withIPv6AddrGenModeStablePrivacy();
+                return this;
+            }
+            @Override
+            public Builder withNetwork(Network network) {
+                super.withNetwork(network);
+                return this;
+            }
+            @Override
+            public Builder withDisplayName(String displayName) {
+                super.withDisplayName(displayName);
+                return this;
+            }
+            @Override
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(super.build());
+            }
+        }
+    }
 
-    /**
-     * Callbacks for handling IpManager events.
-     */
-    public static class Callback {
-        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
-        // when constructing a ProvisioningConfiguration.
-        //
-        // Implementations of onPreDhcpAction() must call
-        // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
-        // to proceed.
-        public void onPreDhcpAction() {}
-        public void onPostDhcpAction() {}
+    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
+        return new ProvisioningConfiguration.Builder();
+    }
 
-        // This is purely advisory and not an indication of provisioning
-        // success or failure.  This is only here for callers that want to
-        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
-        // DHCPv4 or static IPv4 configuration failure or success can be
-        // determined by whether or not the passed-in DhcpResults object is
-        // null or not.
-        public void onNewDhcpResults(DhcpResults dhcpResults) {}
+    public static class InitialConfiguration extends IpClient.InitialConfiguration {
+    }
 
-        public void onProvisioningSuccess(LinkProperties newLp) {}
-        public void onProvisioningFailure(LinkProperties newLp) {}
-
-        // Invoked on LinkProperties changes.
-        public void onLinkPropertiesChange(LinkProperties newLp) {}
-
-        // Called when the internal IpReachabilityMonitor (if enabled) has
-        // detected the loss of a critical number of required neighbors.
-        public void onReachabilityLost(String logMsg) {}
-
-        // Called when the IpManager state machine terminates.
-        public void onQuit() {}
-
-        // Install an APF program to filter incoming packets.
-        public void installPacketFilter(byte[] filter) {}
-
-        // If multicast filtering cannot be accomplished with APF, this function will be called to
-        // actuate multicast filtering using another means.
-        public void setFallbackMulticastFilter(boolean enabled) {}
-
-        // Enabled/disable Neighbor Discover offload functionality. This is
-        // called, for example, whenever 464xlat is being started or stopped.
-        public void setNeighborDiscoveryOffload(boolean enable) {}
+    public static class Callback extends IpClient.Callback {
     }
 
     public static class WaitForProvisioningCallback extends Callback {
@@ -172,1551 +153,24 @@
         }
     }
 
-    // Use a wrapper class to log in order to ensure complete and detailed
-    // logging. This method is lighter weight than annotations/reflection
-    // and has the following benefits:
-    //
-    //     - No invoked method can be forgotten.
-    //       Any new method added to IpManager.Callback must be overridden
-    //       here or it will never be called.
-    //
-    //     - No invoking call site can be forgotten.
-    //       Centralized logging in this way means call sites don't need to
-    //       remember to log, and therefore no call site can be forgotten.
-    //
-    //     - No variation in log format among call sites.
-    //       Encourages logging of any available arguments, and all call sites
-    //       are necessarily logged identically.
-    //
-    // TODO: Find an lighter weight approach.
-    private class LoggingCallbackWrapper extends Callback {
-        private static final String PREFIX = "INVOKE ";
-        private Callback mCallback;
-
-        public LoggingCallbackWrapper(Callback callback) {
-            mCallback = callback;
-        }
-
-        private void log(String msg) {
-            mLog.log(PREFIX + msg);
-        }
-
-        @Override
-        public void onPreDhcpAction() {
-            mCallback.onPreDhcpAction();
-            log("onPreDhcpAction()");
-        }
-        @Override
-        public void onPostDhcpAction() {
-            mCallback.onPostDhcpAction();
-            log("onPostDhcpAction()");
-        }
-        @Override
-        public void onNewDhcpResults(DhcpResults dhcpResults) {
-            mCallback.onNewDhcpResults(dhcpResults);
-            log("onNewDhcpResults({" + dhcpResults + "})");
-        }
-        @Override
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            mCallback.onProvisioningSuccess(newLp);
-            log("onProvisioningSuccess({" + newLp + "})");
-        }
-        @Override
-        public void onProvisioningFailure(LinkProperties newLp) {
-            mCallback.onProvisioningFailure(newLp);
-            log("onProvisioningFailure({" + newLp + "})");
-        }
-        @Override
-        public void onLinkPropertiesChange(LinkProperties newLp) {
-            mCallback.onLinkPropertiesChange(newLp);
-            log("onLinkPropertiesChange({" + newLp + "})");
-        }
-        @Override
-        public void onReachabilityLost(String logMsg) {
-            mCallback.onReachabilityLost(logMsg);
-            log("onReachabilityLost(" + logMsg + ")");
-        }
-        @Override
-        public void onQuit() {
-            mCallback.onQuit();
-            log("onQuit()");
-        }
-        @Override
-        public void installPacketFilter(byte[] filter) {
-            mCallback.installPacketFilter(filter);
-            log("installPacketFilter(byte[" + filter.length + "])");
-        }
-        @Override
-        public void setFallbackMulticastFilter(boolean enabled) {
-            mCallback.setFallbackMulticastFilter(enabled);
-            log("setFallbackMulticastFilter(" + enabled + ")");
-        }
-        @Override
-        public void setNeighborDiscoveryOffload(boolean enable) {
-            mCallback.setNeighborDiscoveryOffload(enable);
-            log("setNeighborDiscoveryOffload(" + enable + ")");
-        }
-    }
-
-    /**
-     * This class encapsulates parameters to be passed to
-     * IpManager#startProvisioning(). A defensive copy is made by IpManager
-     * and the values specified herein are in force until IpManager#stop()
-     * is called.
-     *
-     * Example use:
-     *
-     *     final ProvisioningConfiguration config =
-     *             mIpManager.buildProvisioningConfiguration()
-     *                     .withPreDhcpAction()
-     *                     .withProvisioningTimeoutMs(36 * 1000)
-     *                     .build();
-     *     mIpManager.startProvisioning(config);
-     *     ...
-     *     mIpManager.stop();
-     *
-     * The specified provisioning configuration will only be active until
-     * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
-     * must specify the configuration again.
-     */
-    public static class ProvisioningConfiguration {
-        // TODO: Delete this default timeout once those callers that care are
-        // fixed to pass in their preferred timeout.
-        //
-        // We pick 36 seconds so we can send DHCP requests at
-        //
-        //     t=0, t=2, t=6, t=14, t=30
-        //
-        // allowing for 10% jitter.
-        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
-
-        public static class Builder {
-            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
-            public Builder withoutIPv4() {
-                mConfig.mEnableIPv4 = false;
-                return this;
-            }
-
-            public Builder withoutIPv6() {
-                mConfig.mEnableIPv6 = false;
-                return this;
-            }
-
-            public Builder withoutIpReachabilityMonitor() {
-                mConfig.mUsingIpReachabilityMonitor = false;
-                return this;
-            }
-
-            public Builder withPreDhcpAction() {
-                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
-                return this;
-            }
-
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
-                return this;
-            }
-
-            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
-                mConfig.mInitialConfig = initialConfig;
-                return this;
-            }
-
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                mConfig.mStaticIpConfig = staticConfig;
-                return this;
-            }
-
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                mConfig.mApfCapabilities = apfCapabilities;
-                return this;
-            }
-
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                mConfig.mProvisioningTimeoutMs = timeoutMs;
-                return this;
-            }
-
-            public Builder withIPv6AddrGenModeEUI64() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
-                return this;
-            }
-
-            public Builder withIPv6AddrGenModeStablePrivacy() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-                return this;
-            }
-
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(mConfig);
-            }
-        }
-
-        /* package */ boolean mEnableIPv4 = true;
-        /* package */ boolean mEnableIPv6 = true;
-        /* package */ boolean mUsingIpReachabilityMonitor = true;
-        /* package */ int mRequestedPreDhcpActionMs;
-        /* package */ InitialConfiguration mInitialConfig;
-        /* package */ StaticIpConfiguration mStaticIpConfig;
-        /* package */ ApfCapabilities mApfCapabilities;
-        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
-        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-
-        public ProvisioningConfiguration() {} // used by Builder
-
-        public ProvisioningConfiguration(ProvisioningConfiguration other) {
-            mEnableIPv4 = other.mEnableIPv4;
-            mEnableIPv6 = other.mEnableIPv6;
-            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
-            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
-            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
-            mStaticIpConfig = other.mStaticIpConfig;
-            mApfCapabilities = other.mApfCapabilities;
-            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
-        }
-
-        @Override
-        public String toString() {
-            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
-                    .add("mEnableIPv4: " + mEnableIPv4)
-                    .add("mEnableIPv6: " + mEnableIPv6)
-                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
-                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
-                    .add("mInitialConfig: " + mInitialConfig)
-                    .add("mStaticIpConfig: " + mStaticIpConfig)
-                    .add("mApfCapabilities: " + mApfCapabilities)
-                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
-                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
-                    .toString();
-        }
-
-        public boolean isValid() {
-            return (mInitialConfig == null) || mInitialConfig.isValid();
-        }
-    }
-
-    public static class InitialConfiguration {
-        public final Set<LinkAddress> ipAddresses = new HashSet<>();
-        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
-        public final Set<InetAddress> dnsServers = new HashSet<>();
-        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
-
-        public static InitialConfiguration copy(InitialConfiguration config) {
-            if (config == null) {
-                return null;
-            }
-            InitialConfiguration configCopy = new InitialConfiguration();
-            configCopy.ipAddresses.addAll(config.ipAddresses);
-            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
-            configCopy.dnsServers.addAll(config.dnsServers);
-            return configCopy;
-        }
-
-        @Override
-        public String toString() {
-            return String.format(
-                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
-                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
-                    join(", ", dnsServers), gateway);
-        }
-
-        public boolean isValid() {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            // For every IP address, there must be at least one prefix containing that address.
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
-                    return false;
-                }
-            }
-            // For every dns server, there must be at least one prefix containing that address.
-            for (InetAddress addr : dnsServers) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
-                    return false;
-                }
-            }
-            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
-            // (read: compliant with RFC4291#section2.5.4).
-            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // If directlyConnectedRoutes contains an IPv6 default route
-            // then ipAddresses MUST contain at least one non-ULA GUA.
-            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
-                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
-                return false;
-            }
-            // The prefix length of routes in directlyConnectedRoutes be within reasonable
-            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
-            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // There no more than one IPv4 address
-            if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) {
-                return false;
-            }
-
-            return true;
-        }
-
-        /**
-         * @return true if the given list of addressess and routes satisfies provisioning for this
-         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
-         * because addresses and routes seen by Netlink will contain additional fields like flags,
-         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
-         * provisioning check always fails.
-         *
-         * If the given list of routes is null, only addresses are taken into considerations.
-         */
-        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
-                    return false;
-                }
-            }
-
-            if (routes != null) {
-                for (IpPrefix prefix : directlyConnectedRoutes) {
-                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
-                        return false;
-                    }
-                }
-            }
-
-            return true;
-        }
-
-        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
-            return !route.hasGateway() && prefix.equals(route.getDestination());
-        }
-
-        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
-            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
-        }
-
-        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
-            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
-        }
-
-        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
-            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
-                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
-        }
-
-        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
-            return prefix.getAddress().equals(Inet6Address.ANY);
-        }
-
-        private static boolean isIPv6GUA(LinkAddress addr) {
-            return addr.isIPv6() && addr.isGlobalPreferred();
-        }
-    }
-
-    public static final String DUMP_ARG = "ipmanager";
-    public static final String DUMP_ARG_CONFIRM = "confirm";
-
-    private static final int CMD_TERMINATE_AFTER_STOP             = 1;
-    private static final int CMD_STOP                             = 2;
-    private static final int CMD_START                            = 3;
-    private static final int CMD_CONFIRM                          = 4;
-    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE       = 5;
-    // Sent by NetlinkTracker to communicate netlink events.
-    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6;
-    private static final int CMD_UPDATE_TCP_BUFFER_SIZES          = 7;
-    private static final int CMD_UPDATE_HTTP_PROXY                = 8;
-    private static final int CMD_SET_MULTICAST_FILTER             = 9;
-    private static final int EVENT_PROVISIONING_TIMEOUT           = 10;
-    private static final int EVENT_DHCPACTION_TIMEOUT             = 11;
-
-    private static final int MAX_LOG_RECORDS = 500;
-    private static final int MAX_PACKET_RECORDS = 100;
-
-    private static final boolean NO_CALLBACKS = false;
-    private static final boolean SEND_CALLBACKS = true;
-
-    // This must match the interface prefix in clatd.c.
-    // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
-    private static final String CLAT_PREFIX = "v4-";
-
-    private final State mStoppedState = new StoppedState();
-    private final State mStoppingState = new StoppingState();
-    private final State mStartedState = new StartedState();
-    private final State mRunningState = new RunningState();
-
-    private final String mTag;
-    private final Context mContext;
-    private final String mInterfaceName;
-    private final String mClatInterfaceName;
-    @VisibleForTesting
-    protected final Callback mCallback;
-    private final INetworkManagementService mNwService;
-    private final NetlinkTracker mNetlinkTracker;
-    private final WakeupMessage mProvisioningTimeoutAlarm;
-    private final WakeupMessage mDhcpActionTimeoutAlarm;
-    private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final SharedLog mLog;
-    private final LocalLog mConnectivityPacketLog;
-    private final MessageHandlingLogger mMsgStateLogger;
-    private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    private final InterfaceController mInterfaceCtrl;
-
-    private NetworkInterface mNetworkInterface;
-
-    /**
-     * Non-final member variables accessed only from within our StateMachine.
-     */
-    private LinkProperties mLinkProperties;
-    private ProvisioningConfiguration mConfiguration;
-    private IpReachabilityMonitor mIpReachabilityMonitor;
-    private DhcpClient mDhcpClient;
-    private DhcpResults mDhcpResults;
-    private String mTcpBufferSizes;
-    private ProxyInfo mHttpProxy;
-    private ApfFilter mApfFilter;
-    private boolean mMulticastFiltering;
-    private long mStartTimeMillis;
-
     public IpManager(Context context, String ifName, Callback callback) {
         this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
                 NetdService.getInstance());
     }
 
-    /**
-     * An expanded constructor, useful for dependency injection.
-     * TODO: migrate all test users to mock IpManager directly and remove this ctor.
-     */
     public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService) {
         this(context, ifName, callback, nwService, NetdService.getInstance());
     }
 
     @VisibleForTesting
-    IpManager(Context context, String ifName, Callback callback,
+    public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService, INetd netd) {
-        super(IpManager.class.getSimpleName() + "." + ifName);
-        mTag = getName();
-
-        mContext = context;
-        mInterfaceName = ifName;
-        mClatInterfaceName = CLAT_PREFIX + ifName;
-        mCallback = new LoggingCallbackWrapper(callback);
-        mNwService = nwService;
-
-        mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
-        mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
-        mMsgStateLogger = new MessageHandlingLogger();
-
-        mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog);
-
-        mNetlinkTracker = new NetlinkTracker(
-                mInterfaceName,
-                new NetlinkTracker.Callback() {
-                    @Override
-                    public void update() {
-                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-                    }
-                }) {
-            @Override
-            public void interfaceAdded(String iface) {
-                super.interfaceAdded(iface);
-                if (mClatInterfaceName.equals(iface)) {
-                    mCallback.setNeighborDiscoveryOffload(false);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceAdded(" + iface +")";
-                logMsg(msg);
-            }
-
-            @Override
-            public void interfaceRemoved(String iface) {
-                super.interfaceRemoved(iface);
-                // TODO: Also observe mInterfaceName going down and take some
-                // kind of appropriate action.
-                if (mClatInterfaceName.equals(iface)) {
-                    // TODO: consider sending a message to the IpManager main
-                    // StateMachine thread, in case "NDO enabled" state becomes
-                    // tied to more things that 464xlat operation.
-                    mCallback.setNeighborDiscoveryOffload(true);
-                } else if (!mInterfaceName.equals(iface)) {
-                    return;
-                }
-
-                final String msg = "interfaceRemoved(" + iface +")";
-                logMsg(msg);
-            }
-
-            private void logMsg(String msg) {
-                Log.d(mTag, msg);
-                getHandler().post(() -> { mLog.log("OBSERVED " + msg); });
-            }
-        };
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-
-        mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(),
-                () -> { mLog.log("OBSERVED AvoidBadWifi changed"); });
-
-        mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
-        mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
-                mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
-
-        // Anything the StateMachine may access must have been instantiated
-        // before this point.
-        configureAndStartStateMachine();
-
-        // Anything that may send messages to the StateMachine must only be
-        // configured to do so after the StateMachine has started (above).
-        startStateMachineUpdaters();
-    }
-
-    private void configureAndStartStateMachine() {
-        addState(mStoppedState);
-        addState(mStartedState);
-            addState(mRunningState, mStartedState);
-        addState(mStoppingState);
-
-        setInitialState(mStoppedState);
-
-        super.start();
-    }
-
-    private void startStateMachineUpdaters() {
-        try {
-            mNwService.registerObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't register NetlinkTracker: %s", e);
-        }
-
-        mMultinetworkPolicyTracker.start();
-    }
-
-    private void stopStateMachineUpdaters() {
-        try {
-            mNwService.unregisterObserver(mNetlinkTracker);
-        } catch (RemoteException e) {
-            logError("Couldn't unregister NetlinkTracker: %s", e);
-        }
-
-        mMultinetworkPolicyTracker.shutdown();
-    }
-
-    @Override
-    protected void onQuitting() {
-        mCallback.onQuit();
-    }
-
-    // Shut down this IpManager instance altogether.
-    public void shutdown() {
-        stop();
-        sendMessage(CMD_TERMINATE_AFTER_STOP);
-    }
-
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
+        super(context, ifName, callback, nwService, netd);
     }
 
     public void startProvisioning(ProvisioningConfiguration req) {
-        if (!req.isValid()) {
-            doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-            return;
-        }
-
-        getNetworkInterface();
-
-        mCallback.setNeighborDiscoveryOffload(true);
-        sendMessage(CMD_START, new ProvisioningConfiguration(req));
-    }
-
-    // TODO: Delete this.
-    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
-        startProvisioning(buildProvisioningConfiguration()
-                .withStaticConfiguration(staticIpConfig)
-                .build());
-    }
-
-    public void startProvisioning() {
-        startProvisioning(new ProvisioningConfiguration());
-    }
-
-    public void stop() {
-        sendMessage(CMD_STOP);
-    }
-
-    public void confirmConfiguration() {
-        sendMessage(CMD_CONFIRM);
-    }
-
-    public void completedPreDhcpAction() {
-        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-    }
-
-    /**
-     * Set the TCP buffer sizes to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setTcpBufferSizes(String tcpBufferSizes) {
-        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
-    }
-
-    /**
-     * Set the HTTP Proxy configuration to use.
-     *
-     * This may be called, repeatedly, at any time before or after a call to
-     * #startProvisioning(). The setting is cleared upon calling #stop().
-     */
-    public void setHttpProxy(ProxyInfo proxyInfo) {
-        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
-    }
-
-    /**
-     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
-     * if not, Callback.setFallbackMulticastFilter() is called.
-     */
-    public void setMulticastFilter(boolean enabled) {
-        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
-    }
-
-    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) {
-            // Execute confirmConfiguration() and take no further action.
-            confirmConfiguration();
-            return;
-        }
-
-        // Thread-unsafe access to mApfFilter but just used for debugging.
-        final ApfFilter apfFilter = mApfFilter;
-        final ProvisioningConfiguration provisioningConfig = mConfiguration;
-        final ApfCapabilities apfCapabilities = (provisioningConfig != null)
-                ? provisioningConfig.mApfCapabilities : null;
-
-        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println(mTag + " APF dump:");
-        pw.increaseIndent();
-        if (apfFilter != null) {
-            apfFilter.dump(pw);
-        } else {
-            pw.print("No active ApfFilter; ");
-            if (provisioningConfig == null) {
-                pw.println("IpManager not yet started.");
-            } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) {
-                pw.println("Hardware does not support APF.");
-            } else {
-                pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities);
-            }
-        }
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " current ProvisioningConfiguration:");
-        pw.increaseIndent();
-        pw.println(Objects.toString(provisioningConfig, "N/A"));
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " StateMachine dump:");
-        pw.increaseIndent();
-        mLog.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println(mTag + " connectivity packet log:");
-        pw.println();
-        pw.println("Debug with python and scapy via:");
-        pw.println("shell$ python");
-        pw.println(">>> from scapy import all as scapy");
-        pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()");
-        pw.println();
-
-        pw.increaseIndent();
-        mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
-        pw.decreaseIndent();
-    }
-
-
-    /**
-     * Internals.
-     */
-
-    @Override
-    protected String getWhatToString(int what) {
-        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
-    }
-
-    @Override
-    protected String getLogRecString(Message msg) {
-        final String logLine = String.format(
-                "%s/%d %d %d %s [%s]",
-                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
-                msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
-
-        final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
-        mLog.log(richerLogLine);
-        if (DBG) {
-            Log.d(mTag, richerLogLine);
-        }
-
-        mMsgStateLogger.reset();
-        return logLine;
-    }
-
-    @Override
-    protected boolean recordLogRec(Message msg) {
-        // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
-        // and we already log any LinkProperties change that results in an
-        // invocation of IpManager.Callback#onLinkPropertiesChange().
-        final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
-        if (!shouldLog) {
-            mMsgStateLogger.reset();
-        }
-        return shouldLog;
-    }
-
-    private void logError(String fmt, Object... args) {
-        final String msg = "ERROR " + String.format(fmt, args);
-        Log.e(mTag, msg);
-        mLog.log(msg);
-    }
-
-    private void getNetworkInterface() {
-        try {
-            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
-        } catch (SocketException | NullPointerException e) {
-            // TODO: throw new IllegalStateException.
-            logError("Failed to get interface object: %s", e);
-        }
-    }
-
-    // This needs to be called with care to ensure that our LinkProperties
-    // are in sync with the actual LinkProperties of the interface. For example,
-    // we should only call this if we know for sure that there are no IP addresses
-    // assigned to the interface, etc.
-    private void resetLinkProperties() {
-        mNetlinkTracker.clearLinkProperties();
-        mConfiguration = null;
-        mDhcpResults = null;
-        mTcpBufferSizes = "";
-        mHttpProxy = null;
-
-        mLinkProperties = new LinkProperties();
-        mLinkProperties.setInterfaceName(mInterfaceName);
-    }
-
-    private void recordMetric(final int type) {
-        if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
-        final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
-        mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration));
-    }
-
-    // For now: use WifiStateMachine's historical notion of provisioned.
-    @VisibleForTesting
-    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
-        // For historical reasons, we should connect even if all we have is
-        // an IPv4 address and nothing else.
-        if (lp.hasIPv4Address() || lp.isProvisioned()) {
-            return true;
-        }
-        if (config == null) {
-            return false;
-        }
-
-        // When an InitialConfiguration is specified, ignore any difference with previous
-        // properties and instead check if properties observed match the desired properties.
-        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
-    }
-
-    // TODO: Investigate folding all this into the existing static function
-    // LinkProperties.compareProvisioning() or some other single function that
-    // takes two LinkProperties objects and returns a ProvisioningChange
-    // object that is a correct and complete assessment of what changed, taking
-    // account of the asymmetries described in the comments in this function.
-    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
-    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
-        ProvisioningChange delta;
-        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
-        final boolean wasProvisioned = isProvisioned(oldLp, config);
-        final boolean isProvisioned = isProvisioned(newLp, config);
-
-        if (!wasProvisioned && isProvisioned) {
-            delta = ProvisioningChange.GAINED_PROVISIONING;
-        } else if (wasProvisioned && isProvisioned) {
-            delta = ProvisioningChange.STILL_PROVISIONED;
-        } else if (!wasProvisioned && !isProvisioned) {
-            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
-        } else {
-            // (wasProvisioned && !isProvisioned)
-            //
-            // Note that this is true even if we lose a configuration element
-            // (e.g., a default gateway) that would not be required to advance
-            // into provisioned state. This is intended: if we have a default
-            // router and we lose it, that's a sure sign of a problem, but if
-            // we connect to a network with no IPv4 DNS servers, we consider
-            // that to be a network without DNS servers and connect anyway.
-            //
-            // See the comment below.
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
-        final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
-        final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
-
-        // If bad wifi avoidance is disabled, then ignore IPv6 loss of
-        // provisioning. Otherwise, when a hotspot that loses Internet
-        // access sends out a 0-lifetime RA to its clients, the clients
-        // will disconnect and then reconnect, avoiding the bad hotspot,
-        // instead of getting stuck on the bad hotspot. http://b/31827713 .
-        //
-        // This is incorrect because if the hotspot then regains Internet
-        // access with a different prefix, TCP connections on the
-        // deprecated addresses will remain stuck.
-        //
-        // Note that we can still be disconnected by IpReachabilityMonitor
-        // if the IPv6 default gateway (but not the IPv6 DNS servers; see
-        // accompanying code in IpReachabilityMonitor) is unreachable.
-        final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi();
-
-        // Additionally:
-        //
-        // Partial configurations (e.g., only an IPv4 address with no DNS
-        // servers and no default route) are accepted as long as DHCPv4
-        // succeeds. On such a network, isProvisioned() will always return
-        // false, because the configuration is not complete, but we want to
-        // connect anyway. It might be a disconnected network such as a
-        // Chromecast or a wireless printer, for example.
-        //
-        // Because on such a network isProvisioned() will always return false,
-        // delta will never be LOST_PROVISIONING. So check for loss of
-        // provisioning here too.
-        if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        // Additionally:
-        //
-        // If the previous link properties had a global IPv6 address and an
-        // IPv6 default route then also consider the loss of that default route
-        // to be a loss of provisioning. See b/27962810.
-        if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        return delta;
-    }
-
-    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
-        switch (delta) {
-            case GAINED_PROVISIONING:
-                if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); }
-                recordMetric(IpManagerEvent.PROVISIONING_OK);
-                mCallback.onProvisioningSuccess(newLp);
-                break;
-
-            case LOST_PROVISIONING:
-                if (DBG) { Log.d(mTag, "onProvisioningFailure()"); }
-                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
-                mCallback.onProvisioningFailure(newLp);
-                break;
-
-            default:
-                if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
-                mCallback.onLinkPropertiesChange(newLp);
-                break;
-        }
-    }
-
-    // Updates all IpManager-related state concerned with LinkProperties.
-    // Returns a ProvisioningChange for possibly notifying other interested
-    // parties that are not fronted by IpManager.
-    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
-        if (mApfFilter != null) {
-            mApfFilter.setLinkProperties(newLp);
-        }
-        if (mIpReachabilityMonitor != null) {
-            mIpReachabilityMonitor.updateLinkProperties(newLp);
-        }
-
-        ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
-        mLinkProperties = new LinkProperties(newLp);
-
-        if (delta == ProvisioningChange.GAINED_PROVISIONING) {
-            // TODO: Add a proper ProvisionedState and cancel the alarm in
-            // its enter() method.
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        return delta;
-    }
-
-    private LinkProperties assembleLinkProperties() {
-        // [1] Create a new LinkProperties object to populate.
-        LinkProperties newLp = new LinkProperties();
-        newLp.setInterfaceName(mInterfaceName);
-
-        // [2] Pull in data from netlink:
-        //         - IPv4 addresses
-        //         - IPv6 addresses
-        //         - IPv6 routes
-        //         - IPv6 DNS servers
-        //
-        // N.B.: this is fundamentally race-prone and should be fixed by
-        // changing NetlinkTracker from a hybrid edge/level model to an
-        // edge-only model, or by giving IpManager its own netlink socket(s)
-        // so as to track all required information directly.
-        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
-        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
-        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
-            newLp.addRoute(route);
-        }
-        addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
-
-        // [3] Add in data from DHCPv4, if available.
-        //
-        // mDhcpResults is never shared with any other owner so we don't have
-        // to worry about concurrent modification.
-        if (mDhcpResults != null) {
-            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
-                newLp.addRoute(route);
-            }
-            addAllReachableDnsServers(newLp, mDhcpResults.dnsServers);
-            newLp.setDomains(mDhcpResults.domains);
-
-            if (mDhcpResults.mtu != 0) {
-                newLp.setMtu(mDhcpResults.mtu);
-            }
-        }
-
-        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
-        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-            newLp.setTcpBufferSizes(mTcpBufferSizes);
-        }
-        if (mHttpProxy != null) {
-            newLp.setHttpProxy(mHttpProxy);
-        }
-
-        // [5] Add data from InitialConfiguration
-        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            // Add InitialConfiguration routes and dns server addresses once all addresses
-            // specified in the InitialConfiguration have been observed with Netlink.
-            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
-                for (IpPrefix prefix : config.directlyConnectedRoutes) {
-                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
-                }
-            }
-            addAllReachableDnsServers(newLp, config.dnsServers);
-        }
-        final LinkProperties oldLp = mLinkProperties;
-        if (DBG) {
-            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
-                    netlinkLinkProperties, newLp, oldLp));
-        }
-
-        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
-        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
-        return newLp;
-    }
-
-    private static void addAllReachableDnsServers(
-            LinkProperties lp, Iterable<InetAddress> dnses) {
-        // TODO: Investigate deleting this reachability check.  We should be
-        // able to pass everything down to netd and let netd do evaluation
-        // and RFC6724-style sorting.
-        for (InetAddress dns : dnses) {
-            if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) {
-                lp.addDnsServer(dns);
-            }
-        }
-    }
-
-    // Returns false if we have lost provisioning, true otherwise.
-    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
-        final LinkProperties newLp = assembleLinkProperties();
-        if (Objects.equals(newLp, mLinkProperties)) {
-            return true;
-        }
-        final ProvisioningChange delta = setLinkProperties(newLp);
-        if (sendCallbacks) {
-            dispatchCallback(delta, newLp);
-        }
-        return (delta != ProvisioningChange.LOST_PROVISIONING);
-    }
-
-    private void handleIPv4Success(DhcpResults dhcpResults) {
-        mDhcpResults = new DhcpResults(dhcpResults);
-        final LinkProperties newLp = assembleLinkProperties();
-        final ProvisioningChange delta = setLinkProperties(newLp);
-
-        if (DBG) {
-            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
-        }
-        mCallback.onNewDhcpResults(dhcpResults);
-        dispatchCallback(delta, newLp);
-    }
-
-    private void handleIPv4Failure() {
-        // TODO: Investigate deleting this clearIPv4Address() call.
-        //
-        // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
-        // that could trigger a call to this function. If we missed handling
-        // that message in StartedState for some reason we would still clear
-        // any addresses upon entry to StoppedState.
-        mInterfaceCtrl.clearIPv4Address();
-        mDhcpResults = null;
-        if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
-        mCallback.onNewDhcpResults(null);
-
-        handleProvisioningFailure();
-    }
-
-    private void handleProvisioningFailure() {
-        final LinkProperties newLp = assembleLinkProperties();
-        ProvisioningChange delta = setLinkProperties(newLp);
-        // If we've gotten here and we're still not provisioned treat that as
-        // a total loss of provisioning.
-        //
-        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
-        // there was no usable IPv6 obtained before a non-zero provisioning
-        // timeout expired.
-        //
-        // Regardless: GAME OVER.
-        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
-            delta = ProvisioningChange.LOST_PROVISIONING;
-        }
-
-        dispatchCallback(delta, newLp);
-        if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            transitionTo(mStoppingState);
-        }
-    }
-
-    private void doImmediateProvisioningFailure(int failureType) {
-        logError("onProvisioningFailure(): %s", failureType);
-        recordMetric(failureType);
-        mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
-    }
-
-    private boolean startIPv4() {
-        // If we have a StaticIpConfiguration attempt to apply it and
-        // handle the result accordingly.
-        if (mConfiguration.mStaticIpConfig != null) {
-            if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
-                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
-            } else {
-                return false;
-            }
-        } else {
-            // Start DHCPv4.
-            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
-            mDhcpClient.registerForPreDhcpNotification();
-            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-        }
-
-        return true;
-    }
-
-    private boolean startIPv6() {
-        return mInterfaceCtrl.setIPv6PrivacyExtensions(true) &&
-               mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) &&
-               mInterfaceCtrl.enableIPv6();
-    }
-
-    private boolean applyInitialConfig(InitialConfiguration config) {
-        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
-        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
-            if (!mInterfaceCtrl.addAddress(addr)) return false;
-        }
-
-        return true;
-    }
-
-    private boolean startIpReachabilityMonitor() {
-        try {
-            mIpReachabilityMonitor = new IpReachabilityMonitor(
-                    mContext,
-                    mInterfaceName,
-                    mLog,
-                    new IpReachabilityMonitor.Callback() {
-                        @Override
-                        public void notifyLost(InetAddress ip, String logMsg) {
-                            mCallback.onReachabilityLost(logMsg);
-                        }
-                    },
-                    mMultinetworkPolicyTracker);
-        } catch (IllegalArgumentException iae) {
-            // Failed to start IpReachabilityMonitor. Log it and call
-            // onProvisioningFailure() immediately.
-            //
-            // See http://b/31038971.
-            logError("IpReachabilityMonitor failure: %s", iae);
-            mIpReachabilityMonitor = null;
-        }
-
-        return (mIpReachabilityMonitor != null);
-    }
-
-    private void stopAllIP() {
-        // We don't need to worry about routes, just addresses, because:
-        //     - disableIpv6() will clear autoconf IPv6 routes as well, and
-        //     - we don't get IPv4 routes from netlink
-        // so we neither react to nor need to wait for changes in either.
-
-        mInterfaceCtrl.disableIPv6();
-        mInterfaceCtrl.clearAllAddresses();
-    }
-
-    class StoppedState extends State {
-        @Override
-        public void enter() {
-            stopAllIP();
-
-            resetLinkProperties();
-            if (mStartTimeMillis > 0) {
-                recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
-                mStartTimeMillis = 0;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_TERMINATE_AFTER_STOP:
-                    stopStateMachineUpdaters();
-                    quit();
-                    break;
-
-                case CMD_STOP:
-                    break;
-
-                case CMD_START:
-                    mConfiguration = (ProvisioningConfiguration) msg.obj;
-                    transitionTo(mStartedState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER:
-                    mMulticastFiltering = (boolean) msg.obj;
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // Everything is already stopped.
-                    logError("Unexpected CMD_ON_QUIT (already stopped).");
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StoppingState extends State {
-        @Override
-        public void enter() {
-            if (mDhcpClient == null) {
-                // There's no DHCPv4 for which to wait; proceed to stopped.
-                transitionTo(mStoppedState);
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    mDhcpClient = null;
-                    transitionTo(mStoppedState);
-                    break;
-
-                default:
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    class StartedState extends State {
-        @Override
-        public void enter() {
-            mStartTimeMillis = SystemClock.elapsedRealtime();
-
-            if (mConfiguration.mProvisioningTimeoutMs > 0) {
-                final long alarmTime = SystemClock.elapsedRealtime() +
-                        mConfiguration.mProvisioningTimeoutMs;
-                mProvisioningTimeoutAlarm.schedule(alarmTime);
-            }
-
-            if (readyToProceed()) {
-                transitionTo(mRunningState);
-            } else {
-                // Clear all IPv4 and IPv6 before proceeding to RunningState.
-                // Clean up any leftover state from an abnormal exit from
-                // tethering or during an IpManager restart.
-                stopAllIP();
-            }
-        }
-
-        @Override
-        public void exit() {
-            mProvisioningTimeoutAlarm.cancel();
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    handleLinkPropertiesUpdate(NO_CALLBACKS);
-                    if (readyToProceed()) {
-                        transitionTo(mRunningState);
-                    }
-                    break;
-
-                case EVENT_PROVISIONING_TIMEOUT:
-                    handleProvisioningFailure();
-                    break;
-
-                default:
-                    // It's safe to process messages out of order because the
-                    // only message that can both
-                    //     a) be received at this time and
-                    //     b) affect provisioning state
-                    // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above).
-                    deferMessage(msg);
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-
-        boolean readyToProceed() {
-            return (!mLinkProperties.hasIPv4Address() &&
-                    !mLinkProperties.hasGlobalIPv6Address());
-        }
-    }
-
-    class RunningState extends State {
-        private ConnectivityPacketTracker mPacketTracker;
-        private boolean mDhcpActionInFlight;
-
-        @Override
-        public void enter() {
-            // Get the Configuration for ApfFilter from Context
-            boolean filter802_3Frames =
-                    mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
-
-            int[] ethTypeBlackList = mContext.getResources().getIntArray(
-                    R.array.config_apfEthTypeBlackList);
-
-            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
-                    mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList);
-            // TODO: investigate the effects of any multicast filtering racing/interfering with the
-            // rest of this IP configuration startup.
-            if (mApfFilter == null) {
-                mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-            }
-
-            mPacketTracker = createPacketTracker();
-            if (mPacketTracker != null) mPacketTracker.start();
-
-            if (mConfiguration.mEnableIPv6 && !startIPv6()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            if (mConfiguration.mEnableIPv4 && !startIPv4()) {
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            InitialConfiguration config = mConfiguration.mInitialConfig;
-            if ((config != null) && !applyInitialConfig(config)) {
-                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
-                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
-                transitionTo(mStoppingState);
-                return;
-            }
-
-            if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
-                doImmediateProvisioningFailure(
-                        IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
-                transitionTo(mStoppingState);
-                return;
-            }
-        }
-
-        @Override
-        public void exit() {
-            stopDhcpAction();
-
-            if (mIpReachabilityMonitor != null) {
-                mIpReachabilityMonitor.stop();
-                mIpReachabilityMonitor = null;
-            }
-
-            if (mDhcpClient != null) {
-                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
-                mDhcpClient.doQuit();
-            }
-
-            if (mPacketTracker != null) {
-                mPacketTracker.stop();
-                mPacketTracker = null;
-            }
-
-            if (mApfFilter != null) {
-                mApfFilter.shutdown();
-                mApfFilter = null;
-            }
-
-            resetLinkProperties();
-        }
-
-        private ConnectivityPacketTracker createPacketTracker() {
-            try {
-                return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
-            } catch (IllegalArgumentException e) {
-                return null;
-            }
-        }
-
-        private void ensureDhcpAction() {
-            if (!mDhcpActionInFlight) {
-                mCallback.onPreDhcpAction();
-                mDhcpActionInFlight = true;
-                final long alarmTime = SystemClock.elapsedRealtime() +
-                        mConfiguration.mRequestedPreDhcpActionMs;
-                mDhcpActionTimeoutAlarm.schedule(alarmTime);
-            }
-        }
-
-        private void stopDhcpAction() {
-            mDhcpActionTimeoutAlarm.cancel();
-            if (mDhcpActionInFlight) {
-                mCallback.onPostDhcpAction();
-                mDhcpActionInFlight = false;
-            }
-        }
-
-        @Override
-        public boolean processMessage(Message msg) {
-            switch (msg.what) {
-                case CMD_STOP:
-                    transitionTo(mStoppingState);
-                    break;
-
-                case CMD_START:
-                    logError("ALERT: START received in StartedState. Please fix caller.");
-                    break;
-
-                case CMD_CONFIRM:
-                    // TODO: Possibly introduce a second type of confirmation
-                    // that both probes (a) on-link neighbors and (b) does
-                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
-                    // roams.
-                    if (mIpReachabilityMonitor != null) {
-                        mIpReachabilityMonitor.probeAll();
-                    }
-                    break;
-
-                case EVENT_PRE_DHCP_ACTION_COMPLETE:
-                    // It's possible to reach here if, for example, someone
-                    // calls completedPreDhcpAction() after provisioning with
-                    // a static IP configuration.
-                    if (mDhcpClient != null) {
-                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-
-                case CMD_UPDATE_TCP_BUFFER_SIZES:
-                    mTcpBufferSizes = (String) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_UPDATE_HTTP_PROXY:
-                    mHttpProxy = (ProxyInfo) msg.obj;
-                    // This cannot possibly change provisioning state.
-                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
-                    break;
-
-                case CMD_SET_MULTICAST_FILTER: {
-                    mMulticastFiltering = (boolean) msg.obj;
-                    if (mApfFilter != null) {
-                        mApfFilter.setMulticastFilter(mMulticastFiltering);
-                    } else {
-                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
-                    }
-                    break;
-                }
-
-                case EVENT_DHCPACTION_TIMEOUT:
-                    stopDhcpAction();
-                    break;
-
-                case DhcpClient.CMD_PRE_DHCP_ACTION:
-                    if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
-                        ensureDhcpAction();
-                    } else {
-                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
-                    }
-                    break;
-
-                case DhcpClient.CMD_CLEAR_LINKADDRESS:
-                    mInterfaceCtrl.clearIPv4Address();
-                    break;
-
-                case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
-                    final LinkAddress ipAddress = (LinkAddress) msg.obj;
-                    if (mInterfaceCtrl.setIPv4Address(ipAddress)) {
-                        mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
-                    } else {
-                        logError("Failed to set IPv4 address.");
-                        dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
-                                new LinkProperties(mLinkProperties));
-                        transitionTo(mStoppingState);
-                    }
-                    break;
-                }
-
-                // This message is only received when:
-                //
-                //     a) initial address acquisition succeeds,
-                //     b) renew succeeds or is NAK'd,
-                //     c) rebind succeeds or is NAK'd, or
-                //     c) the lease expires,
-                //
-                // but never when initial address acquisition fails. The latter
-                // condition is now governed by the provisioning timeout.
-                case DhcpClient.CMD_POST_DHCP_ACTION:
-                    stopDhcpAction();
-
-                    switch (msg.arg1) {
-                        case DhcpClient.DHCP_SUCCESS:
-                            handleIPv4Success((DhcpResults) msg.obj);
-                            break;
-                        case DhcpClient.DHCP_FAILURE:
-                            handleIPv4Failure();
-                            break;
-                        default:
-                            logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1);
-                    }
-                    break;
-
-                case DhcpClient.CMD_ON_QUIT:
-                    // DHCPv4 quit early for some reason.
-                    logError("Unexpected CMD_ON_QUIT.");
-                    mDhcpClient = null;
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-
-            mMsgStateLogger.handled(this, getCurrentState());
-            return HANDLED;
-        }
-    }
-
-    private static class MessageHandlingLogger {
-        public String processedInState;
-        public String receivedInState;
-
-        public void reset() {
-            processedInState = null;
-            receivedInState = null;
-        }
-
-        public void handled(State processedIn, IState receivedIn) {
-            processedInState = processedIn.getClass().getSimpleName();
-            receivedInState = receivedIn.getName();
-        }
-
-        public String toString() {
-            return String.format("rcvd_in=%s, proc_in=%s",
-                                 receivedInState, processedInState);
-        }
-    }
-
-    // TODO: extract out into CollectionUtils.
-    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
-        for (T t : coll) {
-            if (fn.test(t)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
-        return !any(coll, not(fn));
-    }
-
-    static <T> Predicate<T> not(Predicate<T> fn) {
-        return (t) -> !fn.test(t);
-    }
-
-    static <T> String join(String delimiter, Collection<T> coll) {
-        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
-    }
-
-    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
-        for (T t: coll) {
-            if (fn.test(t)) {
-              return t;
-            }
-        }
-        return null;
-    }
-
-    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
-        return coll.stream().filter(fn).collect(Collectors.toList());
+        super.startProvisioning((IpClient.ProvisioningConfiguration) req);
     }
 }
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index a9e0cd9..f5f211d 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -96,7 +96,7 @@
         mDescriptor = Os.socket(
                 OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
 
-        Libcore.os.setsockoptInt(
+        Os.setsockoptInt(
                 mDescriptor, OsConstants.SOL_SOCKET,
                 OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
     }
diff --git a/services/net/java/android/net/util/BlockingSocketReader.java b/services/net/java/android/net/util/BlockingSocketReader.java
index 12fa1e5..99bf469 100644
--- a/services/net/java/android/net/util/BlockingSocketReader.java
+++ b/services/net/java/android/net/util/BlockingSocketReader.java
@@ -16,81 +16,106 @@
 
 package android.net.util;
 
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+
 import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.os.MessageQueue.OnFileDescriptorEventListener;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 
-import libcore.io.IoBridge;
+import libcore.io.IoUtils;
 
 import java.io.FileDescriptor;
-import java.io.InterruptedIOException;
 import java.io.IOException;
 
 
 /**
- * A thread that reads from a socket and passes the received packets to a
- * subclass's handlePacket() method.  The packet receive buffer is recycled
- * on every read call, so subclasses should make any copies they would like
- * inside their handlePacket() implementation.
+ * This class encapsulates the mechanics of registering a file descriptor
+ * with a thread's Looper and handling read events (and errors).
  *
- * All public methods may be called from any thread.
+ * Subclasses MUST implement createFd() and SHOULD override handlePacket().
+
+ * Subclasses can expect a call life-cycle like the following:
+ *
+ *     [1] start() calls createFd() and (if all goes well) onStart()
+ *
+ *     [2] yield, waiting for read event or error notification:
+ *
+ *             [a] readPacket() && handlePacket()
+ *
+ *             [b] if (no error):
+ *                     goto 2
+ *                 else:
+ *                     goto 3
+ *
+ *     [3] stop() calls onStop() if not previously stopped
+ *
+ * The packet receive buffer is recycled on every read call, so subclasses
+ * should make any copies they would like inside their handlePacket()
+ * implementation.
+ *
+ * All public methods MUST only be called from the same thread with which
+ * the Handler constructor argument is associated.
+ *
+ * TODO: rename this class to something more correctly descriptive (something
+ * like [or less horrible than] FdReadEventsHandler?).
  *
  * @hide
  */
 public abstract class BlockingSocketReader {
+    private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+    private static final int UNREGISTER_THIS_FD = 0;
+
     public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
 
+    private final Handler mHandler;
+    private final MessageQueue mQueue;
     private final byte[] mPacket;
-    private final Thread mThread;
-    private volatile FileDescriptor mSocket;
-    private volatile boolean mRunning;
-    private volatile long mPacketsReceived;
+    private FileDescriptor mFd;
+    private long mPacketsReceived;
 
-    // Make it slightly easier for subclasses to properly close a socket
-    // without having to know this incantation.
-    public static final void closeSocket(@Nullable FileDescriptor fd) {
-        try {
-            IoBridge.closeAndSignalBlockedThreads(fd);
-        } catch (IOException ignored) {}
+    protected static void closeFd(FileDescriptor fd) {
+        IoUtils.closeQuietly(fd);
     }
 
-    protected BlockingSocketReader() {
-        this(DEFAULT_RECV_BUF_SIZE);
+    protected BlockingSocketReader(Handler h) {
+        this(h, DEFAULT_RECV_BUF_SIZE);
     }
 
-    protected BlockingSocketReader(int recvbufsize) {
-        if (recvbufsize < DEFAULT_RECV_BUF_SIZE) {
-            recvbufsize = DEFAULT_RECV_BUF_SIZE;
+    protected BlockingSocketReader(Handler h, int recvbufsize) {
+        mHandler = h;
+        mQueue = mHandler.getLooper().getQueue();
+        mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
+    }
+
+    public final void start() {
+        if (onCorrectThread()) {
+            createAndRegisterFd();
+        } else {
+            mHandler.post(() -> {
+                logError("start() called from off-thread", null);
+                createAndRegisterFd();
+            });
         }
-        mPacket = new byte[recvbufsize];
-        mThread = new Thread(() -> { mainLoop(); });
-    }
-
-    public final boolean start() {
-        if (mSocket != null) return false;
-
-        try {
-            mSocket = createSocket();
-        } catch (Exception e) {
-            logError("Failed to create socket: ", e);
-            return false;
-        }
-
-        if (mSocket == null) return false;
-
-        mRunning = true;
-        mThread.start();
-        return true;
     }
 
     public final void stop() {
-        mRunning = false;
-        closeSocket(mSocket);
-        mSocket = null;
+        if (onCorrectThread()) {
+            unregisterAndDestroyFd();
+        } else {
+            mHandler.post(() -> {
+                logError("stop() called from off-thread", null);
+                unregisterAndDestroyFd();
+            });
+        }
     }
 
-    public final boolean isRunning() { return mRunning; }
+    public final int recvBufSize() { return mPacket.length; }
 
     public final long numPacketsReceived() { return mPacketsReceived; }
 
@@ -98,11 +123,21 @@
      * Subclasses MUST create the listening socket here, including setting
      * all desired socket options, interface or address/port binding, etc.
      */
-    protected abstract FileDescriptor createSocket();
+    protected abstract FileDescriptor createFd();
+
+    /**
+     * Subclasses MAY override this to change the default read() implementation
+     * in favour of, say, recvfrom().
+     *
+     * Implementations MUST return the bytes read or throw an Exception.
+     */
+    protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
+        return Os.read(fd, packetBuffer, 0, packetBuffer.length);
+    }
 
     /**
      * Called by the main loop for every packet.  Any desired copies of
-     * |recvbuf| should be made in here, and the underlying byte array is
+     * |recvbuf| should be made in here, as the underlying byte array is
      * reused across all reads.
      */
     protected void handlePacket(byte[] recvbuf, int length) {}
@@ -113,43 +148,102 @@
     protected void logError(String msg, Exception e) {}
 
     /**
-     * Called by the main loop just prior to exiting.
+     * Called by start(), if successful, just prior to returning.
      */
-    protected void onExit() {}
+    protected void onStart() {}
 
-    private final void mainLoop() {
+    /**
+     * Called by stop() just prior to returning.
+     */
+    protected void onStop() {}
+
+    private void createAndRegisterFd() {
+        if (mFd != null) return;
+
+        try {
+            mFd = createFd();
+            if (mFd != null) {
+                // Force the socket to be non-blocking.
+                IoUtils.setBlocking(mFd, false);
+            }
+        } catch (Exception e) {
+            logError("Failed to create socket: ", e);
+            closeFd(mFd);
+            mFd = null;
+            return;
+        }
+
+        if (mFd == null) return;
+
+        mQueue.addOnFileDescriptorEventListener(
+                mFd,
+                FD_EVENTS,
+                new OnFileDescriptorEventListener() {
+                    @Override
+                    public int onFileDescriptorEvents(FileDescriptor fd, int events) {
+                        // Always call handleInput() so read/recvfrom are given
+                        // a proper chance to encounter a meaningful errno and
+                        // perhaps log a useful error message.
+                        if (!isRunning() || !handleInput()) {
+                            unregisterAndDestroyFd();
+                            return UNREGISTER_THIS_FD;
+                        }
+                        return FD_EVENTS;
+                    }
+                });
+        onStart();
+    }
+
+    private boolean isRunning() { return (mFd != null) && mFd.valid(); }
+
+    // Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
+    private boolean handleInput() {
         while (isRunning()) {
             final int bytesRead;
 
             try {
-                // Blocking read.
-                // TODO: See if this can be converted to recvfrom.
-                bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length);
+                bytesRead = readPacket(mFd, mPacket);
                 if (bytesRead < 1) {
                     if (isRunning()) logError("Socket closed, exiting", null);
                     break;
                 }
                 mPacketsReceived++;
             } catch (ErrnoException e) {
-                if (e.errno != OsConstants.EINTR) {
-                    if (isRunning()) logError("read error: ", e);
+                if (e.errno == OsConstants.EAGAIN) {
+                    // We've read everything there is to read this time around.
+                    return true;
+                } else if (e.errno == OsConstants.EINTR) {
+                    continue;
+                } else {
+                    if (isRunning()) logError("readPacket error: ", e);
                     break;
                 }
-                continue;
-            } catch (IOException ioe) {
-                if (isRunning()) logError("read error: ", ioe);
-                continue;
+            } catch (Exception e) {
+                if (isRunning()) logError("readPacket error: ", e);
+                break;
             }
 
             try {
                 handlePacket(mPacket, bytesRead);
             } catch (Exception e) {
-                logError("Unexpected exception: ", e);
+                logError("handlePacket error: ", e);
                 break;
             }
         }
 
-        stop();
-        onExit();
+        return false;
+    }
+
+    private void unregisterAndDestroyFd() {
+        if (mFd == null) return;
+
+        mQueue.removeOnFileDescriptorEventListener(mFd);
+        closeFd(mFd);
+        mFd = null;
+        onStop();
+    }
+
+    private boolean onCorrectThread() {
+        return (mHandler.getLooper() == Looper.myLooper());
     }
 }
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 6065268..5a3a8be 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -107,6 +107,20 @@
     public static final int RFC6177_MIN_PREFIX_LENGTH = 48;
 
     /**
+     * ICMP common (v4/v6) constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc792
+     *     - https://tools.ietf.org/html/rfc4443
+     */
+    public static final int ICMP_HEADER_TYPE_OFFSET = 0;
+    public static final int ICMP_HEADER_CODE_OFFSET = 1;
+    public static final int ICMP_HEADER_CHECKSUM_OFFSET = 2;
+    public static final int ICMP_ECHO_IDENTIFIER_OFFSET = 4;
+    public static final int ICMP_ECHO_SEQUENCE_NUMBER_OFFSET = 6;
+    public static final int ICMP_ECHO_DATA_OFFSET = 8;
+
+    /**
      * ICMPv6 constants.
      *
      * See also:
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
index 343d237..bbd3d13 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -106,6 +106,10 @@
         record(Category.NONE, msg);
     }
 
+    public void logf(String fmt, Object... args) {
+        log(String.format(fmt, args));
+    }
+
     public void mark(String msg) {
         record(Category.MARK, msg);
     }
diff --git a/services/net/java/android/net/util/VersionedBroadcastListener.java b/services/net/java/android/net/util/VersionedBroadcastListener.java
new file mode 100644
index 0000000..107c404
--- /dev/null
+++ b/services/net/java/android/net/util/VersionedBroadcastListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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.net.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+
+/**
+ * A utility class that runs the provided callback on the provided handler when
+ * intents matching the provided filter arrive. Intents received by a stale
+ * receiver are safely ignored.
+ *
+ * Calls to startListening() and stopListening() must happen on the same thread.
+ *
+ * @hide
+ */
+public class VersionedBroadcastListener {
+    private static final boolean DBG = false;
+
+    public interface IntentCallback {
+        public void run(Intent intent);
+    }
+
+    private final String mTag;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final IntentFilter mFilter;
+    private final Consumer<Intent> mCallback;
+    private final AtomicInteger mGenerationNumber;
+    private BroadcastReceiver mReceiver;
+
+    public VersionedBroadcastListener(String tag, Context ctx, Handler handler,
+            IntentFilter filter, Consumer<Intent> callback) {
+        mTag = tag;
+        mContext = ctx;
+        mHandler = handler;
+        mFilter = filter;
+        mCallback = callback;
+        mGenerationNumber = new AtomicInteger(0);
+    }
+
+    public void startListening() {
+        if (DBG) Log.d(mTag, "startListening");
+        if (mReceiver != null) return;
+
+        mReceiver = new Receiver(mTag, mGenerationNumber, mCallback);
+        mContext.registerReceiver(mReceiver, mFilter, null, mHandler);
+    }
+
+    public void stopListening() {
+        if (DBG) Log.d(mTag, "stopListening");
+        if (mReceiver == null) return;
+
+        mGenerationNumber.incrementAndGet();
+        mContext.unregisterReceiver(mReceiver);
+        mReceiver = null;
+    }
+
+    private static class Receiver extends BroadcastReceiver {
+        public final String tag;
+        public final AtomicInteger atomicGenerationNumber;
+        public final Consumer<Intent> callback;
+        // Used to verify this receiver is still current.
+        public final int generationNumber;
+
+        public Receiver(
+                String tag, AtomicInteger atomicGenerationNumber, Consumer<Intent> callback) {
+            this.tag = tag;
+            this.atomicGenerationNumber = atomicGenerationNumber;
+            this.callback = callback;
+            generationNumber = atomicGenerationNumber.incrementAndGet();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int currentGenerationNumber = atomicGenerationNumber.get();
+
+            if (DBG) {
+                Log.d(tag, "receiver generationNumber=" + generationNumber +
+                        ", current generationNumber=" + currentGenerationNumber);
+            }
+            if (generationNumber != currentGenerationNumber) return;
+
+            callback.accept(intent);
+        }
+    }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 529ac3a..0b4d61f 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -19,6 +19,7 @@
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
 
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -57,7 +58,13 @@
 import android.service.notification.StatusBarNotification;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
 
+import com.android.internal.util.IntPair;
 import com.android.server.lights.Light;
 
 import org.junit.Before;
@@ -67,6 +74,8 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -80,6 +89,8 @@
     NotificationManagerService.WorkerHandler mHandler;
     @Mock
     NotificationUsageStats mUsageStats;
+    @Mock
+    IAccessibilityManager mAccessibilityService;
 
     private NotificationManagerService mService;
     private String mPkg = "com.android.server.notification";
@@ -111,17 +122,25 @@
     private static final int MAX_VIBRATION_DELAY = 1000;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
         when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
         when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-
         when(mUsageStats.isAlertRateLimited(any())).thenReturn(false);
 
-        mService = new NotificationManagerService(getContext());
+        long serviceReturnValue = IntPair.of(
+                AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED,
+                AccessibilityEvent.TYPES_ALL_MASK);
+        when(mAccessibilityService.addClient(any(), anyInt())).thenReturn(serviceReturnValue);
+        AccessibilityManager accessibilityManager =
+                new AccessibilityManager(Handler.getMain(), mAccessibilityService, 0);
+        verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt());
+        assertTrue(accessibilityManager.isEnabled());
+
+        mService = spy(new NotificationManagerService(getContext()));
         mService.setAudioManager(mAudioManager);
         mService.setVibrator(mVibrator);
         mService.setSystemReady(true);
@@ -130,6 +149,7 @@
         mService.setScreenOn(false);
         mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
         mService.setUsageStats(mUsageStats);
+        mService.setAccessibilityManager(accessibilityManager);
     }
 
     //
@@ -381,6 +401,7 @@
 
         verifyBeepLooped();
         verifyNeverVibrate();
+        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
     }
 
     @Test
@@ -435,6 +456,7 @@
         r.isUpdate = true;
         mService.buzzBeepBlinkLocked(r);
         verifyBeepLooped();
+        verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
     }
 
     @Test
@@ -450,6 +472,7 @@
         // update should not beep
         mService.buzzBeepBlinkLocked(s);
         verifyNeverBeep();
+        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
     }
 
     @Test
@@ -547,7 +570,7 @@
         mService.mInCall = true;
         mService.buzzBeepBlinkLocked(r);
 
-        //verify(mService, times(1)).playInCallNotification();
+        verify(mService, times(1)).playInCallNotification();
         verifyNeverBeep(); // doesn't play normal beep
     }
 
@@ -558,6 +581,7 @@
 
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
 
         mService.buzzBeepBlinkLocked(r);
 
@@ -568,6 +592,22 @@
     }
 
     @Test
+    public void testNoDemoteSoundToVibrateIfNonNotificationStream() throws Exception {
+        NotificationRecord r = getBeepyNotification();
+        assertTrue(r.getSound() != null);
+        assertNull(r.getVibration());
+
+        // the phone is quiet
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(1);
+
+        mService.buzzBeepBlinkLocked(r);
+
+        verifyNeverVibrate();
+        verifyBeepLooped();
+    }
+
+    @Test
     public void testDemoteSoundToVibrate() throws Exception {
         NotificationRecord r = getBeepyNotification();
         assertTrue(r.getSound() != null);
@@ -575,6 +615,7 @@
 
         // the phone is quiet
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
 
         mService.buzzBeepBlinkLocked(r);
 
@@ -824,7 +865,6 @@
         mService.addNotification(r);
 
         mService.buzzBeepBlinkLocked(r);
-
         verifyNeverBeep();
     }
 
@@ -852,7 +892,6 @@
         summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
 
         mService.buzzBeepBlinkLocked(summary);
-
         verify(mUsageStats, never()).isAlertRateLimited(any());
     }
 
@@ -871,6 +910,30 @@
         verifyNeverBeep();
     }
 
+    @Test
+    public void testA11yMinInitialPost() throws Exception {
+        NotificationRecord r = getQuietNotification();
+        r.setImportance(IMPORTANCE_MIN, "");
+        mService.buzzBeepBlinkLocked(r);
+        verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testA11yQuietInitialPost() throws Exception {
+        NotificationRecord r = getQuietNotification();
+        mService.buzzBeepBlinkLocked(r);
+        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testA11yQuietUpdate() throws Exception {
+        NotificationRecord r = getQuietNotification();
+        mService.buzzBeepBlinkLocked(r);
+        r.isUpdate = true;
+        mService.buzzBeepBlinkLocked(r);
+        verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
+    }
+
     static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
         private final int mRepeatIndex;
 
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
index 725e8f2..d767ba2 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -16,6 +16,13 @@
 
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEUTRAL;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_POSITIVE;
+
 import static org.junit.Assert.assertEquals;
 
 import android.app.NotificationChannel;
@@ -60,6 +67,7 @@
             assertEquals(getPeople(key, i), ranking.getAdditionalPeople());
             assertEquals(getSnoozeCriteria(key, i), ranking.getSnoozeCriteria());
             assertEquals(getShowBadge(i), ranking.canShowBadge());
+            assertEquals(getUserSentiment(i), ranking.getUserSentiment());
         }
     }
 
@@ -74,6 +82,7 @@
         Bundle snoozeCriteria = new Bundle();
         Bundle showBadge = new Bundle();
         int[] importance = new int[mKeys.length];
+        Bundle userSentiment = new Bundle();
 
         for (int i = 0; i < mKeys.length; i++) {
             String key = mKeys[i];
@@ -89,11 +98,12 @@
             overridePeople.putStringArrayList(key, getPeople(key, i));
             snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
             showBadge.putBoolean(key, getShowBadge(i));
+            userSentiment.putInt(key, getUserSentiment(i));
         }
         NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
                 interceptedKeys.toArray(new String[0]), visibilityOverrides,
                 suppressedVisualEffects, importance, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge);
+                channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
         return update;
     }
 
@@ -129,6 +139,18 @@
         return index % 3 == 0;
     }
 
+    private int getUserSentiment(int index) {
+        switch(index % 3) {
+            case 0:
+                return USER_SENTIMENT_NEGATIVE;
+            case 1:
+                return USER_SENTIMENT_NEUTRAL;
+            case 2:
+                return USER_SENTIMENT_POSITIVE;
+        }
+        return USER_SENTIMENT_NEUTRAL;
+    }
+
     private ArrayList<String> getPeople(String key, int index) {
         ArrayList<String> people = new ArrayList<>();
         for (int i = 0; i < index; i++) {
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index ddd21df..2452b44 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16,12 +16,16 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -35,6 +39,8 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -56,18 +62,24 @@
 import android.graphics.Color;
 import android.media.AudioManager;
 import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
+import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 
+import com.android.internal.statusbar.NotificationVisibility;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
@@ -77,8 +89,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.*;
 import org.mockito.stubbing.Answer;
 
 import java.io.BufferedInputStream;
@@ -97,14 +108,14 @@
 public class NotificationManagerServiceTest extends NotificationTestCase {
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
     private final int mUid = Binder.getCallingUid();
-    private NotificationManagerService mNotificationManagerService;
+    private NotificationManagerService mService;
     private INotificationManager mBinderService;
     private NotificationManagerInternal mInternalService;
     @Mock
     private IPackageManager mPackageManager;
     @Mock
     private PackageManager mPackageManagerClient;
-    private Context mContext = getContext();
+    private TestableContext mContext = spy(getContext());
     private final String PKG = mContext.getPackageName();
     private TestableLooper mTestableLooper;
     @Mock
@@ -159,18 +170,20 @@
                 Secure.NOTIFICATION_BADGING, 1,
                 UserHandle.getUserHandleForUid(mUid).getIdentifier());
 
-        mNotificationManagerService = new TestableNotificationManagerService(mContext);
+        mService = new TestableNotificationManagerService(mContext);
 
         // Use this testable looper.
         mTestableLooper = TestableLooper.get(this);
-        mHandler = mNotificationManagerService.new WorkerHandler(mTestableLooper.getLooper());
+        mHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
         // MockPackageManager - default returns ApplicationInfo with matching calling UID
+        mContext.setMockPackageManager(mPackageManagerClient);
         final ApplicationInfo applicationInfo = new ApplicationInfo();
         applicationInfo.uid = mUid;
         when(mPackageManager.getApplicationInfo(anyString(), anyInt(), anyInt()))
                 .thenReturn(applicationInfo);
         when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
                 .thenReturn(applicationInfo);
+        when(mPackageManagerClient.getPackageUidAsUser(any(), anyInt())).thenReturn(mUid);
         final LightsManager mockLightsManager = mock(LightsManager.class);
         when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class));
         when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
@@ -200,7 +213,7 @@
         when(mConditionProviders.getConfig()).thenReturn(dndConfig);
 
         try {
-            mNotificationManagerService.init(mTestableLooper.getLooper(),
+            mService.init(mTestableLooper.getLooper(),
                     mPackageManager, mPackageManagerClient, mockLightsManager,
                     mListeners, mAssistants, mConditionProviders,
                     mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
@@ -210,14 +223,15 @@
                 throw e;
             }
         }
-        mNotificationManagerService.setAudioManager(mAudioManager);
+        mService.setAudioManager(mAudioManager);
 
         // Tests call directly into the Binder.
-        mBinderService = mNotificationManagerService.getBinderService();
-        mInternalService = mNotificationManagerService.getInternalService();
+        mBinderService = mService.getBinderService();
+        mInternalService = mService.getInternalService();
 
         mBinderService.createNotificationChannels(
                 PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
+        assertNotNull(mBinderService.getNotificationChannel(PKG, TEST_CHANNEL_ID));
     }
 
     @After
@@ -241,6 +255,7 @@
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
     }
+
     private NotificationRecord generateNotificationRecord(NotificationChannel channel) {
         return generateNotificationRecord(channel, null);
     }
@@ -342,7 +357,7 @@
 
         // Recreating the channel doesn't throw, but ignores importance.
         final NotificationChannel dupeChannel =
-                new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel("id", "name", IMPORTANCE_HIGH);
         mBinderService.createNotificationChannels(PKG,
                 new ParceledListSlice(Arrays.asList(dupeChannel)));
         final NotificationChannel createdChannel =
@@ -378,7 +393,7 @@
 
         // The user modifies importance directly, can no longer be changed by the app.
         final NotificationChannel updatedChannel =
-                new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel("id", "name", IMPORTANCE_HIGH);
         mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
 
         // Recreating with a lower importance leaves channel unchanged.
@@ -388,7 +403,7 @@
                 new ParceledListSlice(Arrays.asList(dupeChannel)));
         final NotificationChannel createdChannel =
                 mBinderService.getNotificationChannel(PKG, "id");
-        assertEquals(NotificationManager.IMPORTANCE_HIGH, createdChannel.getImportance());
+        assertEquals(IMPORTANCE_HIGH, createdChannel.getImportance());
     }
 
     @Test
@@ -397,7 +412,7 @@
         final NotificationChannel channel1 =
                 new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_DEFAULT);
         final NotificationChannel channel2 =
-                new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel("id", "name", IMPORTANCE_HIGH);
         mBinderService.createNotificationChannels(PKG,
                 new ParceledListSlice(Arrays.asList(channel1, channel2)));
         final NotificationChannel createdChannel =
@@ -410,9 +425,9 @@
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
 
         NotificationChannel channel = new NotificationChannel("id", "name",
-                NotificationManager.IMPORTANCE_HIGH);
+                IMPORTANCE_HIGH);
         NotificationRecord r = generateNotificationRecord(channel);
-        assertTrue(mNotificationManagerService.isBlocked(r, mUsageStats));
+        assertTrue(mService.isBlocked(r, mUsageStats));
         verify(mUsageStats, times(1)).registerSuspendedByAdmin(eq(r));
     }
 
@@ -421,24 +436,81 @@
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
 
         NotificationChannel channel = new NotificationChannel("id", "name",
-                NotificationManager.IMPORTANCE_HIGH);
-        channel.setImportance(IMPORTANCE_NONE);
+                NotificationManager.IMPORTANCE_NONE);
         NotificationRecord r = generateNotificationRecord(channel);
-        assertTrue(mNotificationManagerService.isBlocked(r, mUsageStats));
+        assertTrue(mService.isBlocked(r, mUsageStats));
         verify(mUsageStats, times(1)).registerBlocked(eq(r));
+
+        mBinderService.createNotificationChannels(
+                PKG, new ParceledListSlice(Arrays.asList(channel)));
+        final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+        assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
+    }
+
+    @Test
+    public void testEnqueuedBlockedNotifications_appBlockedChannelForegroundService()
+            throws Exception {
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+
+        NotificationChannel channel = new NotificationChannel("blocked", "name",
+                NotificationManager.IMPORTANCE_NONE);
+        mBinderService.createNotificationChannels(
+                PKG, new ParceledListSlice(Arrays.asList(channel)));
+
+        final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
+        sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+        assertEquals(1, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
+        assertEquals(IMPORTANCE_LOW,
+                mService.getNotificationRecord(sbn.getKey()).getImportance());
+        assertEquals(IMPORTANCE_LOW,
+                mBinderService.getNotificationChannel(PKG, channel.getId()).getImportance());
+    }
+
+    @Test
+    public void testEnqueuedBlockedNotifications_userBlockedChannelForegroundService()
+            throws Exception {
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+
+        NotificationChannel channel =
+                new NotificationChannel("blockedbyuser", "name", IMPORTANCE_HIGH);
+        mBinderService.createNotificationChannels(
+                PKG, new ParceledListSlice(Arrays.asList(channel)));
+
+        NotificationChannel update =
+                new NotificationChannel("blockedbyuser", "name", IMPORTANCE_NONE);
+        mBinderService.updateNotificationChannelForPackage(PKG, mUid, update);
+        waitForIdle();
+        assertEquals(IMPORTANCE_NONE,
+                mBinderService.getNotificationChannel(PKG, channel.getId()).getImportance());
+
+        final StatusBarNotification sbn = generateNotificationRecord(channel).sbn;
+        sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+        assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
+        assertNull(mService.getNotificationRecord(sbn.getKey()));
+        assertEquals(IMPORTANCE_NONE,
+                mBinderService.getNotificationChannel(PKG, channel.getId()).getImportance());
     }
 
     @Test
     public void testBlockedNotifications_blockedChannelGroup() throws Exception {
         when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
 
         NotificationChannel channel = new NotificationChannel("id", "name",
                 NotificationManager.IMPORTANCE_HIGH);
         channel.setGroup("something");
         NotificationRecord r = generateNotificationRecord(channel);
-        assertTrue(mNotificationManagerService.isBlocked(r, mUsageStats));
+        assertTrue(mService.isBlocked(r, mUsageStats));
         verify(mUsageStats, times(1)).registerBlocked(eq(r));
     }
 
@@ -456,13 +528,28 @@
     }
 
     @Test
+    public void testEnqueuedBlockedNotifications_blockedAppForegroundService() throws Exception {
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+
+        mBinderService.setNotificationsEnabledForPackage(PKG, mUid, false);
+
+        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        sbn.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+        mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+        assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
+        assertNull(mService.getNotificationRecord(sbn.getKey()));
+    }
+
+    @Test
     public void testEnqueueNotificationWithTag_PopulatesGetActiveNotifications() throws Exception {
         mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
                 generateNotificationRecord(null).getNotification(), 0);
         waitForIdle();
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifs.length);
-        assertEquals(1, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(1, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -474,7 +561,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
         assertEquals(0, notifs.length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -489,12 +576,16 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(PKG);
         assertEquals(0, notifs.length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
+        ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
+        verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture());
+        assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface());
     }
 
     @Test
     public void testCancelNotificationsFromListenerImmediatelyAfterEnqueue() throws Exception {
-        final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
+        NotificationRecord r = generateNotificationRecord(null);
+        final StatusBarNotification sbn = r.sbn;
         mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
                 sbn.getId(), sbn.getNotification(), sbn.getUserId());
         mBinderService.cancelNotificationsFromListener(null, null);
@@ -502,7 +593,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(0, notifs.length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -515,7 +606,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(0, notifs.length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -527,13 +618,16 @@
                 n.sbn.getId(), n.sbn.getNotification(), n.sbn.getUserId());
         waitForIdle();
 
-        mNotificationManagerService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
                 n.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(n.sbn.getPackageName());
         assertEquals(0, notifs.length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
+        ArgumentCaptor<NotificationStats> captor = ArgumentCaptor.forClass(NotificationStats.class);
+        verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture());
+        assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface());
     }
 
     @Test
@@ -551,7 +645,7 @@
 
         mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId());
         waitForIdle();
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -564,7 +658,7 @@
         mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
         waitForIdle();
 
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -592,7 +686,7 @@
                 parentAsChild.sbn.getUserId());
         waitForIdle();
 
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -606,7 +700,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(1, notifs.length);
-        assertEquals(1, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(1, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -620,7 +714,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(1, notifs.length);
-        assertEquals(1, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(1, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -633,7 +727,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(0, notifs.length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -647,7 +741,7 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(sbn.getPackageName());
         assertEquals(1, notifs.length);
-        assertEquals(1, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(1, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -668,8 +762,8 @@
         final NotificationRecord notif = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
         notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
-        mNotificationManagerService.addNotification(notif);
-        mNotificationManagerService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
+        mService.addNotification(notif);
+        mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0, true,
                 notif.getUserId(), 0, null);
         waitForIdle();
         StatusBarNotification[] notifs =
@@ -682,9 +776,9 @@
         final NotificationRecord notif = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group", true);
         notif.getNotification().flags |= Notification.FLAG_NO_CLEAR;
-        mNotificationManagerService.addNotification(notif);
+        mService.addNotification(notif);
 
-        mNotificationManagerService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
                 notif.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
@@ -703,11 +797,11 @@
         child2.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         final NotificationRecord newGroup = generateNotificationRecord(
                 mTestNotificationChannel, 4, "group2", false);
-        mNotificationManagerService.addNotification(parent);
-        mNotificationManagerService.addNotification(child);
-        mNotificationManagerService.addNotification(child2);
-        mNotificationManagerService.addNotification(newGroup);
-        mNotificationManagerService.getBinderService().cancelNotificationsFromListener(null, null);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.getBinderService().cancelNotificationsFromListener(null, null);
         waitForIdle();
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(parent.sbn.getPackageName());
@@ -725,11 +819,11 @@
         child2.getNotification().flags |= Notification.FLAG_NO_CLEAR;
         final NotificationRecord newGroup = generateNotificationRecord(
                 mTestNotificationChannel, 4, "group2", false);
-        mNotificationManagerService.addNotification(parent);
-        mNotificationManagerService.addNotification(child);
-        mNotificationManagerService.addNotification(child2);
-        mNotificationManagerService.addNotification(newGroup);
-        mNotificationManagerService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
+        mService.addNotification(newGroup);
+        mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(),
                 parent.getUserId());
         waitForIdle();
         StatusBarNotification[] notifs =
@@ -764,7 +858,7 @@
         mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
         waitForIdle();
         assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length);
-        assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
+        assertEquals(0, mService.getNotificationRecordCount());
     }
 
     @Test
@@ -772,8 +866,8 @@
         // make sure the same notification can be found in both lists and returned
         final NotificationRecord group1 = generateNotificationRecord(
                 mTestNotificationChannel, 1, "group1", true);
-        mNotificationManagerService.addEnqueuedNotification(group1);
-        mNotificationManagerService.addNotification(group1);
+        mService.addEnqueuedNotification(group1);
+        mService.addNotification(group1);
 
         // should not be returned
         final NotificationRecord group2 = generateNotificationRecord(
@@ -797,7 +891,7 @@
         waitForIdle();
 
         List<NotificationRecord> inGroup1 =
-                mNotificationManagerService.findGroupNotificationsLocked(PKG, group1.getGroupKey(),
+                mService.findGroupNotificationsLocked(PKG, group1.getGroupKey(),
                         group1.sbn.getUserId());
         assertEquals(3, inGroup1.size());
         for (NotificationRecord record : inGroup1) {
@@ -808,11 +902,11 @@
 
     @Test
     public void testTvExtenderChannelOverride_onTv() throws Exception {
-        mNotificationManagerService.setIsTelevision(true);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setIsTelevision(true);
+        mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
-                        new NotificationChannel("foo", "foo", NotificationManager.IMPORTANCE_HIGH));
+                        new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
@@ -823,8 +917,8 @@
 
     @Test
     public void testTvExtenderChannelOverride_notOnTv() throws Exception {
-        mNotificationManagerService.setIsTelevision(false);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setIsTelevision(false);
+        mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.getNotificationChannel(
                 anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
                 mTestNotificationChannel);
@@ -841,7 +935,7 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
@@ -866,7 +960,7 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
         NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m");
 
@@ -886,7 +980,7 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         mTestNotificationChannel.setLightColor(Color.CYAN);
         when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -904,7 +998,7 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
                 eq(mTestNotificationChannel.getId()), anyBoolean()))
                 .thenReturn(mTestNotificationChannel);
@@ -921,7 +1015,7 @@
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
         NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         when(mRankingHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
                 .thenReturn(ncg);
         reset(mListeners);
@@ -933,7 +1027,7 @@
 
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -941,7 +1035,8 @@
         mBinderService.updateNotificationChannelFromPrivilegedListener(
                 null, PKG, Process.myUserHandle(), mTestNotificationChannel);
 
-        verify(mRankingHelper, times(1)).updateNotificationChannel(anyString(), anyInt(), any());
+        verify(mRankingHelper, times(1)).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean());
 
         verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
                 eq(Process.myUserHandle()), eq(mTestNotificationChannel),
@@ -950,7 +1045,7 @@
 
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
 
@@ -962,7 +1057,8 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).updateNotificationChannel(anyString(), anyInt(), any());
+        verify(mRankingHelper, never()).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean());
 
         verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
                 eq(Process.myUserHandle()), eq(mTestNotificationChannel),
@@ -971,7 +1067,7 @@
 
     @Test
     public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -988,7 +1084,8 @@
             // pass
         }
 
-        verify(mRankingHelper, never()).updateNotificationChannel(anyString(), anyInt(), any());
+        verify(mRankingHelper, never()).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean());
 
         verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
                 eq(Process.myUserHandle()), eq(mTestNotificationChannel),
@@ -997,7 +1094,7 @@
 
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_success() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1011,7 +1108,7 @@
 
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
 
@@ -1029,7 +1126,7 @@
 
     @Test
     public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1051,7 +1148,7 @@
 
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1064,7 +1161,7 @@
 
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
 
@@ -1081,7 +1178,7 @@
 
     @Test
     public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         List<String> associations = new ArrayList<>();
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
         mListener = mock(ManagedServices.ManagedServiceInfo.class);
@@ -1103,14 +1200,14 @@
     public void testHasCompanionDevice_failure() throws Exception {
         when(mCompanionMgr.getAssociations(anyString(), anyInt())).thenThrow(
                 new IllegalArgumentException());
-        mNotificationManagerService.hasCompanionDevice(mListener);
+        mService.hasCompanionDevice(mListener);
     }
 
     @Test
     public void testHasCompanionDevice_noService() throws Exception {
-        mNotificationManagerService = new TestableNotificationManagerService(mContext);
+        mService = new TestableNotificationManagerService(mContext);
 
-        assertFalse(mNotificationManagerService.hasCompanionDevice(mListener));
+        assertFalse(mService.hasCompanionDevice(mListener));
     }
 
     @Test
@@ -1119,16 +1216,17 @@
                 mTestNotificationChannel, 1, null, false);
         final NotificationRecord grouped = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
-        mNotificationManagerService.addNotification(grouped);
-        mNotificationManagerService.addNotification(nonGrouped);
+        mService.addNotification(grouped);
+        mService.addNotification(nonGrouped);
 
         NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
-                mNotificationManagerService.new SnoozeNotificationRunnable(
+                mService.new SnoozeNotificationRunnable(
                         nonGrouped.getKey(), 100, null);
         snoozeNotificationRunnable.run();
 
         // only snooze the one notification
         verify(mSnoozeHelper, times(1)).snooze(any(NotificationRecord.class), anyLong());
+        assertTrue(nonGrouped.getStats().hasSnoozed());
     }
 
     @Test
@@ -1139,12 +1237,12 @@
                 mTestNotificationChannel, 2, "group", false);
         final NotificationRecord child2 = generateNotificationRecord(
                 mTestNotificationChannel, 3, "group", false);
-        mNotificationManagerService.addNotification(parent);
-        mNotificationManagerService.addNotification(child);
-        mNotificationManagerService.addNotification(child2);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
 
         NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
-                mNotificationManagerService.new SnoozeNotificationRunnable(
+                mService.new SnoozeNotificationRunnable(
                         parent.getKey(), 100, null);
         snoozeNotificationRunnable.run();
 
@@ -1160,12 +1258,12 @@
                 mTestNotificationChannel, 2, "group", false);
         final NotificationRecord child2 = generateNotificationRecord(
                 mTestNotificationChannel, 3, "group", false);
-        mNotificationManagerService.addNotification(parent);
-        mNotificationManagerService.addNotification(child);
-        mNotificationManagerService.addNotification(child2);
+        mService.addNotification(parent);
+        mService.addNotification(child);
+        mService.addNotification(child2);
 
         NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
-                mNotificationManagerService.new SnoozeNotificationRunnable(
+                mService.new SnoozeNotificationRunnable(
                         child2.getKey(), 100, null);
         snoozeNotificationRunnable.run();
 
@@ -1180,11 +1278,11 @@
         assertTrue(parent.sbn.getNotification().isGroupSummary());
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
-        mNotificationManagerService.addNotification(parent);
-        mNotificationManagerService.addNotification(child);
+        mService.addNotification(parent);
+        mService.addNotification(child);
 
         NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
-                mNotificationManagerService.new SnoozeNotificationRunnable(
+                mService.new SnoozeNotificationRunnable(
                         child.getKey(), 100, null);
         snoozeNotificationRunnable.run();
 
@@ -1196,10 +1294,10 @@
     public void testSnoozeRunnable_snoozeGroupChild_noOthersInGroup() throws Exception {
         final NotificationRecord child = generateNotificationRecord(
                 mTestNotificationChannel, 2, "group", false);
-        mNotificationManagerService.addNotification(child);
+        mService.addNotification(child);
 
         NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
-                mNotificationManagerService.new SnoozeNotificationRunnable(
+                mService.new SnoozeNotificationRunnable(
                         child.getKey(), 100, null);
         snoozeNotificationRunnable.run();
 
@@ -1245,6 +1343,72 @@
     }
 
     @Test
+    public void testSetListenerAccessForUser() throws Exception {
+        UserHandle user = UserHandle.of(10);
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationListenerAccessGrantedForUser(
+                    c, user.getIdentifier(), true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
+        verify(mListeners, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), user.getIdentifier(), true, true);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), user.getIdentifier(), false, true);
+        verify(mAssistants, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testSetAssistantAccessForUser() throws Exception {
+        UserHandle user = UserHandle.of(10);
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationAssistantAccessGrantedForUser(
+                    c, user.getIdentifier(), true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
+        verify(mAssistants, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), user.getIdentifier(), true, true);
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.flattenToString(), user.getIdentifier(), false, true);
+        verify(mListeners, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testSetDndAccessForUser() throws Exception {
+        UserHandle user = UserHandle.of(10);
+        ComponentName c = ComponentName.unflattenFromString("package/Component");
+        try {
+            mBinderService.setNotificationPolicyAccessGrantedForUser(
+                    c.getPackageName(), user.getIdentifier(), true);
+        } catch (SecurityException e) {
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        verify(mContext, times(1)).sendBroadcastAsUser(any(), eq(user), any());
+        verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+                c.getPackageName(), user.getIdentifier(), true, true);
+        verify(mAssistants, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mListeners, never()).setPackageOrComponentEnabled(
+                any(), anyInt(), anyBoolean(), anyBoolean());
+    }
+
+    @Test
     public void testSetListenerAccess() throws Exception {
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         try {
@@ -1347,9 +1511,9 @@
     @Test
     public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
-        mNotificationManagerService.addEnqueuedNotification(r);
+        mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mNotificationManagerService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey());
         runnable.run();
         waitForIdle();
 
@@ -1359,13 +1523,14 @@
     @Test
     public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups()
             throws Exception {
-        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
-        mNotificationManagerService.addNotification(r);
+        NotificationRecord r =
+                generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
+        mService.addNotification(r);
 
         r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
-        mNotificationManagerService.addEnqueuedNotification(r);
+        mService.addEnqueuedNotification(r);
         NotificationManagerService.PostNotificationRunnable runnable =
-                mNotificationManagerService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey());
         runnable.run();
         waitForIdle();
 
@@ -1377,11 +1542,11 @@
             throws Exception {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group",
                 false);
-        mNotificationManagerService.addNotification(r);
-        mNotificationManagerService.addEnqueuedNotification(r);
+        mService.addNotification(r);
+        mService.addEnqueuedNotification(r);
 
         NotificationManagerService.PostNotificationRunnable runnable =
-                mNotificationManagerService.new PostNotificationRunnable(r.getKey());
+                mService.new PostNotificationRunnable(r.getKey());
         runnable.run();
         waitForIdle();
 
@@ -1405,7 +1570,7 @@
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
-        NotificationRecord posted = mNotificationManagerService.findNotificationLocked(
+        NotificationRecord posted = mService.findNotificationLocked(
                 PKG, null, nr.sbn.getId(), nr.sbn.getUserId());
 
         assertFalse(posted.getNotification().isColorized());
@@ -1416,12 +1581,12 @@
         for (int i = 0; i < 20; i++) {
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
-            mNotificationManagerService.addEnqueuedNotification(r);
+            mService.addEnqueuedNotification(r);
         }
         for (int i = 0; i < 20; i++) {
             NotificationRecord r =
                     generateNotificationRecord(mTestNotificationChannel, i, null, false);
-            mNotificationManagerService.addNotification(r);
+            mService.addNotification(r);
         }
 
         // another package
@@ -1434,27 +1599,31 @@
                 n, new UserHandle(mUid), null, 0);
         NotificationRecord otherPackage =
                 new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-        mNotificationManagerService.addEnqueuedNotification(otherPackage);
-        mNotificationManagerService.addNotification(otherPackage);
+        mService.addEnqueuedNotification(otherPackage);
+        mService.addNotification(otherPackage);
 
         // Same notifications are enqueued as posted, everything counts b/c id and tag don't match
         int userId = new UserHandle(mUid).getIdentifier();
-        assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, userId, 0, null));
-        assertEquals(40, mNotificationManagerService.getNotificationCountLocked(PKG, userId, 0, "tag2"));
-        assertEquals(2, mNotificationManagerService.getNotificationCountLocked("a", userId, 0, "banana"));
+        assertEquals(40,
+                mService.getNotificationCountLocked(PKG, userId, 0, null));
+        assertEquals(40,
+                mService.getNotificationCountLocked(PKG, userId, 0, "tag2"));
+        assertEquals(2,
+                mService.getNotificationCountLocked("a", userId, 0, "banana"));
 
         // exclude a known notification - it's excluded from only the posted list, not enqueued
-        assertEquals(39, mNotificationManagerService.getNotificationCountLocked(PKG, userId, 0, "tag"));
+        assertEquals(39,
+                mService.getNotificationCountLocked(PKG, userId, 0, "tag"));
     }
 
     @Test
     public void testAddAutogroup_requestsSort() throws Exception {
         RankingHandler rh = mock(RankingHandler.class);
-        mNotificationManagerService.setRankingHandler(rh);
+        mService.setRankingHandler(rh);
 
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-        mNotificationManagerService.addNotification(r);
-        mNotificationManagerService.addAutogroupKeyLocked(r.getKey());
+        mService.addNotification(r);
+        mService.addAutogroupKeyLocked(r.getKey());
 
         verify(rh, times(1)).requestSort();
     }
@@ -1462,12 +1631,12 @@
     @Test
     public void testRemoveAutogroup_requestsSort() throws Exception {
         RankingHandler rh = mock(RankingHandler.class);
-        mNotificationManagerService.setRankingHandler(rh);
+        mService.setRankingHandler(rh);
 
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.setOverrideGroupKey("TEST");
-        mNotificationManagerService.addNotification(r);
-        mNotificationManagerService.removeAutogroupKeyLocked(r.getKey());
+        mService.addNotification(r);
+        mService.removeAutogroupKeyLocked(r.getKey());
 
         verify(rh, times(1)).requestSort();
     }
@@ -1475,47 +1644,47 @@
     @Test
     public void testReaddAutogroup_noSort() throws Exception {
         RankingHandler rh = mock(RankingHandler.class);
-        mNotificationManagerService.setRankingHandler(rh);
+        mService.setRankingHandler(rh);
 
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
         r.setOverrideGroupKey("TEST");
-        mNotificationManagerService.addNotification(r);
-        mNotificationManagerService.addAutogroupKeyLocked(r.getKey());
+        mService.addNotification(r);
+        mService.addAutogroupKeyLocked(r.getKey());
 
         verify(rh, never()).requestSort();
     }
 
     @Test
     public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         NotificationManagerService.WorkerHandler handler = mock(
                 NotificationManagerService.WorkerHandler.class);
-        mNotificationManagerService.setHandler(handler);
+        mService.setHandler(handler);
 
         Map<String, Answer> answers = getSignalExtractorSideEffects();
         for (String message : answers.keySet()) {
-            mNotificationManagerService.clearNotifications();
+            mService.clearNotifications();
             final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-            mNotificationManagerService.addNotification(r);
+            mService.addNotification(r);
 
             doAnswer(answers.get(message)).when(mRankingHelper).extractSignals(r);
 
-            mNotificationManagerService.handleRankingSort();
+            mService.handleRankingSort();
         }
         verify(handler, times(answers.size())).scheduleSendRankingUpdate();
     }
 
     @Test
     public void testHandleRankingSort_noUpdateWhenNoSignalChange() throws Exception {
-        mNotificationManagerService.setRankingHelper(mRankingHelper);
+        mService.setRankingHelper(mRankingHelper);
         NotificationManagerService.WorkerHandler handler = mock(
                 NotificationManagerService.WorkerHandler.class);
-        mNotificationManagerService.setHandler(handler);
+        mService.setHandler(handler);
 
         final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-        mNotificationManagerService.addNotification(r);
+        mService.addNotification(r);
 
-        mNotificationManagerService.handleRankingSort();
+        mService.handleRankingSort();
         verify(handler, never()).scheduleSendRankingUpdate();
     }
 
@@ -1534,7 +1703,7 @@
                 + "<service_listing approved=\"test\" user=\"0\" primary=\"true\" />"
                 + "</dnd_apps>"
                 + "</notification-policy>";
-        mNotificationManagerService.readPolicyXml(
+        mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())), false);
         verify(mListeners, times(1)).readXml(any());
         verify(mConditionProviders, times(1)).readXml(any());
@@ -1552,7 +1721,7 @@
                 + "<zen></zen>"
                 + "<ranking></ranking>"
                 + "</notification-policy>";
-        mNotificationManagerService.readPolicyXml(
+        mService.readPolicyXml(
                 new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())), false);
         verify(mListeners, never()).readXml(any());
         verify(mConditionProviders, never()).readXml(any());
@@ -1568,10 +1737,132 @@
     @Test
     public void testLocaleChangedCallsUpdateDefaultZenModeRules() throws Exception {
         ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
-        mNotificationManagerService.mZenModeHelper = mZenModeHelper;
-        mNotificationManagerService.mLocaleChangeReceiver.onReceive(mContext,
+        mService.mZenModeHelper = mZenModeHelper;
+        mService.mLocaleChangeReceiver.onReceive(mContext,
                 new Intent(Intent.ACTION_LOCALE_CHANGED));
 
         verify(mZenModeHelper, times(1)).updateDefaultZenRules();
     }
+
+    @Test
+    public void testBumpFGImportance_noChannelChangePreOApp() throws Exception {
+        String preOPkg = "preO";
+        int preOUid = 145;
+        final ApplicationInfo legacy = new ApplicationInfo();
+        legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+        when(mPackageManagerClient.getApplicationInfoAsUser(eq(preOPkg), anyInt(), anyInt()))
+                .thenReturn(legacy);
+        when(mPackageManagerClient.getPackageUidAsUser(eq(preOPkg), anyInt())).thenReturn(preOUid);
+        getContext().setMockPackageManager(mPackageManagerClient);
+
+        Notification.Builder nb = new Notification.Builder(mContext,
+                NotificationChannel.DEFAULT_CHANNEL_ID)
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .setPriority(Notification.PRIORITY_MIN);
+
+        StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", preOUid,
+                0, nb.build(), new UserHandle(preOUid), null, 0);
+
+        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+        assertEquals(IMPORTANCE_LOW,
+                mService.getNotificationRecord(sbn.getKey()).getImportance());
+
+        nb = new Notification.Builder(mContext)
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                .setPriority(Notification.PRIORITY_MIN);
+
+        sbn = new StatusBarNotification(preOPkg, preOPkg, 9, "tag", preOUid,
+                0, nb.build(), new UserHandle(preOUid), null, 0);
+
+        mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, "tag",
+                sbn.getId(), sbn.getNotification(), sbn.getUserId());
+        waitForIdle();
+        assertEquals(IMPORTANCE_LOW,
+                mService.getNotificationRecord(sbn.getKey()).getImportance());
+
+        NotificationChannel defaultChannel = mBinderService.getNotificationChannel(
+                preOPkg, NotificationChannel.DEFAULT_CHANNEL_ID);
+        assertEquals(IMPORTANCE_UNSPECIFIED, defaultChannel.getImportance());
+    }
+
+    @Test
+    public void testStats_updatedOnDirectReply() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
+        assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied());
+    }
+
+    @Test
+    public void testStats_updatedOnExpansion() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true);
+        assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
+        mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false);
+        assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
+    }
+
+    @Test
+    public void testStats_updatedOnViewSettings() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationSettingsViewed(r.getKey());
+        assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasViewedSettings());
+    }
+
+    @Test
+    public void testStats_updatedOnVisibilityChanged() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+
+        NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, true);
+        mService.mNotificationDelegate.onNotificationVisibilityChanged(
+                new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
+        assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
+        mService.mNotificationDelegate.onNotificationVisibilityChanged(
+                new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
+        assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
+    }
+
+    @Test
+    public void testStats_dismissalSurface() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+
+        mService.mNotificationDelegate.onNotificationClear(mUid, 0, PKG, r.sbn.getTag(),
+                r.sbn.getId(), r.getUserId(), r.getKey(), NotificationStats.DISMISSAL_AOD);
+        waitForIdle();
+
+        assertEquals(NotificationStats.DISMISSAL_AOD, r.getStats().getDismissalSurface());
+    }
+
+    @Test
+    public void testUserSentimentChangeTriggersUpdate() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_USER_SENTIMENT,
+                NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
+        Adjustment adjustment = new Adjustment(
+                r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+
+        waitForIdle();
+
+        verify(handler, timeout(300).times(1)).scheduleSendRankingUpdate();
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index 5a6225a..ef26705a 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.server.notification;
 
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEGATIVE;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEUTRAL;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -38,8 +43,10 @@
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.service.notification.Adjustment;
 import android.service.notification.StatusBarNotification;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -481,5 +488,64 @@
                 record.getLogMaker().getTaggedData(MetricsEvent.FIELD_NOTIFICATION_GROUP_ID));
     }
 
+    @Test
+    public void testNotificationStats() throws Exception {
+        StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
 
+        assertFalse(record.getStats().hasSeen());
+        assertFalse(record.isSeen());
+        assertFalse(record.getStats().hasDirectReplied());
+        assertFalse(record.getStats().hasExpanded());
+        assertFalse(record.getStats().hasInteracted());
+        assertFalse(record.getStats().hasViewedSettings());
+        assertFalse(record.getStats().hasSnoozed());
+
+        record.setSeen();
+        assertTrue(record.getStats().hasSeen());
+        assertTrue(record.isSeen());
+        assertFalse(record.getStats().hasDirectReplied());
+        assertFalse(record.getStats().hasExpanded());
+        assertFalse(record.getStats().hasInteracted());
+        assertFalse(record.getStats().hasViewedSettings());
+        assertFalse(record.getStats().hasSnoozed());
+
+        record.recordViewedSettings();
+        assertFalse(record.getStats().hasDirectReplied());
+        assertFalse(record.getStats().hasExpanded());
+        assertTrue(record.getStats().hasViewedSettings());
+        assertFalse(record.getStats().hasSnoozed());
+
+        record.recordSnoozed();
+        assertFalse(record.getStats().hasDirectReplied());
+        assertFalse(record.getStats().hasExpanded());
+        assertTrue(record.getStats().hasSnoozed());
+
+        record.recordExpanded();
+        assertFalse(record.getStats().hasDirectReplied());
+        assertTrue(record.getStats().hasExpanded());
+
+        record.recordDirectReplied();
+        assertTrue(record.getStats().hasDirectReplied());
+    }
+
+    @Test
+    public void testUserSentiment() throws Exception {
+        StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
+                true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+                false /* lights */, false /* defaultLights */, groupId /* group */);
+        NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+
+        assertEquals(USER_SENTIMENT_NEUTRAL, record.getUserSentiment());
+
+        Bundle signals = new Bundle();
+        signals.putInt(Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEGATIVE);
+        record.addAdjustment(new Adjustment(pkg, record.getKey(), signals, null, sbn.getUserId()));
+
+        record.applyAdjustments();
+
+        assertEquals(USER_SENTIMENT_NEGATIVE, record.getUserSentiment());
+    }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationStatsTest.java b/services/tests/notification/src/com/android/server/notification/NotificationStatsTest.java
new file mode 100644
index 0000000..fec2811
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/NotificationStatsTest.java
@@ -0,0 +1,93 @@
+package com.android.server.notification;
+
+import static android.service.notification.NotificationStats.DISMISSAL_PEEK;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.service.notification.NotificationStats;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationStatsTest extends NotificationTestCase {
+
+    @Test
+    public void testConstructor() {
+        NotificationStats stats = new NotificationStats();
+
+        assertFalse(stats.hasSeen());
+        assertFalse(stats.hasDirectReplied());
+        assertFalse(stats.hasExpanded());
+        assertFalse(stats.hasInteracted());
+        assertFalse(stats.hasViewedSettings());
+        assertFalse(stats.hasSnoozed());
+        assertEquals(NotificationStats.DISMISSAL_NOT_DISMISSED, stats.getDismissalSurface());
+    }
+
+    @Test
+    public void testSeen() {
+        NotificationStats stats = new NotificationStats();
+        stats.setSeen();
+        assertTrue(stats.hasSeen());
+        assertFalse(stats.hasInteracted());
+    }
+
+    @Test
+    public void testDirectReplied() {
+        NotificationStats stats = new NotificationStats();
+        stats.setDirectReplied();
+        assertTrue(stats.hasDirectReplied());
+        assertTrue(stats.hasInteracted());
+    }
+
+    @Test
+    public void testExpanded() {
+        NotificationStats stats = new NotificationStats();
+        stats.setExpanded();
+        assertTrue(stats.hasExpanded());
+        assertTrue(stats.hasInteracted());
+    }
+
+    @Test
+    public void testSnoozed() {
+        NotificationStats stats = new NotificationStats();
+        stats.setSnoozed();
+        assertTrue(stats.hasSnoozed());
+        assertTrue(stats.hasInteracted());
+    }
+
+    @Test
+    public void testViewedSettings() {
+        NotificationStats stats = new NotificationStats();
+        stats.setViewedSettings();
+        assertTrue(stats.hasViewedSettings());
+        assertTrue(stats.hasInteracted());
+    }
+
+    @Test
+    public void testDismissalSurface() {
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(DISMISSAL_PEEK);
+        assertEquals(DISMISSAL_PEEK, stats.getDismissalSurface());
+        assertFalse(stats.hasInteracted());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        NotificationStats stats = new NotificationStats();
+        stats.setViewedSettings();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_AOD);
+        Parcel parcel = Parcel.obtain();
+        stats.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationStats stats1 = NotificationStats.CREATOR.createFromParcel(parcel);
+        assertEquals(stats, stats1);
+    }
+}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 306dd98..df989f7 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -25,25 +25,13 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.fail;
 
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import com.android.internal.util.FastXmlSerializer;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
 import android.app.Notification;
-import android.app.NotificationChannelGroup;
-import android.content.Context;
 import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -52,14 +40,28 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.service.notification.StatusBarNotification;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContentResolver;
 import android.util.ArrayMap;
 import android.util.Xml;
 
+import com.android.internal.util.FastXmlSerializer;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -76,6 +78,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
@@ -95,10 +98,17 @@
     private static final int UID2 = 1111;
     private static final UserHandle USER2 = UserHandle.of(10);
     private static final String TEST_CHANNEL_ID = "test_channel_id";
+    private static final String TEST_AUTHORITY = "test";
+    private static final Uri SOUND_URI =
+            Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
+    private static final Uri CANONICAL_SOUND_URI =
+            Uri.parse("content://" + TEST_AUTHORITY
+                    + "/internal/audio/media/10?title=Test&canonical=1");
 
     @Mock NotificationUsageStats mUsageStats;
     @Mock RankingHandler mHandler;
     @Mock PackageManager mPm;
+    @Mock IContentProvider mTestIContentProvider;
     @Mock Context mContext;
 
     private Notification mNotiGroupGSortA;
@@ -134,9 +144,22 @@
         when(mContext.getPackageManager()).thenReturn(mPm);
         when(mContext.getApplicationInfo()).thenReturn(legacy);
         // most tests assume badging is enabled
-        Secure.putIntForUser(getContext().getContentResolver(),
+        TestableContentResolver contentResolver = getContext().getContentResolver();
+        contentResolver.setFallbackToExisting(false);
+        Secure.putIntForUser(contentResolver,
                 Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID));
 
+        ContentProvider testContentProvider = mock(ContentProvider.class);
+        when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
+        contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
+
+        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+                .thenReturn(CANONICAL_SOUND_URI);
+        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(CANONICAL_SOUND_URI);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(SOUND_URI);
+
         mHelper = new RankingHelper(getContext(), mPm, mHandler, mUsageStats,
                 new String[] {ImportanceExtractor.class.getName()});
 
@@ -214,9 +237,12 @@
     }
 
     private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
+        loadByteArrayXml(stream.toByteArray(), forRestore);
+    }
+
+    private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
         XmlPullParser parser = Xml.newPullParser();
-        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(stream.toByteArray())),
-                null);
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
         parser.nextTag();
         mHelper.readXml(parser, forRestore);
     }
@@ -377,7 +403,7 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel2.setDescription("descriptions for all");
-        channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+        channel2.setSound(SOUND_URI, mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
         channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
@@ -439,6 +465,109 @@
     }
 
     @Test
+    public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        // Testing that in restore we are given the canonical version
+        loadStreamXml(baos, true);
+        verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
+    }
+
+    @Test
+    public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
+        Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
+        Uri canonicalBasedOnLocal = localUri.buildUpon()
+                .appendQueryParameter("title", "Test")
+                .appendQueryParameter("canonical", "1")
+                .build();
+        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(canonicalBasedOnLocal);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(localUri);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
+                .thenReturn(localUri);
+
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        loadStreamXml(baos, true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false);
+        assertEquals(localUri, actualChannel.getSound());
+    }
+
+    @Test
+    public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
+        Thread.sleep(3000);
+        when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(null);
+        when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+                .thenReturn(null);
+
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(SOUND_URI, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        loadStreamXml(baos, true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+    }
+
+
+    /**
+     * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
+     * handle its restore properly.
+     */
+    @Test
+    public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
+        // Not a local uncanonicalized uri, simulating that it fails to exist locally
+        when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
+        String id = "id";
+        String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+                + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
+                + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+                + "sound=\"" + SOUND_URI + "\" "
+                + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
+                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+
+        loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+    }
+
+    @Test
+    public void testBackupRestoreXml_withNullSoundUri() throws Exception {
+        NotificationChannel channel =
+                new NotificationChannel("id", "name", IMPORTANCE_LOW);
+        channel.setSound(null, mAudioAttributes);
+        mHelper.createNotificationChannel(PKG, UID, channel, true);
+        ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+        loadStreamXml(baos, true);
+
+        NotificationChannel actualChannel = mHelper.getNotificationChannel(
+                PKG, UID, channel.getId(), false);
+        assertEquals(null, actualChannel.getSound());
+    }
+
+    @Test
     public void testChannelXml_backup() throws Exception {
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
@@ -497,7 +626,7 @@
         final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
                 NotificationChannel.DEFAULT_CHANNEL_ID, false);
         defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
+        mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
                 NotificationChannel.DEFAULT_CHANNEL_ID);
@@ -646,7 +775,7 @@
         channel2.setBypassDnd(false);
         channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
 
-        mHelper.updateNotificationChannel(PKG, UID, channel2);
+        mHelper.updateNotificationChannel(PKG, UID, channel2, true);
 
         // all fields should be changed
         assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
@@ -670,7 +799,7 @@
         defaultChannel.setBypassDnd(true);
         defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
 
-        mHelper.updateNotificationChannel(PKG, UID, defaultChannel);
+        mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
 
         // ensure app level fields are changed
         assertFalse(mHelper.canShowBadge(PKG, UID));
@@ -694,7 +823,7 @@
         channel.setBypassDnd(true);
         channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
 
-        mHelper.updateNotificationChannel(PKG, UID, channel);
+        mHelper.updateNotificationChannel(PKG, UID, channel, true);
 
         // ensure app level fields are not changed
         assertTrue(mHelper.canShowBadge(PKG, UID));
@@ -785,14 +914,14 @@
         update1.setSound(new Uri.Builder().scheme("test").build(),
                 new AudioAttributes.Builder().build());
         update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY); // should be ignored
-        mHelper.updateNotificationChannel(PKG, UID, update1);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
         assertEquals(NotificationChannel.USER_LOCKED_SOUND,
                 mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
                         .getUserLockedFields());
 
         NotificationChannel update2 = getChannel();
         update2.enableVibration(true);
-        mHelper.updateNotificationChannel(PKG, UID, update2);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
         assertEquals(NotificationChannel.USER_LOCKED_SOUND
                         | NotificationChannel.USER_LOCKED_VIBRATION,
                 mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
@@ -805,14 +934,14 @@
 
         final NotificationChannel update1 = getChannel();
         update1.setVibrationPattern(new long[]{7945, 46 ,246});
-        mHelper.updateNotificationChannel(PKG, UID, update1);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
         assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
                 mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
         update2.enableLights(true);
-        mHelper.updateNotificationChannel(PKG, UID, update2);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
         assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
                         | NotificationChannel.USER_LOCKED_LIGHTS,
                 mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
@@ -825,14 +954,14 @@
 
         final NotificationChannel update1 = getChannel();
         update1.setLightColor(Color.GREEN);
-        mHelper.updateNotificationChannel(PKG, UID, update1);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
         assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
                 mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
         update2.setImportance(IMPORTANCE_DEFAULT);
-        mHelper.updateNotificationChannel(PKG, UID, update2);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
         assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
                         | NotificationChannel.USER_LOCKED_IMPORTANCE,
                 mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
@@ -848,14 +977,14 @@
 
         final NotificationChannel update1 = getChannel();
         update1.setBypassDnd(true);
-        mHelper.updateNotificationChannel(PKG, UID, update1);
+        mHelper.updateNotificationChannel(PKG, UID, update1, true);
         assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
                 mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
                         .getUserLockedFields());
 
         final NotificationChannel update2 = getChannel();
         update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-        mHelper.updateNotificationChannel(PKG, UID, update2);
+        mHelper.updateNotificationChannel(PKG, UID, update2, true);
         assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
                         | NotificationChannel.USER_LOCKED_VISIBILITY,
                 mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
@@ -863,7 +992,7 @@
 
         final NotificationChannel update3 = getChannel();
         update3.setShowBadge(false);
-        mHelper.updateNotificationChannel(PKG, UID, update3);
+        mHelper.updateNotificationChannel(PKG, UID, update3, true);
         assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
                         | NotificationChannel.USER_LOCKED_VISIBILITY
                         | NotificationChannel.USER_LOCKED_SHOW_BADGE,
@@ -1276,7 +1405,7 @@
         mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
 
         channel1.setImportance(IMPORTANCE_LOW);
-        mHelper.updateNotificationChannel(PKG, UID, channel1);
+        mHelper.updateNotificationChannel(PKG, UID, channel1, true);
 
         List<NotificationChannelGroup> actual =
                 mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
diff --git a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
new file mode 100644
index 0000000..cbe9650
--- /dev/null
+++ b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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 distriZenbuted 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.server.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.media.AudioAttributes;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeHelperTest extends NotificationTestCase {
+
+    @Mock ConditionProviders mConditionProviders;
+    private TestableLooper mTestableLooper;
+    private ZenModeHelper mZenModeHelperSpy;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTestableLooper = TestableLooper.get(this);
+        mZenModeHelperSpy = spy(new ZenModeHelper(getContext(), mTestableLooper.getLooper(),
+                mConditionProviders));
+    }
+
+    @Test
+    public void testZenOff_NoMuteApplied() {
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_OFF;
+        assertTrue(mZenModeHelperSpy.mConfig.allowAlarms);
+        mZenModeHelperSpy.applyRestrictions();
+
+        doNothing().when(mZenModeHelperSpy).applyRestrictions(anyBoolean(), anyInt());
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_ALARM);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_MEDIA);
+    }
+
+    @Test
+    public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() {
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        assertTrue(mZenModeHelperSpy.mConfig.allowAlarms);
+        assertTrue(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+        mZenModeHelperSpy.applyRestrictions();
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_ALARM);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+                AudioAttributes.USAGE_MEDIA);
+    }
+
+    @Test
+    public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() {
+
+        mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+        mZenModeHelperSpy.mConfig.allowAlarms = false;
+        mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+        assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+        assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+        mZenModeHelperSpy.applyRestrictions();
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_ALARM);
+
+        // Media is a catch-all that includes games and system sounds
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_MEDIA);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_GAME);
+        verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+                AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+    }
+}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index fd83223..8e41a55 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -33,7 +33,10 @@
     aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl
 LOCAL_SRC_FILES += $(call all-java-files-under, test-apps/JobTestApp/src)
 
-LOCAL_JAVA_LIBRARIES := android.test.mock legacy-android-test
+LOCAL_JAVA_LIBRARIES := \
+    android.hidl.manager-V1.0-java \
+    android.test.mock \
+    legacy-android-test \
 
 LOCAL_PACKAGE_NAME := FrameworksServicesTests
 LOCAL_COMPATIBILITY_SUITE := device-tests
@@ -62,6 +65,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
 
+LOCAL_PROGUARD_ENABLED := disabled
+
 include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/services/tests/servicestests/assets/shortcut/shortcut_api27_backup.xml b/services/tests/servicestests/assets/shortcut/shortcut_api27_backup.xml
new file mode 100644
index 0000000..266ab99
--- /dev/null
+++ b/services/tests/servicestests/assets/shortcut/shortcut_api27_backup.xml
@@ -0,0 +1,19 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<user>
+    <launcher-pins package-name="com.android.launcher.1" launcher-user="0">
+        <package-info version="9" last_udpate_time="1230768000000">
+            <signature hash="0X8l7PvMeFf3vr6kaTCL4LJYCUPpbROjrZihNnXEv8I" />
+        </package-info>
+        <package package-name="com.android.test.1" package-user="0">
+            <pin value="s1" />
+        </package>
+    </launcher-pins>
+    <package name="com.android.test.1" call-count="0" last-reset="1506991428317">
+        <package-info version="8" last_udpate_time="1506534668998">
+            <signature hash="zDmdc5A/Bu5pQDKrBTjwVjT/fhzl6OUKwzCocUhPNM8" />
+        </package-info>
+        <shortcut id="s1" activity="com.android.test.1/com.example.android.shortcutsample.Main" title="Leak wakelock" titleid="0" textid="0" dmessageid="0" timestamp="1507674156622" flags="130">
+            <intent intent-base="#Intent;action=com.example.android.shortcutsample.LEAK;launchFlags=0x1000c000;component=com.example.android.shortcutsample/.Main;end" />
+        </shortcut>
+    </package>
+</user>
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 121c1de..1253d44 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -21,6 +21,9 @@
     <string name="shortcut_title2"></string>
     <string name="shortcut_text2"></string>
     <string name="shortcut_disabled_message2"></string>
+    <string name="shortcut_title3"></string>
+    <string name="shortcut_text3"></string>
+    <string name="shortcut_disabled_message3"></string>
     <string name="test_account_type1_authenticator_label">AccountManagerService Test Account Type1</string>
     <string name="test_account_type2_authenticator_label">AccountManagerService Test Account Type2</string>
     <string name="test_account_type1">com.android.server.accounts.account_manager_service_test.account.type1</string>
diff --git a/services/tests/servicestests/res/xml/shortcut_5_altalt.xml b/services/tests/servicestests/res/xml/shortcut_5_altalt.xml
new file mode 100644
index 0000000..b17895e
--- /dev/null
+++ b/services/tests/servicestests/res/xml/shortcut_5_altalt.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
+    <shortcut
+        android:shortcutId="ms1"
+        android:enabled="true"
+        android:icon="@drawable/icon1"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_text1"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
+        >
+        <intent
+            android:action="action1"
+            android:data="http://a.b.c/1"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+        <categories android:name="android.shortcut.media" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms2"
+        android:enabled="true"
+        android:icon="@drawable/icon2"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_text2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
+        >
+        <intent
+            android:action="action2"
+            >
+        </intent>
+        <categories android:name="android.shortcut.conversation" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms3"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        android:shortcutLongLabel="@string/shortcut_title2"
+        android:shortcutDisabledMessage="@string/shortcut_disabled_message3"
+        >
+        <intent
+            android:action="android.intent.action.VIEW"
+            >
+        </intent>
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms4"
+        android:shortcutShortLabel="@string/shortcut_title2"
+        android:shortcutLongLabel="@string/shortcut_title2"
+        >
+        <intent
+            android:action="android.intent.action.VIEW2"
+            >
+        </intent>
+        <categories />
+        <categories android:name="" />
+        <categories android:name="cat" />
+    </shortcut>
+    <shortcut
+        android:shortcutId="ms5"
+        android:shortcutShortLabel="@string/shortcut_title1"
+        >
+        <intent
+            android:action="action"
+            android:data="http://www/"
+            android:targetPackage="abc"
+            android:targetClass=".xyz"
+            android:mimeType="foo/bar"
+            >
+            <categories android:name="cat1" />
+            <categories android:name="cat2" />
+            <extra android:name="key1" android:value="value1" />
+            <extra android:name="key2" android:value="value2" />
+        </intent>
+    </shortcut>
+</shortcuts>
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
new file mode 100644
index 0000000..daaad7a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.server;
+
+import static junit.framework.Assert.*;
+import static org.mockito.Mockito.*;
+
+import android.hardware.health.V2_0.IHealth;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+import android.test.AndroidTestCase;
+import android.util.Slog;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+
+public class BatteryServiceTest extends AndroidTestCase {
+
+    @Mock IServiceManager mMockedManager;
+    @Mock IHealth mMockedHal;
+
+    @Mock BatteryService.HealthServiceWrapper.Callback mCallback;
+    @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier;
+    @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier;
+    BatteryService.HealthServiceWrapper mWrapper;
+
+    private static final String HEALTHD = BatteryService.HealthServiceWrapper.INSTANCE_HEALTHD;
+    private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR;
+
+    @Override
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
+        return new ArgumentMatcher<T>() {
+            @Override public boolean matches(T e) {
+                return collection.contains(e);
+            }
+            @Override public String toString() {
+                return collection.toString();
+            }
+        };
+    }
+
+    private void initForInstances(String... instanceNamesArr) throws Exception {
+        final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
+        doAnswer((invocation) -> {
+                Slog.e("BatteryServiceTest", "health: onRegistration " + invocation.getArguments()[2]);
+                ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
+                        IHealth.kInterfaceName,
+                        (String)invocation.getArguments()[1],
+                        true /* preexisting */);
+                return null;
+            }).when(mMockedManager).registerForNotifications(
+                eq(IHealth.kInterfaceName),
+                argThat(isOneOf(instanceNames)),
+                any(IServiceNotification.class));
+
+        doReturn(mMockedHal).when(mMockedManager)
+            .get(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)));
+
+        doReturn(IServiceManager.Transport.HWBINDER).when(mMockedManager)
+            .getTransport(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)));
+
+        doReturn(mMockedManager).when(mManagerSupplier).get();
+        doReturn(mMockedHal).when(mHealthServiceSupplier)
+            .get(argThat(isOneOf(instanceNames)));
+
+        mWrapper = new BatteryService.HealthServiceWrapper();
+    }
+
+    @SmallTest
+    public void testWrapPreferVendor() throws Exception {
+        initForInstances(VENDOR, HEALTHD);
+        mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
+        verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
+    }
+
+    @SmallTest
+    public void testUseHealthd() throws Exception {
+        initForInstances(HEALTHD);
+        mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
+        verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
+    }
+
+    @SmallTest
+    public void testNoService() throws Exception {
+        initForInstances("unrelated");
+        try {
+            mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
+            fail("Expect NoSuchElementException");
+        } catch (NoSuchElementException ex) {
+            // expected
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
index 02f645a..c8dc9ff 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
@@ -77,7 +77,6 @@
         mAccessibilityCache.clear();
         AccessibilityInteractionClient.getInstance().clearCache();
         assertEquals(0, numA11yWinInfosInUse.get());
-        assertEquals(0, numA11yNodeInfosInUse.get());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
index 50824e3..8a54c4e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationGestureHandlerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.accessibility;
 
-import static android.util.ExceptionUtils.propagate;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
@@ -105,8 +104,8 @@
         MagnificationGestureHandler h = new MagnificationGestureHandler(
                 mContext, mMagnificationController,
                 detectTripleTap, detectShortcutTrigger);
-        mHandler = new TestHandler(h.mDetectingStateHandler, mClock);
-        h.mDetectingStateHandler.mHandler = mHandler;
+        mHandler = new TestHandler(h.mDetectingState, mClock);
+        h.mDetectingState.mHandler = mHandler;
         h.setNext(strictMock(EventStreamTransformation.class));
         return h;
     }
@@ -229,7 +228,7 @@
         allowEventDelegation();
         tap();
         // no fast forward
-        verify(mMgh.mNext, times(2)).onMotionEvent(any(), any(), anyInt());
+        verify(mMgh.getNext(), times(2)).onMotionEvent(any(), any(), anyInt());
     }
 
     private void assertTransition(int fromState, Runnable transitionAction, int toState) {
@@ -250,7 +249,7 @@
     }
 
     private void allowEventDelegation() {
-        doNothing().when(mMgh.mNext).onMotionEvent(any(), any(), anyInt());
+        doNothing().when(mMgh.getNext()).onMotionEvent(any(), any(), anyInt());
     }
 
     private void fastForward1sec() {
@@ -272,7 +271,7 @@
 
             case STATE_IDLE: {
                 check(tapCount() < 2, state);
-                check(!mMgh.mShortcutTriggered, state);
+                check(!mMgh.mDetectingState.mShortcutTriggered, state);
                 check(!isZoomed(), state);
             } break;
             case STATE_ZOOMED: {
@@ -288,28 +287,28 @@
                 check(tapCount() == 2, state);
             } break;
             case STATE_DRAGGING: {
-                check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_VIEWPORT_DRAGGING,
+                check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
                         state);
-                check(mMgh.mViewportDraggingStateHandler.mZoomedInBeforeDrag, state);
+                check(mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
             } break;
             case STATE_DRAGGING_TMP: {
-                check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_VIEWPORT_DRAGGING,
+                check(mMgh.mCurrentState == mMgh.mViewportDraggingState,
                         state);
-                check(!mMgh.mViewportDraggingStateHandler.mZoomedInBeforeDrag, state);
+                check(!mMgh.mViewportDraggingState.mZoomedInBeforeDrag, state);
             } break;
             case STATE_SHORTCUT_TRIGGERED: {
-                check(mMgh.mShortcutTriggered, state);
+                check(mMgh.mDetectingState.mShortcutTriggered, state);
                 check(!isZoomed(), state);
             } break;
             case STATE_PANNING: {
-                check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_PANNING_SCALING,
+                check(mMgh.mCurrentState == mMgh.mPanningScalingState,
                         state);
-                check(!mMgh.mPanningScalingStateHandler.mScaling, state);
+                check(!mMgh.mPanningScalingState.mScaling, state);
             } break;
             case STATE_SCALING_AND_PANNING: {
-                check(mMgh.mCurrentState == MagnificationGestureHandler.STATE_PANNING_SCALING,
+                check(mMgh.mCurrentState == mMgh.mPanningScalingState,
                         state);
-                check(mMgh.mPanningScalingStateHandler.mScaling, state);
+                check(mMgh.mPanningScalingState.mScaling, state);
             } break;
             default: throw new IllegalArgumentException("Illegal state: " + state);
         }
@@ -432,7 +431,7 @@
     }
 
     private int tapCount() {
-        return mMgh.mDetectingStateHandler.tapCount();
+        return mMgh.mDetectingState.tapCount();
     }
 
     private static String stateToString(int state) {
@@ -492,7 +491,7 @@
     }
 
     private long defaultDownTime() {
-        MotionEvent lastDown = mMgh.mDetectingStateHandler.mLastDown;
+        MotionEvent lastDown = mMgh.mDetectingState.mLastDown;
         return lastDown == null ? mClock.now() - 1 : lastDown.getDownTime();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 526f815..cb13e85 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -16,9 +16,15 @@
 
 package com.android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
 import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
 import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT;
+import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -26,14 +32,13 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
-import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import android.view.Display;
 import org.junit.runner.RunWith;
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -46,64 +51,48 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ActivityRecordTests extends ActivityTestsBase {
-    private static final int TEST_STACK_ID = 100;
+    private ActivityManagerService mService;
+    private TestActivityStack mStack;
+    private TaskRecord mTask;
+    private ActivityRecord mActivity;
 
-    private final ComponentName testActivityComponent =
-            ComponentName.unflattenFromString("com.foo/.BarActivity");
-    private final ComponentName secondaryActivityComponent =
-            ComponentName.unflattenFromString("com.foo/.BarActivity2");
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+    }
+
     @Test
     public void testStackCleanupOnClearingTask() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord record = createActivity(service, testActivityComponent, task);
-
-        record.setTask(null);
-        assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 1);
+        mActivity.setTask(null);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 1);
     }
 
     @Test
     public void testStackCleanupOnActivityRemoval() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord record = createActivity(service, testActivityComponent, task);
-
-        task.removeActivity(record);
-        assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID),  1);
+        mTask.removeActivity(mActivity);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(),  1);
     }
 
     @Test
     public void testStackCleanupOnTaskRemoval() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord record = createActivity(service, testActivityComponent, task);
-
-        service.mStackSupervisor.getStack(TEST_STACK_ID)
-                .removeTask(task, null /*reason*/, ActivityStack.REMOVE_TASK_MODE_MOVING);
-
+        mStack.removeTask(mTask, null /*reason*/, REMOVE_TASK_MODE_MOVING);
         // Stack should be gone on task removal.
-        assertNull(service.mStackSupervisor.getStack(TEST_STACK_ID));
+        assertNull(mService.mStackSupervisor.getStack(mStack.mStackId));
     }
 
     @Test
     public void testNoCleanupMovingActivityInSameStack() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord oldTask = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord record = createActivity(service, testActivityComponent, oldTask);
-        final TaskRecord newTask = createTask(service, testActivityComponent, TEST_STACK_ID);
-
-        record.reparent(newTask, 0, null /*reason*/);
-        assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 0);
-    }
-
-    private static int getActivityRemovedFromStackCount(ActivityManagerService service,
-            int stackId) {
-        final ActivityStack stack = service.mStackSupervisor.getStack(stackId);
-        if (stack instanceof ActivityStackReporter) {
-            return ((ActivityStackReporter) stack).onActivityRemovedFromStackInvocationCount();
-        }
-
-        return -1;
+        final TaskRecord newTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack)
+                .build();
+        mActivity.reparent(newTask, 0, null /*reason*/);
+        assertEquals(mStack.onActivityRemovedFromStackInvocationCount(), 0);
     }
 
     @Test
@@ -126,19 +115,15 @@
 
     private void verifyPositionWithLimitedAspectRatio(int navBarPosition, Rect taskBounds,
             float aspectRatio, Rect expectedActivityBounds) {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord record = createActivity(service, testActivityComponent, task);
-
         // Verify with nav bar on the right.
-        when(service.mWindowManager.getNavBarPosition()).thenReturn(navBarPosition);
-        task.getConfiguration().windowConfiguration.setAppBounds(taskBounds);
-        record.info.maxAspectRatio = aspectRatio;
-        record.ensureActivityConfigurationLocked(0 /* globalChanges */, false /* preserveWindow */);
-        assertEquals(expectedActivityBounds, record.getBounds());
+        when(mService.mWindowManager.getNavBarPosition()).thenReturn(navBarPosition);
+        mTask.getConfiguration().windowConfiguration.setAppBounds(taskBounds);
+        mActivity.info.maxAspectRatio = aspectRatio;
+        mActivity.ensureActivityConfigurationLocked(
+                0 /* globalChanges */, false /* preserveWindow */);
+        assertEquals(expectedActivityBounds, mActivity.getBounds());
     }
 
-
     @Test
     public void testCanBeLaunchedOnDisplay() throws Exception {
         testSupportsLaunchingResizeable(false /*taskPresent*/, true /*taskResizeable*/,
@@ -156,26 +141,22 @@
 
     private void testSupportsLaunchingResizeable(boolean taskPresent, boolean taskResizeable,
             boolean activityResizeable, boolean expected) {
-        final ActivityManagerService service = createActivityManagerService();
-        service.mSupportsMultiWindow = true;
-
+        mService.mSupportsMultiWindow = true;
 
         final TaskRecord task = taskPresent
-                ? createTask(service, testActivityComponent, TEST_STACK_ID) : null;
+                ? new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build() : null;
 
         if (task != null) {
-            task.setResizeMode(taskResizeable ? ActivityInfo.RESIZE_MODE_RESIZEABLE
-                    : ActivityInfo.RESIZE_MODE_UNRESIZEABLE);
+            task.setResizeMode(taskResizeable ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE);
         }
 
-        final ActivityRecord record = createActivity(service, secondaryActivityComponent,
-                task);
-        record.info.resizeMode = activityResizeable ? ActivityInfo.RESIZE_MODE_RESIZEABLE
-                : ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+        final ActivityRecord record = new ActivityBuilder(mService).setTask(task).build();
+        record.info.resizeMode = activityResizeable
+                ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE;
 
-        record.canBeLaunchedOnDisplay(Display.DEFAULT_DISPLAY);
+        record.canBeLaunchedOnDisplay(DEFAULT_DISPLAY);
 
-        assertEquals(((TestActivityStackSupervisor) service.mStackSupervisor)
+        assertEquals(((TestActivityStackSupervisor) mService.mStackSupervisor)
                 .getLastResizeableFromCanPlaceEntityOnDisplay(), expected);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
index cd1843b..fc17da8 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java
@@ -16,11 +16,10 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -33,6 +32,7 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.runner.RunWith;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -51,8 +51,20 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ActivityStackSupervisorTests extends ActivityTestsBase {
-    private final ComponentName testActivityComponent =
-            ComponentName.unflattenFromString("com.foo/.BarActivity");
+    private ActivityManagerService mService;
+    private ActivityStackSupervisor mSupervisor;
+    private ActivityStack mFullscreenStack;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mSupervisor = mService.mStackSupervisor;
+        mFullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+    }
 
     /**
      * This test ensures that we do not try to restore a task based off an invalid task id. The
@@ -61,9 +73,8 @@
      */
     @Test
     public void testRestoringInvalidTask() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        TaskRecord task = service.mStackSupervisor.anyTaskForIdLocked(0 /*taskId*/,
-                MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null);
+        TaskRecord task = mSupervisor.anyTaskForIdLocked(0 /*taskId*/,
+                MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
         assertNull(task);
     }
 
@@ -73,43 +84,43 @@
      */
     @Test
     public void testReplacingTaskInPinnedStack() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord firstTask = createTask(service, testActivityComponent,
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        final ActivityRecord firstActivity = createActivity(service, testActivityComponent,
-                firstTask);
-        // Create a new task on the full screen stack
-        final TaskRecord secondTask = createTask(service, testActivityComponent,
-                FULLSCREEN_WORKSPACE_STACK_ID);
-        final ActivityRecord secondActivity = createActivity(service, testActivityComponent,
-                secondTask);
-        service.mStackSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack",
-                service.mStackSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID));
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        final TaskRecord firstTask = firstActivity.getTask();
+
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        final TaskRecord secondTask = secondActivity.getTask();
+
+        mSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack", mFullscreenStack);
 
         // Ensure full screen stack has both tasks.
-        ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, firstTask,
-                secondTask);
+        ensureStackPlacement(mFullscreenStack, firstTask, secondTask);
 
         // Move first activity to pinned stack.
-        service.mStackSupervisor.moveActivityToPinnedStackLocked(firstActivity,
-                new Rect() /*sourceBounds*/, 0f /*aspectRatio*/, false, "initialMove");
+        final Rect sourceBounds = new Rect();
+        mSupervisor.moveActivityToPinnedStackLocked(firstActivity, sourceBounds,
+                0f /*aspectRatio*/, "initialMove");
 
+        final ActivityDisplay display = mFullscreenStack.getDisplay();
+        ActivityStack pinnedStack = display.getPinnedStack();
         // Ensure a task has moved over.
-        ensureStackPlacement(service.mStackSupervisor, PINNED_STACK_ID, firstTask);
-        ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, secondTask);
+        ensureStackPlacement(pinnedStack, firstTask);
+        ensureStackPlacement(mFullscreenStack, secondTask);
 
         // Move second activity to pinned stack.
-        service.mStackSupervisor.moveActivityToPinnedStackLocked(secondActivity,
-                new Rect() /*sourceBounds*/, 0f /*aspectRatio*/ /*destBounds*/, false, "secondMove");
+        mSupervisor.moveActivityToPinnedStackLocked(secondActivity, sourceBounds,
+                0f /*aspectRatio*/, "secondMove");
 
+        // Need to get stacks again as a new instance might have been created.
+        pinnedStack = display.getPinnedStack();
+        mFullscreenStack = display.getStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
         // Ensure stacks have swapped tasks.
-        ensureStackPlacement(service.mStackSupervisor, PINNED_STACK_ID, secondTask);
-        ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, firstTask);
+        ensureStackPlacement(pinnedStack, secondTask);
+        ensureStackPlacement(mFullscreenStack, firstTask);
     }
 
-    private static void ensureStackPlacement(ActivityStackSupervisor supervisor, int stackId,
-            TaskRecord... tasks) {
-        final ActivityStack stack = supervisor.getStack(stackId);
+    private static void ensureStackPlacement(ActivityStack stack, TaskRecord... tasks) {
         final ArrayList<TaskRecord> stackTasks = stack.getAllTasks();
         assertEquals(stackTasks.size(), tasks != null ? tasks.length : 0);
 
@@ -127,15 +138,12 @@
      */
     @Test
     public void testStoppingActivityRemovedWhenResumed() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord firstTask = createTask(service, testActivityComponent,
-            FULLSCREEN_WORKSPACE_STACK_ID);
-        final ActivityRecord firstActivity = createActivity(service, testActivityComponent,
-            firstTask);
-        service.mStackSupervisor.mStoppingActivities.add(firstActivity);
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
+                .setStack(mFullscreenStack).build();
+        mSupervisor.mStoppingActivities.add(firstActivity);
 
         firstActivity.completeResumeLocked();
 
-        assertFalse(service.mStackSupervisor.mStoppingActivities.contains(firstActivity));
+        assertFalse(mSupervisor.mStoppingActivities.contains(firstActivity));
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 02fba08..480b210 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -16,12 +16,22 @@
 
 package com.android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
+import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.os.UserHandle;
@@ -30,6 +40,7 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import org.junit.runner.RunWith;
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -42,84 +53,202 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ActivityStackTests extends ActivityTestsBase {
-    private static final int TEST_STACK_ID = 100;
-    private static final ComponentName testActivityComponent =
-            ComponentName.unflattenFromString("com.foo/.BarActivity");
-    private static final ComponentName testOverlayComponent =
-            ComponentName.unflattenFromString("com.foo/.OverlayActivity");
+    private ActivityManagerService mService;
+    private ActivityStackSupervisor mSupervisor;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mSupervisor = mService.mStackSupervisor;
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mTask = new TaskBuilder(mSupervisor).setStack(mStack).build();
+    }
 
     @Test
     public void testEmptyTaskCleanupOnRemove() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        assertNotNull(task.getWindowContainerController());
-        service.mStackSupervisor.getStack(TEST_STACK_ID).removeTask(task,
-                "testEmptyTaskCleanupOnRemove", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
-        assertNull(task.getWindowContainerController());
+        assertNotNull(mTask.getWindowContainerController());
+        mStack.removeTask(mTask, "testEmptyTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
+        assertNull(mTask.getWindowContainerController());
     }
 
     @Test
     public void testOccupiedTaskCleanupOnRemove() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task);
-        assertNotNull(task.getWindowContainerController());
-        service.mStackSupervisor.getStack(TEST_STACK_ID).removeTask(task,
-                "testOccupiedTaskCleanupOnRemove", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
-        assertNotNull(task.getWindowContainerController());
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        assertNotNull(mTask.getWindowContainerController());
+        mStack.removeTask(mTask, "testOccupiedTaskCleanupOnRemove", REMOVE_TASK_MODE_DESTROYING);
+        assertNotNull(mTask.getWindowContainerController());
     }
 
     @Test
     public void testNoPauseDuringResumeTopActivity() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task);
-        final ActivityStack testStack = service.mStackSupervisor.getStack(TEST_STACK_ID);
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
 
         // Simulate the a resumed activity set during
         // {@link ActivityStack#resumeTopActivityUncheckedLocked}.
-        service.mStackSupervisor.inResumeTopActivity = true;
-        testStack.mResumedActivity = activityRecord;
+        mSupervisor.inResumeTopActivity = true;
+        mStack.mResumedActivity = r;
 
-        final boolean waiting = testStack.goToSleepIfPossible(false);
+        final boolean waiting = mStack.goToSleepIfPossible(false);
 
         // Ensure we report not being ready for sleep.
         assertFalse(waiting);
 
         // Make sure the resumed activity is untouched.
-        assertEquals(testStack.mResumedActivity, activityRecord);
+        assertEquals(mStack.mResumedActivity, r);
     }
 
     @Test
     public void testStopActivityWhenActivityDestroyed() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task);
-        activityRecord.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
-        final ActivityStack testStack = service.mStackSupervisor.getStack(TEST_STACK_ID);
-        service.mStackSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", testStack);
-
-        testStack.stopActivityLocked(activityRecord);
+        final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
+        r.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
+        mSupervisor.setFocusStackUnchecked("testStopActivityWithDestroy", mStack);
+        mStack.stopActivityLocked(r);
+        // Mostly testing to make sure there is a crash in the call part, so if we get here we are
+        // good-to-go!
     }
 
     @Test
     public void testFindTaskWithOverlay() throws Exception {
-        final ActivityManagerService service = createActivityManagerService();
-        final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
-        final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task,
-                0);
+        final ActivityRecord r = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .setStack(mStack)
+                .setUid(0)
+                .build();
+        final TaskRecord task = r.getTask();
         // Overlay must be for a different user to prevent recognizing a matching top activity
-        final ActivityRecord taskOverlay = createActivity(service, testOverlayComponent, task,
-                UserHandle.PER_USER_RANGE * 2);
+        final ActivityRecord taskOverlay = new ActivityBuilder(mService).setTask(task)
+                .setUid(UserHandle.PER_USER_RANGE * 2).build();
         taskOverlay.mTaskOverlay = true;
 
-        final ActivityStack testStack = service.mStackSupervisor.getStack(TEST_STACK_ID);
         final ActivityStackSupervisor.FindTaskResult result =
                 new ActivityStackSupervisor.FindTaskResult();
-        testStack.findTaskLocked(activityRecord, result);
+        mStack.findTaskLocked(r, result);
 
-        assertEquals(task.getTopActivity(false /* includeOverlays */), activityRecord);
+        assertEquals(task.getTopActivity(false /* includeOverlays */), r);
         assertEquals(task.getTopActivity(true /* includeOverlays */), taskOverlay);
         assertNotNull(result.r);
     }
+
+    @Test
+    public void testShouldBeVisible_Fullscreen() throws Exception {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final ActivityStack pinnedStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack fullscreenStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        // Home stack shouldn't be visible behind an opaque fullscreen stack, but pinned stack
+        // should be visible since it is always on-top.
+        fullscreenStack.setIsTranslucent(false);
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+        assertTrue(fullscreenStack.shouldBeVisible(null /* starting */));
+
+        // Home stack should be visible behind a translucent fullscreen stack.
+        fullscreenStack.setIsTranslucent(true);
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(pinnedStack.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testShouldBeVisible_SplitScreen() throws Exception {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack splitScreenPrimary = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final TestActivityStack splitScreenSecondary = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+
+        // Home stack shouldn't be visible if both halves of split-screen are opaque.
+        splitScreenPrimary.setIsTranslucent(false);
+        splitScreenSecondary.setIsTranslucent(false);
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+        // Home stack should be visible if one of the halves of split-screen is translucent.
+        splitScreenPrimary.setIsTranslucent(true);
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack splitScreenSecondary2 = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        // First split-screen secondary shouldn't be visible behind another opaque split-split
+        // secondary.
+        splitScreenSecondary2.setIsTranslucent(false);
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // First split-screen secondary should be visible behind another translucent split-split
+        // secondary.
+        splitScreenSecondary2.setIsTranslucent(true);
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        final TestActivityStack assistantStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, true /* onTop */);
+
+        // Split-screen stacks shouldn't be visible behind an opaque fullscreen stack.
+        assistantStack.setIsTranslucent(false);
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+
+        // Split-screen stacks should be visible behind a translucent fullscreen stack.
+        assistantStack.setIsTranslucent(true);
+        assertTrue(assistantStack.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
+        assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
+    }
+
+    @Test
+    public void testShouldBeVisible_Finishing() throws Exception {
+        final ActivityDisplay display = mService.mStackSupervisor.getDefaultDisplay();
+        final TestActivityStack homeStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */);
+        final TestActivityStack translucentStack = createStackForShouldBeVisibleTest(display,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        translucentStack.setIsTranslucent(true);
+
+        assertTrue(homeStack.shouldBeVisible(null /* starting */));
+        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+
+        final ActivityRecord topRunningHomeActivity = homeStack.topRunningActivityLocked();
+        topRunningHomeActivity.finishing = true;
+        final ActivityRecord topRunningTranslucentActivity =
+                translucentStack.topRunningActivityLocked();
+        topRunningTranslucentActivity.finishing = true;
+
+        // Home shouldn't be visible since its activity is marked as finishing and it isn't the top
+        // of the stack list.
+        assertFalse(homeStack.shouldBeVisible(null /* starting */));
+        // Home should be visible if we are starting an activity within it.
+        assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */));
+        // The translucent stack should be visible since it is the top of the stack list even though
+        // it has its activity marked as finishing.
+        assertTrue(translucentStack.shouldBeVisible(null /* starting */));
+    }
+
+    private <T extends ActivityStack> T createStackForShouldBeVisibleTest(
+            ActivityDisplay display, int windowingMode, int activityType, boolean onTop) {
+        final T stack = display.createStack(windowingMode, activityType, onTop);
+        final ActivityRecord r = new ActivityBuilder(mService).setUid(0).setStack(stack)
+                .setCreateTask(true).build();
+        return stack;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
new file mode 100644
index 0000000..5b1e4b7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -0,0 +1,95 @@
+/*
+ * 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.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static com.android.server.am.ActivityManagerService.ANIMATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.times;
+
+/**
+ * Tests for the {@link ActivityStack} class.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.ActivityStarterTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityStarterTests extends ActivityTestsBase {
+    private ActivityManagerService mService;
+    private ActivityStarter mStarter;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mStarter = new ActivityStarter(mService);
+    }
+
+    @Test
+    public void testUpdateLaunchBounds() throws Exception {
+        // When in a non-resizeable stack, the task bounds should be updated.
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+                .build();
+        final Rect bounds = new Rect(10, 10, 100, 100);
+
+        mStarter.updateBounds(task, bounds);
+        assertEquals(task.mBounds, bounds);
+        assertEquals(task.getStack().mBounds, null);
+
+        // When in a resizeable stack, the stack bounds should be updated as well.
+        final TaskRecord task2 = new TaskBuilder(mService.mStackSupervisor)
+                .setStack(mService.mStackSupervisor.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */))
+                .build();
+        assertTrue(task2.getStack() instanceof PinnedActivityStack);
+        mStarter.updateBounds(task2, bounds);
+
+        verify(mService, times(1)).resizeStack(eq(task2.getStack().mStackId),
+                eq(bounds), anyBoolean(), anyBoolean(), anyBoolean(), anyInt());
+
+        // In the case of no animation, the stack and task bounds should be set immediately.
+        if (!ANIMATE) {
+            assertEquals(task2.getStack().mBounds, bounds);
+            assertEquals(task2.mBounds, bounds);
+        } else {
+            assertEquals(task2.mBounds, null);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 0cf1df8..198cc6d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -16,15 +16,18 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.spy;
 
 import org.mockito.invocation.InvocationOnMock;
 
-import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -32,13 +35,14 @@
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.support.test.InstrumentationRegistry;
 import com.android.server.AttributeCache;
 import com.android.server.wm.AppWindowContainerController;
+import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.StackWindowController;
-
 import com.android.server.wm.TaskWindowContainerController;
 import com.android.server.wm.WindowManagerService;
 import com.android.server.wm.WindowTestUtils;
@@ -50,16 +54,20 @@
  * A base class to handle common operations in activity related unit tests.
  */
 public class ActivityTestsBase {
+    private static boolean sOneTimeSetupDone = false;
+
     private final Context mContext = InstrumentationRegistry.getContext();
     private HandlerThread mHandlerThread;
 
-    // Grabbing an instance of {@link WindowManagerService} creates it if not present so this must
-    // be called at before any tests.
-    private final WindowManagerService mWms = WindowTestUtils.getWindowManagerService(mContext);
-
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
+        if (!sOneTimeSetupDone) {
+            sOneTimeSetupDone = true;
+
+            // Allows to mock package local classes and methods
+            System.setProperty("dexmaker.share_classloader", "true");
+            MockitoAnnotations.initMocks(this);
+        }
         mHandlerThread = new HandlerThread("ActivityTestsBaseThread");
         mHandlerThread.start();
     }
@@ -70,81 +78,180 @@
     }
 
     protected ActivityManagerService createActivityManagerService() {
-        final ActivityManagerService service = new TestActivityManagerService(mContext);
-        service.mWindowManager = WindowTestUtils.getMockWindowManagerService();
+        return setupActivityManagerService(new TestActivityManagerService(mContext));
+    }
+
+    protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
+        service = spy(service);
+        service.mWindowManager = prepareMockWindowManager();
         return service;
     }
 
-    protected static ActivityStack createActivityStack(ActivityManagerService service,
-            int stackId, int displayId, boolean onTop) {
-        if (service.mStackSupervisor instanceof TestActivityStackSupervisor) {
-            return ((TestActivityStackSupervisor) service.mStackSupervisor)
-                    .createTestStack(stackId, onTop);
+    /**
+     * Builder for creating new activities.
+     */
+    protected static class ActivityBuilder {
+        // An id appended to the end of the component name to make it unique
+        private static int sCurrentActivityId = 0;
+
+        // Default package name
+        private static final String DEFAULT_PACKAGE = "com.foo";
+
+        // Default base activity name
+        private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity";
+
+        private final ActivityManagerService mService;
+
+        private ComponentName mComponent;
+        private TaskRecord mTaskRecord;
+        private int mUid;
+        private boolean mCreateTask;
+        private ActivityStack mStack;
+
+        ActivityBuilder(ActivityManagerService service) {
+            mService = service;
         }
 
-        return null;
-    }
-
-    protected static ActivityRecord createActivity(ActivityManagerService service,
-            ComponentName component, TaskRecord task) {
-        return createActivity(service, component, task, 0 /* userId */);
-    }
-
-    protected static ActivityRecord createActivity(ActivityManagerService service,
-            ComponentName component, TaskRecord task, int uid) {
-        Intent intent = new Intent();
-        intent.setComponent(component);
-        final ActivityInfo aInfo = new ActivityInfo();
-        aInfo.applicationInfo = new ApplicationInfo();
-        aInfo.applicationInfo.packageName = component.getPackageName();
-        aInfo.applicationInfo.uid = uid;
-        AttributeCache.init(service.mContext);
-        final ActivityRecord activity = new ActivityRecord(service, null /* caller */,
-                0 /* launchedFromPid */, 0, null, intent, null,
-                aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
-                0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
-                service.mStackSupervisor, null /* options */, null /* sourceRecord */);
-        activity.mWindowContainerController = mock(AppWindowContainerController.class);
-
-        if (task != null) {
-            task.addActivityToTop(activity);
+        ActivityBuilder setComponent(ComponentName component) {
+            mComponent = component;
+            return this;
         }
 
-        return activity;
+        ActivityBuilder setTask(TaskRecord task) {
+            mTaskRecord = task;
+            return this;
+        }
+
+        ActivityBuilder setStack(ActivityStack stack) {
+            mStack = stack;
+            return this;
+        }
+
+        ActivityBuilder setCreateTask(boolean createTask) {
+            mCreateTask = createTask;
+            return this;
+        }
+
+        ActivityBuilder setUid(int uid) {
+            mUid = uid;
+            return this;
+        }
+
+        ActivityRecord build() {
+            if (mComponent == null) {
+                final int id = sCurrentActivityId++;
+                mComponent = ComponentName.createRelative(DEFAULT_PACKAGE,
+                        DEFAULT_BASE_ACTIVITY_NAME + id);
+            }
+
+            if (mCreateTask) {
+                mTaskRecord = new TaskBuilder(mService.mStackSupervisor)
+                        .setComponent(mComponent)
+                        .setStack(mStack).build();
+            }
+
+            Intent intent = new Intent();
+            intent.setComponent(mComponent);
+            final ActivityInfo aInfo = new ActivityInfo();
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = mComponent.getPackageName();
+            aInfo.applicationInfo.uid = mUid;
+            AttributeCache.init(mService.mContext);
+            final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
+                    0 /* launchedFromPid */, 0, null, intent, null,
+                    aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
+                    0 /* reqCode */, false /*componentSpecified*/, false /* rootVoiceInteraction */,
+                    mService.mStackSupervisor, null /* options */, null /* sourceRecord */);
+            activity.mWindowContainerController = mock(AppWindowContainerController.class);
+
+            if (mTaskRecord != null) {
+                mTaskRecord.addActivityToTop(activity);
+            }
+
+            return activity;
+        }
     }
 
-    protected static TaskRecord createTask(ActivityManagerService service,
-            ComponentName component, int stackId) {
-        final ActivityInfo aInfo = new ActivityInfo();
-        aInfo.applicationInfo = new ApplicationInfo();
-        aInfo.applicationInfo.packageName = component.getPackageName();
+    /**
+     * Builder for creating new tasks.
+     */
+    protected static class TaskBuilder {
+        private final ActivityStackSupervisor mSupervisor;
 
-        Intent intent = new Intent();
-        intent.setComponent(component);
+        private ComponentName mComponent;
+        private String mPackage;
+        private int mFlags = 0;
+        private int mTaskId = 0;
 
-        final TaskRecord task = new TaskRecord(service, 0, aInfo, intent /*intent*/,
-                null /*_taskDescription*/);
-        final ActivityStack stack = service.mStackSupervisor.getStack(stackId,
-                true /*createStaticStackIfNeeded*/, true /*onTop*/);
-        service.mStackSupervisor.setFocusStackUnchecked("test", stack);
-        stack.addTask(task, true, "creating test task");
-        task.setStack(stack);
-        task.setWindowContainerController(mock(TaskWindowContainerController.class));
+        private ActivityStack mStack;
 
-        return task;
+        TaskBuilder(ActivityStackSupervisor supervisor) {
+            mSupervisor = supervisor;
+        }
+
+        TaskBuilder setComponent(ComponentName component) {
+            mComponent = component;
+            return this;
+        }
+
+        TaskBuilder setPackage(String packageName) {
+            mPackage = packageName;
+            return this;
+        }
+
+        TaskBuilder setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        TaskBuilder setTaskId(int taskId) {
+            mTaskId = taskId;
+            return this;
+        }
+
+        TaskBuilder setStack(ActivityStack stack) {
+            mStack = stack;
+            return this;
+        }
+
+        TaskRecord build() {
+            if (mStack == null) {
+                mStack = mSupervisor.getDefaultDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+            }
+
+            final ActivityInfo aInfo = new ActivityInfo();
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = mPackage;
+
+            Intent intent = new Intent();
+            intent.setComponent(mComponent);
+            intent.setFlags(mFlags);
+
+            final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
+                    intent /*intent*/, null /*_taskDescription*/);
+            mSupervisor.setFocusStackUnchecked("test", mStack);
+            mStack.addTask(task, true, "creating test task");
+            task.setStack(mStack);
+            task.setWindowContainerController(mock(TaskWindowContainerController.class));
+
+            return task;
+        }
     }
 
-
     /**
      * An {@link ActivityManagerService} subclass which provides a test
      * {@link ActivityStackSupervisor}.
      */
     protected static class TestActivityManagerService extends ActivityManagerService {
-        public TestActivityManagerService(Context context) {
+        TestActivityManagerService(Context context) {
             super(context);
             mSupportsMultiWindow = true;
             mSupportsMultiDisplay = true;
-            mWindowManager = WindowTestUtils.getWindowManagerService(context);
+            mSupportsSplitScreenMultiWindow = true;
+            mSupportsFreeformWindowManagement = true;
+            mSupportsPictureInPicture = true;
+            mWindowManager = WindowTestUtils.getMockWindowManagerService();
         }
 
         @Override
@@ -167,8 +274,16 @@
 
         public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
             super(service, looper);
+            mDisplayManager =
+                    (DisplayManager) mService.mContext.getSystemService(Context.DISPLAY_SERVICE);
             mWindowManager = prepareMockWindowManager();
-            mDisplay = new ActivityDisplay();
+            mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
+            attachDisplay(mDisplay);
+        }
+
+        @Override
+        ActivityDisplay getDefaultDisplay() {
+            return mDisplay;
         }
 
         // TODO: Use Mockito spy instead. Currently not possible due to TestActivityStackSupervisor
@@ -214,38 +329,6 @@
                 boolean preserveWindows) {
         }
 
-        <T extends ActivityStack> T createTestStack(int stackId, boolean onTop) {
-            return (T) createStack(stackId, mDisplay, onTop);
-        }
-
-        @Override
-        ActivityStack createStack(int stackId, ActivityDisplay display, boolean onTop) {
-            final RecentTasks recents =
-                    new RecentTasks(mService, mService.mStackSupervisor);
-            if (stackId == PINNED_STACK_ID) {
-                return new PinnedActivityStack(display, stackId, this, recents, onTop) {
-                    @Override
-                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
-                        return new Rect(50, 50, 100, 100);
-                    }
-                };
-            } else {
-                return new TestActivityStack(display, stackId, this, recents, onTop);
-            }
-        }
-
-        @Override
-        protected <T extends ActivityStack> T getStack(int stackId,
-                boolean createStaticStackIfNeeded, boolean createOnTop) {
-            final T stack = super.getStack(stackId, createStaticStackIfNeeded, createOnTop);
-
-            if (stack != null || !createStaticStackIfNeeded) {
-                return stack;
-            }
-
-            return createTestStack(stackId, createOnTop);
-        }
-
         // Always keep things awake
         @Override
         boolean hasAwakeDisplay() {
@@ -253,8 +336,39 @@
         }
     }
 
+    private static class TestActivityDisplay extends ActivityDisplay {
+
+        private final ActivityStackSupervisor mSupervisor;
+        TestActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
+            super(supervisor, displayId);
+            mSupervisor = supervisor;
+        }
+
+        @Override
+        <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+                int stackId, boolean onTop) {
+            if (windowingMode == WINDOWING_MODE_PINNED) {
+                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) {
+                    @Override
+                    Rect getDefaultPictureInPictureBounds(float aspectRatio) {
+                        return new Rect(50, 50, 100, 100);
+                    }
+
+                    @Override
+                    PinnedStackWindowController createStackWindowController(int displayId,
+                            boolean onTop, Rect outBounds) {
+                        return mock(PinnedStackWindowController.class);
+                    }
+                };
+            } else {
+                return (T) new TestActivityStack(
+                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
+            }
+        }
+    }
+
     private static WindowManagerService prepareMockWindowManager() {
-        final WindowManagerService service = mock(WindowManagerService.class);
+        final WindowManagerService service = WindowTestUtils.getMockWindowManagerService();
 
         doAnswer((InvocationOnMock invocationOnMock) -> {
             final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
@@ -267,22 +381,24 @@
         return service;
     }
 
-    protected interface ActivityStackReporter {
-        int onActivityRemovedFromStackInvocationCount();
-    }
-
     /**
-     * Override of {@link ActivityStack} that tracks test metrics, such as the number of times a
+     * Overrided of {@link ActivityStack} that tracks test metrics, such as the number of times a
      * method is called. Note that its functionality depends on the implementations of the
      * construction arguments.
      */
     protected static class TestActivityStack<T extends StackWindowController>
-            extends ActivityStack<T> implements ActivityStackReporter {
+            extends ActivityStack<T> {
         private int mOnActivityRemovedFromStackCount = 0;
         private T mContainerController;
-        TestActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-                ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
-            super(display, stackId, supervisor, recentTasks, onTop);
+
+        static final int IS_TRANSLUCENT_UNSET = 0;
+        static final int IS_TRANSLUCENT_FALSE = 1;
+        static final int IS_TRANSLUCENT_TRUE = 2;
+        private int mIsTranslucent = IS_TRANSLUCENT_UNSET;
+
+        TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+                int windowingMode, int activityType, boolean onTop) {
+            super(display, stackId, supervisor, windowingMode, activityType, onTop);
         }
 
         @Override
@@ -292,8 +408,7 @@
         }
 
         // Returns the number of times {@link #onActivityRemovedFromStack} has been called
-        @Override
-        public int onActivityRemovedFromStackInvocationCount() {
+        int onActivityRemovedFromStackInvocationCount() {
             return mOnActivityRemovedFromStackCount;
         }
 
@@ -307,37 +422,22 @@
         T getWindowContainerController() {
             return mContainerController;
         }
-    }
 
-
-    protected static class ActivityStackBuilder {
-        private boolean mOnTop = true;
-        private int mStackId = 0;
-        private int mDisplayId = 1;
-
-        private final ActivityManagerService mService;
-
-        public ActivityStackBuilder(ActivityManagerService ams) {
-            mService = ams;
+        void setIsTranslucent(boolean isTranslucent) {
+            mIsTranslucent = isTranslucent ? IS_TRANSLUCENT_TRUE : IS_TRANSLUCENT_FALSE;
         }
 
-        public ActivityStackBuilder setOnTop(boolean onTop) {
-            mOnTop = onTop;
-            return this;
-        }
-
-        public ActivityStackBuilder setStackId(int id) {
-            mStackId = id;
-            return this;
-        }
-
-        public ActivityStackBuilder setDisplayId(int id) {
-            mDisplayId = id;
-            return this;
-        }
-
-        public ActivityStack build() {
-            return createActivityStack(mService, mStackId, mDisplayId, mOnTop);
+        @Override
+        boolean isStackTranslucent(ActivityRecord starting) {
+            switch (mIsTranslucent) {
+                case IS_TRANSLUCENT_TRUE:
+                    return true;
+                case IS_TRANSLUCENT_FALSE:
+                    return false;
+                case IS_TRANSLUCENT_UNSET:
+                default:
+                    return super.isStackTranslucent(starting);
+            }
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
new file mode 100644
index 0000000..bec46db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.server.am;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+import android.view.IWindowManager;
+
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Note: Currently, we only support fetching the screenshot for the current application, so the
+ * screenshot checks are hardcoded accordingly.
+ *
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/AssistDataRequesterTest.java
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AssistDataRequesterTest extends ActivityTestsBase {
+
+    private static final String TAG = AssistDataRequesterTest.class.getSimpleName();
+
+    private static final boolean CURRENT_ACTIVITY_ASSIST_ALLOWED = true;
+    private static final boolean CALLER_ASSIST_STRUCTURE_ALLOWED = true;
+    private static final boolean CALLER_ASSIST_SCREENSHOT_ALLOWED = true;
+    private static final boolean FETCH_DATA = true;
+    private static final boolean FETCH_SCREENSHOTS = true;
+
+    private static final int TEST_UID = 0;
+    private static final String TEST_PACKAGE = "";
+
+    private Context mContext;
+    private AssistDataRequester mDataRequester;
+    private Callbacks mCallbacks;
+    private Object mCallbacksLock;
+    private Handler mHandler;
+    private IActivityManager mAm;
+    private IWindowManager mWm;
+    private AppOpsManager mAppOpsManager;
+
+    /**
+     * The requests to fetch assist data are done incrementally from the text thread, and we
+     * immediately post onto the main thread handler below, which would immediately make the
+     * callback and decrement the pending counts. In order to assert the pending counts, we defer
+     * the callbacks on the test-side until after we flip the gate, after which we can drain the
+     * main thread handler and make assertions on the actual callbacks
+     */
+    private CountDownLatch mGate;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mAm = mock(IActivityManager.class);
+        mWm = mock(IWindowManager.class);
+        mAppOpsManager = mock(AppOpsManager.class);
+        mContext =  InstrumentationRegistry.getContext();
+        mHandler = new Handler(Looper.getMainLooper());
+        mCallbacksLock = new Object();
+        mCallbacks = new Callbacks();
+        mDataRequester = new AssistDataRequester(mContext, mAm, mWm, mAppOpsManager, mCallbacks,
+                mCallbacksLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
+
+        // Gate the continuation of the assist data callbacks until we are ready within the tests
+        mGate = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    mDataRequester.onHandleAssistData(new Bundle());
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+            return true;
+        }).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(), anyBoolean(),
+                anyBoolean());
+        doAnswer(invocation -> {
+            mHandler.post(() -> {
+                try {
+                    mGate.await(10, TimeUnit.SECONDS);
+                    mDataRequester.onHandleAssistScreenshot(Bitmap.createBitmap(1, 1,
+                            ARGB_8888));
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Failed to wait", e);
+                }
+            });
+            return true;
+        }).when(mWm).requestAssistScreenshot(any());
+    }
+
+    private void setupMocks(boolean currentActivityAssistAllowed, boolean assistStructureAllowed,
+            boolean assistScreenshotAllowed) throws Exception {
+        doReturn(currentActivityAssistAllowed).when(mAm).isAssistDataAllowedOnCurrentActivity();
+        doReturn(assistStructureAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+                .checkOpNoThrow(eq(OP_ASSIST_STRUCTURE), anyInt(), anyString());
+        doReturn(assistScreenshotAllowed ? MODE_ALLOWED : MODE_ERRORED).when(mAppOpsManager)
+                .checkOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString());
+    }
+
+    @Test
+    public void testRequestData() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(5, 5, 1, 1);
+    }
+
+    @Test
+    public void testEmptyActivities_expectNoCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(0), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testCurrentAppDisallow_expectNoCallbacks() throws Exception {
+        setupMocks(!CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testProcessPendingData() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mCallbacks.canHandleReceivedData = false;
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertTrue(mDataRequester.getPendingDataCount() == 5);
+        assertTrue(mDataRequester.getPendingScreenshotCount() == 1);
+        mGate.countDown();
+        waitForIdle(mHandler);
+
+        // Callbacks still not ready to receive, but all pending data is received
+        assertTrue(mDataRequester.getPendingDataCount() == 0);
+        assertTrue(mDataRequester.getPendingScreenshotCount() == 0);
+        assertTrue(mCallbacks.receivedData.isEmpty());
+        assertTrue(mCallbacks.receivedScreenshots.isEmpty());
+
+        mCallbacks.canHandleReceivedData = true;
+        mDataRequester.processPendingAssistData();
+        assertTrue(mCallbacks.receivedData.size() == 5);
+        assertTrue(mCallbacks.receivedScreenshots.size() == 1);
+    }
+
+    @Test
+    public void testNoFetchData_expectNoCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), !FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(0, 0, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistStructure_expectNullDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, !CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        // Expect a single null data when the appops is denied
+        assertReceivedDataCount(0, 1, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistContextExtras_expectNullDataCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+        doReturn(false).when(mAm).requestAssistContextExtras(anyInt(), any(), any(), any(),
+                anyBoolean(), anyBoolean());
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        // Expect a single null data when requestAssistContextExtras() fails
+        assertReceivedDataCount(0, 1, 0, 0);
+    }
+
+    @Test
+    public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, !FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        assertReceivedDataCount(5, 5, 0, 0);
+    }
+
+    @Test
+    public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
+        setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
+                !CALLER_ASSIST_SCREENSHOT_ALLOWED);
+
+        mDataRequester.requestAssistData(createActivityList(5), FETCH_DATA, FETCH_SCREENSHOTS,
+                TEST_UID, TEST_PACKAGE);
+        // Expect a single null screenshot when the appops is denied
+        assertReceivedDataCount(5, 5, 0, 1);
+    }
+
+    private void assertReceivedDataCount(int numPendingData, int numReceivedData,
+            int numPendingScreenshots, int numReceivedScreenshots) throws Exception {
+        assertTrue("Expected " + numPendingData + " pending data, got "
+                        + mDataRequester.getPendingDataCount(),
+                mDataRequester.getPendingDataCount() == numPendingData);
+        assertTrue("Expected " + numPendingScreenshots + " pending screenshots, got "
+                        + mDataRequester.getPendingScreenshotCount(),
+                mDataRequester.getPendingScreenshotCount() == numPendingScreenshots);
+        mGate.countDown();
+        waitForIdle(mHandler);
+        assertTrue("Expected " + numReceivedData + " data, received "
+                        + mCallbacks.receivedData.size(),
+                mCallbacks.receivedData.size() == numReceivedData);
+        assertTrue("Expected " + numReceivedScreenshots + " screenshots, received "
+                        + mCallbacks.receivedScreenshots.size(),
+                mCallbacks.receivedScreenshots.size() == numReceivedScreenshots);
+    }
+
+    private List<IBinder> createActivityList(int size) {
+        ArrayList<IBinder> activities = new ArrayList<>();
+        for (int i = 0; i < size; i++) {
+            activities.add(mock(IBinder.class));
+        }
+        return activities;
+    }
+
+    public void waitForIdle(Handler h) throws Exception {
+        if (Looper.myLooper() == h.getLooper()) {
+            throw new RuntimeException("This method can not be called from the waiting looper");
+        }
+        CountDownLatch latch = new CountDownLatch(1);
+        h.post(() -> latch.countDown());
+        latch.await(2, TimeUnit.SECONDS);
+    }
+
+    private static class Callbacks implements AssistDataRequesterCallbacks {
+
+        boolean canHandleReceivedData = true;
+        ArrayList<Bundle> receivedData = new ArrayList<>();
+        ArrayList<Bitmap> receivedScreenshots = new ArrayList<>();
+
+        @Override
+        public boolean canHandleReceivedAssistDataLocked() {
+            return canHandleReceivedData;
+        }
+
+        @Override
+        public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+            receivedData.add(data);
+        }
+
+        @Override
+        public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+            receivedScreenshots.add(screenshot);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java
new file mode 100644
index 0000000..62fa764
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java
@@ -0,0 +1,143 @@
+/*
+ * 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.server.am;
+
+import android.app.ActivityOptions;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.doAnswer;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+/**
+ * Tests for exercising resizing bounds due to activity options.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.LaunchingActivityPositionerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingActivityPositionerTests extends ActivityTestsBase {
+    private LaunchingActivityPositioner mPositioner;
+    private ActivityManagerService mService;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+    private ActivityRecord mActivity;
+
+    private Rect mCurrent;
+    private Rect mResult;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mService = createActivityManagerService();
+        mPositioner = new LaunchingActivityPositioner(mService.mStackSupervisor);
+        mCurrent = new Rect();
+        mResult = new Rect();
+
+
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+        mActivity = new ActivityBuilder(mService).setTask(mTask).build();
+    }
+
+
+    @Test
+    public void testSkippedInvocations() throws Exception {
+        // No specified activity should be ignored
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                null /*activity*/, null /*source*/, null /*options*/, mCurrent, mResult));
+
+        // No specified activity options should be ignored
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, null /*options*/, mCurrent, mResult));
+
+        // launch bounds specified should be ignored.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // Non-resizeable records should be ignored
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+        assertFalse(mActivity.isResizeable());
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // make record resizeable
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+        assertTrue(mActivity.isResizeable());
+
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // Does not support freeform
+        mService.mSupportsFreeformWindowManagement = false;
+        assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        mService.mSupportsFreeformWindowManagement = true;
+        options.setLaunchBounds(new Rect());
+        assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
+
+        // Invalid bounds
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+        options.setLaunchBounds(new Rect(0, 0, -1, -1));
+        assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+
+        // Valid bounds should cause the positioner to be applied.
+        options.setLaunchBounds(new Rect(0, 0, 100, 100));
+        assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+    }
+
+    @Test
+    public void testBoundsExtraction() throws Exception {
+        // Make activity resizeable and enable freeform mode.
+        mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+        mService.mSupportsFreeformWindowManagement = true;
+
+        ActivityOptions options = ActivityOptions.makeBasic();
+        final Rect proposedBounds = new Rect(20, 30, 45, 40);
+        options.setLaunchBounds(proposedBounds);
+
+        assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/,
+                mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
+        assertEquals(mResult, proposedBounds);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java
new file mode 100644
index 0000000..0715174
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java
@@ -0,0 +1,194 @@
+/*
+ * 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.server.am;
+
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for exercising {@link LaunchingBoundsController}.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.LaunchingBoundsControllerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingBoundsControllerTests extends ActivityTestsBase {
+    private LaunchingBoundsController mController;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mController = new LaunchingBoundsController();
+    }
+
+    /**
+     * Makes sure positioners get values passed to controller.
+     */
+    @Test
+    public void testArgumentPropagation() {
+        final ActivityManagerService service = createActivityManagerService();
+        final LaunchingBoundsPositioner positioner = mock(LaunchingBoundsPositioner.class);
+        mController.registerPositioner(positioner);
+
+        final ActivityRecord record = new ActivityBuilder(service).build();
+        final ActivityRecord source = new ActivityBuilder(service).build();
+        final WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0);
+        final ActivityOptions options = mock(ActivityOptions.class);
+
+        mController.calculateBounds(record.getTask(), layout, record, source, options, new Rect());
+        verify(positioner, times(1)).onCalculateBounds(eq(record.getTask()), eq(layout), eq(record),
+                eq(source), eq(options), any(), any());
+    }
+
+    /**
+     * Ensures positioners further down the chain are not called when RESULT_DONE is returned.
+     */
+    @Test
+    public void testEarlyExit() {
+        final LaunchingBoundsPositioner ignoredPositioner = mock(LaunchingBoundsPositioner.class);
+        final LaunchingBoundsPositioner earlyExitPositioner =
+                (task, layout, activity, source, options, current, result) -> RESULT_DONE;
+
+        mController.registerPositioner(ignoredPositioner);
+        mController.registerPositioner(earlyExitPositioner);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new Rect());
+        verify(ignoredPositioner, never()).onCalculateBounds(any(), any(), any(), any(), any(),
+                any(), any());
+    }
+
+    /**
+     * Ensures that positioners are called in the correct order.
+     */
+    @Test
+    public void testRegistration() {
+        LaunchingBoundsPositioner earlyExitPositioner =
+                new InstrumentedPositioner(RESULT_DONE, new Rect());
+
+        final LaunchingBoundsPositioner firstPositioner = spy(earlyExitPositioner);
+
+        mController.registerPositioner(firstPositioner);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new Rect());
+        verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                any(), any());
+
+        final LaunchingBoundsPositioner secondPositioner = spy(earlyExitPositioner);
+
+        mController.registerPositioner(secondPositioner);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new Rect());
+        verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                any(), any());
+        verify(secondPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                any(), any());
+    }
+
+    /**
+     * Makes sure positioners further down the registration chain are called.
+     */
+    @Test
+    public void testPassThrough() {
+        final LaunchingBoundsPositioner positioner1 = mock(LaunchingBoundsPositioner.class);
+        final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE,
+                new Rect (0, 0, 30, 20));
+
+        mController.registerPositioner(positioner1);
+        mController.registerPositioner(positioner2);
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, new Rect());
+
+        verify(positioner1, times(1)).onCalculateBounds(any(), any(), any(), any(), any(),
+                eq(positioner2.getLaunchBounds()), any());
+    }
+
+    /**
+     * Ensures skipped results are not propagated.
+     */
+    @Test
+    public void testSkip() {
+        final InstrumentedPositioner positioner1 =
+                new InstrumentedPositioner(RESULT_SKIP, new Rect(0, 0, 10, 10));
+
+
+        final InstrumentedPositioner positioner2 =
+                new InstrumentedPositioner(RESULT_CONTINUE, new Rect(0, 0, 20, 30));
+
+        mController.registerPositioner(positioner1);
+        mController.registerPositioner(positioner2);
+
+        final Rect resultBounds = new Rect();
+
+        mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/,
+                null /*source*/, null /*options*/, resultBounds);
+
+        assertEquals(resultBounds, positioner2.getLaunchBounds());
+    }
+
+    public static class InstrumentedPositioner implements LaunchingBoundsPositioner {
+        private int mReturnVal;
+        private Rect mBounds;
+        InstrumentedPositioner(int returnVal, Rect bounds) {
+            mReturnVal = returnVal;
+            mBounds = bounds;
+        }
+
+        @Override
+        public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout,
+                ActivityRecord activity, ActivityRecord source,
+                ActivityOptions options, Rect current, Rect result) {
+            result.set(mBounds);
+            return mReturnVal;
+        }
+
+        Rect getLaunchBounds() {
+            return mBounds;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
new file mode 100644
index 0000000..01e2da6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java
@@ -0,0 +1,251 @@
+/*
+ * 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.server.am;
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo.WindowLayout;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import android.view.Gravity;
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.invocation.InvocationOnMock;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
+import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.doAnswer;
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * Tests for exercising resizing bounds.
+ *
+ * Build/Install/Run:
+ *  bit FrameworksServicesTests:com.android.server.am.LaunchingTaskPositionerTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchingTaskPositionerTests extends ActivityTestsBase {
+    private final static int STACK_WIDTH = 100;
+    private final static int STACK_HEIGHT = 200;
+
+    private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT);
+
+    private ActivityManagerService mService;
+    private ActivityStack mStack;
+    private TaskRecord mTask;
+
+    private LaunchingTaskPositioner mPositioner;
+
+    private Rect mCurrent;
+    private Rect mResult;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mStack.requestResize(STACK_BOUNDS);
+
+        // We must create the task after resizing to make sure it does not inherit the stack
+        // dimensions on resize.
+        mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+
+        mPositioner = new LaunchingTaskPositioner();
+
+        mResult = new Rect();
+        mCurrent = new Rect();
+    }
+
+    /**
+     * Ensures that the setup bounds are set as expected with the stack bounds set and the task
+     * bounds still {@code null}.
+     * @throws Exception
+     */
+    @Test
+    public void testInitialBounds() throws Exception {
+        assertEquals(mStack.mBounds, STACK_BOUNDS);
+        assertEquals(mTask.mBounds, null);
+    }
+
+    /**
+     * Ensures that a task positioned with no {@link WindowLayout} receives the default launch
+     * position.
+     * @throws Exception
+     */
+    @Test
+    public void testLaunchNoWindowLayout() throws Exception {
+        assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, null /*layout*/,
+                null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult));
+        assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult);
+    }
+
+    /**
+     * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch
+     * position.
+     * @throws Exception
+     */
+    @Test
+    public void testlaunchEmptyWindowLayout() throws Exception {
+        assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask,
+                new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/,
+                null /*source*/, null /*options*/, mCurrent, mResult));
+        assertEquals(mResult, getDefaultBounds(Gravity.NO_GRAVITY));
+    }
+
+    /**
+     * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned
+     * according to specification.
+     * @throws Exception
+     */
+    @Test
+    public void testlaunchWindowLayoutGravity() throws Exception {
+        // Unspecified gravity should be ignored
+        testGravity(Gravity.NO_GRAVITY);
+
+        // Unsupported gravity should be ignored
+        testGravity(Gravity.LEFT);
+        testGravity(Gravity.RIGHT);
+
+        // Test defaults for vertical gravities
+        testGravity(Gravity.TOP);
+        testGravity(Gravity.BOTTOM);
+
+        // Test corners
+        testGravity(Gravity.TOP | Gravity.LEFT);
+        testGravity(Gravity.TOP | Gravity.RIGHT);
+        testGravity(Gravity.BOTTOM | Gravity.LEFT);
+        testGravity(Gravity.BOTTOM | Gravity.RIGHT);
+    }
+
+    private void testGravity(int gravity) {
+        try {
+            assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask,
+                    new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/,
+                    null /*source*/, null /*options*/, mCurrent, mResult));
+            assertEquals(mResult, getDefaultBounds(gravity));
+        } finally {
+            mCurrent.setEmpty();
+            mResult.setEmpty();
+        }
+    }
+
+    /**
+     * Ensures that a task which causes a conflict with another task when positioned is adjusted as
+     * expected.
+     * @throws Exception
+     */
+    @Test
+    public void testLaunchWindowCenterConflict() throws Exception {
+        testConflict(Gravity.NO_GRAVITY);
+        testConflict(Gravity.TOP);
+        testConflict(Gravity.BOTTOM);
+        testConflict(Gravity.TOP | Gravity.LEFT);
+        testConflict(Gravity.TOP | Gravity.RIGHT);
+        testConflict(Gravity.BOTTOM | Gravity.LEFT);
+        testConflict(Gravity.BOTTOM | Gravity.RIGHT);
+    }
+
+    private void testConflict(int gravity) {
+        final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
+
+        // layout first task
+        mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(mTask, layout);
+
+        // Second task will be laid out on top of the first so starting bounds is the same.
+        final Rect expectedBounds = new Rect(mTask.mBounds);
+
+        ActivityRecord activity = null;
+        TaskRecord secondTask = null;
+
+        // wrap with try/finally to ensure cleanup of activity/stack.
+        try {
+            // empty tasks are ignored in conflicts
+            activity = new ActivityBuilder(mService).setTask(mTask).build();
+
+            // Create secondary task
+            secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+
+            // layout second task
+            assertEquals(RESULT_CONTINUE,
+                    mPositioner.onCalculateBounds(secondTask, layout, null /*activity*/,
+                            null /*source*/, null /*options*/, mCurrent, mResult));
+
+            if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
+                    || (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
+                    == (Gravity.BOTTOM | Gravity.RIGHT)) {
+                expectedBounds.offset(-LaunchingTaskPositioner.getHorizontalStep(mStack.mBounds),
+                        0);
+            } else if ((gravity & Gravity.TOP) == Gravity.TOP
+                    || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+                expectedBounds.offset(LaunchingTaskPositioner.getHorizontalStep(mStack.mBounds), 0);
+            } else {
+                expectedBounds.offset(LaunchingTaskPositioner.getHorizontalStep(mStack.mBounds),
+                        LaunchingTaskPositioner.getVerticalStep(mStack.mBounds));
+            }
+
+            assertEquals(mResult, expectedBounds);
+        } finally {
+            // Remove task and activity to prevent influencing future tests
+            if (activity != null) {
+                mTask.removeActivity(activity);
+            }
+
+            if (secondTask != null) {
+                mStack.removeTask(secondTask, "cleanup", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
+            }
+        }
+    }
+
+    private Rect getDefaultBounds(int gravity) {
+        final Rect bounds = new Rect();
+        bounds.set(mStack.mBounds);
+
+        final int verticalInset = LaunchingTaskPositioner.getFreeformStartTop(mStack.mBounds);
+        final int horizontalInset = LaunchingTaskPositioner.getFreeformStartLeft(mStack.mBounds);
+
+        bounds.inset(horizontalInset, verticalInset);
+
+        if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)) {
+            bounds.offsetTo(horizontalInset * 2, 0);
+        } else if ((gravity & Gravity.TOP) == Gravity.TOP) {
+            bounds.offsetTo(0, 0);
+        } else if ((gravity & (Gravity.BOTTOM | Gravity.RIGHT))
+                == (Gravity.BOTTOM | Gravity.RIGHT)) {
+            bounds.offsetTo(horizontalInset * 2, verticalInset * 2);
+        } else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+            bounds.offsetTo(0, verticalInset * 2);
+        }
+
+        return bounds;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index f9d7f9d4..1adfb67 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -19,6 +19,16 @@
 import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.StatusBarManager.DISABLE2_MASK;
+import static android.app.StatusBarManager.DISABLE2_NONE;
+import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
+import static android.app.StatusBarManager.DISABLE_HOME;
+import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
+import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.os.Process.SYSTEM_UID;
 
 import static com.android.server.am.LockTaskController.STATUS_BAR_MASK_LOCKED;
@@ -29,6 +39,7 @@
 import static org.mockito.Mockito.*;
 
 import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -42,6 +53,7 @@
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
+import android.util.Pair;
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
@@ -136,10 +148,10 @@
         // THEN the lock task mode state should be LOCKED
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
         // THEN the task should be locked
-        assertTrue(mLockTaskController.checkLockedTask(tr));
+        assertTrue(mLockTaskController.isTaskLocked(tr));
 
         // THEN lock task mode should be started
-        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
     }
 
     @Test
@@ -155,11 +167,11 @@
         // THEN the lock task mode state should be LOCKED
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
         // THEN neither of the tasks should be able to move to back of stack
-        assertTrue(mLockTaskController.checkLockedTask(tr1));
-        assertTrue(mLockTaskController.checkLockedTask(tr2));
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        assertTrue(mLockTaskController.isTaskLocked(tr2));
 
         // THEN lock task mode should be started
-        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
     }
 
     @Test
@@ -185,10 +197,12 @@
         // THEN the lock task mode state should be PINNED
         assertEquals(LOCK_TASK_MODE_PINNED, mLockTaskController.getLockTaskModeState());
         // THEN the task should be locked
-        assertTrue(mLockTaskController.checkLockedTask(tr));
+        assertTrue(mLockTaskController.isTaskLocked(tr));
 
         // THEN lock task mode should be started
-        verifyLockTaskStarted(STATUS_BAR_MASK_PINNED);
+        verifyLockTaskStarted(STATUS_BAR_MASK_PINNED, DISABLE2_NONE);
+        // THEN screen pinning toast should be shown
+        verify(mLockTaskNotify).showPinningStartToast();
     }
 
     @Test
@@ -224,39 +238,37 @@
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN the same caller calls stopLockTaskMode
-        mLockTaskController.stopLockTaskMode(false, TEST_UID);
+        mLockTaskController.stopLockTaskMode(tr, false, TEST_UID);
 
         // THEN the lock task mode should be NONE
         assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
         // THEN the task should no longer be locked
-        assertFalse(mLockTaskController.checkLockedTask(tr));
+        assertFalse(mLockTaskController.isTaskLocked(tr));
         // THEN lock task mode should have been finished
         verifyLockTaskStopped(times(1));
     }
 
     @Test(expected = SecurityException.class)
-    public void testStopLockTaskMode_DifferentCaller() throws Exception {
+    public void testStopLockTaskMode_differentCaller() throws Exception {
         // GIVEN one task record with whitelisted auth that is in lock task mode
         TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN a different caller calls stopLockTaskMode
-        mLockTaskController.stopLockTaskMode(false, TEST_UID + 1);
+        mLockTaskController.stopLockTaskMode(tr, false, TEST_UID + 1);
 
         // THEN security exception should be thrown, because different caller tried to unlock
     }
 
     @Test
-    public void testStopLockTaskMode_SystemCaller() throws Exception {
+    public void testStopLockTaskMode_systemCaller() throws Exception {
         // GIVEN one task record with whitelisted auth that is in lock task mode
         TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN system calls stopLockTaskMode
-        mLockTaskController.stopLockTaskMode(true, SYSTEM_UID);
+        mLockTaskController.stopLockTaskMode(tr, true, SYSTEM_UID);
 
-        // THEN a lock tash toast should be shown
-        verify(mLockTaskNotify).showToast(LOCK_TASK_MODE_LOCKED);
         // THEN lock task mode should still be active
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
     }
@@ -270,19 +282,40 @@
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
         // WHEN calling stopLockTaskMode
-        mLockTaskController.stopLockTaskMode(false, TEST_UID);
+        mLockTaskController.stopLockTaskMode(tr2, false, TEST_UID);
 
         // THEN the lock task mode should still be active
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
         // THEN the first task should still be locked
-        assertTrue(mLockTaskController.checkLockedTask(tr1));
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
         // THEN the top task should no longer be locked
-        assertFalse(mLockTaskController.checkLockedTask(tr2));
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
         // THEN lock task mode should not have been finished
         verifyLockTaskStopped(never());
     }
 
     @Test
+    public void testStopLockTaskMode_rootTask() throws Exception {
+        // GIVEN two task records with whitelisted auth that is in lock task mode
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+        // WHEN calling stopLockTaskMode on the root task
+        mLockTaskController.stopLockTaskMode(tr1, false, TEST_UID);
+
+        // THEN the lock task mode should be inactive
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        // THEN the first task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr1));
+        // THEN the top task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
+        // THEN lock task mode should be finished
+        verifyLockTaskStopped(times(1));
+    }
+
+    @Test
     public void testStopLockTaskMode_pinned() throws Exception {
         // GIVEN one task records that is in pinned mode
         TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_PINNABLE);
@@ -291,17 +324,43 @@
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1);
 
+        // reset invocation counter
+        reset(mStatusBarService);
+
         // WHEN calling stopLockTask
-        mLockTaskController.stopLockTaskMode(true, SYSTEM_UID);
+        mLockTaskController.stopLockTaskMode(null, true, SYSTEM_UID);
 
         // THEN the lock task mode should no longer be active
         assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
         // THEN the task should no longer be locked
-        assertFalse(mLockTaskController.checkLockedTask(tr));
+        assertFalse(mLockTaskController.isTaskLocked(tr));
         // THEN lock task mode should have been finished
         verifyLockTaskStopped(times(1));
         // THEN the keyguard should be shown
         verify(mLockPatternUtils).requireCredentialEntry(UserHandle.USER_ALL);
+        // THEN screen pinning toast should be shown
+        verify(mLockTaskNotify).showPinningExitToast();
+    }
+
+    @Test
+    public void testClearLockedTasks() throws Exception {
+        // GIVEN two task records with whitelisted auth that is in lock task mode
+        TaskRecord tr1 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        TaskRecord tr2 = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
+        mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
+
+        // WHEN calling stopLockTaskMode on the root task
+        mLockTaskController.clearLockedTasks("testClearLockedTasks");
+
+        // THEN the lock task mode should be inactive
+        assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
+        // THEN the first task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr1));
+        // THEN the top task should no longer be locked
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
+        // THEN lock task mode should be finished
+        verifyLockTaskStopped(times(1));
     }
 
     @Test
@@ -350,9 +409,9 @@
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
-        assertTrue(mLockTaskController.checkLockedTask(tr1));
-        assertTrue(mLockTaskController.checkLockedTask(tr2));
-        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        assertTrue(mLockTaskController.isTaskLocked(tr2));
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
 
         // WHEN removing one package from whitelist
         whitelist = new String[] {TEST_PACKAGE_NAME};
@@ -360,11 +419,11 @@
 
         // THEN the task running that package should be stopped
         verify(tr2).performClearTaskLocked();
-        assertFalse(mLockTaskController.checkLockedTask(tr2));
+        assertFalse(mLockTaskController.isTaskLocked(tr2));
         // THEN the other task should remain locked
         assertEquals(LOCK_TASK_MODE_LOCKED, mLockTaskController.getLockTaskModeState());
-        assertTrue(mLockTaskController.checkLockedTask(tr1));
-        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED);
+        assertTrue(mLockTaskController.isTaskLocked(tr1));
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
 
         // WHEN removing the last package from whitelist
         whitelist = new String[] {};
@@ -372,11 +431,136 @@
 
         // THEN the last task should be cleared, and the system should quit LockTask mode
         verify(tr1).performClearTaskLocked();
-        assertFalse(mLockTaskController.checkLockedTask(tr1));
+        assertFalse(mLockTaskController.isTaskLocked(tr1));
         assertEquals(LOCK_TASK_MODE_NONE, mLockTaskController.getLockTaskModeState());
         verifyLockTaskStopped(times(1));
     }
 
+    @Test
+    public void testUpdateLockTaskFeatures() throws Exception {
+        // GIVEN a locked task
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN lock task mode should be started with default status bar masks
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN home button is enabled for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_HOME);
+
+        // THEN status bar should be updated to reflect this change
+        int expectedFlags = STATUS_BAR_MASK_LOCKED
+                & ~DISABLE_HOME;
+        int expectedFlags2 = DISABLE2_MASK;
+        verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
+                eq(mContext.getPackageName()));
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN notifications are enabled for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NOTIFICATIONS);
+
+        // THEN status bar should be updated to reflect this change
+        expectedFlags = STATUS_BAR_MASK_LOCKED
+                & ~DISABLE_NOTIFICATION_ICONS
+                & ~DISABLE_NOTIFICATION_ALERTS;
+        expectedFlags2 = DISABLE2_MASK
+                & ~DISABLE2_NOTIFICATION_SHADE;
+        verify(mStatusBarService).disable(eq(expectedFlags), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService).disable2(eq(expectedFlags2), any(IBinder.class),
+                eq(mContext.getPackageName()));
+    }
+
+    @Test
+    public void testUpdateLockTaskFeatures_differentUser() throws Exception {
+        // GIVEN a locked task
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN lock task mode should be started with default status bar masks
+        verifyLockTaskStarted(STATUS_BAR_MASK_LOCKED, DISABLE2_MASK);
+
+        // reset invocation counter
+        reset(mStatusBarService);
+
+        // WHEN home button is enabled for lock task mode for another user
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID + 1, LOCK_TASK_FEATURE_HOME);
+
+        // THEN status bar shouldn't change
+        verify(mStatusBarService, never()).disable(anyInt(), any(IBinder.class),
+                eq(mContext.getPackageName()));
+        verify(mStatusBarService, never()).disable2(anyInt(), any(IBinder.class),
+                eq(mContext.getPackageName()));
+    }
+
+    @Test
+    public void testUpdateLockTaskFeatures_keyguard() throws Exception {
+        // GIVEN a locked task
+        TaskRecord tr = getTaskRecord(TaskRecord.LOCK_TASK_AUTH_WHITELISTED);
+        mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+        // THEN keyguard should be disabled
+        verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
+
+        // WHEN keyguard is enabled for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_KEYGUARD);
+
+        // THEN keyguard should be enabled
+        verify(mWindowManager).reenableKeyguard(any(IBinder.class));
+
+        // WHEN keyguard is disabled again for lock task mode
+        mLockTaskController.updateLockTaskFeatures(TEST_USER_ID, LOCK_TASK_FEATURE_NONE);
+
+        // THEN keyguard should be disabled
+        verify(mWindowManager, times(2)).disableKeyguard(any(IBinder.class), anyString());
+    }
+
+    @Test
+    public void testGetStatusBarDisableFlags() {
+        // Note that we don't enumerate all StatusBarManager flags, but only choose a subset to test
+
+        // WHEN nothing is enabled
+        Pair<Integer, Integer> flags = mLockTaskController.getStatusBarDisableFlags(
+                LOCK_TASK_FEATURE_NONE);
+        // THEN unsupported feature flags should still be untouched
+        assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+        // THEN everything else should be disabled
+        assertTrue((StatusBarManager.DISABLE_CLOCK & flags.first) != 0);
+        assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
+
+        // WHEN only home button is enabled
+        flags = mLockTaskController.getStatusBarDisableFlags(
+                LOCK_TASK_FEATURE_HOME);
+        // THEN unsupported feature flags should still be untouched
+        assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+        // THEN home button should indeed be enabled
+        assertTrue((StatusBarManager.DISABLE_HOME & flags.first) == 0);
+        // THEN other feature flags should remain disabled
+        assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) != 0);
+
+        // WHEN only global actions menu and notifications are enabled
+        flags = mLockTaskController.getStatusBarDisableFlags(
+                DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
+                        | DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS);
+        // THEN unsupported feature flags should still be untouched
+        assertTrue((~STATUS_BAR_MASK_LOCKED & flags.first) == 0);
+        // THEN notifications should be enabled
+        assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ICONS & flags.first) == 0);
+        assertTrue((StatusBarManager.DISABLE_NOTIFICATION_ALERTS & flags.first) == 0);
+        assertTrue((StatusBarManager.DISABLE2_NOTIFICATION_SHADE & flags.second) == 0);
+        // THEN global actions should be enabled
+        assertTrue((StatusBarManager.DISABLE2_GLOBAL_ACTIONS & flags.second) == 0);
+        // THEN quick settings should still be disabled
+        assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
+    }
+
     private TaskRecord getTaskRecord(int lockTaskAuth) {
         return getTaskRecord(TEST_PACKAGE_NAME, lockTaskAuth);
     }
@@ -409,12 +593,14 @@
         return tr;
     }
 
-    private void verifyLockTaskStarted(int statusBarMask) throws Exception {
+    private void verifyLockTaskStarted(int statusBarMask, int statusBarMask2) throws Exception {
         // THEN the keyguard should have been disabled
         verify(mWindowManager).disableKeyguard(any(IBinder.class), anyString());
         // THEN the status bar should have been disabled
         verify(mStatusBarService).disable(eq(statusBarMask), any(IBinder.class),
                 eq(mContext.getPackageName()));
+        verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class),
+                eq(mContext.getPackageName()));
         // THEN the DO/PO should be informed about the operation
         verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME,
                 TEST_USER_ID);
@@ -426,6 +612,8 @@
         // THEN the status bar should have been disabled
         verify(mStatusBarService, mode).disable(eq(StatusBarManager.DISABLE_NONE),
                 any(IBinder.class), eq(mContext.getPackageName()));
+        verify(mStatusBarService, mode).disable2(eq(StatusBarManager.DISABLE2_NONE),
+                any(IBinder.class), eq(mContext.getPackageName()));
         // THEN the DO/PO should be informed about the operation
         verify(mDevicePolicyManager, mode).notifyLockTaskModeChanged(false, null, TEST_USER_ID);
     }
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
new file mode 100644
index 0000000..f5ea60f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -0,0 +1,647 @@
+/*
+ * 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.server.am;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+
+import static java.lang.Integer.MAX_VALUE;
+
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.MutableLong;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.server.am.RecentTasks.Callbacks;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RecentTasksTest extends ActivityTestsBase {
+    private static final int TEST_USER_0_ID = 0;
+    private static final int TEST_USER_1_ID = 10;
+    private static final int TEST_QUIET_USER_ID = 20;
+    private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
+    private static final UserInfo QUIET_USER_INFO = new UserInfo();
+    private static final ComponentName MY_COMPONENT = new ComponentName(
+            RecentTasksTest.class.getPackage().getName(), RecentTasksTest.class.getName());
+    private static int LAST_TASK_ID = 1;
+    private static int INVALID_STACK_ID = 999;
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private ActivityManagerService mService;
+    private ActivityStack mStack;
+    private TestTaskPersister mTaskPersister;
+    private RecentTasks mRecentTasks;
+    private RunningTasks mRunningTasks;
+
+    private static ArrayList<TaskRecord> mTasks = new ArrayList<>();
+    private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>();
+
+    private CallbacksRecorder mCallbacksRecorder;
+
+    class TestUserController extends UserController {
+        TestUserController(ActivityManagerService service) {
+            super(service);
+        }
+
+        @Override
+        int[] getCurrentProfileIds() {
+            return new int[] { TEST_USER_0_ID, TEST_QUIET_USER_ID };
+        }
+
+        @Override
+        Set<Integer> getProfileIds(int userId) {
+            Set<Integer> profileIds = new HashSet<>();
+            profileIds.add(TEST_USER_0_ID);
+            profileIds.add(TEST_QUIET_USER_ID);
+            return profileIds;
+        }
+
+        @Override
+        UserInfo getUserInfo(int userId) {
+            switch (userId) {
+                case TEST_USER_0_ID:
+                case TEST_USER_1_ID:
+                    return DEFAULT_USER_INFO;
+                case TEST_QUIET_USER_ID:
+                    return QUIET_USER_INFO;
+            }
+            return null;
+        }
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
+        mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
+        mRecentTasks = mService.getRecentTasks();
+        mRecentTasks.loadParametersFromResources(mContext.getResources());
+        mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        mCallbacksRecorder = new CallbacksRecorder();
+        mRecentTasks.registerCallback(mCallbacksRecorder);
+        QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
+
+        mTasks.add(createTask(".Task1"));
+        mTasks.add(createTask(".Task2"));
+        mTasks.add(createTask(".Task3"));
+        mTasks.add(createTask(".Task4"));
+        mTasks.add(createTask(".Task5"));
+
+        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */));
+        mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */));
+    }
+
+    @Test
+    public void testCallbacks() throws Exception {
+        // Add some tasks
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        assertTrue(mCallbacksRecorder.added.contains(mTasks.get(0))
+                && mCallbacksRecorder.added.contains(mTasks.get(1)));
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+        mCallbacksRecorder.clear();
+
+        // Remove some tasks
+        mRecentTasks.remove(mTasks.get(0));
+        mRecentTasks.remove(mTasks.get(1));
+        assertTrue(mCallbacksRecorder.added.isEmpty());
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(0)));
+        assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(1)));
+        mCallbacksRecorder.clear();
+
+        // Add a task which will trigger the trimming of another
+        TaskRecord documentTask1 = createDocumentTask(".DocumentTask1", null /* affinity */);
+        documentTask1.maxRecents = 1;
+        TaskRecord documentTask2 = createDocumentTask(".DocumentTask1", null /* affinity */);
+        mRecentTasks.add(documentTask1);
+        mRecentTasks.add(documentTask2);
+        assertTrue(mCallbacksRecorder.added.contains(documentTask1));
+        assertTrue(mCallbacksRecorder.added.contains(documentTask2));
+        assertTrue(mCallbacksRecorder.trimmed.contains(documentTask1));
+        assertTrue(mCallbacksRecorder.removed.contains(documentTask1));
+        mCallbacksRecorder.clear();
+
+        // Remove the callback, ensure we don't get any calls
+        mRecentTasks.unregisterCallback(mCallbacksRecorder);
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.remove(mTasks.get(0));
+        assertTrue(mCallbacksRecorder.added.isEmpty());
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testUsersTasks() throws Exception {
+        // Setup some tasks for the users
+        mTaskPersister.userTaskIdsOverride = new SparseBooleanArray();
+        mTaskPersister.userTaskIdsOverride.put(1, true);
+        mTaskPersister.userTaskIdsOverride.put(2, true);
+        mTaskPersister.userTasksOverride = new ArrayList<>();
+        mTaskPersister.userTasksOverride.add(createTask(".UserTask1"));
+        mTaskPersister.userTasksOverride.add(createTask(".UserTask2"));
+
+        // Assert no user tasks are initially loaded
+        assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0);
+
+        // Load user 0 tasks
+        mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID);
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
+
+        // Load user 1 tasks
+        mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID);
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_1_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_1_ID));
+
+        // Unload user 1 tasks
+        mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_1_ID);
+        assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
+        assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
+        assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
+
+        // Unload user 0 tasks
+        mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID);
+        assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
+        assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
+    }
+
+    @Test
+    public void testOrderedIteration() throws Exception {
+        MutableLong prevLastActiveTime = new MutableLong(0);
+        final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks();
+        for (int i = 0; i < tasks.size(); i++) {
+            final TaskRecord task = tasks.get(i);
+            assertTrue(task.lastActiveTime >= prevLastActiveTime.value);
+            prevLastActiveTime.value = task.lastActiveTime;
+        }
+    }
+
+    @Test
+    public void testTrimToGlobalMaxNumRecents() throws Exception {
+        // Limit the global maximum number of recent tasks to a fixed size
+        mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */);
+
+        // Add N+1 tasks
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        mRecentTasks.add(mTasks.get(2));
+
+        // Ensure that the last task was trimmed as an inactive task
+        assertTrimmed(mTasks.get(0));
+    }
+
+    @Test
+    public void testTrimQuietProfileTasks() throws Exception {
+        TaskRecord qt1 = createTask(".QuietTask1", TEST_QUIET_USER_ID);
+        TaskRecord qt2 = createTask(".QuietTask2", TEST_QUIET_USER_ID);
+        mRecentTasks.add(qt1);
+        mRecentTasks.add(qt2);
+
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+
+        // Ensure that the quiet user's tasks was trimmed once the new tasks were added
+        assertTrimmed(qt1, qt2);
+    }
+
+    @Test
+    public void testSessionDuration() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */);
+
+        TaskRecord t1 = createTask(".Task1");
+        t1.touchActiveTime();
+        mRecentTasks.add(t1);
+
+        // Force a small sleep just beyond the session duration
+        SystemClock.sleep(75);
+
+        TaskRecord t2 = createTask(".Task2");
+        t2.touchActiveTime();
+        mRecentTasks.add(t2);
+
+        // Assert that the old task has been removed due to being out of the active session
+        assertTrimmed(t1);
+    }
+
+    @Test
+    public void testVisibleTasks_excludedFromRecents() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */);
+
+        TaskRecord excludedTask1 = createTask(".ExcludedTask1", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
+                TEST_USER_0_ID);
+        TaskRecord excludedTask2 = createTask(".ExcludedTask2", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
+                TEST_USER_0_ID);
+
+        mRecentTasks.add(excludedTask1);
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(mTasks.get(1));
+        mRecentTasks.add(mTasks.get(2));
+        mRecentTasks.add(excludedTask2);
+
+        // The last excluded task should be trimmed, while the first-most excluded task should not
+        assertTrimmed(excludedTask1);
+    }
+
+    @Test
+    public void testVisibleTasks_minNum() throws Exception {
+        mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */);
+
+        for (int i = 0; i < 4; i++) {
+            final TaskRecord task = mTasks.get(i);
+            task.touchActiveTime();
+            mRecentTasks.add(task);
+        }
+
+        // Force a small sleep just beyond the session duration
+        SystemClock.sleep(50);
+
+        // Add a new task to trigger tasks to be trimmed
+        mRecentTasks.add(mTasks.get(4));
+
+        // Ensure that there are a minimum number of tasks regardless of session length
+        assertTrue(mCallbacksRecorder.trimmed.isEmpty());
+        assertTrue(mCallbacksRecorder.removed.isEmpty());
+    }
+
+    @Test
+    public void testVisibleTasks_maxNum() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
+
+        for (int i = 0; i < 5; i++) {
+            final TaskRecord task = mTasks.get(i);
+            task.touchActiveTime();
+            mRecentTasks.add(task);
+        }
+
+        // Ensure that only the last number of max tasks are kept
+        assertTrimmed(mTasks.get(0), mTasks.get(1));
+    }
+
+    @Test
+    public void testNotRecentsComponent_denyApiAccess() throws Exception {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+                anyInt(), anyInt());
+
+        // Expect the following methods to fail due to recents component not being set
+        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(
+                TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
+        testRecentTasksApis(false /* expectNoSecurityException */);
+        // Don't throw for the following tests
+        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.DENY);
+        testGetTasksApis(false /* expectNoSecurityException */);
+    }
+
+    @Test
+    public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception {
+        doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
+                anyInt(), anyInt());
+
+        // Set the recents component and ensure that the following calls do not fail
+        ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.GRANT);
+        testRecentTasksApis(true /* expectNoSecurityException */);
+        testGetTasksApis(true /* expectNoSecurityException */);
+    }
+
+    private void testRecentTasksApis(boolean expectCallable) {
+        assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
+        assertSecurityException(expectCallable,
+                () -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED}));
+        assertSecurityException(expectCallable,
+                () -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED}));
+        assertSecurityException(expectCallable, () -> mService.removeTask(0));
+        assertSecurityException(expectCallable,
+                () -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
+        assertSecurityException(expectCallable,
+                () -> mService.setTaskWindowingModeSplitScreenPrimary(0,
+                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect()));
+        assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
+        assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
+        assertSecurityException(expectCallable,
+                () -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
+        assertSecurityException(expectCallable,
+                () -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
+        assertSecurityException(expectCallable,
+                () -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
+                        new Rect()));
+        assertSecurityException(expectCallable,
+                () -> mService.resizePinnedStack(new Rect(), new Rect()));
+        assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
+        assertSecurityException(expectCallable,
+                () -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.getFocusedStackInfo();
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable,
+                () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
+        assertSecurityException(expectCallable,
+                () -> mService.startActivityFromRecents(0, new Bundle()));
+        assertSecurityException(expectCallable,
+                () -> mService.getTaskSnapshot(0, true));
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.registerTaskStackListener(null);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable, () -> {
+            try {
+                mService.unregisterTaskStackListener(null);
+            } catch (RemoteException e) {
+                // Ignore
+            }
+        });
+        assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
+        assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
+        assertSecurityException(expectCallable, () -> mService.cancelTaskThumbnailTransition(0));
+        assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null, 0));
+    }
+
+    private void testGetTasksApis(boolean expectCallable) {
+        mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
+        mService.getTasks(MAX_VALUE);
+        if (expectCallable) {
+            assertTrue(((TestRecentTasks) mRecentTasks).mLastAllowed);
+            assertTrue(((TestRunningTasks) mRunningTasks).mLastAllowed);
+        } else {
+            assertFalse(((TestRecentTasks) mRecentTasks).mLastAllowed);
+            assertFalse(((TestRunningTasks) mRunningTasks).mLastAllowed);
+        }
+    }
+
+    private ComponentName createComponent(String className) {
+        return new ComponentName(mContext.getPackageName(), className);
+    }
+
+    private TaskRecord createTask(String className) {
+        return createTask(className, TEST_USER_0_ID);
+    }
+
+    private TaskRecord createTask(String className, int userId) {
+        return createTask(className, 0 /* flags */, userId);
+    }
+
+    private TaskRecord createTask(String className, int flags, int userId) {
+        TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setComponent(createComponent(className))
+                .setStack(mStack).setFlags(flags).setTaskId(LAST_TASK_ID++).build();
+        task.userId = userId;
+        task.touchActiveTime();
+        return task;
+    }
+
+    private TaskRecord createDocumentTask(String className, String affinity) {
+        TaskRecord task = createTask(className, FLAG_ACTIVITY_NEW_DOCUMENT, TEST_USER_0_ID);
+        task.affinity = affinity;
+        return task;
+    }
+
+    private boolean arrayContainsUser(int[] userIds, int targetUserId) {
+        Arrays.sort(userIds);
+        return Arrays.binarySearch(userIds, targetUserId) >= 0;
+    }
+
+    private void assertTrimmed(TaskRecord... tasks) {
+        final ArrayList<TaskRecord> trimmed = mCallbacksRecorder.trimmed;
+        final ArrayList<TaskRecord> removed = mCallbacksRecorder.removed;
+        assertTrue("Expected " + tasks.length + " trimmed tasks, got " + trimmed.size(),
+                trimmed.size() == tasks.length);
+        assertTrue("Expected " + tasks.length + " removed tasks, got " + removed.size(),
+                removed.size() == tasks.length);
+        for (TaskRecord task : tasks) {
+            assertTrue("Expected trimmed task: " + task, trimmed.contains(task));
+            assertTrue("Expected removed task: " + task, removed.contains(task));
+        }
+    }
+
+    private void assertSecurityException(boolean expectCallable, Runnable runnable) {
+        boolean noSecurityException = true;
+        try {
+            runnable.run();
+        } catch (SecurityException se) {
+            noSecurityException = false;
+        } catch (Exception e) {
+            // We only care about SecurityExceptions, fall through here
+            e.printStackTrace();
+        }
+        if (noSecurityException != expectCallable) {
+            fail("Expected callable: " + expectCallable + " but got no security exception: "
+                    + noSecurityException);
+        }
+    }
+
+    private class MyTestActivityManagerService extends TestActivityManagerService {
+        MyTestActivityManagerService(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected ActivityStackSupervisor createStackSupervisor() {
+            return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper());
+        }
+
+        @Override
+        protected RecentTasks createRecentTasks() {
+            return new TestRecentTasks(this, mTaskPersister, new TestUserController(this));
+        }
+
+        @Override
+        public boolean isUserRunning(int userId, int flags) {
+            return true;
+        }
+    }
+
+    private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
+        public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
+            super(service, looper);
+        }
+
+        @Override
+        RunningTasks createRunningTasks() {
+            mRunningTasks = new TestRunningTasks();
+            return mRunningTasks;
+        }
+    }
+
+    private static class CallbacksRecorder implements Callbacks {
+        ArrayList<TaskRecord> added = new ArrayList<>();
+        ArrayList<TaskRecord> trimmed = new ArrayList<>();
+        ArrayList<TaskRecord> removed = new ArrayList<>();
+
+        void clear() {
+            added.clear();
+            trimmed.clear();
+            removed.clear();
+        }
+
+        @Override
+        public void onRecentTaskAdded(TaskRecord task) {
+            added.add(task);
+        }
+
+        @Override
+        public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+            if (wasTrimmed) {
+                trimmed.add(task);
+            }
+            removed.add(task);
+        }
+    }
+
+    private static class TestTaskPersister extends TaskPersister {
+
+        SparseBooleanArray userTaskIdsOverride;
+        ArrayList<TaskRecord> userTasksOverride;
+
+        TestTaskPersister(File workingDir) {
+            super(workingDir);
+        }
+
+        @Override
+        SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
+            if (userTaskIdsOverride != null) {
+                return userTaskIdsOverride;
+            }
+            return super.loadPersistedTaskIdsForUser(userId);
+        }
+
+        @Override
+        List<TaskRecord> restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) {
+            if (userTasksOverride != null) {
+                return userTasksOverride;
+            }
+            return super.restoreTasksForUserLocked(userId, preaddedTasks);
+        }
+    }
+
+    private static class TestRecentTasks extends RecentTasks {
+        static final int GRANT = 0;
+        static final int DENY = 1;
+        static final int DENY_THROW_SECURITY_EXCEPTION = 2;
+
+        private boolean mOverrideIsCallerRecents;
+        private int mIsCallerRecentsPolicy;
+        boolean mLastAllowed;
+
+        TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
+                UserController userController) {
+            super(service, taskPersister, userController);
+        }
+
+        @Override
+        boolean isCallerRecents(int callingUid) {
+            if (mOverrideIsCallerRecents) {
+                switch (mIsCallerRecentsPolicy) {
+                    case GRANT:
+                        return true;
+                    case DENY:
+                        return false;
+                    case DENY_THROW_SECURITY_EXCEPTION:
+                        throw new SecurityException();
+                }
+            }
+            return super.isCallerRecents(callingUid);
+        }
+
+        void setIsCallerRecentsOverride(int policy) {
+            mOverrideIsCallerRecents = true;
+            mIsCallerRecentsPolicy = policy;
+        }
+
+        @Override
+        ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
+                boolean getTasksAllowed,
+                boolean getDetailedTasks, int userId, int callingUid) {
+            mLastAllowed = getTasksAllowed;
+            return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
+                    callingUid);
+        }
+    }
+
+    private static class TestRunningTasks extends RunningTasks {
+        boolean mLastAllowed;
+
+        @Override
+        void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
+                int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
+                int callingUid, boolean allowed) {
+            mLastAllowed = allowed;
+            super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
+                    callingUid, allowed);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
new file mode 100644
index 0000000..fc75628
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.server.am;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RunningTasksTest extends ActivityTestsBase {
+
+    private Context mContext = InstrumentationRegistry.getContext();
+    private ActivityManagerService mService;
+
+    private RunningTasks mRunningTasks;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mService = createActivityManagerService();
+        mRunningTasks = new RunningTasks();
+    }
+
+    @Test
+    public void testCollectTasksByLastActiveTime() throws Exception {
+        // Create a number of stacks with tasks (of incrementing active time)
+        final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+        final SparseArray<ActivityDisplay> displays = new SparseArray<>();
+        final ActivityDisplay display = new ActivityDisplay(supervisor, DEFAULT_DISPLAY);
+        displays.put(DEFAULT_DISPLAY, display);
+
+        final int numStacks = 2;
+        for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
+            final ActivityStack stack = new TestActivityStack(display, stackIndex, supervisor,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true);
+            display.addChild(stack, POSITION_BOTTOM);
+        }
+
+        final int numTasks = 10;
+        int activeTime = 0;
+        for (int i = 0; i < numTasks; i++) {
+            createTask(display.getChildAt(i % numStacks), ".Task" + i, i, activeTime++);
+        }
+
+        // Ensure that the latest tasks were returned in order of decreasing last active time,
+        // collected from all tasks across all the stacks
+        final int numFetchTasks = 5;
+        ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
+        mRunningTasks.getTasks(5, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
+                displays, -1 /* callingUid */, true /* allowed */);
+        assertTrue(tasks.size() == numFetchTasks);
+        for (int i = 0; i < numFetchTasks; i++) {
+            assertTrue(tasks.get(i).id == (numTasks - i - 1));
+        }
+
+        // Ensure that requesting more than the total number of tasks only returns the subset
+        // and does not crash
+        tasks.clear();
+        mRunningTasks.getTasks(100, tasks, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED,
+                displays, -1 /* callingUid */, true /* allowed */);
+        assertTrue(tasks.size() == numTasks);
+        for (int i = 0; i < numTasks; i++) {
+            assertTrue(tasks.get(i).id == (numTasks - i - 1));
+        }
+    }
+
+    /**
+     * Create a task with a single activity in it, with the given last active time.
+     */
+    private TaskRecord createTask(ActivityStack stack, String className, int taskId,
+            int lastActiveTime) {
+        final TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
+                .setComponent(new ComponentName(mContext.getPackageName(), className))
+                .setTaskId(taskId)
+                .setStack(stack)
+                .build();
+        task.lastActiveTime = lastActiveTime;
+        final ActivityRecord activity = new ActivityBuilder(mService)
+                .setTask(task)
+                .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
+                .build();
+        return task;
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 419a161..6fdb029 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -30,6 +30,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserManagerInternal;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -61,12 +62,17 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+/**
+ * Usage: bit FrameworksServicesTests:com.android.server.am.UserControllerTest
+ */
+@Presubmit
 public class UserControllerTest extends AndroidTestCase {
     private static final int TEST_USER_ID = 10;
     private static final int NONEXIST_USER_ID = 2;
@@ -96,8 +102,11 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        System.setProperty("dexmaker.share_classloader", "true");
-        mInjector = new TestInjector(getContext());
+        mInjector = Mockito.spy(new TestInjector(getContext()));
+        doNothing().when(mInjector).clearAllLockedTasks(anyString());
+        doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
+        doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any());
+        doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity();
         mUserController = new UserController(mInjector);
         setUpUser(TEST_USER_ID, 0);
     }
@@ -106,6 +115,7 @@
     protected void tearDown() throws Exception {
         super.tearDown();
         mInjector.handlerThread.quit();
+        Mockito.validateMockitoUsage();
     }
 
     @SmallTest
@@ -115,7 +125,7 @@
         Mockito.verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
         Mockito.verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
         Mockito.verify(mInjector.getWindowManager()).setSwitchingUser(true);
-        Mockito.verify(mInjector.getLockTaskController()).clearLockTaskMode(anyString());
+        Mockito.verify(mInjector).clearAllLockedTasks(anyString());
         startForegroundUserAssertions();
     }
 
@@ -125,7 +135,7 @@
         Mockito.verify(
                 mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         Mockito.verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
-        verifyZeroInteractions(mInjector.getLockTaskController());
+        Mockito.verify(mInjector, never()).clearAllLockedTasks(anyString());
         startBackgroundUserAssertions();
     }
 
@@ -306,16 +316,14 @@
         return result;
     }
 
-    private static class TestInjector extends UserController.Injector {
-        final Object lock = new Object();
+    // Should be public to allow mocking
+    public static class TestInjector extends UserController.Injector {
         TestHandler handler;
         TestHandler uiHandler;
         HandlerThread handlerThread;
         UserManagerService userManagerMock;
         UserManagerInternal userManagerInternalMock;
         WindowManagerService windowManagerMock;
-        ActivityStackSupervisor activityStackSupervisor;
-        LockTaskController lockTaskController;
         private Context mCtx;
         List<Intent> sentIntents = new ArrayList<>();
 
@@ -329,13 +337,6 @@
             userManagerMock = mock(UserManagerService.class);
             userManagerInternalMock = mock(UserManagerInternal.class);
             windowManagerMock = mock(WindowManagerService.class);
-            activityStackSupervisor = mock(ActivityStackSupervisor.class);
-            lockTaskController = mock(LockTaskController.class);
-        }
-
-        @Override
-        protected Object getLock() {
-            return lock;
         }
 
         @Override
@@ -375,12 +376,12 @@
         }
 
         @Override
-        void updateUserConfigurationLocked() {
-            Log.i(TAG, "updateUserConfigurationLocked");
+        void updateUserConfiguration() {
+            Log.i(TAG, "updateUserConfiguration");
         }
 
         @Override
-        protected int broadcastIntentLocked(Intent intent, String resolvedType,
+        protected int broadcastIntent(Intent intent, String resolvedType,
                 IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras,
                 String[] requiredPermissions, int appOp, Bundle bOptions, boolean ordered,
                 boolean sticky, int callingPid, int callingUid, int userId) {
@@ -388,20 +389,6 @@
             sentIntents.add(intent);
             return 0;
         }
-
-        @Override
-        void startHomeActivityLocked(int userId, String reason) {
-            Log.i(TAG, "startHomeActivityLocked " + userId);
-        }
-
-        @Override
-        ActivityStackSupervisor getActivityStackSupervisor() {
-            return activityStackSupervisor;
-        }
-
-        LockTaskController getLockTaskController() {
-            return lockTaskController;
-        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index f4c4ea9..1bb93cc 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -247,6 +247,26 @@
         assertEquals(7, updates.size());
     }
 
+    public void testUpdatesReceived_queueEmptyAfterStartListening() {
+        int widgetId = setupHostAndWidget();
+        int widgetId2 = bindNewWidget();
+        mService.stopListening(mPkgName, HOST_ID);
+
+        sendDummyUpdates(widgetId, 22, 23);
+        sendDummyUpdates(widgetId2, 100, 101, 102);
+
+        List<PendingHostUpdate> updates = mService.startListening(
+                mMockHost, mPkgName, HOST_ID, new int[]{widgetId, widgetId2}).getList();
+        // 3 updates for first widget and 4 for second
+        assertEquals(7, updates.size());
+
+        // Stop and start listening again
+        mService.stopListening(mPkgName, HOST_ID);
+        updates = mService.startListening(
+                mMockHost, mPkgName, HOST_ID, new int[]{widgetId, widgetId2}).getList();
+        assertTrue(updates.isEmpty());
+    }
+
     public void testGetInstalledProvidersForPackage() {
         List<AppWidgetProviderInfo> allProviders = mManager.getInstalledProviders();
         assertTrue(!allProviders.isEmpty());
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 5471715..ae4b569 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.devicepolicy;
 
+import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -34,11 +35,13 @@
 import android.os.UserManager;
 import android.os.UserManagerInternal;
 import android.security.KeyChain;
+import android.support.annotation.NonNull;
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Pair;
 import android.view.IWindowManager;
 
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.widget.LockPatternUtils;
 
 import java.io.File;
@@ -194,6 +197,9 @@
         }
 
         @Override
+        AlarmManager getAlarmManager() {return services.alarmManager;}
+
+        @Override
         LockPatternUtils newLockPatternUtils() {
             return services.lockPatternUtils;
         }
@@ -234,6 +240,11 @@
         }
 
         @Override
+        void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) {
+            context.binder.withCleanCallingIdentity(action);
+        }
+
+        @Override
         int binderGetCallingUid() {
             return context.binder.getCallingUid();
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 87b0db8..9d23fe9 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3090,6 +3090,47 @@
         assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
     }
 
+    public void testSetTime() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        dpm.setTime(admin1, 0);
+        verify(getServices().alarmManager).setTime(0);
+    }
+
+    public void testSetTimeFailWithPO() throws Exception {
+        setupProfileOwner();
+        assertExpectException(SecurityException.class, null, () -> dpm.setTime(admin1, 0));
+    }
+
+    public void testSetTimeWithAutoTimeOn() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        when(getServices().settings.settingsGlobalGetInt(Settings.Global.AUTO_TIME, 0))
+                .thenReturn(1);
+        assertFalse(dpm.setTime(admin1, 0));
+    }
+
+    public void testSetTimeZone() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        dpm.setTimeZone(admin1, "Asia/Shanghai");
+        verify(getServices().alarmManager).setTimeZone("Asia/Shanghai");
+    }
+
+    public void testSetTimeZoneFailWithPO() throws Exception {
+        setupProfileOwner();
+        assertExpectException(SecurityException.class, null,
+                () -> dpm.setTimeZone(admin1, "Asia/Shanghai"));
+    }
+
+    public void testSetTimeZoneWithAutoTimeZoneOn() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        when(getServices().settings.settingsGlobalGetInt(Settings.Global.AUTO_TIME_ZONE, 0))
+                .thenReturn(1);
+        assertFalse(dpm.setTimeZone(admin1, "Asia/Shanghai"));
+    }
+
     public void testGetLastBugReportRequestTime() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -3299,13 +3340,15 @@
         MoreAsserts.assertEmpty(targetUsers);
     }
 
-    public void testLockTaskPackagesAllowedForAffiliatedUsers() throws Exception {
+    public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
         // Setup a device owner.
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
-        // Lock task packages are updated when loading user data.
-        verify(getServices().iactivityManager)
-                .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(new String[0]));
+        // Lock task policy is updated when loading user data.
+        verify(getServices().iactivityManager).updateLockTaskPackages(
+                UserHandle.USER_SYSTEM, new String[0]);
+        verify(getServices().iactivityManager).updateLockTaskFeatures(
+                UserHandle.USER_SYSTEM, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
 
         // Set up a managed profile managed by different package (package name shouldn't matter)
         final int MANAGED_PROFILE_USER_ID = 15;
@@ -3313,8 +3356,10 @@
         final ComponentName adminDifferentPackage =
                 new ComponentName("another.package", "whatever.class");
         addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
-        verify(getServices().iactivityManager)
-                .updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(new String[0]));
+        verify(getServices().iactivityManager).updateLockTaskPackages(
+                MANAGED_PROFILE_USER_ID, new String[0]);
+        verify(getServices().iactivityManager).updateLockTaskFeatures(
+                MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
 
         // The DO can still set lock task packages
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -3323,8 +3368,14 @@
         MoreAsserts.assertEquals(doPackages, dpm.getLockTaskPackages(admin1));
         assertTrue(dpm.isLockTaskPermitted("doPackage1"));
         assertFalse(dpm.isLockTaskPermitted("anotherPackage"));
-        verify(getServices().iactivityManager)
-                .updateLockTaskPackages(eq(UserHandle.USER_SYSTEM), eq(doPackages));
+        verify(getServices().iactivityManager).updateLockTaskPackages(
+                UserHandle.USER_SYSTEM, doPackages);
+        // And the DO can still set lock task features
+        final int doFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+                | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+        dpm.setLockTaskFeatures(admin1, doFlags);
+        verify(getServices().iactivityManager).updateLockTaskFeatures(
+                UserHandle.USER_SYSTEM, doFlags);
 
         // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
         mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
@@ -3334,6 +3385,11 @@
         assertExpectException(SecurityException.class, /* messageRegex =*/ null,
                 () -> dpm.getLockTaskPackages(adminDifferentPackage));
         assertFalse(dpm.isLockTaskPermitted("doPackage1"));
+        // And it shouldn't be able to setLockTaskFeatures.
+        final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
+                | DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS;
+        assertExpectException(SecurityException.class, /* messageRegex =*/ null,
+                () -> dpm.setLockTaskFeatures(adminDifferentPackage, poFlags));
 
         // Setting same affiliation ids
         final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
@@ -3348,15 +3404,21 @@
         MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
         assertTrue(dpm.isLockTaskPermitted("poPackage1"));
         assertFalse(dpm.isLockTaskPermitted("doPackage2"));
-        verify(getServices().iactivityManager)
-                .updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(poPackages));
+        verify(getServices().iactivityManager).updateLockTaskPackages(
+                MANAGED_PROFILE_USER_ID, poPackages);
+        // And it can set lock task features.
+        dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
+        verify(getServices().iactivityManager).updateLockTaskFeatures(
+                MANAGED_PROFILE_USER_ID, poFlags);
 
         // Unaffiliate the profile, lock task mode no longer available on the profile.
         dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
         assertFalse(dpm.isLockTaskPermitted("poPackage1"));
         // Lock task packages cleared when loading user data and when the user becomes unaffiliated.
-        verify(getServices().iactivityManager, times(2))
-                .updateLockTaskPackages(eq(MANAGED_PROFILE_USER_ID), eq(new String[0]));
+        verify(getServices().iactivityManager, times(2)).updateLockTaskPackages(
+                MANAGED_PROFILE_USER_ID, new String[0]);
+        verify(getServices().iactivityManager, times(2)).updateLockTaskFeatures(
+                MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
 
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         assertTrue(dpm.isLockTaskPermitted("doPackage1"));
@@ -3421,6 +3483,9 @@
         // Even if the caller is the managed profile, the current user is the user 0
         when(getServices().iactivityManager.getCurrentUser())
                 .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
+        // Get mock reason string since we throw an IAE with empty string input.
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
+                thenReturn("Just a test string.");
 
         dpm.wipeData(0);
         verify(getServices().userManagerInternal).removeUserEvenWhenDisallowed(
@@ -3440,6 +3505,9 @@
                 UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
                 UserHandle.of(MANAGED_PROFILE_USER_ID)))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
+                thenReturn("Just a test string.");
+
         mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
         // The PO is not allowed to remove the profile if the user restriction was set on the
         // profile by the system
@@ -3453,6 +3521,8 @@
                 UserManager.DISALLOW_FACTORY_RESET,
                 UserHandle.SYSTEM))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
+                thenReturn("Just a test string.");
 
         dpm.wipeData(0);
         verify(getServices().recoverySystem).rebootWipeUserData(
@@ -3466,6 +3536,8 @@
             UserManager.DISALLOW_FACTORY_RESET,
             UserHandle.SYSTEM))
             .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
+                thenReturn("Just a test string.");
 
         dpm.wipeData(WIPE_EUICC);
         verify(getServices().recoverySystem).rebootWipeUserData(
@@ -3479,6 +3551,8 @@
                 UserManager.DISALLOW_FACTORY_RESET,
                 UserHandle.SYSTEM))
                 .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
+        when(mContext.getResources().getString(R.string.work_profile_deleted_description_dpm_wipe)).
+                thenReturn("Just a test string.");
         // The DO is not allowed to wipe the device if the user restriction was set
         // by the system
         assertExpectException(SecurityException.class, /* messageRegex= */ null,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 9702118..7e11e87 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -30,8 +30,12 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.support.annotation.NonNull;
 import android.test.mock.MockContext;
 import android.util.ArrayMap;
+import android.util.ExceptionUtils;
+
+import com.android.internal.util.FunctionalUtils;
 
 import org.junit.Assert;
 
@@ -95,6 +99,21 @@
             callingPid = (int) token;
         }
 
+        public void withCleanCallingIdentity(@NonNull FunctionalUtils.ThrowingRunnable action) {
+            long callingIdentity = clearCallingIdentity();
+            Throwable throwableToPropagate = null;
+            try {
+                action.run();
+            } catch (Throwable throwable) {
+                throwableToPropagate = throwable;
+            } finally {
+                restoreCallingIdentity(callingIdentity);
+                if (throwableToPropagate != null) {
+                    throw ExceptionUtils.propagate(throwableToPropagate);
+                }
+            }
+        }
+
         public int getCallingUid() {
             return callingUid;
         }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 13cf9df..7cba280 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -106,7 +106,8 @@
                 return mService;
             }
         };
-        mSpManager = new MockSyntheticPasswordManager(mStorage, mGateKeeperService, mUserManager);
+        mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
+                mUserManager);
         mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
                 mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
                 mSpManager);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
index cf03593..6f68179 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockSyntheticPasswordManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.locksettings;
 
+import android.content.Context;
 import android.hardware.weaver.V1_0.IWeaver;
 import android.os.RemoteException;
 import android.os.UserManager;
@@ -35,9 +36,9 @@
     private FakeGateKeeperService mGateKeeper;
     private IWeaver mWeaverService;
 
-    public MockSyntheticPasswordManager(LockSettingsStorage storage,
+    public MockSyntheticPasswordManager(Context context, LockSettingsStorage storage,
             FakeGateKeeperService gatekeeper, UserManager userManager) {
-        super(storage, userManager);
+        super(context, storage, userManager);
         mGateKeeper = gatekeeper;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 2c9aa9d..2ad0580 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -58,7 +58,7 @@
         final int USER_ID = 10;
         final String PASSWORD = "user-password";
         final String BADPASSWORD = "bad-password";
-        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mStorage,
+        MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
                 mGateKeeperService, mUserManager);
         AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
                 null, USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 3c64582..6bb5bc6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -319,7 +319,8 @@
         }
 
         @Override
-        boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId) {
+        boolean hasShortcutHostPermission(@NonNull String callingPackage, int userId,
+                int callingPid, int callingUid) {
             return mDefaultLauncherChecker.test(callingPackage, userId);
         }
 
@@ -452,6 +453,11 @@
         }
 
         @Override
+        boolean injectCheckAccessShortcutsPermission(int callingPid, int callingUid) {
+            return mInjectCheckAccessShortcutsPermission;
+        }
+
+        @Override
         void wtf(String message, Throwable th) {
             // During tests, WTF is fatal.
             fail(message + "  exception: " + th + "\n" + Log.getStackTraceString(th));
@@ -697,6 +703,8 @@
 
     protected String mInjectedBuildFingerprint = "build1";
 
+    protected boolean mInjectCheckAccessShortcutsPermission = false;
+
     static {
         QUERY_ALL.setQueryFlags(
                 ShortcutQuery.FLAG_GET_ALL_KINDS);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index 3cd24b8..fd105bc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
@@ -51,6 +52,9 @@
 
 import com.android.internal.os.AtomicFile;
 import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerInternal;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,8 +83,11 @@
             throws ReflectiveOperationException, IllegalAccessException {
         /* write out files and read */
         writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
         Settings settings =
-                new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         verifyKeySetMetaData(settings);
     }
@@ -91,8 +98,11 @@
             throws ReflectiveOperationException, IllegalAccessException {
         // write out files and read
         writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
         Settings settings =
-                new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         // write out, read back in and verify the same
@@ -105,8 +115,11 @@
     public void testSettingsReadOld() {
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
         Settings settings =
-                new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue()));
         assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue()));
@@ -125,13 +138,17 @@
     public void testNewPackageRestrictionsFile() throws ReflectiveOperationException {
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
         Settings settings =
-                new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
         settings.writeLPr();
 
         // Create Settings again to make it read from the new files
-        settings = new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+        settings =
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2);
@@ -143,8 +160,11 @@
     public void testEnableDisable() {
         // Write the package files and make sure they're parsed properly the first time
         writeOldFiles();
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
         Settings settings =
-                new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         assertThat(settings.readLPw(createFakeUsers()), is(true));
 
         // Enable/Disable a package
@@ -334,7 +354,11 @@
     /** Update package; changing shared user throws exception */
     @Test
     public void testUpdatePackageSetting03() {
-        final Settings testSettings01 = new Settings(new Object() /*lock*/);
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
+        final Settings testSettings01 =
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         final SharedUserSetting testUserSetting01 = createSharedUserSetting(
                 testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
         final PackageSetting testPkgSetting01 =
@@ -449,7 +473,11 @@
     /** Create PackageSetting for a shared user */
     @Test
     public void testCreateNewSetting03() {
-        final Settings testSettings01 = new Settings(new Object() /*lock*/);
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
+        final Settings testSettings01 =
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         final SharedUserSetting testUserSetting01 = createSharedUserSetting(
                 testSettings01, "TestUser", 10064, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/);
         final PackageSetting testPkgSetting01 = Settings.createNewSetting(
@@ -542,8 +570,11 @@
         final PackageParser.Package pkg = new PackageParser.Package(PACKAGE_NAME);
         pkg.applicationInfo.setCodePath(ps.codePathString);
         pkg.applicationInfo.setResourcePath(ps.resourcePathString);
+        final Context context = InstrumentationRegistry.getContext();
+        final Object lock = new Object();
+        PermissionManagerInternal pmInt = PermissionManagerService.create(context, null, lock);
         final Settings settings =
-                new Settings(InstrumentationRegistry.getContext().getFilesDir(), new Object());
+                new Settings(context.getFilesDir(), pmInt.getPermissionSettings(), lock);
         pkg.usesStaticLibraries = new ArrayList<>(
                 Arrays.asList("foo.bar1", "foo.bar2", "foo.bar3"));
         pkg.usesStaticLibrariesVersions = new int[] {2, 4, 6};
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 951f226..b5e8e1c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -15,6 +15,11 @@
  */
 package com.android.server.pm;
 
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_NOT_DISABLED;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
+import static android.content.pm.ShortcutInfo.DISABLED_REASON_VERSION_LOWER;
+
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyOrNull;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyStringOrNull;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertAllDisabled;
@@ -71,7 +76,9 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
@@ -101,7 +108,6 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.function.BiConsumer;
-import java.util.function.BiPredicate;
 
 /**
  * Tests for ShortcutService and ShortcutManager.
@@ -250,7 +256,7 @@
         final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
                 getTestContext().getResources(), R.drawable.icon2));
         final Icon icon3 = Icon.createWithAdaptiveBitmap(BitmapFactory.decodeResource(
-            getTestContext().getResources(), R.drawable.icon2));
+                getTestContext().getResources(), R.drawable.icon2));
 
         final ShortcutInfo si1 = makeShortcut(
                 "shortcut1",
@@ -706,13 +712,13 @@
         assertBitmapSize(128, 128, bmp);
 
         Drawable dr = mLauncherApps.getShortcutIconDrawable(
-            makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
+                makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
         assertTrue(dr instanceof AdaptiveIconDrawable);
         float viewportPercentage = 1 / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
         assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
-            dr.getIntrinsicWidth());
+                dr.getIntrinsicWidth());
         assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),
-            dr.getIntrinsicHeight());
+                dr.getIntrinsicHeight());
     }
 
     public void testCleanupDanglingBitmaps() throws Exception {
@@ -1118,8 +1124,8 @@
             // Set resource icon
             assertTrue(mManager.updateShortcuts(list(
                     new ShortcutInfo.Builder(mClientContext, "s1")
-                    .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
-                    .build()
+                            .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32))
+                            .build()
             )));
 
             assertWith(getCallerShortcuts())
@@ -1131,9 +1137,9 @@
             // Set bitmap icon
             assertTrue(mManager.updateShortcuts(list(
                     new ShortcutInfo.Builder(mClientContext, "s1")
-                    .setIcon(Icon.createWithBitmap(BitmapFactory.decodeResource(
-                            getTestContext().getResources(), R.drawable.black_64x64)))
-                    .build()
+                            .setIcon(Icon.createWithBitmap(BitmapFactory.decodeResource(
+                                    getTestContext().getResources(), R.drawable.black_64x64)))
+                            .build()
             )));
 
             assertWith(getCallerShortcuts())
@@ -1669,6 +1675,10 @@
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
 
+            mManager.updateShortcuts(list(
+                    new ShortcutInfo.Builder(mClientContext, "s2").setDisabledMessage("xyz")
+                            .build()));
+
             mManager.disableShortcuts(list("s2"));
 
             assertShortcutIds(mManager.getPinnedShortcuts(), "s2");
@@ -1704,6 +1714,10 @@
             assertWith(mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
                     /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser()))
                     .haveIds("s2")
+                    .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BY_APP)
+                    .forAllShortcuts(si -> {
+                        assertEquals("xyz", si.getDisabledMessage());
+                    })
                     .areAllPinned()
                     .areAllNotWithKeyFieldsOnly()
                     .areAllDisabled();
@@ -1888,7 +1902,7 @@
                     "s1", "s2");
         });
 
-        dumpsysOnLogcat();
+        dumpsysOnLogcat("Before launcher 2");
 
         runWithCaller(LAUNCHER_2, USER_0, () -> {
             // Launcher2 still has no pinned ones.
@@ -1901,6 +1915,27 @@
                     /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED), getCallingUser())))
                     /* none */);
 
+            // Make sure FLAG_MATCH_ALL_PINNED will be ignored.
+            assertWith(mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_MATCH_PINNED
+                            | ShortcutQuery.FLAG_MATCH_ALL_PINNED), getCallingUser()))
+                    .isEmpty();
+
+            // Make sure the special permission works.
+            mInjectCheckAccessShortcutsPermission = true;
+
+            dumpsysOnLogcat("All-pinned");
+
+            assertWith(mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_MATCH_PINNED
+                            | ShortcutQuery.FLAG_MATCH_ALL_PINNED), getCallingUser()))
+                    .haveIds("s1", "s2");
+            assertWith(mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_2,
+                    /* activity =*/ null, ShortcutQuery.FLAG_MATCH_PINNED), getCallingUser()))
+                    .isEmpty();
+
+            mInjectCheckAccessShortcutsPermission = false;
+
             assertShortcutIds(assertAllDynamic(
                     mLauncherApps.getShortcuts(buildQuery(/* time =*/ 0, CALLING_PACKAGE_1,
                     /* activity =*/ null, ShortcutQuery.FLAG_GET_PINNED
@@ -2126,7 +2161,7 @@
 
             final ShortcutQuery q = new ShortcutQuery().setQueryFlags(
                     ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_PINNED
-                    | ShortcutQuery.FLAG_MATCH_MANIFEST);
+                            | ShortcutQuery.FLAG_MATCH_MANIFEST);
 
             // No shortcuts are visible.
             assertWith(mLauncherApps.getShortcuts(q, HANDLE_USER_0)).isEmpty();
@@ -2668,10 +2703,10 @@
                     "Title 1",
                     makeComponent(ShortcutActivity.class),
                     /* icon =*/ null,
-                    new Intent[] {makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+                    new Intent[]{makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
                             "key1", "val1", "nest", makeBundle("key", 123))
                             .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK),
-                    new Intent("act2").setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)},
+                            new Intent("act2").setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)},
                     /* rank */ 10);
 
             final ShortcutInfo s1_2 = makeShortcut(
@@ -2776,8 +2811,8 @@
             // Not launchable.
             doReturn(ActivityManager.START_CLASS_NOT_FOUND)
                     .when(mMockActivityManagerInternal).startActivitiesAsPackage(
-                            anyStringOrNull(), anyInt(),
-                            anyOrNull(Intent[].class), anyOrNull(Bundle.class));
+                    anyStringOrNull(), anyInt(),
+                    anyOrNull(Intent[].class), anyOrNull(Bundle.class));
             assertStartShortcutThrowsException(CALLING_PACKAGE_1, "s1", USER_0,
                     ActivityNotFoundException.class);
 
@@ -2911,7 +2946,7 @@
                     .revertToOriginalList()
                     .selectByIds("s1", "s2")
                     .areAllDynamic()
-                    ;
+            ;
         });
 
         // 3 Update the app with no manifest shortcuts.  (Pinned one will survive.)
@@ -3309,13 +3344,13 @@
         });
 
 
-        final SparseArray<ShortcutUser> users =  mService.getShortcutsForTest();
+        final SparseArray<ShortcutUser> users = mService.getShortcutsForTest();
         assertEquals(2, users.size());
         assertEquals(USER_0, users.keyAt(0));
         assertEquals(USER_10, users.keyAt(1));
 
-        final ShortcutUser user0 =  users.get(USER_0);
-        final ShortcutUser user10 =  users.get(USER_10);
+        final ShortcutUser user0 = users.get(USER_0);
+        final ShortcutUser user10 = users.get(USER_10);
 
 
         // Check the registered packages.
@@ -3531,7 +3566,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3548,7 +3583,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -3853,52 +3888,77 @@
         assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
     }
 
-    protected void checkCanRestoreTo(boolean expected, ShortcutPackageInfo spi,
-            int version, String... signatures) {
-        assertEquals(expected, spi.canRestoreTo(mService, genPackage(
-                "dummy", /* uid */ 0, version, signatures)));
+    protected void checkCanRestoreTo(int expected, ShortcutPackageInfo spi,
+            boolean anyVersionOk, int version, boolean nowBackupAllowed, String... signatures) {
+        final PackageInfo pi = genPackage("dummy", /* uid */ 0, version, signatures);
+        if (!nowBackupAllowed) {
+            pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP;
+        }
+        assertEquals(expected, spi.canRestoreTo(mService, pi, anyVersionOk));
     }
 
     public void testCanRestoreTo() {
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
-        addPackage(CALLING_PACKAGE_2, CALLING_UID_1, 10, "sig1", "sig2");
+        addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2");
+        addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1");
+
+        updatePackageInfo(CALLING_PACKAGE_3,
+                pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
 
         final ShortcutPackageInfo spi1 = ShortcutPackageInfo.generateForInstalledPackageForTest(
                 mService, CALLING_PACKAGE_1, USER_0);
         final ShortcutPackageInfo spi2 = ShortcutPackageInfo.generateForInstalledPackageForTest(
                 mService, CALLING_PACKAGE_2, USER_0);
+        final ShortcutPackageInfo spi3 = ShortcutPackageInfo.generateForInstalledPackageForTest(
+                mService, CALLING_PACKAGE_3, USER_0);
 
-        checkCanRestoreTo(true, spi1, 10, "sig1");
-        checkCanRestoreTo(true, spi1, 10, "x", "sig1");
-        checkCanRestoreTo(true, spi1, 10, "sig1", "y");
-        checkCanRestoreTo(true, spi1, 10, "x", "sig1", "y");
-        checkCanRestoreTo(true, spi1, 11, "sig1");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "sig1");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "x", "sig1");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "sig1", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 10, true, "x", "sig1", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, false, 11, true, "sig1");
 
-        checkCanRestoreTo(false, spi1, 10 /* empty */);
-        checkCanRestoreTo(false, spi1, 10, "x");
-        checkCanRestoreTo(false, spi1, 10, "x", "y");
-        checkCanRestoreTo(false, spi1, 10, "x");
-        checkCanRestoreTo(false, spi1, 9, "sig1");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true/* empty */);
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x", "y");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x");
+        checkCanRestoreTo(DISABLED_REASON_VERSION_LOWER, spi1, false, 9, true, "sig1");
 
-        checkCanRestoreTo(true, spi2, 10, "sig1", "sig2");
-        checkCanRestoreTo(true, spi2, 10, "sig2", "sig1");
-        checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2");
-        checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1");
-        checkCanRestoreTo(true, spi2, 10, "sig1", "sig2", "y");
-        checkCanRestoreTo(true, spi2, 10, "sig2", "sig1", "y");
-        checkCanRestoreTo(true, spi2, 10, "x", "sig1", "sig2", "y");
-        checkCanRestoreTo(true, spi2, 10, "x", "sig2", "sig1", "y");
-        checkCanRestoreTo(true, spi2, 11, "x", "sig2", "sig1", "y");
+        // Any version okay.
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1");
 
-        checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x");
-        checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x");
-        checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2");
-        checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1");
-        checkCanRestoreTo(false, spi2, 10, "sig1", "sig2x", "y");
-        checkCanRestoreTo(false, spi2, 10, "sig2", "sig1x", "y");
-        checkCanRestoreTo(false, spi2, 10, "x", "sig1x", "sig2", "y");
-        checkCanRestoreTo(false, spi2, 10, "x", "sig2x", "sig1", "y");
-        checkCanRestoreTo(false, spi2, 11, "x", "sig2x", "sig1", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig1", "sig2");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig2", "sig1");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig1", "sig2");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig2", "sig1");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig1", "sig2", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "sig2", "sig1", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig1", "sig2", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 10, true, "x", "sig2", "sig1", "y");
+        checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi2, false, 11, true, "x", "sig2", "sig1", "y");
+
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "sig1", "sig2x");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "sig2", "sig1x");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "x", "sig1x", "sig2");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "x", "sig2x", "sig1");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "sig1", "sig2x", "y");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "sig2", "sig1x", "y");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "x", "sig1x", "sig2", "y");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 10, true, "x", "sig2x", "sig1", "y");
+        checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH,
+                spi2, false, 11, true, "x", "sig2x", "sig1", "y");
+
+        checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi1, true, 10, false, "sig1");
+        checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi3, true, 10, true, "sig1");
     }
 
     public void testHandlePackageDelete() {
@@ -3929,7 +3989,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_1);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertWith(getCallerShortcuts())
@@ -4080,7 +4140,7 @@
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_2, USER_10));
         assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageDataClear(CALLING_PACKAGE_1, USER_0));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4099,7 +4159,7 @@
 
         mRunningUsers.put(USER_10, true);
 
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageDataClear(CALLING_PACKAGE_2, USER_10));
 
         assertNull(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "s1", USER_0));
@@ -4126,7 +4186,7 @@
                 new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
                 R.xml.shortcut_2);
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4389,7 +4449,7 @@
 
         // Update the package.
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -4524,7 +4584,7 @@
         mRunningUsers.put(USER_10, true);
 
         updatePackageVersion(CALLING_PACKAGE_1, 1);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4552,11 +4612,11 @@
                     .revertToOriginalList()
                     .selectByIds("ms1-alt", "s2")
                     .areAllWithActivity(ACTIVITY2)
-                    ;
+            ;
         });
 
         // First, no changes.
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4579,7 +4639,7 @@
 
         // Disable activity 1
         mEnabledActivityChecker = (activity, userId) -> !ACTIVITY1.equals(activity);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4599,7 +4659,7 @@
         // Re-enable activity 1.
         // Manifest shortcuts will be re-published, but dynamic ones are not.
         mEnabledActivityChecker = (activity, userId) -> true;
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4617,13 +4677,13 @@
                     .revertToOriginalList()
                     .selectByIds("ms1-alt", "s2")
                     .areAllWithActivity(ACTIVITY2)
-                    ;
+            ;
         });
 
         // Disable activity 2
         // Because "ms1-alt" and "s2" are both pinned, they will remain, but disabled.
         mEnabledActivityChecker = (activity, userId) -> !ACTIVITY2.equals(activity);
-                mService.mPackageMonitor.onReceive(getTestContext(),
+        mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageChangedIntent(CALLING_PACKAGE_1, USER_10));
 
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
@@ -4686,7 +4746,7 @@
         setCaller(LAUNCHER_1, USER_0);
         assertForLauncherCallback(mLauncherApps, () -> {
             updatePackageVersion(CALLING_PACKAGE_1, 1);
-                    mService.mPackageMonitor.onReceive(getTestContext(),
+            mService.mPackageMonitor.onReceive(getTestContext(),
                     genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
         }).assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_0)
                 // Make sure the launcher gets callbacks.
@@ -4708,12 +4768,11 @@
                     .areAllNotDynamic()
                     .areAllDisabled()
                     .areAllPinned()
-                    ;
+            ;
         });
     }
 
     protected void prepareForBackupTest() {
-
         prepareCrossProfileDataSet();
 
         backupAndRestore();
@@ -4749,66 +4808,45 @@
         assertFileExistsWithContent("user-0/shortcut_dump/restore-4.txt");
         assertFileExistsWithContent("user-0/shortcut_dump/restore-5-finish.txt");
 
-        checkBackupAndRestore_success();
+        checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
     public void testBackupAndRestore_backupRestoreTwice() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        dumpsysOnLogcat("Before second backup");
+        checkBackupAndRestore_success(/*firstRestore=*/ true);
+
+        // Run a backup&restore again. Note the shortcuts that weren't restored in the previous
+        // restore are disabled, so they won't be restored again.
+        dumpsysOnLogcat("Before second backup&restore");
 
         backupAndRestore();
 
-        dumpsysOnLogcat("After second backup");
+        dumpsysOnLogcat("After second backup&restore");
 
-        checkBackupAndRestore_success();
-    }
-
-    public void testBackupAndRestore_backupRestoreMultiple() {
-        prepareForBackupTest();
-
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
-        // This also shouldn't affect the result.
-        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
-            assertTrue(mManager.setDynamicShortcuts(list(
-                    makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"),
-                    makeShortcut("s4"), makeShortcut("s5"), makeShortcut("s6"))));
-        });
-
-        backupAndRestore();
-
-        checkBackupAndRestore_success();
+        checkBackupAndRestore_success(/*firstRestore=*/ false);
     }
 
     public void testBackupAndRestore_restoreToNewVersion() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 5);
 
-        checkBackupAndRestore_success();
+        checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
     public void testBackupAndRestore_restoreToSuperSetSignatures() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         // Change package signatures.
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 1, "sigx", CALLING_PACKAGE_1);
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 4, LAUNCHER_1, "sigy");
 
-        checkBackupAndRestore_success();
+        checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
-    protected void checkBackupAndRestore_success() {
+    protected void checkBackupAndRestore_success(boolean firstRestore) {
         // Make sure non-system user is not restored.
         final ShortcutUser userP0 = mService.getUserShortcutsLocked(USER_P0);
         assertEquals(0, userP0.getAllPackagesForTest().size());
@@ -4818,12 +4856,13 @@
         final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
+
+        assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
                 PackageWithUser.of(USER_0, LAUNCHER_1)));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
                 PackageWithUser.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
         assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
 
@@ -4835,14 +4874,16 @@
 
                     .revertToOriginalList()
                     .selectPinned()
-                    .haveIds("s1", "s2");
+                    .haveIds("s1", "s2")
+                    .areAllEnabled();
         });
 
         installPackage(USER_0, LAUNCHER_1);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s1");
+                    .haveIds("s1")
+                    .areAllEnabled();
 
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
                     .isEmpty();
@@ -4862,17 +4903,20 @@
 
                     .revertToOriginalList()
                     .selectPinned()
-                    .haveIds("s1", "s2", "s3");
+                    .haveIds("s1", "s2", "s3")
+                    .areAllEnabled();
         });
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s1");
+                    .haveIds("s1")
+                    .areAllEnabled();
 
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s1", "s2");
+                    .haveIds("s1", "s2")
+                    .areAllEnabled();
 
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
                     .isEmpty();
@@ -4910,14 +4954,28 @@
         runWithCaller(LAUNCHER_2, USER_0, () -> {
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s2");
+                    .haveIds("s2")
+                    .areAllEnabled();
 
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s2", "s3");
+                    .haveIds("s2", "s3")
+                    .areAllEnabled();
 
-            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    .isEmpty();
+            if (firstRestore) {
+                assertWith(
+                        mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                        .haveIds("s2", "s3", "s4")
+                        .areAllDisabled()
+                        .areAllPinned()
+                        .areAllNotDynamic()
+                        .areAllWithDisabledReason(
+                                ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
+            } else {
+                assertWith(
+                        mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                        .isEmpty();
+            }
 
             assertWith(mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0))
                     .isEmpty();
@@ -4937,14 +4995,27 @@
         runWithCaller(LAUNCHER_1, USER_0, () -> {
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s1");
+                    .haveIds("s1")
+                    .areAllEnabled();
 
             assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
                     .areAllPinned()
-                    .haveIds("s1", "s2");
+                    .haveIds("s1", "s2")
+                    .areAllEnabled();
 
-            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    .isEmpty();
+            if (firstRestore) {
+                assertWith(
+                        mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                        .haveIds("s1", "s2", "s3")
+                        .areAllDisabled()
+                        .areAllPinned()
+                        .areAllNotDynamic()
+                        .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
+            } else {
+                assertWith(
+                        mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                        .isEmpty();
+            }
 
             assertWith(mLauncherApps.getShortcuts(QUERY_ALL, HANDLE_USER_P0))
                     .isEmpty();
@@ -4954,45 +5025,39 @@
         runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
             assertWith(getCallerVisibleShortcuts())
                     .areAllPinned()
-                    .haveIds("s1", "s2", "s3");
+                    .haveIds("s1", "s2", "s3")
+                    .areAllEnabled();
         });
     }
 
     public void testBackupAndRestore_publisherLowerVersion() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
 
-        checkBackupAndRestore_publisherNotRestored();
+        checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_VERSION_LOWER);
     }
 
     public void testBackupAndRestore_publisherWrongSignature() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
 
-        checkBackupAndRestore_publisherNotRestored();
+        checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH);
     }
 
     public void testBackupAndRestore_publisherNoLongerBackupTarget() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         updatePackageInfo(CALLING_PACKAGE_1,
                 pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
 
-        checkBackupAndRestore_publisherNotRestored();
+        checkBackupAndRestore_publisherNotRestored(
+                ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
     }
 
-    protected void checkBackupAndRestore_publisherNotRestored() {
+    protected void checkBackupAndRestore_publisherNotRestored(
+            int package1DisabledReason) {
         installPackage(USER_0, CALLING_PACKAGE_1);
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertEquals(0, mManager.getDynamicShortcuts().size());
@@ -5014,27 +5079,53 @@
 
         installPackage(USER_0, LAUNCHER_1);
         runWithCaller(LAUNCHER_1, USER_0, () -> {
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
-                    /* empty */);
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
-                    "s1", "s2");
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    .haveIds("s1")
+                    .areAllPinned()
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(package1DisabledReason)
+                    .forAllShortcuts(si -> {
+                        switch (package1DisabledReason) {
+                            case ShortcutInfo.DISABLED_REASON_VERSION_LOWER:
+                                assertEquals("This shortcut requires latest app",
+                                        si.getDisabledMessage());
+                                break;
+                            case ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH:
+                                assertEquals(
+                                        "Couldn\u2019t restore shortcut because of app"
+                                        + " signature mismatch",
+                                        si.getDisabledMessage());
+                                break;
+                            case ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED:
+                                assertEquals(
+                                        "Couldn\u2019t restore shortcut because app"
+                                        + " doesn\u2019t support backup and restore",
+                                        si.getDisabledMessage());
+                                break;
+                            default:
+                                fail("Unhandled disabled reason: " + package1DisabledReason);
+                        }
+                    });
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    .haveIds("s1", "s2")
+                    .areAllPinned()
+                    .areAllEnabled();
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    .isEmpty();
         });
         installPackage(USER_0, LAUNCHER_2);
         runWithCaller(LAUNCHER_2, USER_0, () -> {
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
-                    /* empty */);
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
-                    "s2", "s3");
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    .haveIds("s2")
+                    .areAllPinned()
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(package1DisabledReason);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    .haveIds("s2", "s3")
+                    .areAllPinned()
+                    .areAllEnabled();
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    .isEmpty();
         });
 
         installPackage(USER_0, CALLING_PACKAGE_3);
@@ -5044,46 +5135,51 @@
         });
 
         runWithCaller(LAUNCHER_1, USER_0, () -> {
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
-                    /* empty */);
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
-                    "s1", "s2");
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    .haveIds("s1")
+                    .areAllPinned()
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(package1DisabledReason);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    .haveIds("s1", "s2")
+                    .areAllPinned()
+                    .areAllEnabled();
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    .haveIds("s1", "s2", "s3")
+                    .areAllPinned()
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
         });
         runWithCaller(LAUNCHER_2, USER_0, () -> {
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
-                    /* empty */);
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
-                    "s2", "s3");
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    .haveIds("s2")
+                    .areAllPinned()
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(package1DisabledReason);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    .haveIds("s2", "s3")
+                    .areAllPinned()
+                    .areAllEnabled();
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    .haveIds("s2", "s3", "s4")
+                    .areAllPinned()
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
         });
     }
 
     public void testBackupAndRestore_launcherLowerVersion() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
 
-        checkBackupAndRestore_launcherNotRestored();
+        // Note, we restore pinned shortcuts even if the launcher is of a lower version.
+        checkBackupAndRestore_success(/*firstRestore=*/ true);
     }
 
     public void testBackupAndRestore_launcherWrongSignature() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
 
         checkBackupAndRestore_launcherNotRestored();
@@ -5092,9 +5188,6 @@
     public void testBackupAndRestore_launcherNoLongerBackupTarget() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         updatePackageInfo(LAUNCHER_1,
                 pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
 
@@ -5185,18 +5278,12 @@
             assertShortcutIds(assertAllPinned(
                     mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
                     "s2", "s3");
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
         });
     }
 
     public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
         prepareForBackupTest();
 
-        // Note doing a backup & restore again here shouldn't affect the result.
-        backupAndRestore();
-
         updatePackageInfo(CALLING_PACKAGE_1,
                 pi -> pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_ALLOW_BACKUP);
 
@@ -5235,15 +5322,15 @@
         });
         installPackage(USER_0, LAUNCHER_2);
         runWithCaller(LAUNCHER_2, USER_0, () -> {
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
-                    /* empty */);
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
-                    "s2", "s3");
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    .areAllPinned()
+                    .haveIds("s2")
+                    .areAllDisabled();
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    .areAllPinned()
+                    .haveIds("s2", "s3");
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
+                    .isEmpty();
         });
 
         // Because launcher 1 wasn't restored, "s1" is no longer pinned.
@@ -5272,15 +5359,21 @@
                     /* empty */);
         });
         runWithCaller(LAUNCHER_2, USER_0, () -> {
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
-                    /* empty */);
-            assertShortcutIds(assertAllPinned(
-                    mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0)),
-                    "s2", "s3");
-            assertShortcutIds(assertAllPinned(
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_1), HANDLE_USER_0))
+                    .areAllPinned()
+                    .haveIds("s2")
+                    .areAllDisabled();
+            assertWith(mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_2), HANDLE_USER_0))
+                    .areAllPinned()
+                    .haveIds("s2", "s3");
+            assertWith(
                     mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0))
-                    /* empty */);
+                    .haveIds("s2", "s3", "s4")
+                    .areAllDisabled()
+                    .areAllPinned()
+                    .areAllNotDynamic()
+                    .areAllWithDisabledReason(
+                            ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED);
         });
     }
 
@@ -5305,12 +5398,12 @@
         final ShortcutUser user0 = mService.getUserShortcutsLocked(USER_0);
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_1));
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
+        assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
                 PackageWithUser.of(USER_0, LAUNCHER_1)));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
                 PackageWithUser.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
         assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
 
@@ -5421,7 +5514,7 @@
                     .revertToOriginalList()
                     .selectByIds("s1", "s2")
                     .areAllNotDynamic()
-                    ;
+            ;
         });
     }
 
@@ -5555,6 +5648,287 @@
         });
     }
 
+
+    /**
+     * Restored to a lower version with no manifest shortcuts. All shortcuts are now invisible,
+     * and all calls from the publisher should ignore them.
+     */
+    public void testBackupAndRestore_disabledShortcutsAreIgnored() {
+        // Publish two manifest shortcuts.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_5_altalt);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(
+                    makeShortcutWithShortLabel("s1", "original-title"),
+                    makeShortcut("s2"), makeShortcut("s3"))));
+        });
+
+        // Pin from launcher 1.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1,
+                    list("ms1", "ms2", "ms3", "ms4", "s1", "s2"), HANDLE_USER_0);
+        });
+
+        backupAndRestore();
+
+        // Lower the version and remove the manifest shortcuts.
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_0);
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version
+
+        // When re-installing the app, the manifest shortcut should be re-published.
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+        mService.mPackageMonitor.onReceive(mServiceContext,
+                genPackageAddIntent(LAUNCHER_1, USER_0));
+
+        // No shortcuts should be visible to the publisher.
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerVisibleShortcuts())
+                    .isEmpty();
+        });
+
+        final Runnable checkAllDisabledForLauncher = () -> {
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                assertWith(getShortcutAsLauncher(USER_0))
+                        .areAllPinned()
+                        .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2")
+                        .areAllDisabled()
+                        .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER)
+
+                        .forShortcutWithId("s1", si -> {
+                            assertEquals("original-title", si.getShortLabel());
+                        })
+                        .forShortcutWithId("ms1", si -> {
+                            assertEquals("string-com.android.test.1-user:0-res:"
+                                            + R.string.shortcut_title1 + "/en"
+                                    , si.getShortLabel());
+                        })
+                        .forShortcutWithId("ms2", si -> {
+                            assertEquals("string-com.android.test.1-user:0-res:"
+                                            + R.string.shortcut_title2 + "/en"
+                                    , si.getShortLabel());
+                        })
+                        .forShortcutWithId("ms3", si -> {
+                            assertEquals("string-com.android.test.1-user:0-res:"
+                                            + R.string.shortcut_title1 + "/en"
+                                    , si.getShortLabel());
+                            assertEquals("string-com.android.test.1-user:0-res:"
+                                            + R.string.shortcut_title2 + "/en"
+                                    , si.getLongLabel());
+                        })
+                        .forShortcutWithId("ms4", si -> {
+                            assertEquals("string-com.android.test.1-user:0-res:"
+                                            + R.string.shortcut_title2 + "/en"
+                                    , si.getShortLabel());
+                            assertEquals("string-com.android.test.1-user:0-res:"
+                                            + R.string.shortcut_title2 + "/en"
+                                    , si.getLongLabel());
+                        });
+            });
+        };
+
+        checkAllDisabledForLauncher.run();
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+
+            makeCallerForeground(); // CALLING_PACKAGE_1 is now in the foreground.
+
+            // All changing API calls should be ignored.
+
+            getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+            checkAllDisabledForLauncher.run();
+
+            getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+            checkAllDisabledForLauncher.run();
+
+            getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+            checkAllDisabledForLauncher.run();
+
+            getManager().removeAllDynamicShortcuts();
+            getManager().removeDynamicShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2"));
+            checkAllDisabledForLauncher.run();
+
+            getManager().updateShortcuts(list(makeShortcutWithShortLabel("s1", "new-title")));
+            checkAllDisabledForLauncher.run();
+
+
+            // Add a shortcut -- even though ms1 was immutable, it will succeed.
+            assertTrue(getManager().addDynamicShortcuts(list(
+                    makeShortcutWithShortLabel("ms1", "original-title"))));
+
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                assertWith(getShortcutAsLauncher(USER_0))
+                        .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2")
+
+                        .selectByIds("ms1")
+                        .areAllEnabled()
+                        .areAllDynamic()
+                        .areAllPinned()
+                        .forAllShortcuts(si -> {
+                            assertEquals("original-title", si.getShortLabel());
+                        })
+
+                        // The rest still exist and disabled.
+                        .revertToOriginalList()
+                        .selectByIds("ms2", "ms3", "ms4", "s1", "s2")
+                        .areAllDisabled()
+                        .areAllPinned()
+                ;
+            });
+
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcutWithShortLabel("ms2", "new-title-2"))));
+
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                assertWith(getShortcutAsLauncher(USER_0))
+                        .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2")
+
+                        .selectByIds("ms1")
+                        .areAllEnabled()
+                        .areAllNotDynamic() // ms1 was not in the list, so no longer dynamic.
+                        .areAllPinned()
+                        .areAllMutable()
+                        .forAllShortcuts(si -> {
+                            assertEquals("original-title", si.getShortLabel());
+                        })
+
+                        .revertToOriginalList()
+                        .selectByIds("ms2")
+                        .areAllEnabled()
+                        .areAllDynamic()
+                        .areAllPinned()
+                        .areAllMutable()
+                        .forAllShortcuts(si -> {
+                            assertEquals("new-title-2", si.getShortLabel());
+                        })
+
+                        // The rest still exist and disabled.
+                        .revertToOriginalList()
+                        .selectByIds("ms3", "ms4", "s1", "s2")
+                        .areAllDisabled()
+                        .areAllPinned()
+                ;
+            });
+
+            // Prepare for requestPinShortcut().
+            setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+            mPinConfirmActivityFetcher = (packageName, userId) ->
+                    new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
+
+            mManager.requestPinShortcut(
+                    makeShortcutWithShortLabel("ms3", "new-title-3"),
+                    /*PendingIntent=*/ null);
+
+            // Note this was pinned, so it'll be accepted right away.
+            runWithCaller(LAUNCHER_1, USER_0, () -> {
+                assertWith(getShortcutAsLauncher(USER_0))
+                        .selectByIds("ms3")
+                        .areAllEnabled()
+                        .areAllNotDynamic()
+                        .areAllPinned()
+                        .areAllMutable()
+                        .forAllShortcuts(si -> {
+                            assertEquals("new-title-3", si.getShortLabel());
+                            // The new one replaces the old manifest shortcut, so the long label
+                            // should be gone now.
+                            assertNull(si.getLongLabel());
+                        });
+            });
+
+            // Now, change the launcher to launcher2, and request pin again.
+            setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
+
+            reset(mServiceContext);
+
+            assertTrue(mManager.isRequestPinShortcutSupported());
+            mManager.requestPinShortcut(
+                    makeShortcutWithShortLabel("ms4", "new-title-4"),
+                    /*PendingIntent=*/ null);
+
+            // Initially there should be no pinned shortcuts for L2.
+            runWithCaller(LAUNCHER_2, USER_0, () -> {
+                assertWith(getShortcutAsLauncher(USER_0))
+                        .selectPinned()
+                        .isEmpty();
+
+                final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+                verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+                assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT,
+                        intent.getValue().getAction());
+                assertEquals(LAUNCHER_2, intent.getValue().getComponent().getPackageName());
+
+                // Check the request object.
+                final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+                assertNotNull(request);
+                assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, request.getRequestType());
+
+                assertWith(request.getShortcutInfo())
+                        .haveIds("ms4")
+                        .areAllOrphan()
+                        .forAllShortcuts(si -> {
+                            assertEquals("new-title-4", si.getShortLabel());
+                            // The new one replaces the old manifest shortcut, so the long label
+                            // should be gone now.
+                            assertNull(si.getLongLabel());
+                        });
+                assertTrue(request.accept());
+
+                assertWith(getShortcutAsLauncher(USER_0))
+                        .selectPinned()
+                        .haveIds("ms4")
+                        .areAllEnabled();
+            });
+        });
+    }
+
+    /**
+     * Test for restoring the pre-P backup format.
+     */
+    public void testBackupAndRestore_api27format() throws Exception {
+        final byte[] payload = readTestAsset("shortcut/shortcut_api27_backup.xml").getBytes();
+
+        addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "22222");
+        addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "11111");
+
+        runWithSystemUid(() -> mService.applyRestore(payload, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertWith(getCallerShortcuts())
+                    .areAllPinned()
+                    .haveIds("s1")
+                    .areAllEnabled();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertWith(getShortcutAsLauncher(USER_0))
+                    .areAllPinned()
+                    .haveIds("s1")
+                    .areAllEnabled();
+        });
+        // Make sure getBackupSourceVersionCode and isBackupSourceBackupAllowed
+        // are correct. We didn't have them in the old format.
+        assertEquals(8, mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0)
+                .getPackageInfo().getBackupSourceVersionCode());
+        assertTrue(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0)
+                .getPackageInfo().isBackupSourceBackupAllowed());
+
+        assertEquals(9, mService.getLauncherShortcutForTest(LAUNCHER_1, USER_0)
+                .getPackageInfo().getBackupSourceVersionCode());
+        assertTrue(mService.getLauncherShortcutForTest(LAUNCHER_1, USER_0)
+                .getPackageInfo().isBackupSourceBackupAllowed());
+
+    }
+
     public void testSaveAndLoad_crossProfile() {
         prepareCrossProfileDataSet();
 
@@ -6048,7 +6422,7 @@
 
         addManifestShortcutResource(
                 new ComponentName(CALLING_PACKAGE_2, ShortcutActivity.class.getName()),
-                R.xml.shortcut_5);
+                R.xml.shortcut_5_altalt);
         updatePackageVersion(CALLING_PACKAGE_2, 1);
                 mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
@@ -6090,6 +6464,8 @@
                 mService.mPackageMonitor.onReceive(getTestContext(),
                 genPackageAddIntent(CALLING_PACKAGE_2, USER_0));
 
+        dumpsysOnLogcat("After updating package 2");
+
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertShortcutIds(assertAllManifest(assertAllImmutable(assertAllEnabled(
                     mManager.getManifestShortcuts()))),
@@ -6110,11 +6486,27 @@
                     "ms2", "ms3");
             // ms3 is no longer in manifest, so should be disabled.
             // but ms1 and ms2 should be enabled.
-            assertAllEnabled(list(getCallerShortcut("ms1")));
-            assertAllEnabled(list(getCallerShortcut("ms2")));
-            assertAllDisabled(list(getCallerShortcut("ms3")));
+            assertWith(getCallerShortcuts())
+                    .selectByIds("ms1", "ms2")
+                    .areAllEnabled()
+
+                    .revertToOriginalList()
+                    .selectByIds("ms3")
+                    .areAllDisabled()
+                    .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_APP_CHANGED);
         });
 
+        // Make sure the launcher see the correct disabled reason.
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            assertWith(getShortcutAsLauncher(USER_0))
+                    .forShortcutWithId("ms3", si -> {
+                        assertEquals("string-com.android.test.2-user:0-res:"
+                                        + R.string.shortcut_disabled_message3 + "/en",
+                                si.getDisabledMessage());
+                    });
+        });
+
+
         // Package 2 on user 10 has no shortcuts yet.
         runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
             assertEmpty(mManager.getManifestShortcuts());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 3220ea9..fcdadac 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -1131,7 +1131,8 @@
         assertEquals(0, si.getRank());
         assertEquals(1, si.getExtras().getInt("k"));
 
-        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
+                | ShortcutInfo.FLAG_SHADOW , si.getFlags());
         assertNull(si.getBitmapPath()); // No icon.
         assertEquals(0, si.getIconResourceId());
 
@@ -1198,7 +1199,8 @@
         assertEquals(0, si.getRank());
         assertEquals(1, si.getExtras().getInt("k"));
 
-        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED, si.getFlags());
+        assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_STRINGS_RESOLVED
+                | ShortcutInfo.FLAG_SHADOW , si.getFlags());
         assertNull(si.getBitmapPath()); // No icon.
         assertEquals(0, si.getIconResourceId());
         assertEquals(null, si.getIconResName());
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
index 7a676e25..bb35beb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDataPreparerTest.java
@@ -154,9 +154,6 @@
         File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
         systemDeDir.mkdirs();
         writeFile(new File(systemDeDir, "file"), "-----" );
-        File miscDeDir = mUserDataPreparer.getDataMiscDeDirectory(TEST_USER_ID);
-        miscDeDir.mkdirs();
-        writeFile(new File(miscDeDir, "file"), "-----" );
 
         mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
 
@@ -168,8 +165,6 @@
         assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(systemDir)));
         assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
                 systemDeDir)));
-        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
-                miscDeDir)));
     }
 
     @Test
@@ -177,9 +172,6 @@
         File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
         systemCeDir.mkdirs();
         writeFile(new File(systemCeDir, "file"), "-----" );
-        File miscCeDir = mUserDataPreparer.getDataMiscCeDirectory(TEST_USER_ID);
-        miscCeDir.mkdirs();
-        writeFile(new File(miscCeDir, "file"), "-----" );
 
         mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
 
@@ -190,8 +182,6 @@
 
         assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
                 systemCeDir)));
-        assertEquals(Collections.emptyList(), Arrays.asList(FileUtils.listFilesOrEmpty(
-                miscCeDir)));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
index b64716c..c2072df 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java
@@ -52,6 +52,7 @@
         assertFalse(opt.isDexoptOnlySharedDex());
         assertFalse(opt.isDowngrade());
         assertFalse(opt.isForce());
+        assertFalse(opt.isDexoptIdleBackgroundJob());
     }
 
     @Test
@@ -63,7 +64,8 @@
                 DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
                 DexoptOptions.DEXOPT_ONLY_SHARED_DEX |
                 DexoptOptions.DEXOPT_DOWNGRADE  |
-                DexoptOptions.DEXOPT_AS_SHARED_LIBRARY;
+                DexoptOptions.DEXOPT_AS_SHARED_LIBRARY |
+                DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
 
         DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags);
         assertEquals(mPackageName, opt.getPackageName());
@@ -76,6 +78,7 @@
         assertTrue(opt.isDowngrade());
         assertTrue(opt.isForce());
         assertTrue(opt.isDexoptAsSharedLibrary());
+        assertTrue(opt.isDexoptIdleBackgroundJob());
     }
 
     @Test
@@ -137,4 +140,4 @@
 
         assertTrue(gotException);
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
index 375edf3..b647b99 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
@@ -149,11 +149,13 @@
         assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(3L);
         assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(10L);
         assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(7L);
-        assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(15L);
+        assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(10L);
+        assertThat(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY)).isEqualTo(5L);
         assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(55L);
         assertThat(
                 json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(1L);
         assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(1L);
+        assertThat(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY).length()).isEqualTo(1L);
         assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(1L);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 42ddedf..67ffe58 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -16,6 +16,11 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.AppStandby.REASON_TIMEOUT;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
+
+import android.app.usage.AppStandby;
 import android.os.FileUtils;
 import android.test.AndroidTestCase;
 
@@ -28,6 +33,8 @@
     final static String PACKAGE_1 = "com.android.testpackage1";
     final static String PACKAGE_2 = "com.android.testpackage2";
 
+    final static int USER_ID = 0;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -42,7 +49,6 @@
     }
 
     public void testFilesCreation() {
-        final int userId = 0;
         AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0);
 
         aih.updateDisplay(true, /* elapsedRealtime= */ 1000);
@@ -50,9 +56,9 @@
         // Screen On time file should be written right away
         assertTrue(aih.getScreenOnTimeFile().exists());
 
-        aih.writeAppIdleTimes(userId);
+        aih.writeAppIdleTimes(USER_ID);
         // stats file should be written now
-        assertTrue(new File(new File(mStorageDir, "users/" + userId),
+        assertTrue(new File(new File(mStorageDir, "users/" + USER_ID),
                 AppIdleHistory.APP_IDLE_FILENAME).exists());
     }
 
@@ -77,24 +83,33 @@
         assertEquals(aih2.getScreenOnTime(13000), 4000);
     }
 
-    public void testPackageEvents() {
+    public void testBuckets() {
         AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
-        aih.setThresholds(4000, 1000);
-        aih.updateDisplay(true, 1000);
-        // App is not-idle by default
-        assertFalse(aih.isIdle(PACKAGE_1, 0, 1500));
-        // Still not idle
-        assertFalse(aih.isIdle(PACKAGE_1, 0, 3000));
-        // Idle now
-        assertTrue(aih.isIdle(PACKAGE_1, 0, 8000));
-        // Not idle
-        assertFalse(aih.isIdle(PACKAGE_2, 0, 9000));
 
-        // Screen off
-        aih.updateDisplay(false, 9100);
-        // Still idle after 10 seconds because screen hasn't been on long enough
-        assertFalse(aih.isIdle(PACKAGE_2, 0, 20000));
-        aih.updateDisplay(true, 21000);
-        assertTrue(aih.isIdle(PACKAGE_2, 0, 23000));
+        aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE,
+                AppStandby.REASON_USAGE);
+        // ACTIVE means not idle
+        assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000));
+
+        aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
+                AppStandby.REASON_USAGE);
+        aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
+                REASON_TIMEOUT);
+
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT);
+
+        // RARE is considered idle
+        assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000));
+        assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000));
+
+        // Check persistence
+        aih.writeAppIdleDurations();
+        aih.writeAppIdleTimes(USER_ID);
+        aih = new AppIdleHistory(mStorageDir, 4000);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
+        assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT);
     }
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
new file mode 100644
index 0000000..9846d6f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -0,0 +1,322 @@
+/*
+ * 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.server.usage;
+
+import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE;
+import static android.app.usage.AppStandby.STANDBY_BUCKET_WORKING_SET;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.app.usage.AppStandby;
+import android.app.usage.UsageEvents;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+
+import com.android.server.SystemService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test for AppStandbyController.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppStandbyControllerTests {
+
+    private static final String PACKAGE_1 = "com.example.foo";
+    private static final int UID_1 = 10000;
+    private static final int USER_ID = 0;
+
+    private static final long MINUTE_MS = 60 * 1000;
+    private static final long HOUR_MS = 60 * MINUTE_MS;
+    private static final long DAY_MS = 24 * HOUR_MS;
+
+    private MyInjector mInjector;
+
+    static class MyContextWrapper extends ContextWrapper {
+        PackageManager mockPm = mock(PackageManager.class);
+
+        public MyContextWrapper(Context base) {
+            super(base);
+        }
+
+        public PackageManager getPackageManager() {
+            return mockPm;
+        }
+    }
+
+    static class MyInjector extends AppStandbyController.Injector {
+        long mElapsedRealtime;
+        boolean mIsCharging;
+        List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
+        boolean mDisplayOn;
+        DisplayManager.DisplayListener mDisplayListener;
+        String mBoundWidgetPackage;
+
+        MyInjector(Context context, Looper looper) {
+            super(context, looper);
+        }
+
+        @Override
+        void onBootPhase(int phase) {
+        }
+
+        @Override
+        int getBootPhase() {
+            return SystemService.PHASE_BOOT_COMPLETED;
+        }
+
+        @Override
+        long elapsedRealtime() {
+            return mElapsedRealtime;
+        }
+
+        @Override
+        long currentTimeMillis() {
+            return mElapsedRealtime;
+        }
+
+        @Override
+        boolean isAppIdleEnabled() {
+            return true;
+        }
+
+        @Override
+        boolean isCharging() {
+            return mIsCharging;
+        }
+
+        @Override
+        boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+            return mPowerSaveWhitelistExceptIdle.contains(packageName);
+        }
+
+        @Override
+        File getDataSystemDirectory() {
+            return new File(getContext().getFilesDir(), Long.toString(Math.randomLongInternal()));
+        }
+
+        @Override
+        void noteEvent(int event, String packageName, int uid) throws RemoteException {
+        }
+
+        @Override
+        boolean isPackageEphemeral(int userId, String packageName) {
+            // TODO: update when testing ephemeral apps scenario
+            return false;
+        }
+
+        @Override
+        int[] getRunningUserIds() {
+            return new int[] {USER_ID};
+        }
+
+        @Override
+        boolean isDefaultDisplayOn() {
+            return mDisplayOn;
+        }
+
+        @Override
+        void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayListener = listener;
+        }
+
+        @Override
+        String getActiveNetworkScorer() {
+            return null;
+        }
+
+        @Override
+        public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+                int userId) {
+            return packageName != null && packageName.equals(mBoundWidgetPackage);
+        }
+
+        // Internal methods
+
+        void setDisplayOn(boolean on) {
+            mDisplayOn = on;
+            if (mDisplayListener != null) {
+                mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
+            }
+        }
+    }
+
+    private void setupPm(PackageManager mockPm) throws PackageManager.NameNotFoundException {
+        List<PackageInfo> packages = new ArrayList<>();
+        PackageInfo pi = new PackageInfo();
+        pi.applicationInfo = new ApplicationInfo();
+        pi.applicationInfo.uid = UID_1;
+        pi.packageName = PACKAGE_1;
+        packages.add(pi);
+
+        doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
+        try {
+            doReturn(UID_1).when(mockPm).getPackageUidAsUser(anyString(), anyInt(), anyInt());
+            doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(anyString(), anyInt());
+        } catch (PackageManager.NameNotFoundException nnfe) {}
+    }
+
+    private void setChargingState(AppStandbyController controller, boolean charging) {
+        mInjector.mIsCharging = charging;
+        if (controller != null) {
+            controller.setChargingState(charging);
+        }
+    }
+
+    private AppStandbyController setupController() throws Exception {
+        mInjector.mElapsedRealtime = 0;
+        AppStandbyController controller = new AppStandbyController(mInjector);
+        controller.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mInjector.setDisplayOn(false);
+        mInjector.setDisplayOn(true);
+        setChargingState(controller, false);
+        setupPm(mInjector.getContext().getPackageManager());
+        controller.checkIdleStates(USER_ID);
+
+        return controller;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
+        mInjector = new MyInjector(myContext, Looper.getMainLooper());
+    }
+
+    @Test
+    public void testCharging() throws Exception {
+        AppStandbyController controller = setupController();
+
+        setChargingState(controller, true);
+        mInjector.mElapsedRealtime = 8 * DAY_MS;
+        assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
+                mInjector.mElapsedRealtime, false));
+
+        setChargingState(controller, false);
+        mInjector.mElapsedRealtime = 16 * DAY_MS;
+        controller.checkIdleStates(USER_ID);
+        assertTrue(controller.isAppIdleFilteredOrParoled(PACKAGE_1, USER_ID,
+                mInjector.mElapsedRealtime, false));
+        setChargingState(controller, true);
+        assertFalse(controller.isAppIdleFilteredOrParoled(PACKAGE_1,USER_ID,
+                mInjector.mElapsedRealtime, false));
+    }
+
+    private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
+        mInjector.mElapsedRealtime = elapsedTime;
+        controller.checkIdleStates(USER_ID);
+        assertEquals(bucket,
+                controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+                        false));
+    }
+
+    @Test
+    public void testBuckets() throws Exception {
+        AppStandbyController controller = setupController();
+
+        // ACTIVE bucket
+        assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+
+        // WORKING_SET bucket
+        assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+        // WORKING_SET bucket
+        assertTimeout(controller, 47 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+        // FREQUENT bucket
+        assertTimeout(controller, 4 * DAY_MS, STANDBY_BUCKET_FREQUENT);
+
+        // RARE bucket
+        assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_RARE);
+
+        // Back to ACTIVE on event
+        UsageEvents.Event ev = new UsageEvents.Event();
+        ev.mPackage = PACKAGE_1;
+        ev.mEventType = UsageEvents.Event.USER_INTERACTION;
+        controller.reportEvent(ev, mInjector.mElapsedRealtime, USER_ID);
+
+        assertTimeout(controller, 9 * DAY_MS, STANDBY_BUCKET_ACTIVE);
+
+        // RARE bucket
+        assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testScreenTimeAndBuckets() throws Exception {
+        AppStandbyController controller = setupController();
+        mInjector.setDisplayOn(false);
+
+        // ACTIVE bucket
+        assertTimeout(controller, 11 * HOUR_MS, STANDBY_BUCKET_ACTIVE);
+
+        // WORKING_SET bucket
+        assertTimeout(controller, 25 * HOUR_MS, STANDBY_BUCKET_WORKING_SET);
+
+        // RARE bucket, should fail because the screen wasn't ON.
+        mInjector.mElapsedRealtime = 9 * DAY_MS;
+        controller.checkIdleStates(USER_ID);
+        assertNotEquals(STANDBY_BUCKET_RARE,
+                controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
+                false));
+
+        mInjector.setDisplayOn(true);
+        assertTimeout(controller, 18 * DAY_MS, STANDBY_BUCKET_RARE);
+    }
+
+    @Test
+    public void testForcedIdle() throws Exception {
+        AppStandbyController controller = setupController();
+        setChargingState(controller, false);
+
+        controller.forceIdleState(PACKAGE_1, USER_ID, true);
+        assertEquals(STANDBY_BUCKET_RARE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
+                true));
+        assertTrue(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+
+        controller.forceIdleState(PACKAGE_1, USER_ID, false);
+        assertEquals(STANDBY_BUCKET_ACTIVE, controller.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
+                true));
+        assertFalse(controller.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
index 8a312f6..da45d81 100644
--- a/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/PriorityDumpTest.java
@@ -19,6 +19,7 @@
 import static com.android.server.utils.PriorityDump.dump;
 
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.same;
 import static org.mockito.Mockito.verify;
@@ -59,13 +60,13 @@
     @Test
     public void testNullArgs() {
         dump(mDumper, mFd, mPw, null);
-        verify(mDumper).dump(same(mFd), same(mPw), eq(null));
+        verify(mDumper).dump(same(mFd), same(mPw), eq(null), /* asProto= */ eq(false));
     }
 
     @Test
     public void testNoArgs() {
         dump(mDumper, mFd, mPw, EMPTY_ARGS);
-        verify(mDumper).dump(same(mFd), same(mPw), same(EMPTY_ARGS));
+        verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
     }
 
     @Test
@@ -74,7 +75,7 @@
                 "--dumb_priority"
         };
         dump(mDumper, mFd, mPw, args);
-        verify(mDumper).dump(same(mFd), same(mPw), same(args));
+        verify(mDumper).dump(same(mFd), same(mPw), eq(args), /* asProto= */ eq(false));
     }
 
     @Test
@@ -83,7 +84,7 @@
                 "--dump-priority"
         };
         dump(mDumper, mFd, mPw, args);
-        verify(mDumper).dump(same(mFd), same(mPw), same(args));
+        verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
     }
 
     @Test
@@ -92,7 +93,7 @@
                 "--dump-priority", "SUPER_HIGH"
         };
         dump(mDumper, mFd, mPw, args);
-        verify(mDumper).dump(same(mFd), same(mPw), same(args));
+        verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
     }
 
     @Test
@@ -101,7 +102,9 @@
                 "--dump-priority", "SUPER_HIGH", "--high", "--five"
         };
         dump(mDumper, mFd, mPw, args);
-        verify(mDumper).dump(same(mFd), same(mPw), same(args));
+        verify(mDumper).dump(same(mFd), same(mPw), eq(new String[] {
+            "--high", "--five"
+        }), /* asProto= */ eq(false));
     }
 
     @Test
@@ -117,13 +120,13 @@
 
         assertSame(mFd, fakeDumper.criticalFd);
         assertSame(mPw, fakeDumper.criticalPw);
-        assertSame(args, fakeDumper.criticalArgs);
+        assertArrayEquals(args, fakeDumper.criticalArgs);
         assertSame(mFd, fakeDumper.highFd);
         assertSame(mPw, fakeDumper.highPw);
-        assertSame(args, fakeDumper.highArgs);
+        assertArrayEquals(args, fakeDumper.highArgs);
         assertSame(mFd, fakeDumper.normalFd);
         assertSame(mPw, fakeDumper.normalPw);
-        assertSame(args, fakeDumper.normalArgs);
+        assertArrayEquals(args, fakeDumper.normalArgs);
     }
 
     @Test
@@ -131,7 +134,8 @@
         dump(mDumper, mFd, mPw, new String[] {
                 "--dump-priority", "CRITICAL"
         });
-        verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(EMPTY_ARGS));
+        verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(EMPTY_ARGS),
+                /* asProto= */ eq(false));
     }
 
     @Test
@@ -141,7 +145,27 @@
         });
         verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
                 "--high", "--five"
-        }));
+        }), /* asProto= */ eq(false));
+    }
+
+    @Test
+    public void testCriticalExtraArgsInMiddle() {
+        dump(mDumper, mFd, mPw, new String[] {
+                "--high", "--dump-priority", "CRITICAL", "--five"
+        });
+        verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
+                "--high", "--five"
+        }), /* asProto= */ eq(false));
+    }
+
+    @Test
+    public void testCriticalExtraArgsAtEnd() {
+        dump(mDumper, mFd, mPw, new String[] {
+                "--high", "--five", "--dump-priority", "CRITICAL"
+        });
+        verify(mDumper).dumpCritical(same(mFd), same(mPw), eq(new String[] {
+                "--high", "--five"
+        }), /* asProto= */ eq(false));
     }
 
     @Test
@@ -149,7 +173,7 @@
         dump(mDumper, mFd, mPw, new String[] {
                 "--dump-priority", "HIGH"
         });
-        verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(EMPTY_ARGS));
+        verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
     }
 
     @Test
@@ -159,7 +183,7 @@
         });
         verify(mDumper).dumpHigh(same(mFd), same(mPw), eq(new String[] {
                 "--high", "--five"
-        }));
+        }), /* asProto= */ eq(false));
     }
 
     @Test
@@ -167,17 +191,58 @@
         dump(mDumper, mFd, mPw, new String[] {
                 "--dump-priority", "NORMAL"
         });
-        verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(EMPTY_ARGS));
+        verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(false));
     }
 
     @Test
     public void testNormalExtraArgs() {
-        dump(mDumper, mFd, mPw, new String[] {
+        dump(mDumper, mFd, mPw, new String[]{
                 "--dump-priority", "NORMAL", "--high", "--five"
         });
-        verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(new String[] {
+        verify(mDumper).dumpNormal(same(mFd), same(mPw), eq(new String[]{
                 "--high", "--five"
-        }));
+        }), /* asProto= */ eq(false));
+    }
+
+    @Test
+    public void testProtoArgs() {
+        dump(mDumper, mFd, mPw, new String[]{"--proto"});
+        verify(mDumper).dump(same(mFd), same(mPw), eq(EMPTY_ARGS), /* asProto= */ eq(true));
+    }
+
+    @Test
+    public void testProtoArgsWithPriorityArgs() {
+        dump(mDumper, mFd, mPw, new String[]{"--proto", "--dump-priority", "NORMAL", "--five"});
+        verify(mDumper).dumpNormal(same(mFd), same(mPw),
+                eq(new String[]{"--five"}), /* asProto= */ eq(true));
+    }
+
+    @Test
+    public void testProtoArgsWithPriorityArgsReverseOrder() {
+        dump(mDumper, mFd, mPw, new String[]{"--dump-priority", "NORMAL", "--proto", "--five"});
+        verify(mDumper).dumpNormal(same(mFd), same(mPw),
+                eq(new String[]{"--five"}), /* asProto= */ eq(true));
+    }
+
+    @Test
+    public void testProtoArgsInMiddle() {
+        dump(mDumper, mFd, mPw, new String[]{"--unknown", "--proto", "--five"});
+        verify(mDumper).dump(same(mFd), same(mPw),
+                eq(new String[]{"--unknown", "--five"}), /* asProto= */ eq(true));
+    }
+
+    @Test
+    public void testProtoArgsAtEnd() {
+        dump(mDumper, mFd, mPw, new String[]{"args", "-left", "--behind", "--proto"});
+        verify(mDumper).dump(same(mFd), same(mPw),
+                eq(new String[]{"args", "-left", "--behind"}), /* asProto= */ eq(true));
+    }
+
+    @Test
+    public void testProtoArgsWithInvalidPriorityType() {
+        dump(mDumper, mFd, mPw, new String[]{"--dump-priority", "HIGH?", "--proto"});
+        verify(mDumper).dump(same(mFd), same(mPw),
+                eq(EMPTY_ARGS), /* asProto= */ eq(true));
     }
 
     private final class FakeDumper implements PriorityDumper {
@@ -187,21 +252,22 @@
         PrintWriter criticalPw, highPw, normalPw;
 
         @Override
-        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+                boolean asProto) {
             criticalFd = fd;
             criticalPw = pw;
             criticalArgs = args;
         }
 
         @Override
-        public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args) {
+        public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
             highFd = fd;
             highPw = pw;
             highArgs = args;
         }
 
         @Override
-        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
             normalFd = fd;
             normalPw = pw;
             normalArgs = args;
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
index f732503..27c5eab 100644
--- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -16,7 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -347,17 +348,17 @@
      */
     @Test
     public void testPinnedStackLocation() {
-        createStackControllerOnStackOnDisplay(PINNED_STACK_ID, mDisplayContent);
-        final int initialStackCount = mDisplayContent.getStackCount();
-        // Ensure that the pinned stack was placed at the end
-        assertEquals(initialStackCount - 1, mDisplayContent.getStackPosById(PINNED_STACK_ID));
+        final TaskStack pinnedStack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
+        // Ensure that the pinned stack is the top stack
+        assertEquals(pinnedStack, mDisplayContent.getPinnedStack());
+        assertEquals(pinnedStack, mDisplayContent.getTopStack());
         // By default, this should try to create a new stack on top
-        createTaskStackOnDisplay(mDisplayContent);
-        final int afterStackCount = mDisplayContent.getStackCount();
-        // Make sure the stack count has increased
-        assertEquals(initialStackCount + 1, afterStackCount);
+        final TaskStack otherStack = createTaskStackOnDisplay(mDisplayContent);
+        // Ensure that the other stack is on the display.
+        assertEquals(mDisplayContent, otherStack.getDisplayContent());
         // Ensure that the pinned stack is still on top
-        assertEquals(afterStackCount - 1, mDisplayContent.getStackPosById(PINNED_STACK_ID));
+        assertEquals(pinnedStack, mDisplayContent.getTopStack());
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
index 29bbe6e..71bcae7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -16,26 +16,23 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-
-import android.content.res.Configuration;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.After;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.After;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
 /**
  * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
  *
@@ -52,13 +49,8 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        final Configuration overrideConfig = new Configuration();
-        overrideConfig.windowConfiguration.setWindowingMode(
-                getWindowingModeForStackId(PINNED_STACK_ID, false /* inSplitScreenMode */));
-        mPinnedStack = new StackWindowController(PINNED_STACK_ID, null,
-                mDisplayContent.getDisplayId(), true /* onTop */, new Rect(), sWm).mContainer;
-        mPinnedStack.onOverrideConfigurationChanged(overrideConfig);
-
+        mPinnedStack = createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer;
         // Stack should contain visible app window to be considered visible.
         final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
         assertFalse(mPinnedStack.isVisible());
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
index 31aad79..e78224c 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -26,10 +26,10 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.view.DisplayInfo;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_WINDOWING_MODE;
 import static android.content.pm.ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
@@ -155,7 +155,7 @@
         shiftedBounds.offset(10, 10);
         final Rect expectedBounds = new Rect(mParentBounds);
         expectedBounds.intersect(shiftedBounds);
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, shiftedBounds,
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, shiftedBounds,
                 expectedBounds);
     }
 
@@ -163,7 +163,7 @@
     @Test
     public void testAppBounds_EmptyBounds() throws Exception {
         final Rect emptyBounds = new Rect();
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, emptyBounds,
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, emptyBounds,
                 null /*ExpectedBounds*/);
     }
 
@@ -172,7 +172,7 @@
     public void testAppBounds_FreeFormBounds() throws Exception {
         final Rect freeFormBounds = new Rect(mParentBounds);
         freeFormBounds.offset(10, 10);
-        testStackBoundsConfiguration(FREEFORM_WORKSPACE_STACK_ID, mParentBounds, freeFormBounds,
+        testStackBoundsConfiguration(WINDOWING_MODE_FREEFORM, mParentBounds, freeFormBounds,
                 freeFormBounds);
     }
 
@@ -181,7 +181,8 @@
     public void testAppBounds_ContainedBounds() throws Exception {
         final Rect insetBounds = new Rect(mParentBounds);
         insetBounds.inset(5, 5, 5, 5);
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, insetBounds, insetBounds);
+        testStackBoundsConfiguration(
+                WINDOWING_MODE_FULLSCREEN, mParentBounds, insetBounds, insetBounds);
     }
 
     /** Ensures that full screen free form bounds are clipped */
@@ -189,15 +190,14 @@
     public void testAppBounds_FullScreenFreeFormBounds() throws Exception {
         final Rect fullScreenBounds = new Rect(0, 0, mDisplayInfo.logicalWidth,
                 mDisplayInfo.logicalHeight);
-        testStackBoundsConfiguration(null /*stackId*/, mParentBounds, fullScreenBounds,
+        testStackBoundsConfiguration(WINDOWING_MODE_FULLSCREEN, mParentBounds, fullScreenBounds,
                 mParentBounds);
     }
 
-    private void testStackBoundsConfiguration(Integer stackId, Rect parentBounds, Rect bounds,
+    private void testStackBoundsConfiguration(int windowingMode, Rect parentBounds, Rect bounds,
             Rect expectedConfigBounds) {
-        final StackWindowController stackController = stackId != null ?
-                createStackControllerOnStackOnDisplay(stackId, mDisplayContent)
-                : createStackControllerOnDisplay(mDisplayContent);
+        final StackWindowController stackController = createStackControllerOnStackOnDisplay(
+                        windowingMode, ACTIVITY_TYPE_STANDARD, mDisplayContent);
 
         final Configuration parentConfig = mDisplayContent.getConfiguration();
         parentConfig.windowConfiguration.setAppBounds(parentBounds);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
index e1f318d..3c3514f 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowLayersControllerTests.java
@@ -23,9 +23,11 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -160,12 +162,14 @@
 
     @Test
     public void testStackLayers() throws Exception {
-        WindowState pinnedStackWindow = createWindowOnStack(null, PINNED_STACK_ID,
-                TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow");
-        WindowState dockedStackWindow = createWindowOnStack(null, DOCKED_STACK_ID,
-                TYPE_BASE_APPLICATION, mDisplayContent, "dockedStackWindow");
-        WindowState assistantStackWindow = createWindowOnStack(null, ASSISTANT_STACK_ID,
-                TYPE_BASE_APPLICATION, mDisplayContent, "assistantStackWindow");
+        WindowState pinnedStackWindow = createWindowOnStack(null, WINDOWING_MODE_PINNED,
+                ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow");
+        WindowState dockedStackWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "dockedStackWindow");
+        WindowState assistantStackWindow = createWindowOnStack(null, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
+                mDisplayContent, "assistantStackWindow");
 
         mLayersController.assignWindowLayers(mDisplayContent);
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
index 0315c8d..1aafac6 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -32,6 +32,7 @@
 import static android.content.res.Configuration.EMPTY;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  * A collection of static functions that can be referenced by other test packages to provide access
@@ -51,7 +52,10 @@
      * Retrieves an instance of a mock {@link WindowManagerService}.
      */
     public static WindowManagerService getMockWindowManagerService() {
-        return mock(WindowManagerService.class);
+        final WindowManagerService service = mock(WindowManagerService.class);
+        final WindowHashMap windowMap = new WindowHashMap();
+        when(service.getWindowManagerLock()).thenReturn(windowMap);
+        return service;
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 3df13ab..0980f7e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,7 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.View.VISIBLE;
 
@@ -35,8 +36,6 @@
 import android.view.IWindow;
 import android.view.WindowManager;
 
-import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.AppOpsManager.OP_NONE;
 import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
@@ -64,12 +63,12 @@
 class WindowTestsBase {
     static WindowManagerService sWm = null;
     private static final IWindow sIWindow = new TestIWindow();
-    private static final Session sMockSession = mock(Session.class);
+    private static Session sMockSession;
     // The default display is removed in {@link #setUp} and then we iterate over all displays to
     // make sure we don't collide with any existing display. If we run into no other display, the
     // added display should be treated as default. This cannot be the default display
     private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
-    private static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
+    static int sNextStackId = 1000;
 
     private static boolean sOneTimeSetupDone = false;
     DisplayContent mDisplayContent;
@@ -90,7 +89,11 @@
     public void setUp() throws Exception {
         if (!sOneTimeSetupDone) {
             sOneTimeSetupDone = true;
+
+            // Allows to mock package local classes and methods
+            System.setProperty("dexmaker.share_classloader", "true");
             MockitoAnnotations.initMocks(this);
+            sMockSession = mock(Session.class);
         }
 
         final Context context = InstrumentationRegistry.getTargetContext();
@@ -167,14 +170,14 @@
         sWm.mAnimationHandler.runWithScissors(() -> { }, 0);
     }
 
-    private WindowToken createWindowToken(DisplayContent dc, int stackId, int type) {
+    private WindowToken createWindowToken(
+            DisplayContent dc, int windowingMode, int activityType, int type) {
         if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
             return new WindowTestUtils.TestWindowToken(type, dc);
         }
 
-        final TaskStack stack = stackId == INVALID_STACK_ID
-                ? createTaskStackOnDisplay(dc)
-                : createStackControllerOnStackOnDisplay(stackId, dc).mContainer;
+        final TaskStack stack =
+                createStackControllerOnStackOnDisplay(windowingMode, activityType, dc).mContainer;
         final Task task = createTaskInStack(stack, 0 /* userId */);
         final WindowTestUtils.TestAppWindowToken token = new WindowTestUtils.TestAppWindowToken(dc);
         task.addChild(token, 0);
@@ -187,9 +190,9 @@
                 : createWindow(parent, type, parent.mToken, name);
     }
 
-    WindowState createWindowOnStack(WindowState parent, int stackId, int type,
-            DisplayContent dc, String name) {
-        final WindowToken token = createWindowToken(dc, stackId, type);
+    WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType,
+            int type, DisplayContent dc, String name) {
+        final WindowToken token = createWindowToken(dc, windowingMode, activityType, type);
         return createWindow(parent, type, token, name);
     }
 
@@ -200,13 +203,15 @@
     }
 
     WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
-        final WindowToken token = createWindowToken(dc, INVALID_STACK_ID, type);
+        final WindowToken token = createWindowToken(
+                dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
         return createWindow(parent, type, token, name);
     }
 
     WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
             boolean ownerCanAddInternalSystemWindow) {
-        final WindowToken token = createWindowToken(dc, INVALID_STACK_ID, type);
+        final WindowToken token = createWindowToken(
+                dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
         return createWindow(parent, type, token, name, ownerCanAddInternalSystemWindow);
     }
 
@@ -233,14 +238,16 @@
     }
 
     StackWindowController createStackControllerOnDisplay(DisplayContent dc) {
-        final int stackId = ++sNextStackId;
-        return createStackControllerOnStackOnDisplay(stackId, dc);
+        return createStackControllerOnStackOnDisplay(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
     }
 
-    StackWindowController createStackControllerOnStackOnDisplay(int stackId, DisplayContent dc) {
+    StackWindowController createStackControllerOnStackOnDisplay(
+            int windowingMode, int activityType, DisplayContent dc) {
         final Configuration overrideConfig = new Configuration();
-        overrideConfig.windowConfiguration.setWindowingMode(
-                getWindowingModeForStackId(stackId, false /* inSplitScreenMode */));
+        overrideConfig.windowConfiguration.setWindowingMode(windowingMode);
+        overrideConfig.windowConfiguration.setActivityType(activityType);
+        final int stackId = ++sNextStackId;
         final StackWindowController controller = new StackWindowController(stackId, null,
                 dc.getDisplayId(), true /* onTop */, new Rect(), sWm);
         controller.onOverrideConfigurationChanged(overrideConfig);
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 926a606..99eb846 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -22,6 +22,7 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyList;
 import static org.mockito.Matchers.anyString;
@@ -59,9 +60,7 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
 import org.mockito.ArgumentMatchers;
-import org.mockito.Mockito;
 import org.mockito.hamcrest.MockitoHamcrest;
 
 import java.io.BufferedReader;
@@ -898,11 +897,14 @@
 
         public ShortcutListAsserter areAllEnabled() {
             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isEnabled()));
+            areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED);
             return this;
         }
 
         public ShortcutListAsserter areAllDisabled() {
             forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isEnabled()));
+            forAllShortcuts(s -> assertNotEquals("id=" + s.getId(),
+                    ShortcutInfo.DISABLED_REASON_NOT_DISABLED, s.getDisabledReason()));
             return this;
         }
 
@@ -930,6 +932,16 @@
             return this;
         }
 
+        public ShortcutListAsserter areAllVisibleToPublisher() {
+            forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.isVisibleToPublisher()));
+            return this;
+        }
+
+        public ShortcutListAsserter areAllNotVisibleToPublisher() {
+            forAllShortcuts(s -> assertFalse("id=" + s.getId(), s.isVisibleToPublisher()));
+            return this;
+        }
+
         public ShortcutListAsserter areAllWithKeyFieldsOnly() {
             forAllShortcuts(s -> assertTrue("id=" + s.getId(), s.hasKeyFieldsOnly()));
             return this;
@@ -960,6 +972,17 @@
             return this;
         }
 
+        public ShortcutListAsserter areAllWithDisabledReason(int disabledReason) {
+            forAllShortcuts(s -> assertEquals("id=" + s.getId(),
+                    disabledReason, s.getDisabledReason()));
+            if (disabledReason >= ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
+                areAllNotVisibleToPublisher();
+            } else {
+                areAllVisibleToPublisher();
+            }
+            return this;
+        }
+
         public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
             boolean found = false;
             for (int i = 0; i < mList.size(); i++) {
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index f298559..e5d3915 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,6 +16,9 @@
 
 package com.android.server.usage;
 
+import static android.app.usage.AppStandby.*;
+
+import android.app.usage.AppStandby;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.util.ArrayMap;
@@ -51,8 +54,10 @@
 
     private static final String TAG = "AppIdleHistory";
 
+    private static final boolean DEBUG = AppStandbyController.DEBUG;
+
     // History for all users and all packages
-    private SparseArray<ArrayMap<String,PackageHistory>> mIdleHistory = new SparseArray<>();
+    private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
     private long mLastPeriod = 0;
     private static final long ONE_MINUTE = 60 * 1000;
     private static final int HISTORY_SIZE = 100;
@@ -70,6 +75,13 @@
     private static final String ATTR_SCREEN_IDLE = "screenIdleTime";
     // Elapsed timebase time when app was last used
     private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime";
+    private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
+    private static final String ATTR_BUCKETING_REASON = "bucketReason";
+
+    // State that was last informed to listeners, since boot
+    private static final int STATE_UNINFORMED = 0;
+    private static final int STATE_ACTIVE = 1;
+    private static final int STATE_IDLE = 2;
 
     // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
     private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -85,17 +97,15 @@
 
     private boolean mScreenOn;
 
-    private static class PackageHistory {
+    private static class AppUsageHistory {
         final byte[] recent = new byte[HISTORY_SIZE];
         long lastUsedElapsedTime;
         long lastUsedScreenTime;
+        @StandbyBuckets int currentBucket;
+        String bucketingReason;
+        int lastInformedState;
     }
 
-    AppIdleHistory(long elapsedRealtime) {
-        this(Environment.getDataSystemDirectory(), elapsedRealtime);
-    }
-
-    @VisibleForTesting
     AppIdleHistory(File storageDir, long elapsedRealtime) {
         mElapsedSnapshot = elapsedRealtime;
         mScreenOnSnapshot = elapsedRealtime;
@@ -119,6 +129,9 @@
             mElapsedDuration += elapsedRealtime - mElapsedSnapshot;
             mElapsedSnapshot = elapsedRealtime;
         }
+        if (DEBUG) Slog.d(TAG, "mScreenOnSnapshot=" + mScreenOnSnapshot
+                + ", mScreenOnDuration=" + mScreenOnDuration
+                + ", mScreenOn=" + mScreenOn);
     }
 
     public long getScreenOnTime(long elapsedRealtime) {
@@ -174,29 +187,35 @@
     }
 
     public void reportUsage(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
 
         shiftHistoryToNow(userHistory, elapsedRealtime);
 
-        packageHistory.lastUsedElapsedTime = mElapsedDuration
+        appUsageHistory.lastUsedElapsedTime = mElapsedDuration
                 + (elapsedRealtime - mElapsedSnapshot);
-        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-        packageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+        appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+        appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
+        appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+        if (DEBUG) {
+            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                    + ", reason=" + appUsageHistory.bucketingReason);
+        }
     }
 
     public void setIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
 
         shiftHistoryToNow(userHistory, elapsedRealtime);
 
-        packageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
+        appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
     }
 
-    private void shiftHistoryToNow(ArrayMap<String, PackageHistory> userHistory,
+    private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
             long elapsedRealtime) {
         long thisPeriod = elapsedRealtime / PERIOD_DURATION;
         // Has the period switched over? Slide all users' package histories
@@ -206,7 +225,7 @@
             final int NUSERS = mIdleHistory.size();
             for (int u = 0; u < NUSERS; u++) {
                 userHistory = mIdleHistory.valueAt(u);
-                for (PackageHistory idleState : userHistory.values()) {
+                for (AppUsageHistory idleState : userHistory.values()) {
                     // Shift left
                     System.arraycopy(idleState.recent, diff, idleState.recent, 0,
                             HISTORY_SIZE - diff);
@@ -221,8 +240,8 @@
         mLastPeriod = thisPeriod;
     }
 
-    private ArrayMap<String, PackageHistory> getUserHistory(int userId) {
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+    private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         if (userHistory == null) {
             userHistory = new ArrayMap<>();
             mIdleHistory.put(userId, userHistory);
@@ -231,16 +250,18 @@
         return userHistory;
     }
 
-    private PackageHistory getPackageHistory(ArrayMap<String, PackageHistory> userHistory,
-            String packageName, long elapsedRealtime) {
-        PackageHistory packageHistory = userHistory.get(packageName);
-        if (packageHistory == null) {
-            packageHistory = new PackageHistory();
-            packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
-            packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
-            userHistory.put(packageName, packageHistory);
+    private AppUsageHistory getPackageHistory(ArrayMap<String, AppUsageHistory> userHistory,
+            String packageName, long elapsedRealtime, boolean create) {
+        AppUsageHistory appUsageHistory = userHistory.get(packageName);
+        if (appUsageHistory == null && create) {
+            appUsageHistory = new AppUsageHistory();
+            appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
+            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
+            appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_NEVER;
+            appUsageHistory.bucketingReason = REASON_DEFAULT;
+            userHistory.put(packageName, appUsageHistory);
         }
-        return packageHistory;
+        return appUsageHistory;
     }
 
     public void onUserRemoved(int userId) {
@@ -248,48 +269,124 @@
     }
 
     public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory =
-                getPackageHistory(userHistory, packageName, elapsedRealtime);
-        if (packageHistory == null) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        if (appUsageHistory == null) {
             return false; // Default to not idle
         } else {
-            return hasPassedThresholds(packageHistory, elapsedRealtime);
+            return appUsageHistory.currentBucket >= AppStandby.STANDBY_BUCKET_RARE;
+            // Whether or not it's passed will now be externally calculated and the
+            // bucket will be pushed to the history using setAppStandbyBucket()
+            //return hasPassedThresholds(appUsageHistory, elapsedRealtime);
         }
     }
 
+    public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
+            int bucket, String reason) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        appUsageHistory.currentBucket = bucket;
+        appUsageHistory.bucketingReason = reason;
+        if (DEBUG) {
+            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                    + ", reason=" + appUsageHistory.bucketingReason);
+        }
+    }
+
+    public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
+        return appUsageHistory.currentBucket;
+    }
+
+    public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory =
+                getPackageHistory(userHistory, packageName, elapsedRealtime, false);
+        return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
+    }
+
     private long getElapsedTime(long elapsedRealtime) {
         return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
     }
 
     public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
-        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
-                elapsedRealtime);
-        packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime)
-                - mElapsedTimeThreshold;
-        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime)
-                - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        if (idle) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
+            appUsageHistory.bucketingReason = REASON_FORCED;
+        } else {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+            // This is to pretend that the app was just used, don't freeze the state anymore.
+            appUsageHistory.bucketingReason = REASON_USAGE;
+        }
     }
 
     public void clearUsage(String packageName, int userId) {
-        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         userHistory.remove(packageName);
     }
 
-    private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) {
-        return (packageHistory.lastUsedScreenTime
-                    <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold)
-                && (packageHistory.lastUsedElapsedTime
-                        <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold);
+    boolean shouldInformListeners(String packageName, int userId,
+            long elapsedRealtime, boolean isIdle) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        int targetState = isIdle? STATE_IDLE : STATE_ACTIVE;
+        if (appUsageHistory.lastInformedState != (isIdle ? STATE_IDLE : STATE_ACTIVE)) {
+            appUsageHistory.lastInformedState = targetState;
+            return true;
+        }
+        return false;
     }
 
-    private File getUserFile(int userId) {
+    /**
+     * Returns the index in the arrays of screenTimeThresholds and elapsedTimeThresholds
+     * that corresponds to how long since the app was used.
+     * @param packageName
+     * @param userId
+     * @param elapsedRealtime current time
+     * @param screenTimeThresholds Array of screen times, in ascending order, first one is 0
+     * @param elapsedTimeThresholds Array of elapsed time, in ascending order, first one is 0
+     * @return The index whose values the app's used time exceeds (in both arrays)
+     */
+    int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
+            long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, false);
+        // If we don't have any state for the app, assume never used
+        if (appUsageHistory == null) return screenTimeThresholds.length - 1;
+
+        long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
+        long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
+
+        if (DEBUG) Slog.d(TAG, packageName
+                + " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
+                + " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
+        if (DEBUG) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
+                + ", elapsed=" + elapsedDelta);
+        for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
+            if (screenOnDelta >= screenTimeThresholds[i]
+                && elapsedDelta >= elapsedTimeThresholds[i]) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    @VisibleForTesting
+    File getUserFile(int userId) {
         return new File(new File(new File(mStorageDir, "users"),
                 Integer.toString(userId)), APP_IDLE_FILENAME);
     }
 
-    private void readAppIdleTimes(int userId, ArrayMap<String, PackageHistory> userHistory) {
+    private void readAppIdleTimes(int userId, ArrayMap<String, AppUsageHistory> userHistory) {
         FileInputStream fis = null;
         try {
             AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -315,12 +412,22 @@
                     final String name = parser.getName();
                     if (name.equals(TAG_PACKAGE)) {
                         final String packageName = parser.getAttributeValue(null, ATTR_NAME);
-                        PackageHistory packageHistory = new PackageHistory();
-                        packageHistory.lastUsedElapsedTime =
+                        AppUsageHistory appUsageHistory = new AppUsageHistory();
+                        appUsageHistory.lastUsedElapsedTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
-                        packageHistory.lastUsedScreenTime =
+                        appUsageHistory.lastUsedScreenTime =
                                 Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
-                        userHistory.put(packageName, packageHistory);
+                        String currentBucketString = parser.getAttributeValue(null,
+                                ATTR_CURRENT_BUCKET);
+                        appUsageHistory.currentBucket = currentBucketString == null
+                                ? AppStandby.STANDBY_BUCKET_ACTIVE
+                                : Integer.parseInt(currentBucketString);
+                        appUsageHistory.bucketingReason =
+                                parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
+                        if (appUsageHistory.bucketingReason == null) {
+                            appUsageHistory.bucketingReason = REASON_DEFAULT;
+                        }
+                        userHistory.put(packageName, appUsageHistory);
                     }
                 }
             }
@@ -345,17 +452,20 @@
 
             xml.startTag(null, TAG_PACKAGES);
 
-            ArrayMap<String,PackageHistory> userHistory = getUserHistory(userId);
+            ArrayMap<String,AppUsageHistory> userHistory = getUserHistory(userId);
             final int N = userHistory.size();
             for (int i = 0; i < N; i++) {
                 String packageName = userHistory.keyAt(i);
-                PackageHistory history = userHistory.valueAt(i);
+                AppUsageHistory history = userHistory.valueAt(i);
                 xml.startTag(null, TAG_PACKAGE);
                 xml.attribute(null, ATTR_NAME, packageName);
                 xml.attribute(null, ATTR_ELAPSED_IDLE,
                         Long.toString(history.lastUsedElapsedTime));
                 xml.attribute(null, ATTR_SCREEN_IDLE,
                         Long.toString(history.lastUsedScreenTime));
+                xml.attribute(null, ATTR_CURRENT_BUCKET,
+                        Integer.toString(history.currentBucket));
+                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
                 xml.endTag(null, TAG_PACKAGE);
             }
 
@@ -368,10 +478,10 @@
         }
     }
 
-    public void dump(IndentingPrintWriter idpw, int userId) {
+    public void dump(IndentingPrintWriter idpw, int userId, String pkg) {
         idpw.println("Package idle stats:");
         idpw.increaseIndent();
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         final long totalElapsedTime = getElapsedTime(elapsedRealtime);
         final long screenOnTime = getScreenOnTime(elapsedRealtime);
@@ -379,13 +489,18 @@
         final int P = userHistory.size();
         for (int p = 0; p < P; p++) {
             final String packageName = userHistory.keyAt(p);
-            final PackageHistory packageHistory = userHistory.valueAt(p);
+            final AppUsageHistory appUsageHistory = userHistory.valueAt(p);
+            if (pkg != null && !pkg.equals(packageName)) {
+                continue;
+            }
             idpw.print("package=" + packageName);
             idpw.print(" lastUsedElapsed=");
-            TimeUtils.formatDuration(totalElapsedTime - packageHistory.lastUsedElapsedTime, idpw);
+            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
             idpw.print(" lastUsedScreenOn=");
-            TimeUtils.formatDuration(screenOnTime - packageHistory.lastUsedScreenTime, idpw);
+            TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
+            idpw.print(" bucket=" + appUsageHistory.currentBucket
+                    + " reason=" + appUsageHistory.bucketingReason);
             idpw.println();
         }
         idpw.println();
@@ -399,7 +514,7 @@
     }
 
     public void dumpHistory(IndentingPrintWriter idpw, int userId) {
-        ArrayMap<String, PackageHistory> userHistory = mIdleHistory.get(userId);
+        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
         if (userHistory == null) return;
         final int P = userHistory.size();
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
new file mode 100644
index 0000000..17fde57
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -0,0 +1,1216 @@
+/**
+ * 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.server.usage;
+
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManager;
+import android.app.usage.AppStandby;
+import android.app.usage.AppStandby.StandbyBuckets;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IDeviceIdleController;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Manages the standby state of an app, listening to various events.
+ */
+public class AppStandbyController {
+
+    private static final String TAG = "AppStandbyController";
+    static final boolean DEBUG = false;
+
+    static final boolean COMPRESS_TIME = false;
+    private static final long ONE_MINUTE = 60 * 1000;
+    private static final long ONE_HOUR = ONE_MINUTE * 60;
+    private static final long ONE_DAY = ONE_HOUR * 24;
+
+    static final long[] SCREEN_TIME_THRESHOLDS = {
+            0,
+            0,
+            COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
+            COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+    };
+
+    static final long[] ELAPSED_TIME_THRESHOLDS = {
+            0,
+            COMPRESS_TIME ?  1 * ONE_MINUTE : 12 * ONE_HOUR,
+            COMPRESS_TIME ?  4 * ONE_MINUTE :  2 * ONE_DAY,
+            COMPRESS_TIME ? 16 * ONE_MINUTE :  8 * ONE_DAY
+    };
+
+    static final int[] THRESHOLD_BUCKETS = {
+            AppStandby.STANDBY_BUCKET_ACTIVE,
+            AppStandby.STANDBY_BUCKET_WORKING_SET,
+            AppStandby.STANDBY_BUCKET_FREQUENT,
+            AppStandby.STANDBY_BUCKET_RARE
+    };
+
+    // To name the lock for stack traces
+    static class Lock {}
+
+    /** Lock to protect the app's standby state. Required for calls into AppIdleHistory */
+    private final Object mAppIdleLock = new Lock();
+
+    /** Keeps the history and state for each app. */
+    @GuardedBy("mAppIdleLock")
+    private AppIdleHistory mAppIdleHistory;
+
+    @GuardedBy("mAppIdleLock")
+    private ArrayList<AppIdleStateChangeListener>
+            mPackageAccessListeners = new ArrayList<>();
+
+    /** Whether we've queried the list of carrier privileged apps. */
+    @GuardedBy("mAppIdleLock")
+    private boolean mHaveCarrierPrivilegedApps;
+
+    /** List of carrier-privileged apps that should be excluded from standby */
+    @GuardedBy("mAppIdleLock")
+    private List<String> mCarrierPrivilegedApps;
+
+    // Messages for the handler
+    static final int MSG_INFORM_LISTENERS = 3;
+    static final int MSG_FORCE_IDLE_STATE = 4;
+    static final int MSG_CHECK_IDLE_STATES = 5;
+    static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
+    static final int MSG_PAROLE_END_TIMEOUT = 7;
+    static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
+    static final int MSG_PAROLE_STATE_CHANGED = 9;
+    static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
+
+    long mAppIdleScreenThresholdMillis;
+    long mCheckIdleIntervalMillis;
+    long mAppIdleWallclockThresholdMillis;
+    long mAppIdleParoleIntervalMillis;
+    long mAppIdleParoleDurationMillis;
+    long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
+    long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
+
+    boolean mAppIdleEnabled;
+    boolean mAppIdleTempParoled;
+    boolean mCharging;
+    private long mLastAppIdleParoledTime;
+    private boolean mSystemServicesReady = false;
+
+    private volatile boolean mPendingOneTimeCheckIdleStates;
+
+    private final Handler mHandler;
+    private final Context mContext;
+
+    // TODO: Provide a mechanism to set an external bucketing service
+    private boolean mUseInternalBucketingHeuristics = true;
+
+    private AppWidgetManager mAppWidgetManager;
+    private PowerManager mPowerManager;
+    private PackageManager mPackageManager;
+    private Injector mInjector;
+
+
+    AppStandbyController(Context context, Looper looper) {
+        this(new Injector(context, looper));
+    }
+
+    AppStandbyController(Injector injector) {
+        mInjector = injector;
+        mContext = mInjector.getContext();
+        mHandler = new AppStandbyHandler(mInjector.getLooper());
+        mPackageManager = mContext.getPackageManager();
+        mAppIdleEnabled = mInjector.isAppIdleEnabled();
+
+        if (mAppIdleEnabled) {
+            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+            mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
+        }
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
+                    mInjector.elapsedRealtime());
+        }
+
+        IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
+                null, mHandler);
+    }
+
+    public void onBootPhase(int phase) {
+        mInjector.onBootPhase(phase);
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            // Observe changes to the threshold
+            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+            settingsObserver.registerObserver();
+            settingsObserver.updateSettings();
+
+            mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+            mPowerManager = mContext.getSystemService(PowerManager.class);
+
+            mInjector.registerDisplayListener(mDisplayListener, mHandler);
+            synchronized (mAppIdleLock) {
+                mAppIdleHistory.updateDisplay(isDisplayOn(), mInjector.elapsedRealtime());
+            }
+
+            if (mPendingOneTimeCheckIdleStates) {
+                postOneTimeCheckIdleStates();
+            }
+
+            mSystemServicesReady = true;
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            setChargingState(mInjector.isCharging());
+        }
+    }
+
+    void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
+        // Get sync adapters for the authority
+        String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
+                authority, userId);
+        for (String packageName: packages) {
+            // Only force the sync adapters to active if the provider is not in the same package and
+            // the sync adapter is a system package.
+            try {
+                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
+                        packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
+                if (pi == null || pi.applicationInfo == null) {
+                    continue;
+                }
+                if (!packageName.equals(providerPkgName)) {
+                    setAppIdleAsync(packageName, false, userId);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen
+            }
+        }
+    }
+
+    void setChargingState(boolean charging) {
+        synchronized (mAppIdleLock) {
+            if (mCharging != charging) {
+                mCharging = charging;
+                postParoleStateChanged();
+            }
+        }
+    }
+
+    /** Paroled here means temporary pardon from being inactive */
+    void setAppIdleParoled(boolean paroled) {
+        synchronized (mAppIdleLock) {
+            final long now = mInjector.currentTimeMillis();
+            if (mAppIdleTempParoled != paroled) {
+                mAppIdleTempParoled = paroled;
+                if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
+                if (paroled) {
+                    postParoleEndTimeout();
+                } else {
+                    mLastAppIdleParoledTime = now;
+                    postNextParoleTimeout(now);
+                }
+                postParoleStateChanged();
+            }
+        }
+    }
+
+    boolean isParoledOrCharging() {
+        synchronized (mAppIdleLock) {
+            return mAppIdleTempParoled || mCharging;
+        }
+    }
+
+    private void postNextParoleTimeout(long now) {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
+        mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
+        // Compute when the next parole needs to happen. We check more frequently than necessary
+        // since the message handler delays are based on elapsedRealTime and not wallclock time.
+        // The comparison is done in wallclock time.
+        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
+        if (timeLeft < 0) {
+            timeLeft = 0;
+        }
+        mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
+    }
+
+    private void postParoleEndTimeout() {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
+        mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
+        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
+    }
+
+    private void postParoleStateChanged() {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
+        mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
+        mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
+    }
+
+    void postCheckIdleStates(int userId) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
+    }
+
+    /**
+     * We send a different message to check idle states once, otherwise we would end up
+     * scheduling a series of repeating checkIdleStates each time we fired off one.
+     */
+    void postOneTimeCheckIdleStates() {
+        if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) {
+            // Not booted yet; wait for it!
+            mPendingOneTimeCheckIdleStates = true;
+        } else {
+            mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
+            mPendingOneTimeCheckIdleStates = false;
+        }
+    }
+
+    /**
+     * Check all running users' or specified user's apps to see if they enter an idle state.
+     * @return Returns whether checking should continue periodically.
+     */
+    boolean checkIdleStates(int checkUserId) {
+        if (!mAppIdleEnabled) {
+            return false;
+        }
+
+        final int[] runningUserIds;
+        try {
+            runningUserIds = mInjector.getRunningUserIds();
+            if (checkUserId != UserHandle.USER_ALL
+                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+                return false;
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+        for (int i = 0; i < runningUserIds.length; i++) {
+            final int userId = runningUserIds[i];
+            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+                continue;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Checking idle state for user " + userId);
+            }
+            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                    PackageManager.MATCH_DISABLED_COMPONENTS,
+                    userId);
+            final int packageCount = packages.size();
+            for (int p = 0; p < packageCount; p++) {
+                final PackageInfo pi = packages.get(p);
+                final String packageName = pi.packageName;
+                final boolean isSpecial = isAppSpecial(packageName,
+                        UserHandle.getAppId(pi.applicationInfo.uid),
+                        userId);
+                if (DEBUG) {
+                    Slog.d(TAG, "   Checking idle state for " + packageName);
+                }
+                if (isSpecial) {
+                    maybeInformListeners(packageName, userId, elapsedRealtime, false);
+                } else if (mUseInternalBucketingHeuristics) {
+                    synchronized (mAppIdleLock) {
+                        int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
+                                elapsedRealtime);
+                        String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
+                                userId, elapsedRealtime);
+                        if (bucketingReason != null
+                                && (bucketingReason.equals(AppStandby.REASON_FORCED)
+                                    || bucketingReason.startsWith(AppStandby.REASON_PREDICTED))) {
+                            continue;
+                        }
+                        int newBucket = getBucketForLocked(packageName, userId,
+                                            elapsedRealtime);
+                        if (DEBUG) {
+                            Slog.d(TAG, "     Old bucket=" + oldBucket
+                                    + ", newBucket=" + newBucket);
+                        }
+                        if (oldBucket != newBucket) {
+                            mAppIdleHistory.setAppStandbyBucket(packageName, userId,
+                                    elapsedRealtime, newBucket, AppStandby.REASON_TIMEOUT);
+                            maybeInformListeners(packageName, userId, elapsedRealtime,
+                                    newBucket >= AppStandby.STANDBY_BUCKET_RARE);
+                        }
+                    }
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "checkIdleStates took "
+                    + (mInjector.elapsedRealtime() - elapsedRealtime));
+        }
+        return true;
+    }
+
+    private void maybeInformListeners(String packageName, int userId,
+            long elapsedRealtime, boolean isIdle) {
+        synchronized (mAppIdleLock) {
+            if (mAppIdleHistory.shouldInformListeners(packageName, userId,
+                    elapsedRealtime, isIdle)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+            }
+        }
+    }
+
+    @StandbyBuckets int getBucketForLocked(String packageName, int userId,
+            long elapsedRealtime) {
+        int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
+                elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
+        return THRESHOLD_BUCKETS[bucketIndex];
+    }
+
+    /** Check if it's been a while since last parole and let idle apps do some work */
+    void checkParoleTimeout() {
+        boolean setParoled = false;
+        synchronized (mAppIdleLock) {
+            final long now = mInjector.currentTimeMillis();
+            if (!mAppIdleTempParoled) {
+                final long timeSinceLastParole = now - mLastAppIdleParoledTime;
+                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
+                    if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
+                    setParoled = true;
+                } else {
+                    if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
+                    postNextParoleTimeout(now);
+                }
+            }
+        }
+        if (setParoled) {
+            setAppIdleParoled(true);
+        }
+    }
+
+    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
+        try {
+            final int uid = mPackageManager.getPackageUidAsUser(packageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+            if (idle) {
+                mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+                        packageName, uid);
+            } else {
+                mInjector.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+                        packageName, uid);
+            }
+        } catch (PackageManager.NameNotFoundException | RemoteException e) {
+        }
+    }
+
+    void onDeviceIdleModeChanged() {
+        final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
+        if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
+        boolean paroled = false;
+        synchronized (mAppIdleLock) {
+            final long timeSinceLastParole = mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
+            if (!deviceIdle
+                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+                }
+                paroled = true;
+            } else if (deviceIdle) {
+                if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
+                paroled = false;
+            } else {
+                return;
+            }
+        }
+        setAppIdleParoled(paroled);
+    }
+
+    void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+        synchronized (mAppIdleLock) {
+            // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
+            // about apps that are on some kind of whitelist anyway.
+            final boolean previouslyIdle = mAppIdleHistory.isIdle(
+                    event.mPackage, userId, elapsedRealtime);
+            // Inform listeners if necessary
+            if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
+                    || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
+                    || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
+                    || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
+                mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+                if (previouslyIdle) {
+                    maybeInformListeners(event.mPackage, userId, elapsedRealtime, false);
+                    notifyBatteryStats(event.mPackage, userId, false);
+                }
+            }
+        }
+    }
+
+    /**
+     * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
+     * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
+     * the threshold for idle.
+     *
+     * This method is always called from the handler thread, so not much synchronization is
+     * required.
+     */
+    void forceIdleState(String packageName, int userId, boolean idle) {
+        final int appId = getAppId(packageName);
+        if (appId < 0) return;
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+
+        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
+                userId, elapsedRealtime);
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+        }
+        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
+                userId, elapsedRealtime);
+        // Inform listeners if necessary
+        if (previouslyIdle != stillIdle) {
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+                    /* idle = */ stillIdle ? 1 : 0, packageName));
+            if (!stillIdle) {
+                notifyBatteryStats(packageName, userId, idle);
+            }
+        }
+    }
+
+    public void onUserRemoved(int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.onUserRemoved(userId);
+        }
+    }
+
+    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
+        synchronized (mAppIdleLock) {
+            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
+        }
+    }
+
+    void addListener(AppIdleStateChangeListener listener) {
+        synchronized (mAppIdleLock) {
+            if (!mPackageAccessListeners.contains(listener)) {
+                mPackageAccessListeners.add(listener);
+            }
+        }
+    }
+
+    void removeListener(AppIdleStateChangeListener listener) {
+        synchronized (mAppIdleLock) {
+            mPackageAccessListeners.remove(listener);
+        }
+    }
+
+    int getAppId(String packageName) {
+        try {
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
+                    PackageManager.MATCH_ANY_USER
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
+            return ai.uid;
+        } catch (PackageManager.NameNotFoundException re) {
+            return -1;
+        }
+    }
+
+    boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+            boolean shouldObfuscateInstantApps) {
+        if (isParoledOrCharging()) {
+            return false;
+        }
+        if (shouldObfuscateInstantApps &&
+                mInjector.isPackageEphemeral(userId, packageName)) {
+            return false;
+        }
+        return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
+    }
+
+    /** Returns true if this app should be whitelisted for some reason, to never go into standby */
+    boolean isAppSpecial(String packageName, int appId, int userId) {
+        if (packageName == null) return false;
+        // If not enabled at all, of course nobody is ever idle.
+        if (!mAppIdleEnabled) {
+            return true;
+        }
+        if (appId < Process.FIRST_APPLICATION_UID) {
+            // System uids never go idle.
+            return true;
+        }
+        if (packageName.equals("android")) {
+            // Nor does the framework (which should be redundant with the above, but for MR1 we will
+            // retain this for safety).
+            return true;
+        }
+        if (mSystemServicesReady) {
+            try {
+                // We allow all whitelisted apps, including those that don't want to be whitelisted
+                // for idle mode, because app idle (aka app standby) is really not as big an issue
+                // for controlling who participates vs. doze mode.
+                if (mInjector.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+                    return true;
+                }
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+
+            if (isActiveDeviceAdmin(packageName, userId)) {
+                return true;
+            }
+
+            if (isActiveNetworkScorer(packageName)) {
+                return true;
+            }
+
+            if (mAppWidgetManager != null
+                    && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
+                return true;
+            }
+
+            if (isDeviceProvisioningPackage(packageName)) {
+                return true;
+            }
+        }
+
+        // Check this last, as it can be the most expensive check
+        if (isCarrierApp(packageName)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks if an app has been idle for a while and filters out apps that are excluded.
+     * It returns false if the current system state allows all apps to be considered active.
+     * This happens if the device is plugged in or temporarily allowed to make exceptions.
+     * Called by interface impls.
+     */
+    boolean isAppIdleFiltered(String packageName, int appId, int userId,
+            long elapsedRealtime) {
+        if (isAppSpecial(packageName, appId, userId)) {
+            return false;
+        } else {
+            return isAppIdleUnfiltered(packageName, userId, elapsedRealtime);
+        }
+    }
+
+    int[] getIdleUidsForUser(int userId) {
+        if (!mAppIdleEnabled) {
+            return new int[0];
+        }
+
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+
+        List<ApplicationInfo> apps;
+        try {
+            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
+                    .getInstalledApplications(/* flags= */ 0, userId);
+            if (slice == null) {
+                return new int[0];
+            }
+            apps = slice.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
+        // associated with that uid, upper 16 bits is the number of those apps that is idle.
+        SparseIntArray uidStates = new SparseIntArray();
+
+        // Now resolve all app state.  Iterating over all apps, keeping track of how many
+        // we find for each uid and how many of those are idle.
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            ApplicationInfo ai = apps.get(i);
+
+            // Check whether this app is idle.
+            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+                    userId, elapsedRealtime);
+
+            int index = uidStates.indexOfKey(ai.uid);
+            if (index < 0) {
+                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
+            } else {
+                int value = uidStates.valueAt(index);
+                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "getIdleUids took " + (mInjector.elapsedRealtime() - elapsedRealtime));
+        }
+        int numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                numIdle++;
+            }
+        }
+
+        int[] res = new int[numIdle];
+        numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                res[numIdle] = uidStates.keyAt(i);
+                numIdle++;
+            }
+        }
+
+        return res;
+    }
+
+    void setAppIdleAsync(String packageName, boolean idle, int userId) {
+        if (packageName == null) return;
+
+        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
+                .sendToTarget();
+    }
+
+    @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+            long elapsedRealtime, boolean shouldObfuscateInstantApps) {
+        if (shouldObfuscateInstantApps &&
+                mInjector.isPackageEphemeral(userId, packageName)) {
+            return AppStandby.STANDBY_BUCKET_ACTIVE;
+        }
+
+        return mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime);
+    }
+
+    void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+            String reason, long elapsedRealtime) {
+        mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket, reason);
+    }
+
+    private boolean isActiveDeviceAdmin(String packageName, int userId) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (dpm == null) return false;
+        return dpm.packageHasActiveAdmins(packageName, userId);
+    }
+
+    /**
+     * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
+     * returns {@code false}.
+     */
+    private boolean isDeviceProvisioningPackage(String packageName) {
+        String deviceProvisioningPackage = mContext.getResources().getString(
+                com.android.internal.R.string.config_deviceProvisioningPackage);
+        return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
+    }
+
+    private boolean isCarrierApp(String packageName) {
+        synchronized (mAppIdleLock) {
+            if (!mHaveCarrierPrivilegedApps) {
+                fetchCarrierPrivilegedAppsLocked();
+            }
+            if (mCarrierPrivilegedApps != null) {
+                return mCarrierPrivilegedApps.contains(packageName);
+            }
+            return false;
+        }
+    }
+
+    void clearCarrierPrivilegedApps() {
+        if (DEBUG) {
+            Slog.i(TAG, "Clearing carrier privileged apps list");
+        }
+        synchronized (mAppIdleLock) {
+            mHaveCarrierPrivilegedApps = false;
+            mCarrierPrivilegedApps = null; // Need to be refetched.
+        }
+    }
+
+    @GuardedBy("mAppIdleLock")
+    private void fetchCarrierPrivilegedAppsLocked() {
+        TelephonyManager telephonyManager =
+                mContext.getSystemService(TelephonyManager.class);
+        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
+        mHaveCarrierPrivilegedApps = true;
+        if (DEBUG) {
+            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+        }
+    }
+
+    private boolean isActiveNetworkScorer(String packageName) {
+        String activeScorer = mInjector.getActiveNetworkScorer();
+        return packageName != null && packageName.equals(activeScorer);
+    }
+
+    void informListeners(String packageName, int userId, boolean isIdle) {
+        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
+            listener.onAppIdleStateChanged(packageName, userId, isIdle);
+        }
+    }
+
+    void informParoleStateChanged() {
+        final boolean paroled = isParoledOrCharging();
+        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
+            listener.onParoleStateChanged(paroled);
+        }
+    }
+
+    void flushToDisk(int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleTimes(userId);
+        }
+    }
+
+    void flushDurationsToDisk() {
+        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
+        // considered not-idle, which is the safest outcome in such an event.
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleDurations();
+        }
+    }
+
+    boolean isDisplayOn() {
+        return mInjector.isDefaultDisplayOn();
+    }
+
+    void clearAppIdleForPackage(String packageName, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.clearUsage(packageName, userId);
+        }
+    }
+
+    private class PackageReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                clearCarrierPrivilegedApps();
+            }
+            if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+                    Intent.ACTION_PACKAGE_ADDED.equals(action))
+                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
+                        getSendingUserId());
+            }
+        }
+    }
+
+    void initializeDefaultsForSystemApps(int userId) {
+        Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
+        final long elapsedRealtime = mInjector.elapsedRealtime();
+        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                PackageManager.MATCH_DISABLED_COMPONENTS,
+                userId);
+        final int packageCount = packages.size();
+        synchronized (mAppIdleLock) {
+            for (int i = 0; i < packageCount; i++) {
+                final PackageInfo pi = packages.get(i);
+                String packageName = pi.packageName;
+                if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
+                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
+                }
+            }
+        }
+    }
+
+    void postReportContentProviderUsage(String name, String packageName, int userId) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = name;
+        args.arg2 = packageName;
+        args.arg3 = userId;
+        mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
+                .sendToTarget();
+    }
+
+    void dumpHistory(IndentingPrintWriter idpw, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.dumpHistory(idpw, userId);
+        }
+    }
+
+    void dumpUser(IndentingPrintWriter idpw, int userId, String pkg) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.dump(idpw, userId, pkg);
+        }
+    }
+
+    void dumpState(String[] args, PrintWriter pw) {
+        synchronized (mAppIdleLock) {
+            pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
+                    + "): " + mCarrierPrivilegedApps);
+        }
+
+        pw.println();
+        pw.println("Settings:");
+
+        pw.print("  mAppIdleDurationMillis=");
+        TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleWallclockThresholdMillis=");
+        TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
+        pw.println();
+
+        pw.print("  mCheckIdleIntervalMillis=");
+        TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleParoleIntervalMillis=");
+        TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleParoleDurationMillis=");
+        TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
+        pw.println();
+
+        pw.println();
+        pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+        pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
+        pw.print(" mCharging="); pw.print(mCharging);
+        pw.print(" mLastAppIdleParoledTime=");
+        TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
+        pw.println();
+        pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
+        pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
+    }
+
+    /**
+     * Injector for interaction with external code. Override methods to provide a mock
+     * implementation for tests.
+     * onBootPhase() must be called with at least the PHASE_SYSTEM_SERVICES_READY
+     */
+    static class Injector {
+
+        private final Context mContext;
+        private final Looper mLooper;
+        private IDeviceIdleController mDeviceIdleController;
+        private IBatteryStats mBatteryStats;
+        private PackageManagerInternal mPackageManagerInternal;
+        private DisplayManager mDisplayManager;
+        int mBootPhase;
+
+        Injector(Context context, Looper looper) {
+            mContext = context;
+            mLooper = looper;
+        }
+
+        Context getContext() {
+            return mContext;
+        }
+
+        Looper getLooper() {
+            return mLooper;
+        }
+
+        void onBootPhase(int phase) {
+            if (phase == PHASE_SYSTEM_SERVICES_READY) {
+                mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                        ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+                mBatteryStats = IBatteryStats.Stub.asInterface(
+                        ServiceManager.getService(BatteryStats.SERVICE_NAME));
+                mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+                mDisplayManager = (DisplayManager) mContext.getSystemService(
+                        Context.DISPLAY_SERVICE);
+            }
+            mBootPhase = phase;
+        }
+
+        int getBootPhase() {
+            return mBootPhase;
+        }
+
+        /**
+         * Returns the elapsed realtime since the device started. Override this
+         * to control the clock.
+         * @return elapsed realtime
+         */
+        long elapsedRealtime() {
+            return SystemClock.elapsedRealtime();
+        }
+
+        long currentTimeMillis() {
+            return System.currentTimeMillis();
+        }
+
+        boolean isAppIdleEnabled() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_enableAutoPowerModes);
+        }
+
+        boolean isCharging() {
+            return mContext.getSystemService(BatteryManager.class).isCharging();
+        }
+
+        boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
+            return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
+        }
+
+        File getDataSystemDirectory() {
+            return Environment.getDataSystemDirectory();
+        }
+
+        void noteEvent(int event, String packageName, int uid) throws RemoteException {
+            mBatteryStats.noteEvent(event, packageName, uid);
+        }
+
+        boolean isPackageEphemeral(int userId, String packageName) {
+            return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
+        }
+
+        int[] getRunningUserIds() throws RemoteException {
+            return ActivityManager.getService().getRunningUserIds();
+        }
+
+        boolean isDefaultDisplayOn() {
+            return mDisplayManager
+                    .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+        }
+
+        void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
+            mDisplayManager.registerDisplayListener(listener, handler);
+        }
+
+        String getActiveNetworkScorer() {
+            NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+                    Context.NETWORK_SCORE_SERVICE);
+            return nsm.getActiveScorerPackage();
+        }
+
+        public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
+                int userId) {
+            return appWidgetManager.isBoundWidgetPackage(packageName, userId);
+        }
+    }
+
+    class AppStandbyHandler extends Handler {
+
+        AppStandbyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_INFORM_LISTENERS:
+                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    break;
+
+                case MSG_FORCE_IDLE_STATE:
+                    forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    break;
+
+                case MSG_CHECK_IDLE_STATES:
+                    if (checkIdleStates(msg.arg1)) {
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+                                mCheckIdleIntervalMillis);
+                    }
+                    break;
+
+                case MSG_ONE_TIME_CHECK_IDLE_STATES:
+                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
+                    checkIdleStates(UserHandle.USER_ALL);
+                    break;
+
+                case MSG_CHECK_PAROLE_TIMEOUT:
+                    checkParoleTimeout();
+                    break;
+
+                case MSG_PAROLE_END_TIMEOUT:
+                    if (DEBUG) Slog.d(TAG, "Ending parole");
+                    setAppIdleParoled(false);
+                    break;
+
+                case MSG_REPORT_CONTENT_PROVIDER_USAGE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    reportContentProviderUsage((String) args.arg1, // authority name
+                            (String) args.arg2, // package name
+                            (int) args.arg3); // userId
+                    args.recycle();
+                    break;
+
+                case MSG_PAROLE_STATE_CHANGED:
+                    if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
+                            + ", Charging state:" + mCharging);
+                    informParoleStateChanged();
+                    break;
+                default:
+                    super.handleMessage(msg);
+                    break;
+
+            }
+        }
+    };
+
+    private class DeviceStateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+                setChargingState(intent.getIntExtra("plugged", 0) != 0);
+            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                onDeviceIdleModeChanged();
+            }
+        }
+    }
+
+    private final DisplayManager.DisplayListener mDisplayListener
+            = new DisplayManager.DisplayListener() {
+
+        @Override public void onDisplayAdded(int displayId) {
+        }
+
+        @Override public void onDisplayRemoved(int displayId) {
+        }
+
+        @Override public void onDisplayChanged(int displayId) {
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                final boolean displayOn = isDisplayOn();
+                synchronized (mAppIdleLock) {
+                    mAppIdleHistory.updateDisplay(displayOn, mInjector.elapsedRealtime());
+                }
+            }
+        }
+    };
+
+    /**
+     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
+     */
+    private class SettingsObserver extends ContentObserver {
+        /**
+         * This flag has been used to disable app idle on older builds with bug b/26355386.
+         */
+        @Deprecated
+        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
+
+        private static final String KEY_IDLE_DURATION = "idle_duration2";
+        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
+        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
+        private static final String KEY_PAROLE_DURATION = "parole_duration";
+        private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
+        private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void registerObserver() {
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            updateSettings();
+            postOneTimeCheckIdleStates();
+        }
+
+        void updateSettings() {
+            synchronized (mAppIdleLock) {
+                // Look at global settings for this.
+                // TODO: Maybe apply different thresholds for different users.
+                try {
+                    mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+                            Settings.Global.APP_IDLE_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+                    // fallthrough, mParser is empty and all defaults will be returned.
+                }
+
+                // Default: 12 hours of screen-on time sans dream-time
+                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
+
+                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
+                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
+
+                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
+                        COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
+
+                // Default: 24 hours between paroles
+                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
+                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
+
+                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
+                        mAppIdleScreenThresholdMillis);
+
+                String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
+                mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
+                        SCREEN_TIME_THRESHOLDS);
+
+                String elapsedThresholdsValue = mParser.getString(KEY_ELAPSED_TIME_THRESHOLDS, null);
+                mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
+                        ELAPSED_TIME_THRESHOLDS);
+            }
+        }
+
+        long[] parseLongArray(String values, long[] defaults) {
+            if (values == null) return defaults;
+            if (values.isEmpty()) {
+                // Reset to defaults
+                return defaults;
+            } else {
+                String[] thresholds = values.split("/");
+                if (thresholds.length == THRESHOLD_BUCKETS.length) {
+                    long[] array = new long[THRESHOLD_BUCKETS.length];
+                    for (int i = 0; i < THRESHOLD_BUCKETS.length; i++) {
+                        array[i] = Long.parseLong(thresholds[i]);
+                    }
+                    return array;
+                } else {
+                    return defaults;
+                }
+            }
+        }
+    }
+}
+
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 25e471c..44e6a6c 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -18,37 +18,25 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
-import android.app.admin.DevicePolicyManager;
+import android.app.usage.AppStandby;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
-import android.net.NetworkScoreManager;
-import android.os.BatteryManager;
-import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -56,7 +44,6 @@
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -64,21 +51,12 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
 import android.util.ArraySet;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.view.Display;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
@@ -88,7 +66,6 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -107,7 +84,6 @@
     static final boolean COMPRESS_TIME = false;
 
     private static final long TEN_SECONDS = 10 * 1000;
-    private static final long ONE_MINUTE = 60 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
     private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
@@ -115,24 +91,10 @@
     private static final boolean ENABLE_KERNEL_UPDATES = true;
     private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
 
-    long mAppIdleScreenThresholdMillis;
-    long mCheckIdleIntervalMillis;
-    long mAppIdleWallclockThresholdMillis;
-    long mAppIdleParoleIntervalMillis;
-    long mAppIdleParoleDurationMillis;
-
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
     static final int MSG_FLUSH_TO_DISK = 1;
     static final int MSG_REMOVE_USER = 2;
-    static final int MSG_INFORM_LISTENERS = 3;
-    static final int MSG_FORCE_IDLE_STATE = 4;
-    static final int MSG_CHECK_IDLE_STATES = 5;
-    static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
-    static final int MSG_PAROLE_END_TIMEOUT = 7;
-    static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
-    static final int MSG_PAROLE_STATE_CHANGED = 9;
-    static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -140,11 +102,7 @@
     UserManager mUserManager;
     PackageManager mPackageManager;
     PackageManagerInternal mPackageManagerInternal;
-    AppWidgetManager mAppWidgetManager;
     IDeviceIdleController mDeviceIdleController;
-    private DisplayManager mDisplayManager;
-    private PowerManager mPowerManager;
-    private IBatteryStats mBatteryStats;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
@@ -152,26 +110,7 @@
     long mRealTimeSnapshot;
     long mSystemTimeSnapshot;
 
-    boolean mAppIdleEnabled;
-    boolean mAppIdleTempParoled;
-    boolean mCharging;
-    private long mLastAppIdleParoledTime;
-
-    private volatile boolean mPendingOneTimeCheckIdleStates;
-    private boolean mSystemServicesReady = false;
-
-    private final Object mAppIdleLock = new Object();
-    @GuardedBy("mAppIdleLock")
-    private AppIdleHistory mAppIdleHistory;
-
-    @GuardedBy("mAppIdleLock")
-    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
-            mPackageAccessListeners = new ArrayList<>();
-
-    @GuardedBy("mAppIdleLock")
-    private boolean mHaveCarrierPrivilegedApps;
-    @GuardedBy("mAppIdleLock")
-    private List<String> mCarrierPrivilegedApps;
+    AppStandbyController mAppStandby;
 
     public UsageStatsService(Context context) {
         super(context);
@@ -185,6 +124,8 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mHandler = new H(BackgroundThread.get().getLooper());
 
+        mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
+
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
         mUsageStatsDir = new File(systemDataDir, "usagestats");
         mUsageStatsDir.mkdirs();
@@ -198,30 +139,9 @@
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                 null, mHandler);
 
-        IntentFilter packageFilter = new IntentFilter();
-        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageFilter.addDataScheme("package");
-
-        getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
-                null, mHandler);
-
-        mAppIdleEnabled = getContext().getResources().getBoolean(
-                com.android.internal.R.bool.config_enableAutoPowerModes);
-        if (mAppIdleEnabled) {
-            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
-            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
-        }
-
         synchronized (mLock) {
             cleanUpRemovedUsersLocked();
         }
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
-        }
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -233,28 +153,10 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            // Observe changes to the threshold
-            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
-            settingsObserver.registerObserver();
-            settingsObserver.updateSettings();
+            mAppStandby.onBootPhase(phase);
 
-            mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
             mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                     ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-            mBatteryStats = IBatteryStats.Stub.asInterface(
-                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
-            mDisplayManager = (DisplayManager) getContext().getSystemService(
-                    Context.DISPLAY_SERVICE);
-            mPowerManager = getContext().getSystemService(PowerManager.class);
-
-            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
-            }
-
-            if (mPendingOneTimeCheckIdleStates) {
-                postOneTimeCheckIdleStates();
-            }
 
             if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) {
                 try {
@@ -268,18 +170,9 @@
             } else {
                 Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
             }
-
-            mSystemServicesReady = true;
-        } else if (phase == PHASE_BOOT_COMPLETED) {
-            setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
         }
     }
 
-    private boolean isDisplayOn() {
-        return mDisplayManager
-                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
-    }
-
     private class UserActionsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -291,60 +184,12 @@
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >=0) {
-                    postCheckIdleStates(userId);
+                    mAppStandby.postCheckIdleStates(userId);
                 }
             }
         }
     }
 
-    private class PackageReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
-                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                clearCarrierPrivilegedApps();
-            }
-            if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_ADDED.equals(action))
-                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
-                        getSendingUserId());
-            }
-        }
-    }
-
-    private class DeviceStateReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-                setChargingState(intent.getIntExtra("plugged", 0) != 0);
-            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
-                onDeviceIdleModeChanged();
-            }
-        }
-    }
-
-    private final DisplayManager.DisplayListener mDisplayListener
-            = new DisplayManager.DisplayListener() {
-
-        @Override public void onDisplayAdded(int displayId) {
-        }
-
-        @Override public void onDisplayRemoved(int displayId) {
-        }
-
-        @Override public void onDisplayChanged(int displayId) {
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                final boolean displayOn = isDisplayOn();
-                synchronized (UsageStatsService.this.mAppIdleLock) {
-                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
-                }
-            }
-        }
-    };
-
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -388,42 +233,18 @@
 
     @Override
     public void onStatsReloaded() {
-        postOneTimeCheckIdleStates();
+        mAppStandby.postOneTimeCheckIdleStates();
     }
 
     @Override
     public void onNewUpdate(int userId) {
-        initializeDefaultsForSystemApps(userId);
-    }
-
-    private void initializeDefaultsForSystemApps(int userId) {
-        Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                PackageManager.MATCH_DISABLED_COMPONENTS,
-                userId);
-        final int packageCount = packages.size();
-        synchronized (mAppIdleLock) {
-            for (int i = 0; i < packageCount; i++) {
-                final PackageInfo pi = packages.get(i);
-                String packageName = pi.packageName;
-                if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
-                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
-                }
-            }
-        }
+        mAppStandby.initializeDefaultsForSystemApps(userId);
     }
 
     private boolean shouldObfuscateInstantAppsForCaller(int callingUid, int userId) {
         return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
     }
 
-    void clearAppIdleForPackage(String packageName, int userId) {
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.clearUsage(packageName, userId);
-        }
-    }
-
     private void cleanUpRemovedUsersLocked() {
         final List<UserInfo> users = mUserManager.getUsers(true);
         if (users == null || users.size() == 0) {
@@ -451,195 +272,6 @@
         }
     }
 
-    void setChargingState(boolean charging) {
-        synchronized (mAppIdleLock) {
-            if (mCharging != charging) {
-                mCharging = charging;
-                postParoleStateChanged();
-            }
-        }
-    }
-
-    /** Paroled here means temporary pardon from being inactive */
-    void setAppIdleParoled(boolean paroled) {
-        synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
-            if (mAppIdleTempParoled != paroled) {
-                mAppIdleTempParoled = paroled;
-                if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
-                if (paroled) {
-                    postParoleEndTimeout();
-                } else {
-                    mLastAppIdleParoledTime = now;
-                    postNextParoleTimeout(now);
-                }
-                postParoleStateChanged();
-            }
-        }
-    }
-
-    boolean isParoledOrCharging() {
-        synchronized (mAppIdleLock) {
-            return mAppIdleTempParoled || mCharging;
-        }
-    }
-
-    private void postNextParoleTimeout(long now) {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
-        mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
-        // Compute when the next parole needs to happen. We check more frequently than necessary
-        // since the message handler delays are based on elapsedRealTime and not wallclock time.
-        // The comparison is done in wallclock time.
-        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
-        if (timeLeft < 0) {
-            timeLeft = 0;
-        }
-        mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
-    }
-
-    private void postParoleEndTimeout() {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
-        mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
-    }
-
-    private void postParoleStateChanged() {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
-        mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
-        mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
-    }
-
-    void postCheckIdleStates(int userId) {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
-    }
-
-    /**
-     * We send a different message to check idle states once, otherwise we would end up
-     * scheduling a series of repeating checkIdleStates each time we fired off one.
-     */
-    void postOneTimeCheckIdleStates() {
-        if (mDeviceIdleController == null) {
-            // Not booted yet; wait for it!
-            mPendingOneTimeCheckIdleStates = true;
-        } else {
-            mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
-            mPendingOneTimeCheckIdleStates = false;
-        }
-    }
-
-    /**
-     * Check all running users' or specified user's apps to see if they enter an idle state.
-     * @return Returns whether checking should continue periodically.
-     */
-    boolean checkIdleStates(int checkUserId) {
-        if (!mAppIdleEnabled) {
-            return false;
-        }
-
-        final int[] runningUserIds;
-        try {
-            runningUserIds = ActivityManager.getService().getRunningUserIds();
-            if (checkUserId != UserHandle.USER_ALL
-                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
-                return false;
-            }
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        for (int i = 0; i < runningUserIds.length; i++) {
-            final int userId = runningUserIds[i];
-            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
-                continue;
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Checking idle state for user " + userId);
-            }
-            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                    PackageManager.MATCH_DISABLED_COMPONENTS,
-                    userId);
-            final int packageCount = packages.size();
-            for (int p = 0; p < packageCount; p++) {
-                final PackageInfo pi = packages.get(p);
-                final String packageName = pi.packageName;
-                final boolean isIdle = isAppIdleFiltered(packageName,
-                        UserHandle.getAppId(pi.applicationInfo.uid),
-                        userId, elapsedRealtime);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
-                if (isIdle) {
-                    synchronized (mAppIdleLock) {
-                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
-                    }
-                }
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "checkIdleStates took "
-                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
-        }
-        return true;
-    }
-
-    /** Check if it's been a while since last parole and let idle apps do some work */
-    void checkParoleTimeout() {
-        boolean setParoled = false;
-        synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
-            if (!mAppIdleTempParoled) {
-                final long timeSinceLastParole = now - mLastAppIdleParoledTime;
-                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
-                    if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
-                    setParoled = true;
-                } else {
-                    if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
-                    postNextParoleTimeout(now);
-                }
-            }
-        }
-        if (setParoled) {
-            setAppIdleParoled(true);
-        }
-    }
-
-    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
-        try {
-            final int uid = mPackageManager.getPackageUidAsUser(packageName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-            if (idle) {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
-                        packageName, uid);
-            } else {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
-                        packageName, uid);
-            }
-        } catch (NameNotFoundException | RemoteException e) {
-        }
-    }
-
-    void onDeviceIdleModeChanged() {
-        final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
-        if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
-        boolean paroled = false;
-        synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
-            if (!deviceIdle
-                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
-                }
-                paroled = true;
-            } else if (deviceIdle) {
-                if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
-                paroled = false;
-            } else {
-                return;
-            }
-        }
-        setAppIdleParoled(paroled);
-    }
-
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -724,76 +356,7 @@
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
             service.reportEvent(event);
 
-            synchronized (mAppIdleLock) {
-                // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
-                // about apps that are on some kind of whitelist anyway.
-                final boolean previouslyIdle = mAppIdleHistory.isIdle(
-                        event.mPackage, userId, elapsedRealtime);
-                // Inform listeners if necessary
-                if ((event.mEventType == Event.MOVE_TO_FOREGROUND
-                        || event.mEventType == Event.MOVE_TO_BACKGROUND
-                        || event.mEventType == Event.SYSTEM_INTERACTION
-                        || event.mEventType == Event.USER_INTERACTION)) {
-                    mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
-                    if (previouslyIdle) {
-                        mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                                /* idle = */ 0, event.mPackage));
-                        notifyBatteryStats(event.mPackage, userId, false);
-                    }
-                }
-            }
-        }
-    }
-
-    void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
-        // Get sync adapters for the authority
-        String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
-                authority, userId);
-        for (String packageName: packages) {
-            // Only force the sync adapters to active if the provider is not in the same package and
-            // the sync adapter is a system package.
-            try {
-                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
-                        packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
-                if (pi == null || pi.applicationInfo == null) {
-                    continue;
-                }
-                if (!packageName.equals(providerPkgName)) {
-                    setAppIdleAsync(packageName, false, userId);
-                }
-            } catch (NameNotFoundException e) {
-                // Shouldn't happen
-            }
-        }
-    }
-
-    /**
-     * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
-     * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
-     * the threshold for idle.
-     *
-     * This method is always called from the handler thread, so not much synchronization is
-     * required.
-     */
-    void forceIdleState(String packageName, int userId, boolean idle) {
-        final int appId = getAppId(packageName);
-        if (appId < 0) return;
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-
-        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
-                userId, elapsedRealtime);
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
-        }
-        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
-                userId, elapsedRealtime);
-        // Inform listeners if necessary
-        if (previouslyIdle != stillIdle) {
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                    /* idle = */ stillIdle ? 1 : 0, packageName));
-            if (!stillIdle) {
-                notifyBatteryStats(packageName, userId, idle);
-            }
+            mAppStandby.reportEvent(event, elapsedRealtime, userId);
         }
     }
 
@@ -813,9 +376,7 @@
         synchronized (mLock) {
             Slog.i(TAG, "Removing user " + userId + " and all data.");
             mUserState.remove(userId);
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.onUserRemoved(userId);
-            }
+            mAppStandby.onUserRemoved(userId);
             cleanUpRemovedUsersLocked();
         }
     }
@@ -887,253 +448,6 @@
         }
     }
 
-    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
-        synchronized (mAppIdleLock) {
-            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
-        }
-    }
-
-    void addListener(AppIdleStateChangeListener listener) {
-        synchronized (mAppIdleLock) {
-            if (!mPackageAccessListeners.contains(listener)) {
-                mPackageAccessListeners.add(listener);
-            }
-        }
-    }
-
-    void removeListener(AppIdleStateChangeListener listener) {
-        synchronized (mAppIdleLock) {
-            mPackageAccessListeners.remove(listener);
-        }
-    }
-
-    int getAppId(String packageName) {
-        try {
-            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
-                    PackageManager.MATCH_ANY_USER
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            return ai.uid;
-        } catch (NameNotFoundException re) {
-            return -1;
-        }
-    }
-
-    boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
-            boolean shouldObfuscateInstantApps) {
-        if (isParoledOrCharging()) {
-            return false;
-        }
-        if (shouldObfuscateInstantApps &&
-                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
-            return false;
-        }
-        return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
-    }
-
-    /**
-     * Checks if an app has been idle for a while and filters out apps that are excluded.
-     * It returns false if the current system state allows all apps to be considered active.
-     * This happens if the device is plugged in or temporarily allowed to make exceptions.
-     * Called by interface impls.
-     */
-    private boolean isAppIdleFiltered(String packageName, int appId, int userId,
-            long elapsedRealtime) {
-        if (packageName == null) return false;
-        // If not enabled at all, of course nobody is ever idle.
-        if (!mAppIdleEnabled) {
-            return false;
-        }
-        if (appId < Process.FIRST_APPLICATION_UID) {
-            // System uids never go idle.
-            return false;
-        }
-        if (packageName.equals("android")) {
-            // Nor does the framework (which should be redundant with the above, but for MR1 we will
-            // retain this for safety).
-            return false;
-        }
-        if (mSystemServicesReady) {
-            try {
-                // We allow all whitelisted apps, including those that don't want to be whitelisted
-                // for idle mode, because app idle (aka app standby) is really not as big an issue
-                // for controlling who participates vs. doze mode.
-                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
-                    return false;
-                }
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-
-            if (isActiveDeviceAdmin(packageName, userId)) {
-                return false;
-            }
-
-            if (isActiveNetworkScorer(packageName)) {
-                return false;
-            }
-
-            if (mAppWidgetManager != null
-                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
-                return false;
-            }
-
-            if (isDeviceProvisioningPackage(packageName)) {
-                return false;
-            }
-        }
-
-        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
-            return false;
-        }
-
-        // Check this last, as it is the most expensive check
-        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
-        if (isCarrierApp(packageName)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    int[] getIdleUidsForUser(int userId) {
-        if (!mAppIdleEnabled) {
-            return new int[0];
-        }
-
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-
-        List<ApplicationInfo> apps;
-        try {
-            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
-                    .getInstalledApplications(/* flags= */ 0, userId);
-            if (slice == null) {
-                return new int[0];
-            }
-            apps = slice.getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
-        // associated with that uid, upper 16 bits is the number of those apps that is idle.
-        SparseIntArray uidStates = new SparseIntArray();
-
-        // Now resolve all app state.  Iterating over all apps, keeping track of how many
-        // we find for each uid and how many of those are idle.
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            ApplicationInfo ai = apps.get(i);
-
-            // Check whether this app is idle.
-            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
-                    userId, elapsedRealtime);
-
-            int index = uidStates.indexOfKey(ai.uid);
-            if (index < 0) {
-                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
-            } else {
-                int value = uidStates.valueAt(index);
-                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
-        }
-        int numIdle = 0;
-        for (int i = uidStates.size() - 1; i >= 0; i--) {
-            int value = uidStates.valueAt(i);
-            if ((value&0x7fff) == (value>>16)) {
-                numIdle++;
-            }
-        }
-
-        int[] res = new int[numIdle];
-        numIdle = 0;
-        for (int i = uidStates.size() - 1; i >= 0; i--) {
-            int value = uidStates.valueAt(i);
-            if ((value&0x7fff) == (value>>16)) {
-                res[numIdle] = uidStates.keyAt(i);
-                numIdle++;
-            }
-        }
-
-        return res;
-    }
-
-    void setAppIdleAsync(String packageName, boolean idle, int userId) {
-        if (packageName == null) return;
-
-        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
-                .sendToTarget();
-    }
-
-    private boolean isActiveDeviceAdmin(String packageName, int userId) {
-        DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
-        if (dpm == null) return false;
-        return dpm.packageHasActiveAdmins(packageName, userId);
-    }
-
-    /**
-     * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
-     * returns {@code false}.
-     */
-    private boolean isDeviceProvisioningPackage(String packageName) {
-        String deviceProvisioningPackage = getContext().getResources().getString(
-                com.android.internal.R.string.config_deviceProvisioningPackage);
-        return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
-    }
-
-    private boolean isCarrierApp(String packageName) {
-        synchronized (mAppIdleLock) {
-            if (!mHaveCarrierPrivilegedApps) {
-                fetchCarrierPrivilegedAppsLA();
-            }
-            if (mCarrierPrivilegedApps != null) {
-                return mCarrierPrivilegedApps.contains(packageName);
-            }
-            return false;
-        }
-    }
-
-    void clearCarrierPrivilegedApps() {
-        if (DEBUG) {
-            Slog.i(TAG, "Clearing carrier privileged apps list");
-        }
-        synchronized (mAppIdleLock) {
-            mHaveCarrierPrivilegedApps = false;
-            mCarrierPrivilegedApps = null; // Need to be refetched.
-        }
-    }
-
-    @GuardedBy("mAppIdleLock")
-    private void fetchCarrierPrivilegedAppsLA() {
-        TelephonyManager telephonyManager =
-                getContext().getSystemService(TelephonyManager.class);
-        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
-        mHaveCarrierPrivilegedApps = true;
-        if (DEBUG) {
-            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
-        }
-    }
-
-    private boolean isActiveNetworkScorer(String packageName) {
-        NetworkScoreManager nsm = (NetworkScoreManager) getContext().getSystemService(
-                Context.NETWORK_SCORE_SERVICE);
-        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
-    }
-
-    void informListeners(String packageName, int userId, boolean isIdle) {
-        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onAppIdleStateChanged(packageName, userId, isIdle);
-        }
-    }
-
-    void informParoleStateChanged() {
-        final boolean paroled = isParoledOrCharging();
-        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onParoleStateChanged(paroled);
-        }
-    }
-
     private static boolean validRange(long currentTime, long beginTime, long endTime) {
         return beginTime <= currentTime && beginTime < endTime;
     }
@@ -1143,15 +457,10 @@
         for (int i = 0; i < userCount; i++) {
             UserUsageStatsService service = mUserState.valueAt(i);
             service.persistActiveStats();
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.writeAppIdleTimes(mUserState.keyAt(i));
-            }
+            mAppStandby.flushToDisk(mUserState.keyAt(i));
         }
-        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
-        // considered not-idle, which is the safest outcome in such an event.
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.writeAppIdleDurations();
-        }
+        mAppStandby.flushDurationsToDisk();
+
         mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
@@ -1161,72 +470,56 @@
     void dump(String[] args, PrintWriter pw) {
         synchronized (mLock) {
             IndentingPrintWriter idpw = new IndentingPrintWriter(pw, "  ");
-            ArraySet<String> argSet = new ArraySet<>();
-            argSet.addAll(Arrays.asList(args));
+
+            boolean checkin = false;
+            boolean history = false;
+            String pkg = null;
+
+            if (args != null) {
+                for (int i = 0; i < args.length; i++) {
+                    String arg = args[i];
+                    if ("--checkin".equals(arg)) {
+                        checkin = true;
+                    } else if ("--history".equals(arg)) {
+                        history = true;
+                    } else if ("history".equals(arg)) {
+                        history = true;
+                        break;
+                    } else if ("flush".equals(arg)) {
+                        flushToDiskLocked();
+                        pw.println("Flushed stats to disk");
+                        return;
+                    } else {
+                        // Anything else is a pkg to filter
+                        pkg = arg;
+                        break;
+                    }
+                }
+            }
 
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
-                idpw.printPair("user", mUserState.keyAt(i));
+                int userId = mUserState.keyAt(i);
+                idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
-                if (argSet.contains("--checkin")) {
+                if (checkin) {
                     mUserState.valueAt(i).checkin(idpw);
                 } else {
-                    mUserState.valueAt(i).dump(idpw);
+                    mUserState.valueAt(i).dump(idpw, pkg);
                     idpw.println();
-                    if (args.length > 0) {
-                        if ("history".equals(args[0])) {
-                            synchronized (mAppIdleLock) {
-                                mAppIdleHistory.dumpHistory(idpw, mUserState.keyAt(i));
-                            }
-                        } else if ("flush".equals(args[0])) {
-                            UsageStatsService.this.flushToDiskLocked();
-                            pw.println("Flushed stats to disk");
-                        }
+                    if (history) {
+                        mAppStandby.dumpHistory(idpw, userId);
                     }
                 }
-                synchronized (mAppIdleLock) {
-                    mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
-                }
+                mAppStandby.dumpUser(idpw, userId, pkg);
                 idpw.decreaseIndent();
             }
 
-            pw.println();
-            synchronized (mAppIdleLock) {
-                pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
-                        + "): " + mCarrierPrivilegedApps);
+            if (pkg == null) {
+                pw.println();
+                mAppStandby.dumpState(args, pw);
             }
-
-            pw.println();
-            pw.println("Settings:");
-
-            pw.print("  mAppIdleDurationMillis=");
-            TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleWallclockThresholdMillis=");
-            TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
-            pw.println();
-
-            pw.print("  mCheckIdleIntervalMillis=");
-            TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleParoleIntervalMillis=");
-            TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleParoleDurationMillis=");
-            TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
-            pw.println();
-
-            pw.println();
-            pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
-            pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
-            pw.print(" mCharging="); pw.print(mCharging);
-            pw.print(" mLastAppIdleParoledTime=");
-            TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
-            pw.println();
         }
     }
 
@@ -1250,50 +543,6 @@
                     onUserRemoved(msg.arg1);
                     break;
 
-                case MSG_INFORM_LISTENERS:
-                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
-                    break;
-
-                case MSG_FORCE_IDLE_STATE:
-                    forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
-                    break;
-
-                case MSG_CHECK_IDLE_STATES:
-                    if (checkIdleStates(msg.arg1)) {
-                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
-                                mCheckIdleIntervalMillis);
-                    }
-                    break;
-
-                case MSG_ONE_TIME_CHECK_IDLE_STATES:
-                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
-                    checkIdleStates(UserHandle.USER_ALL);
-                    break;
-
-                case MSG_CHECK_PAROLE_TIMEOUT:
-                    checkParoleTimeout();
-                    break;
-
-                case MSG_PAROLE_END_TIMEOUT:
-                    if (DEBUG) Slog.d(TAG, "Ending parole");
-                    setAppIdleParoled(false);
-                    break;
-
-                case MSG_REPORT_CONTENT_PROVIDER_USAGE:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    reportContentProviderUsage((String) args.arg1, // authority name
-                            (String) args.arg2, // package name
-                            (int) args.arg3); // userId
-                    args.recycle();
-                    break;
-
-                case MSG_PAROLE_STATE_CHANGED:
-                    if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
-                            + ", Charging state:" + mCharging);
-                    informParoleStateChanged();
-                    break;
-
                 default:
                     super.handleMessage(msg);
                     break;
@@ -1301,72 +550,6 @@
         }
     }
 
-    /**
-     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
-     */
-    private class SettingsObserver extends ContentObserver {
-        /**
-         * This flag has been used to disable app idle on older builds with bug b/26355386.
-         */
-        @Deprecated
-        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
-
-        private static final String KEY_IDLE_DURATION = "idle_duration2";
-        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
-        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
-        private static final String KEY_PAROLE_DURATION = "parole_duration";
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-
-        SettingsObserver(Handler handler) {
-            super(handler);
-        }
-
-        void registerObserver() {
-            getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            updateSettings();
-            postOneTimeCheckIdleStates();
-        }
-
-        void updateSettings() {
-            synchronized (mAppIdleLock) {
-                // Look at global settings for this.
-                // TODO: Maybe apply different thresholds for different users.
-                try {
-                    mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
-                            Settings.Global.APP_IDLE_CONSTANTS));
-                } catch (IllegalArgumentException e) {
-                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
-                    // fallthrough, mParser is empty and all defaults will be returned.
-                }
-
-                // Default: 12 hours of screen-on time sans dream-time
-                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
-                       COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
-                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
-                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
-                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
-                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
-
-                // Default: 24 hours between paroles
-                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
-                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
-
-                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
-                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
-                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
-                        mAppIdleScreenThresholdMillis);
-            }
-        }
-    }
-
     private final class BinderService extends IUsageStatsManager.Stub {
 
         private boolean hasPermission(String callingPackage) {
@@ -1462,7 +645,8 @@
                     Binder.getCallingUid(), userId);
             final long token = Binder.clearCallingIdentity();
             try {
-                return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
+                return mAppStandby.isAppIdleFilteredOrParoled(
+                        packageName, userId,
                         SystemClock.elapsedRealtime(), obfuscateInstantApps);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1483,9 +667,58 @@
                     "No permission to change app idle state");
             final long token = Binder.clearCallingIdentity();
             try {
-                final int appId = getAppId(packageName);
+                final int appId = mAppStandby.getAppId(packageName);
                 if (appId < 0) return;
-                UsageStatsService.this.setAppIdleAsync(packageName, idle, userId);
+                mAppStandby.setAppIdleAsync(packageName, idle, userId);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getAppStandbyBucket(String packageName, String callingPackage, int userId) {
+            if (!hasPermission(callingPackage)) {
+                throw new SecurityException("Don't have permission to query app standby bucket");
+            }
+
+            final int callingUid = Binder.getCallingUid();
+            try {
+                userId = ActivityManager.getService().handleIncomingUser(
+                        Binder.getCallingPid(), callingUid, userId, false, true,
+                        "getAppStandbyBucket", null);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+            final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller(callingUid,
+                    userId);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                return mAppStandby.getAppStandbyBucket(packageName, userId,
+                        SystemClock.elapsedRealtime(), obfuscateInstantApps);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setAppStandbyBucket(String packageName,
+                int bucket, int userId) {
+            getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
+                    "No permission to change app standby state");
+
+            final int callingUid = Binder.getCallingUid();
+            try {
+                userId = ActivityManager.getService().handleIncomingUser(
+                        Binder.getCallingPid(), callingUid, userId, false, true,
+                        "setAppStandbyBucket", null);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mAppStandby.setAppStandbyBucket(packageName, userId, bucket,
+                        AppStandby.REASON_PREDICTED + ":" + callingUid,
+                        SystemClock.elapsedRealtime());
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1509,7 +742,7 @@
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.BIND_CARRIER_SERVICES,
                     "onCarrierPrivilegedAppsChanged can only be called by privileged apps.");
-            UsageStatsService.this.clearCarrierPrivilegedApps();
+            mAppStandby.clearCarrierPrivilegedApps();
         }
 
         @Override
@@ -1624,28 +857,23 @@
 
         @Override
         public void reportContentProviderUsage(String name, String packageName, int userId) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = name;
-            args.arg2 = packageName;
-            args.arg3 = userId;
-            mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
-                    .sendToTarget();
+            mAppStandby.postReportContentProviderUsage(name, packageName, userId);
         }
 
         @Override
         public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
-            return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
-                    SystemClock.elapsedRealtime());
+            return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
+                    userId, SystemClock.elapsedRealtime());
         }
 
         @Override
         public int[] getIdleUidsForUser(int userId) {
-            return UsageStatsService.this.getIdleUidsForUser(userId);
+            return mAppStandby.getIdleUidsForUser(userId);
         }
 
         @Override
         public boolean isAppIdleParoleOn() {
-            return isParoledOrCharging();
+            return mAppStandby.isParoledOrCharging();
         }
 
         @Override
@@ -1658,20 +886,20 @@
 
         @Override
         public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
-            UsageStatsService.this.addListener(listener);
+            mAppStandby.addListener(listener);
             listener.onParoleStateChanged(isAppIdleParoleOn());
         }
 
         @Override
         public void removeAppIdleStateChangeListener(
                 AppIdleStateChangeListener listener) {
-            UsageStatsService.this.removeListener(listener);
+            mAppStandby.removeListener(listener);
         }
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
             // Check to ensure that only user 0's data is b/r for now
-            synchronized (UsageStatsService.this.mLock) {
+            synchronized (mLock) {
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats =
                             getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
@@ -1684,7 +912,7 @@
 
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
-            synchronized (UsageStatsService.this.mLock) {
+            synchronized (mLock) {
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats =
                             getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 0abbb82..0b10590 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -474,19 +474,19 @@
         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
             @Override
             public boolean checkin(IntervalStats stats) {
-                printIntervalStats(pw, stats, false);
+                printIntervalStats(pw, stats, false, null);
                 return true;
             }
         });
     }
 
-    void dump(IndentingPrintWriter pw) {
+    void dump(IndentingPrintWriter pw, String pkg) {
         // This is not a check-in, only dump in-memory stats.
         for (int interval = 0; interval < mCurrentStats.length; interval++) {
             pw.print("In-memory ");
             pw.print(intervalToString(interval));
             pw.println(" stats");
-            printIntervalStats(pw, mCurrentStats[interval], true);
+            printIntervalStats(pw, mCurrentStats[interval], true, pkg);
         }
     }
 
@@ -505,7 +505,7 @@
     }
 
     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
-            boolean prettyDates) {
+            boolean prettyDates, String pkg) {
         if (prettyDates) {
             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
@@ -521,6 +521,9 @@
         final int pkgCount = pkgStats.size();
         for (int i = 0; i < pkgCount; i++) {
             final UsageStats usageStats = pkgStats.valueAt(i);
+            if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+                continue;
+            }
             pw.printPair("package", usageStats.mPackageName);
             pw.printPair("totalTime",
                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
@@ -533,6 +536,9 @@
         pw.println("ChooserCounts");
         pw.increaseIndent();
         for (UsageStats usageStats : pkgStats.values()) {
+            if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
+                continue;
+            }
             pw.printPair("package", usageStats.mPackageName);
             if (usageStats.mChooserCounts != null) {
                 final int chooserCountSize = usageStats.mChooserCounts.size();
@@ -555,19 +561,22 @@
         }
         pw.decreaseIndent();
 
-        pw.println("configurations");
-        pw.increaseIndent();
-        final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
-        final int configCount = configStats.size();
-        for (int i = 0; i < configCount; i++) {
-            final ConfigurationStats config = configStats.valueAt(i);
-            pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
-            pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
-            pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
-            pw.printPair("count", config.mActivationCount);
-            pw.println();
+        if (pkg == null) {
+            pw.println("configurations");
+            pw.increaseIndent();
+            final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
+            final int configCount = configStats.size();
+            for (int i = 0; i < configCount; i++) {
+                final ConfigurationStats config = configStats.valueAt(i);
+                pw.printPair("config", Configuration.resourceQualifierString(
+                        config.mConfiguration));
+                pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
+                pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
+                pw.printPair("count", config.mActivationCount);
+                pw.println();
+            }
+            pw.decreaseIndent();
         }
-        pw.decreaseIndent();
 
         pw.println("events");
         pw.increaseIndent();
@@ -575,6 +584,9 @@
         final int eventCount = events != null ? events.size() : 0;
         for (int i = 0; i < eventCount; i++) {
             final UsageEvents.Event event = events.valueAt(i);
+            if (pkg != null && !pkg.equals(event.mPackage)) {
+                continue;
+            }
             pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
             pw.printPair("type", eventToString(event.mEventType));
             pw.printPair("package", event.mPackage);
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 68c1d5f..d359b70 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -132,7 +132,9 @@
         mHasMidiFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI);
 
         // initial scan
-        mCardsParser.scan();
+        if (mCardsParser.scan() != AlsaCardsParser.SCANSTATUS_SUCCESS) {
+            Slog.e(TAG, "Error scanning ASLA cards file.");
+        }
     }
 
     public void systemReady() {
@@ -314,7 +316,11 @@
             return null;
         }
 
-        mDevicesParser.scan();
+        if (mDevicesParser.scan() != AlsaDevicesParser.SCANSTATUS_SUCCESS) {
+            Slog.e(TAG, "Error parsing ALSA devices file.");
+            return null;
+        }
+
         int device = mDevicesParser.getDefaultDeviceNum(card);
 
         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
@@ -526,6 +532,9 @@
     //
     // called by UsbService.dump
     public void dump(IndentingPrintWriter pw) {
+        pw.println("Parsers Scan Status:");
+        pw.println("  Cards Parser: " + mCardsParser.getScanStatus());
+        pw.println("  Devices Parser: " + mDevicesParser.getScanStatus());
         pw.println("USB Audio Devices:");
         for (UsbDevice device : mAudioDevices.keySet()) {
             pw.println("  " + device.getDeviceName() + ": " + mAudioDevices.get(device));
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index c657a1b..095fdc6 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -376,6 +376,8 @@
                 }
             }
         }
+
+        mUsbAlsaManager.dump(pw);
     }
 
     private native void monitorUsbHostBus();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index b040a63..7541b92 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -55,6 +55,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
@@ -162,13 +163,16 @@
                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
         }
         List<IBinder> activityTokens = null;
-        if (activityToken == null) {
+        if (activityToken != null) {
+            activityTokens = new ArrayList<>();
+            activityTokens.add(activityToken);
+        } else {
             // Let's get top activities from all visible stacks
             activityTokens = LocalServices.getService(ActivityManagerInternal.class)
                     .getTopVisibleActivities();
         }
         return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
-                activityToken, activityTokens);
+                activityTokens);
     }
 
     public boolean hideSessionLocked() {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index d394d63..925219d 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -16,6 +16,16 @@
 
 package com.android.server.voiceinteraction;
 
+import static android.app.ActivityManagerInternal.ASSIST_KEY_CONTENT;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_DATA;
+import static android.app.ActivityManagerInternal.ASSIST_KEY_STRUCTURE;
+import static android.app.AppOpsManager.OP_ASSIST_SCREENSHOT;
+import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
@@ -43,31 +53,24 @@
 import android.service.voice.VoiceInteractionSession;
 import android.util.Slog;
 import android.view.IWindowManager;
-import android.view.WindowManager;
 
 import com.android.internal.app.AssistUtils;
-import com.android.internal.app.IAssistScreenshotReceiver;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.am.AssistDataRequester;
+import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
-
-final class VoiceInteractionSessionConnection implements ServiceConnection {
+final class VoiceInteractionSessionConnection implements ServiceConnection,
+        AssistDataRequesterCallbacks {
 
     final static String TAG = "VoiceInteractionServiceManager";
 
-    private static final String KEY_RECEIVER_EXTRA_COUNT = "count";
-    private static final String KEY_RECEIVER_EXTRA_INDEX = "index";
-
     final IBinder mToken = new Binder();
     final Object mLock;
     final ComponentName mSessionComponentName;
@@ -90,27 +93,8 @@
     IVoiceInteractionSessionService mService;
     IVoiceInteractionSession mSession;
     IVoiceInteractor mInteractor;
-    boolean mHaveAssistData;
-    int mPendingAssistDataCount;
-    ArrayList<AssistDataForActivity> mAssistData = new ArrayList<>();
-    boolean mHaveScreenshot;
-    Bitmap mScreenshot;
     ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
-
-    static class AssistDataForActivity {
-        int activityIndex;
-        int activityCount;
-        Bundle data;
-
-        public AssistDataForActivity(Bundle data) {
-            this.data = data;
-            Bundle receiverExtras = data.getBundle(VoiceInteractionSession.KEY_RECEIVER_EXTRAS);
-            if (receiverExtras != null) {
-                activityIndex = receiverExtras.getInt(KEY_RECEIVER_EXTRA_INDEX);
-                activityCount = receiverExtras.getInt(KEY_RECEIVER_EXTRA_COUNT);
-            }
-        }
-    }
+    AssistDataRequester mAssistDataRequester;
 
     IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -146,32 +130,6 @@
         }
     };
 
-    final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
-        @Override
-        public void send(int resultCode, Bundle resultData) throws RemoteException {
-            synchronized (mLock) {
-                if (mShown) {
-                    mHaveAssistData = true;
-                    mAssistData.add(new AssistDataForActivity(resultData));
-                    deliverSessionDataLocked();
-                }
-            }
-        }
-    };
-
-    final IAssistScreenshotReceiver mScreenshotReceiver = new IAssistScreenshotReceiver.Stub() {
-        @Override
-        public void send(Bitmap screenshot) throws RemoteException {
-            synchronized (mLock) {
-                if (mShown) {
-                    mHaveScreenshot = true;
-                    mScreenshot = screenshot;
-                    deliverSessionDataLocked();
-                }
-            }
-        }
-    };
-
     public VoiceInteractionSessionConnection(Object lock, ComponentName component, int user,
             Context context, Callback callback, int callingUid, Handler handler) {
         mLock = lock;
@@ -185,6 +143,9 @@
         mIWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService(Context.WINDOW_SERVICE));
         mAppOps = context.getSystemService(AppOpsManager.class);
+        mAssistDataRequester = new AssistDataRequester(mContext, mAm, mIWindowManager,
+                (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
+                this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
         IBinder permOwner = null;
         try {
             permOwner = mAm.newUriPermissionOwner("voicesession:"
@@ -224,8 +185,7 @@
     }
 
     public boolean showLocked(Bundle args, int flags, int disabledContext,
-            IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken,
-            List<IBinder> topActivities) {
+            IVoiceInteractionSessionShowCallback showCallback, List<IBinder> topActivities) {
         if (mBound) {
             if (!mFullyBound) {
                 mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -233,75 +193,18 @@
                                 | Context.BIND_FOREGROUND_SERVICE,
                         new UserHandle(mUser));
             }
+
             mShown = true;
-            boolean isAssistDataAllowed = true;
-            try {
-                isAssistDataAllowed = mAm.isAssistDataAllowedOnCurrentActivity();
-            } catch (RemoteException e) {
-            }
-            disabledContext |= getUserDisabledShowContextLocked();
-            boolean structureEnabled = isAssistDataAllowed
-                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_ASSIST) == 0;
-            boolean screenshotEnabled = isAssistDataAllowed && structureEnabled
-                    && (disabledContext&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0;
             mShowArgs = args;
             mShowFlags = flags;
-            mHaveAssistData = false;
-            mPendingAssistDataCount = 0;
-            boolean needDisclosure = false;
-            if ((flags&VoiceInteractionSession.SHOW_WITH_ASSIST) != 0) {
-                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_STRUCTURE, mCallingUid,
-                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
-                        && structureEnabled) {
-                    mAssistData.clear();
-                    final int count = activityToken != null ? 1 : topActivities.size();
-                    for (int i = 0; i < count; i++) {
-                        IBinder topActivity = count == 1 ? activityToken : topActivities.get(i);
-                        try {
-                            MetricsLogger.count(mContext, "assist_with_context", 1);
-                            Bundle receiverExtras = new Bundle();
-                            receiverExtras.putInt(KEY_RECEIVER_EXTRA_INDEX, i);
-                            receiverExtras.putInt(KEY_RECEIVER_EXTRA_COUNT, count);
-                            if (mAm.requestAssistContextExtras(ActivityManager.ASSIST_CONTEXT_FULL,
-                                    mAssistReceiver, receiverExtras, topActivity,
-                                    /* focused= */ i == 0, /* newSessionId= */ i == 0)) {
-                                needDisclosure = true;
-                                mPendingAssistDataCount++;
-                            } else if (i == 0) {
-                                // Wasn't allowed... given that, let's not do the screenshot either.
-                                mHaveAssistData = true;
-                                mAssistData.clear();
-                                screenshotEnabled = false;
-                                break;
-                            }
-                        } catch (RemoteException e) {
-                        }
-                    }
-                } else {
-                    mHaveAssistData = true;
-                    mAssistData.clear();
-                }
-            } else {
-                mAssistData.clear();
-            }
-            mHaveScreenshot = false;
-            if ((flags&VoiceInteractionSession.SHOW_WITH_SCREENSHOT) != 0) {
-                if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ASSIST_SCREENSHOT, mCallingUid,
-                        mSessionComponentName.getPackageName()) == AppOpsManager.MODE_ALLOWED
-                        && screenshotEnabled) {
-                    try {
-                        MetricsLogger.count(mContext, "assist_with_screen", 1);
-                        needDisclosure = true;
-                        mIWindowManager.requestAssistScreenshot(mScreenshotReceiver);
-                    } catch (RemoteException e) {
-                    }
-                } else {
-                    mHaveScreenshot = true;
-                    mScreenshot = null;
-                }
-            } else {
-                mScreenshot = null;
-            }
+
+            mAssistDataRequester.requestAssistData(topActivities,
+                    (disabledContext & VoiceInteractionSession.SHOW_WITH_ASSIST) == 0,
+                    (disabledContext & VoiceInteractionSession.SHOW_WITH_SCREENSHOT) == 0,
+                    mCallingUid, mSessionComponentName.getPackageName());
+
+            boolean needDisclosure = mAssistDataRequester.getPendingDataCount() > 0
+                    || mAssistDataRequester.getPendingScreenshotCount() > 0;
             if (needDisclosure && AssistUtils.shouldDisclose(mContext, mSessionComponentName)) {
                 mHandler.post(mShowAssistDisclosureRunnable);
             }
@@ -312,7 +215,6 @@
                     mShowFlags = 0;
                 } catch (RemoteException e) {
                 }
-                deliverSessionDataLocked();
             } else if (showCallback != null) {
                 mPendingShowCallbacks.add(showCallback);
             }
@@ -328,6 +230,57 @@
         return false;
     }
 
+    @Override
+    public boolean canHandleReceivedAssistDataLocked() {
+        return mSession != null;
+    }
+
+    @Override
+    public void onAssistDataReceivedLocked(Bundle data, int activityIndex, int activityCount) {
+        if (data == null) {
+            try {
+                mSession.handleAssist(null, null, null, 0, 0);
+            } catch (RemoteException e) {
+                // Can't happen
+            }
+        } else {
+            final Bundle assistData = data.getBundle(ASSIST_KEY_DATA);
+            final AssistStructure structure = data.getParcelable(ASSIST_KEY_STRUCTURE);
+            final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+            final int uid = data.getInt(Intent.EXTRA_ASSIST_UID, -1);
+            if (uid >= 0 && content != null) {
+                Intent intent = content.getIntent();
+                if (intent != null) {
+                    ClipData clipData = intent.getClipData();
+                    if (clipData != null && Intent.isAccessUriMode(intent.getFlags())) {
+                        grantClipDataPermissions(clipData, intent.getFlags(), uid,
+                                mCallingUid, mSessionComponentName.getPackageName());
+                    }
+                }
+                ClipData clipData = content.getClipData();
+                if (clipData != null) {
+                    grantClipDataPermissions(clipData, FLAG_GRANT_READ_URI_PERMISSION,
+                            uid, mCallingUid, mSessionComponentName.getPackageName());
+                }
+            }
+            try {
+                mSession.handleAssist(assistData, structure, content, activityIndex,
+                        activityCount);
+            } catch (RemoteException e) {
+                // Can't happen
+            }
+        }
+    }
+
+    @Override
+    public void onAssistScreenshotReceivedLocked(Bitmap screenshot) {
+        try {
+            mSession.handleScreenshot(screenshot);
+        } catch (RemoteException e) {
+            // Can't happen
+        }
+    }
+
     void grantUriPermission(Uri uri, int mode, int srcUid, int destUid, String destPkg) {
         if (!"content".equals(uri.getScheme())) {
             return;
@@ -341,7 +294,7 @@
             int sourceUserId = ContentProvider.getUserIdFromUri(uri, mUser);
             uri = ContentProvider.getUriWithoutUserId(uri);
             mAm.grantUriPermissionFromOwner(mPermissionOwner, srcUid, destPkg,
-                    uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
+                    uri, FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, mUser);
         } catch (RemoteException e) {
         } catch (SecurityException e) {
             Slog.w(TAG, "Can't propagate permission", e);
@@ -370,89 +323,13 @@
         }
     }
 
-    void deliverSessionDataLocked() {
-        if (mSession == null) {
-            return;
-        }
-        if (mHaveAssistData) {
-            AssistDataForActivity assistData;
-            if (mAssistData.isEmpty()) {
-                // We're not actually going to get any data, deliver some nothing
-                try {
-                    mSession.handleAssist(null, null, null, 0, 0);
-                } catch (RemoteException e) {
-                }
-            } else {
-                while (!mAssistData.isEmpty()) {
-                    if (mPendingAssistDataCount <= 0) {
-                        Slog.e(TAG, "mPendingAssistDataCount is " + mPendingAssistDataCount);
-                    }
-                    mPendingAssistDataCount--;
-                    assistData = mAssistData.remove(0);
-                    if (assistData.data == null) {
-                        try {
-                            mSession.handleAssist(null, null, null, assistData.activityIndex,
-                                    assistData.activityCount);
-                        } catch (RemoteException e) {
-                        }
-                    } else {
-                        deliverSessionDataLocked(assistData);
-                    }
-                }
-            }
-            if (mPendingAssistDataCount <= 0) {
-                mHaveAssistData = false;
-            } // else, more to come
-        }
-        if (mHaveScreenshot) {
-            try {
-                mSession.handleScreenshot(mScreenshot);
-            } catch (RemoteException e) {
-            }
-            mScreenshot = null;
-            mHaveScreenshot = false;
-        }
-    }
-
-    private void deliverSessionDataLocked(AssistDataForActivity assistDataForActivity) {
-        Bundle assistData = assistDataForActivity.data.getBundle(
-                VoiceInteractionSession.KEY_DATA);
-        AssistStructure structure = assistDataForActivity.data.getParcelable(
-                VoiceInteractionSession.KEY_STRUCTURE);
-        AssistContent content = assistDataForActivity.data.getParcelable(
-                VoiceInteractionSession.KEY_CONTENT);
-        int uid = assistDataForActivity.data.getInt(Intent.EXTRA_ASSIST_UID, -1);
-        if (uid >= 0 && content != null) {
-            Intent intent = content.getIntent();
-            if (intent != null) {
-                ClipData data = intent.getClipData();
-                if (data != null && Intent.isAccessUriMode(intent.getFlags())) {
-                    grantClipDataPermissions(data, intent.getFlags(), uid,
-                            mCallingUid, mSessionComponentName.getPackageName());
-                }
-            }
-            ClipData data = content.getClipData();
-            if (data != null) {
-                grantClipDataPermissions(data,
-                        Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                        uid, mCallingUid, mSessionComponentName.getPackageName());
-            }
-        }
-        try {
-            mSession.handleAssist(assistData, structure, content,
-                    assistDataForActivity.activityIndex, assistDataForActivity.activityCount);
-        } catch (RemoteException e) {
-        }
-    }
-
     public boolean hideLocked() {
         if (mBound) {
             if (mShown) {
                 mShown = false;
                 mShowArgs = null;
                 mShowFlags = 0;
-                mHaveAssistData = false;
-                mAssistData.clear();
+                mAssistDataRequester.cancel();
                 mPendingShowCallbacks.clear();
                 if (mSession != null) {
                     try {
@@ -462,8 +339,7 @@
                 }
                 try {
                     mAm.revokeUriPermissionFromOwner(mPermissionOwner, null,
-                            Intent.FLAG_GRANT_READ_URI_PERMISSION
-                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                            FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION,
                             mUser);
                 } catch (RemoteException e) {
                 }
@@ -529,7 +405,7 @@
                 mShowFlags = 0;
             } catch (RemoteException e) {
             }
-            deliverSessionDataLocked();
+            mAssistDataRequester.processPendingAssistData();
         }
         return true;
     }
@@ -587,10 +463,7 @@
             pw.print(prefix); pw.print("mSession="); pw.println(mSession);
             pw.print(prefix); pw.print("mInteractor="); pw.println(mInteractor);
         }
-        pw.print(prefix); pw.print("mHaveAssistData="); pw.println(mHaveAssistData);
-        if (mHaveAssistData) {
-            pw.print(prefix); pw.print("mAssistData="); pw.println(mAssistData);
-        }
+        mAssistDataRequester.dump(prefix, pw);
     }
 
     private Runnable mShowAssistDisclosureRunnable = new Runnable() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 689ce95..03d5f9d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -210,6 +210,12 @@
     public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
 
     /**
+     * Determine whether user can edit voicemail number in Settings.
+     */
+    public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL =
+            "editable_voicemail_number_setting_bool";
+
+    /**
      * Since the default voicemail number is empty, if a SIM card does not have a voicemail number
      * available the user cannot use voicemail. This flag allows the user to edit the voicemail
      * number in such cases, and is false by default.
@@ -346,6 +352,17 @@
     public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
 
     /**
+     * Flag that specifies to use the user's own phone number as the voicemail number when there is
+     * no pre-loaded voicemail number on the SIM card.
+     * <p>
+     * {@link #KEY_DEFAULT_VM_NUMBER_STRING} takes precedence over this flag.
+     * <p>
+     * If false, the system default (*86) will be used instead.
+     */
+    public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL =
+            "config_telephony_use_own_number_for_voicemail_bool";
+
+    /**
      * When {@code true}, changes to the mobile data enabled switch will not cause the VT
      * registration state to change.  That is, turning on or off mobile data will not cause VT to be
      * enabled or disabled.
@@ -763,6 +780,18 @@
     public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int";
 
     /**
+     * Some carriers will send call forwarding responses for voicemail in a format that is not 3gpp
+     * compliant, which causes issues during parsing. This causes the
+     * {@link com.android.internal.telephony.CallForwardInfo#number} to contain non-numerical
+     * characters instead of a number.
+     *
+     * If true, we will detect the non-numerical characters and replace them with "Voicemail".
+     * @hide
+     */
+    public static final String KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL =
+            "call_forwarding_map_non_number_to_voicemail_bool";
+
+    /**
      * Determines whether conference calls are supported by a carrier.  When {@code true},
      * conference calling is supported, {@code false otherwise}.
      */
@@ -1169,24 +1198,21 @@
      */
     public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int";
 
-
     /**
-     * @hide
-     * The default value for preferred CDMA roaming mode (aka CDMA system select.)
-     *          CDMA_ROAMING_MODE_RADIO_DEFAULT = the default roaming mode from the radio
-     *          CDMA_ROAMING_MODE_HOME = Home Networks
-     *          CDMA_ROAMING_MODE_AFFILIATED = Roaming on Affiliated networks
-     *          CDMA_ROAMING_MODE_ANY = Roaming on any networks
+     * The CDMA roaming mode (aka CDMA system select).
+     *
+     * <p>The value should be one of the CDMA_ROAMING_MODE_ constants in {@link TelephonyManager}.
+     * Values other than {@link TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT} (which is the
+     * default) will take precedence over user selection.
+     *
+     * @see TelephonyManager#CDMA_ROAMING_MODE_RADIO_DEFAULT
+     * @see TelephonyManager#CDMA_ROAMING_MODE_HOME
+     * @see TelephonyManager#CDMA_ROAMING_MODE_AFFILIATED
+     * @see TelephonyManager#CDMA_ROAMING_MODE_ANY
      */
     public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_HOME = 0;
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
-    /** @hide */
-    public static final int CDMA_ROAMING_MODE_ANY = 2;
+
+
     /**
      * Boolean indicating if support is provided for directly dialing FDN number from FDN list.
      * If false, this feature is not supported.
@@ -1407,6 +1433,17 @@
         "support_3gpp_call_forwarding_while_roaming_bool";
 
     /**
+     * Boolean indicating whether to display voicemail number as default call forwarding number in
+     * call forwarding settings.
+     * If true, display vm number when cf number is null.
+     * If false, display the cf number from network.
+     * By default this value is false.
+     * @hide
+     */
+    public static final String KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL =
+            "display_voicemail_number_as_default_call_forwarding_number";
+
+    /**
      * When {@code true}, the user will be notified when they attempt to place an international call
      * when the call is placed using wifi calling.
      * @hide
@@ -1573,6 +1610,32 @@
     public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
             "show_ims_registration_status_bool";
 
+    /**
+     * The flag to disable the popup dialog which warns the user of data charges.
+     * @hide
+     */
+    public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
+            "disable_charge_indication_bool";
+
+    /**
+     * Boolean indicating whether to skip the call forwarding (CF) fail-to-disable dialog.
+     * The logic used to determine whether we succeeded in disabling is carrier specific,
+     * so the dialog may not always be accurate.
+     * {@code false} - show CF fail-to-disable dialog.
+     * {@code true}  - skip showing CF fail-to-disable dialog.
+     *
+     * @hide
+     */
+    public static final String KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL =
+            "skip_cf_fail_to_disable_dialog_bool";
+
+    /**
+     * List of the FAC (feature access codes) to dial as a normal call.
+     * @hide
+     */
+    public static final String KEY_FEATURE_ACCESS_CODES_STRING_ARRAY =
+            "feature_access_codes_string_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1590,6 +1653,7 @@
         sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
         sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
+        sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
         sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
         sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
@@ -1632,6 +1696,7 @@
         sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true);
         sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false);
+        sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false);
         sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false);
@@ -1703,6 +1768,7 @@
         sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0);
         sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
         sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
+        sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
@@ -1726,6 +1792,7 @@
         sDefaults.putString(KEY_CARRIER_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL, false);
+        sDefaults.putBoolean(KEY_SKIP_CF_FAIL_TO_DISABLE_DIALOG_BOOL, false);
 
         // MMS defaults
         sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false);
@@ -1763,7 +1830,8 @@
         sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true);
         sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false);
         sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false);
-        sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT);
+        sDefaults.putInt(
+                KEY_CDMA_ROAMING_MODE_INT, TelephonyManager.CDMA_ROAMING_MODE_RADIO_DEFAULT);
         sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, "");
 
         // Carrier Signalling Receivers
@@ -1827,6 +1895,8 @@
         sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1);
         sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+        sDefaults.putBoolean(KEY_DISPLAY_VOICEMAIL_NUMBER_AS_DEFAULT_CALL_FORWARDING_NUMBER_BOOL,
+                false);
         sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false);
         sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
                 null);
@@ -1840,6 +1910,8 @@
         sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
+        sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
+        sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index b39b4c7..ddc938e 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -50,6 +51,10 @@
      * to +90 degrees).
      */
     private final int mLatitude;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
@@ -60,6 +65,8 @@
         mBasestationId = Integer.MAX_VALUE;
         mLongitude = Integer.MAX_VALUE;
         mLatitude = Integer.MAX_VALUE;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
 
     /**
@@ -75,19 +82,37 @@
      * @hide
      */
     public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) {
+        this(nid, sid, bid, lon, lat, null, null);
+    }
+
+    /**
+     * public constructor
+     * @param nid Network Id 0..65535
+     * @param sid CDMA System Id 0..32767
+     * @param bid Base Station Id 0..65535
+     * @param lon Longitude is a decimal number ranges from -2592000
+     *        to 2592000
+     * @param lat Latitude is a decimal number ranges from -1296000
+     *        to 1296000
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @hide
+     */
+    public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat, String alphal,
+                             String alphas) {
         mNetworkId = nid;
         mSystemId = sid;
         mBasestationId = bid;
         mLongitude = lon;
         mLatitude = lat;
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityCdma(CellIdentityCdma cid) {
-        mNetworkId = cid.mNetworkId;
-        mSystemId = cid.mSystemId;
-        mBasestationId = cid.mBasestationId;
-        mLongitude = cid.mLongitude;
-        mLatitude = cid.mLatitude;
+        this(cid.mNetworkId, cid.mSystemId, cid.mBasestationId, cid.mLongitude, cid.mLatitude,
+                cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityCdma copy() {
@@ -137,9 +162,26 @@
         return mLatitude;
     }
 
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude);
+        return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude,
+                mAlphaLong, mAlphaShort);
     }
 
     @Override
@@ -153,11 +195,14 @@
         }
 
         CellIdentityCdma o = (CellIdentityCdma) other;
+
         return mNetworkId == o.mNetworkId &&
                 mSystemId == o.mSystemId &&
                 mBasestationId == o.mBasestationId &&
                 mLatitude == o.mLatitude &&
-                mLongitude == o.mLongitude;
+                mLongitude == o.mLongitude &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
@@ -168,6 +213,8 @@
         sb.append(" mBasestationId="); sb.append(mBasestationId);
         sb.append(" mLongitude="); sb.append(mLongitude);
         sb.append(" mLatitude="); sb.append(mLatitude);
+        sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+        sb.append(" mAlphaShort="); sb.append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -188,15 +235,15 @@
         dest.writeInt(mBasestationId);
         dest.writeInt(mLongitude);
         dest.writeInt(mLatitude);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityCdma(Parcel in) {
-        mNetworkId = in.readInt();
-        mSystemId = in.readInt();
-        mBasestationId = in.readInt();
-        mLongitude = in.readInt();
-        mLatitude = in.readInt();
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readInt(),
+                in.readString(), in.readString());
+
         if (DBG) log("CellIdentityCdma(Parcel): " + toString());
     }
 
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index ec008e2..6276626 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -30,10 +31,6 @@
     private static final String LOG_TAG = "CellIdentityGsm";
     private static final boolean DBG = false;
 
-    // 3-digit Mobile Country Code, 0..999
-    private final int mMcc;
-    // 2 or 3-digit Mobile Network Code, 0..999
-    private final int mMnc;
     // 16-bit Location Area Code, 0..65535
     private final int mLac;
     // 16-bit GSM Cell Identity described in TS 27.007, 0..65535
@@ -42,17 +39,27 @@
     private final int mArfcn;
     // 6-bit Base Station Identity Code
     private final int mBsic;
+    // 3-digit Mobile Country Code in string format
+    private final String mMccStr;
+    // 2 or 3-digit Mobile Network Code in string format
+    private final String mMncStr;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
      */
     public CellIdentityGsm() {
-        mMcc = Integer.MAX_VALUE;
-        mMnc = Integer.MAX_VALUE;
         mLac = Integer.MAX_VALUE;
         mCid = Integer.MAX_VALUE;
         mArfcn = Integer.MAX_VALUE;
         mBsic = Integer.MAX_VALUE;
+        mMccStr = null;
+        mMncStr = null;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
     /**
      * public constructor
@@ -64,7 +71,8 @@
      * @hide
      */
     public CellIdentityGsm (int mcc, int mnc, int lac, int cid) {
-        this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE);
+        this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE,
+                String.valueOf(mcc), String.valueOf(mnc), null, null);
     }
 
     /**
@@ -79,39 +87,81 @@
      * @hide
      */
     public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) {
-        mMcc = mcc;
-        mMnc = mnc;
+        this(lac, cid, arfcn, bsic, String.valueOf(mcc), String.valueOf(mnc), null, null);
+    }
+
+    /**
+     * public constructor
+     * @param lac 16-bit Location Area Code, 0..65535
+     * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity
+     * @param arfcn 16-bit GSM Absolute RF Channel Number
+     * @param bsic 6-bit Base Station Identity Code
+     * @param mccStr 3-digit Mobile Country Code in string format
+     * @param mncStr 2 or 3-digit Mobile Network Code in string format
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+     * not a 2 or 3-digit code.
+     *
+     * @hide
+     */
+    public CellIdentityGsm (int lac, int cid, int arfcn, int bsic, String mccStr,
+                            String mncStr, String alphal, String alphas) {
         mLac = lac;
         mCid = cid;
         mArfcn = arfcn;
-        mBsic = bsic;
+        // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
+        // for inbound parcels
+        mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic;
+
+        if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+            mMccStr = mccStr;
+        } else if (mccStr.isEmpty()) {
+            // If the mccStr parsed from Parcel is empty, set it as null.
+            mMccStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MCC format");
+        }
+
+        if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+            mMncStr = mncStr;
+        } else if (mncStr.isEmpty()) {
+            // If the mncStr parsed from Parcel is empty, set it as null.
+            mMncStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MNC format");
+        }
+
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityGsm(CellIdentityGsm cid) {
-        mMcc = cid.mMcc;
-        mMnc = cid.mMnc;
-        mLac = cid.mLac;
-        mCid = cid.mCid;
-        mArfcn = cid.mArfcn;
-        mBsic = cid.mBsic;
+        this(cid.mLac, cid.mCid, cid.mArfcn, cid.mBsic, cid.mMccStr,
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityGsm copy() {
-       return new CellIdentityGsm(this);
+        return new CellIdentityGsm(this);
     }
 
     /**
      * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMccStr} instead.
      */
+    @Deprecated
     public int getMcc() {
-        return mMcc;
+        return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
     }
 
     /**
      * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMncStr} instead.
      */
+    @Deprecated
     public int getMnc() {
-        return mMnc;
+        return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
     }
 
     /**
@@ -144,6 +194,43 @@
         return mBsic;
     }
 
+    /**
+     * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+     */
+    public String getMobileNetworkOperator() {
+        return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+    }
+
+    /**
+     * @return Mobile Country Code in string format, null if unknown
+     */
+    public String getMccStr() {
+        return mMccStr;
+    }
+
+    /**
+     * @return Mobile Network Code in string format, null if unknown
+     */
+    public String getMncStr() {
+        return mMncStr;
+    }
+
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
 
     /**
      * @return Integer.MAX_VALUE, undefined for GSM
@@ -155,7 +242,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mMcc, mMnc, mLac, mCid);
+        return Objects.hash(mMccStr, mMncStr, mLac, mCid, mAlphaLong, mAlphaShort);
     }
 
     @Override
@@ -169,23 +256,27 @@
         }
 
         CellIdentityGsm o = (CellIdentityGsm) other;
-        return mMcc == o.mMcc &&
-                mMnc == o.mMnc &&
-                mLac == o.mLac &&
+        return mLac == o.mLac &&
                 mCid == o.mCid &&
                 mArfcn == o.mArfcn &&
-                mBsic == o.mBsic;
+                mBsic == o.mBsic &&
+                TextUtils.equals(mMccStr, o.mMccStr) &&
+                TextUtils.equals(mMncStr, o.mMncStr) &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("CellIdentityGsm:{");
-        sb.append(" mMcc=").append(mMcc);
-        sb.append(" mMnc=").append(mMnc);
         sb.append(" mLac=").append(mLac);
         sb.append(" mCid=").append(mCid);
         sb.append(" mArfcn=").append(mArfcn);
         sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic));
+        sb.append(" mMcc=").append(mMccStr);
+        sb.append(" mMnc=").append(mMncStr);
+        sb.append(" mAlphaLong=").append(mAlphaLong);
+        sb.append(" mAlphaShort=").append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -201,26 +292,20 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mMcc);
-        dest.writeInt(mMnc);
         dest.writeInt(mLac);
         dest.writeInt(mCid);
         dest.writeInt(mArfcn);
         dest.writeInt(mBsic);
+        dest.writeString(mMccStr);
+        dest.writeString(mMncStr);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityGsm(Parcel in) {
-        mMcc = in.readInt();
-        mMnc = in.readInt();
-        mLac = in.readInt();
-        mCid = in.readInt();
-        mArfcn = in.readInt();
-        int bsic = in.readInt();
-        // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator
-        // for inbound parcels
-        if (bsic == 0xFF) bsic = Integer.MAX_VALUE;
-        mBsic = bsic;
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+                in.readString(), in.readString(), in.readString());
 
         if (DBG) log("CellIdentityGsm(Parcel): " + toString());
     }
@@ -229,16 +314,16 @@
     @SuppressWarnings("hiding")
     public static final Creator<CellIdentityGsm> CREATOR =
             new Creator<CellIdentityGsm>() {
-        @Override
-        public CellIdentityGsm createFromParcel(Parcel in) {
-            return new CellIdentityGsm(in);
-        }
+                @Override
+                public CellIdentityGsm createFromParcel(Parcel in) {
+                    return new CellIdentityGsm(in);
+                }
 
-        @Override
-        public CellIdentityGsm[] newArray(int size) {
-            return new CellIdentityGsm[size];
-        }
-    };
+                @Override
+                public CellIdentityGsm[] newArray(int size) {
+                    return new CellIdentityGsm[size];
+                }
+            };
 
     /**
      * log
@@ -246,4 +331,4 @@
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index ce74383..74d2966 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -30,10 +31,6 @@
     private static final String LOG_TAG = "CellIdentityLte";
     private static final boolean DBG = false;
 
-    // 3-digit Mobile Country Code, 0..999
-    private final int mMcc;
-    // 2 or 3-digit Mobile Network Code, 0..999
-    private final int mMnc;
     // 28-bit cell identity
     private final int mCi;
     // physical cell id 0..503
@@ -42,17 +39,27 @@
     private final int mTac;
     // 18-bit Absolute RF Channel Number
     private final int mEarfcn;
+    // 3-digit Mobile Country Code in string format
+    private final String mMccStr;
+    // 2 or 3-digit Mobile Network Code in string format
+    private final String mMncStr;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
      */
     public CellIdentityLte() {
-        mMcc = Integer.MAX_VALUE;
-        mMnc = Integer.MAX_VALUE;
         mCi = Integer.MAX_VALUE;
         mPci = Integer.MAX_VALUE;
         mTac = Integer.MAX_VALUE;
         mEarfcn = Integer.MAX_VALUE;
+        mMccStr = null;
+        mMncStr = null;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
 
     /**
@@ -66,7 +73,7 @@
      * @hide
      */
     public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) {
-        this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE);
+        this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null);
     }
 
     /**
@@ -81,21 +88,57 @@
      * @hide
      */
     public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
-        mMcc = mcc;
-        mMnc = mnc;
+        this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+    }
+
+    /**
+     *
+     * @param ci 28-bit Cell Identity
+     * @param pci Physical Cell Id 0..503
+     * @param tac 16-bit Tracking Area Code
+     * @param earfcn 18-bit LTE Absolute RF Channel Number
+     * @param mccStr 3-digit Mobile Country Code in string format
+     * @param mncStr 2 or 3-digit Mobile Network Code in string format
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+     * not a 2 or 3-digit code.
+     *
+     * @hide
+     */
+    public CellIdentityLte (int ci, int pci, int tac, int earfcn, String mccStr,
+                            String mncStr, String alphal, String alphas) {
         mCi = ci;
         mPci = pci;
         mTac = tac;
         mEarfcn = earfcn;
+
+        if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+            mMccStr = mccStr;
+        } else if (mccStr.isEmpty()) {
+            // If the mccStr parsed from Parcel is empty, set it as null.
+            mMccStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MCC format");
+        }
+
+        if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+            mMncStr = mncStr;
+        } else if (mncStr.isEmpty()) {
+            // If the mncStr parsed from Parcel is empty, set it as null.
+            mMncStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MNC format");
+        }
+
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityLte(CellIdentityLte cid) {
-        mMcc = cid.mMcc;
-        mMnc = cid.mMnc;
-        mCi = cid.mCi;
-        mPci = cid.mPci;
-        mTac = cid.mTac;
-        mEarfcn = cid.mEarfcn;
+        this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr,
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityLte copy() {
@@ -104,16 +147,20 @@
 
     /**
      * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMccStr} instead.
      */
+    @Deprecated
     public int getMcc() {
-        return mMcc;
+        return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
     }
 
     /**
      * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMncStr} instead.
      */
+    @Deprecated
     public int getMnc() {
-        return mMnc;
+        return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
     }
 
     /**
@@ -144,9 +191,46 @@
         return mEarfcn;
     }
 
+    /**
+     * @return Mobile Country Code in string format, null if unknown
+     */
+    public String getMccStr() {
+        return mMccStr;
+    }
+
+    /**
+     * @return Mobile Network Code in string format, null if unknown
+     */
+    public String getMncStr() {
+        return mMncStr;
+    }
+
+    /**
+     * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+     */
+    public String getMobileNetworkOperator() {
+        return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+    }
+
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mMcc, mMnc, mCi, mPci, mTac);
+        return Objects.hash(mMccStr, mMncStr, mCi, mPci, mTac, mAlphaLong, mAlphaShort);
     }
 
     @Override
@@ -160,23 +244,27 @@
         }
 
         CellIdentityLte o = (CellIdentityLte) other;
-        return mMcc == o.mMcc &&
-                mMnc == o.mMnc &&
-                mCi == o.mCi &&
+        return mCi == o.mCi &&
                 mPci == o.mPci &&
                 mTac == o.mTac &&
-                mEarfcn == o.mEarfcn;
+                mEarfcn == o.mEarfcn &&
+                TextUtils.equals(mMccStr, o.mMccStr) &&
+                TextUtils.equals(mMncStr, o.mMncStr) &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("CellIdentityLte:{");
-        sb.append(" mMcc="); sb.append(mMcc);
-        sb.append(" mMnc="); sb.append(mMnc);
         sb.append(" mCi="); sb.append(mCi);
         sb.append(" mPci="); sb.append(mPci);
         sb.append(" mTac="); sb.append(mTac);
         sb.append(" mEarfcn="); sb.append(mEarfcn);
+        sb.append(" mMcc="); sb.append(mMccStr);
+        sb.append(" mMnc="); sb.append(mMncStr);
+        sb.append(" mAlphaLong="); sb.append(mAlphaLong);
+        sb.append(" mAlphaShort="); sb.append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -192,22 +280,21 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mMcc);
-        dest.writeInt(mMnc);
         dest.writeInt(mCi);
         dest.writeInt(mPci);
         dest.writeInt(mTac);
         dest.writeInt(mEarfcn);
+        dest.writeString(mMccStr);
+        dest.writeString(mMncStr);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityLte(Parcel in) {
-        mMcc = in.readInt();
-        mMnc = in.readInt();
-        mCi = in.readInt();
-        mPci = in.readInt();
-        mTac = in.readInt();
-        mEarfcn = in.readInt();
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+                in.readString(), in.readString(), in.readString());
+
         if (DBG) log("CellIdentityLte(Parcel): " + toString());
     }
 
@@ -215,16 +302,16 @@
     @SuppressWarnings("hiding")
     public static final Creator<CellIdentityLte> CREATOR =
             new Creator<CellIdentityLte>() {
-        @Override
-        public CellIdentityLte createFromParcel(Parcel in) {
-            return new CellIdentityLte(in);
-        }
+                @Override
+                public CellIdentityLte createFromParcel(Parcel in) {
+                    return new CellIdentityLte(in);
+                }
 
-        @Override
-        public CellIdentityLte[] newArray(int size) {
-            return new CellIdentityLte[size];
-        }
-    };
+                @Override
+                public CellIdentityLte[] newArray(int size) {
+                    return new CellIdentityLte[size];
+                }
+            };
 
     /**
      * log
@@ -232,4 +319,4 @@
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 0d13efd..51b11aa 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -30,10 +31,6 @@
     private static final String LOG_TAG = "CellIdentityWcdma";
     private static final boolean DBG = false;
 
-    // 3-digit Mobile Country Code, 0..999
-    private final int mMcc;
-    // 2 or 3-digit Mobile Network Code, 0..999
-    private final int mMnc;
     // 16-bit Location Area Code, 0..65535
     private final int mLac;
     // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455
@@ -42,17 +39,27 @@
     private final int mPsc;
     // 16-bit UMTS Absolute RF Channel Number
     private final int mUarfcn;
+    // 3-digit Mobile Country Code in string format
+    private final String mMccStr;
+    // 2 or 3-digit Mobile Network Code in string format
+    private final String mMncStr;
+    // long alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaLong;
+    // short alpha Operator Name String or Enhanced Operator Name String
+    private final String mAlphaShort;
 
     /**
      * @hide
      */
     public CellIdentityWcdma() {
-        mMcc = Integer.MAX_VALUE;
-        mMnc = Integer.MAX_VALUE;
         mLac = Integer.MAX_VALUE;
         mCid = Integer.MAX_VALUE;
         mPsc = Integer.MAX_VALUE;
         mUarfcn = Integer.MAX_VALUE;
+        mMccStr = null;
+        mMncStr = null;
+        mAlphaLong = null;
+        mAlphaShort = null;
     }
     /**
      * public constructor
@@ -65,7 +72,8 @@
      * @hide
      */
     public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) {
-        this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE);
+        this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+                null, null);
     }
 
     /**
@@ -80,39 +88,79 @@
      * @hide
      */
     public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) {
-        mMcc = mcc;
-        mMnc = mnc;
+        this(lac, cid, psc, uarfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+    }
+
+    /**
+     * public constructor
+     * @param lac 16-bit Location Area Code, 0..65535
+     * @param cid 28-bit UMTS Cell Identity
+     * @param psc 9-bit UMTS Primary Scrambling Code
+     * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+     * @param mccStr 3-digit Mobile Country Code in string format
+     * @param mncStr 2 or 3-digit Mobile Network Code in string format
+     * @param alphal long alpha Operator Name String or Enhanced Operator Name String
+     * @param alphas short alpha Operator Name String or Enhanced Operator Name String
+     *
+     * @throws IllegalArgumentException if the input MCC is not a 3-digit code or the input MNC is
+     * not a 2 or 3-digit code.
+     *
+     * @hide
+     */
+    public CellIdentityWcdma (int lac, int cid, int psc, int uarfcn,
+                              String mccStr, String mncStr, String alphal, String alphas) {
         mLac = lac;
         mCid = cid;
         mPsc = psc;
         mUarfcn = uarfcn;
+
+        if (mccStr == null || mccStr.matches("^[0-9]{3}$")) {
+            mMccStr = mccStr;
+        } else if (mccStr.isEmpty()) {
+            // If the mccStr parsed from Parcel is empty, set it as null.
+            mMccStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MCC format");
+        }
+
+        if (mncStr == null || mncStr.matches("^[0-9]{2,3}$")) {
+            mMncStr = mncStr;
+        } else if (mncStr.isEmpty()) {
+            // If the mncStr parsed from Parcel is empty, set it as null.
+            mMncStr = null;
+        } else {
+            throw new IllegalArgumentException("invalid MNC format");
+        }
+
+        mAlphaLong = alphal;
+        mAlphaShort = alphas;
     }
 
     private CellIdentityWcdma(CellIdentityWcdma cid) {
-        mMcc = cid.mMcc;
-        mMnc = cid.mMnc;
-        mLac = cid.mLac;
-        mCid = cid.mCid;
-        mPsc = cid.mPsc;
-        mUarfcn = cid.mUarfcn;
+        this(cid.mLac, cid.mCid, cid.mPsc, cid.mUarfcn, cid.mMccStr,
+                cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
     CellIdentityWcdma copy() {
-       return new CellIdentityWcdma(this);
+        return new CellIdentityWcdma(this);
     }
 
     /**
      * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMccStr} instead.
      */
+    @Deprecated
     public int getMcc() {
-        return mMcc;
+        return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE;
     }
 
     /**
      * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown
+     * @deprecated Use {@link #getMncStr} instead.
      */
+    @Deprecated
     public int getMnc() {
-        return mMnc;
+        return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE;
     }
 
     /**
@@ -138,9 +186,46 @@
         return mPsc;
     }
 
+    /**
+     * @return Mobile Country Code in string version, null if unknown
+     */
+    public String getMccStr() {
+        return mMccStr;
+    }
+
+    /**
+     * @return Mobile Network Code in string version, null if unknown
+     */
+    public String getMncStr() {
+        return mMncStr;
+    }
+
+    /**
+     * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown
+     */
+    public String getMobileNetworkOperator() {
+        return (mMncStr == null || mMncStr == null) ? null : mMccStr + mMncStr;
+    }
+
+    /**
+     * @return The long alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string). May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaLong() {
+        return mAlphaLong;
+    }
+
+    /**
+     * @return The short alpha tag associated with the current scan result (may be the operator
+     * name string or extended operator name string).  May be null if unknown.
+     */
+    public CharSequence getOperatorAlphaShort() {
+        return mAlphaShort;
+    }
+
     @Override
     public int hashCode() {
-        return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc);
+        return Objects.hash(mMccStr, mMncStr, mLac, mCid, mPsc, mAlphaLong, mAlphaShort);
     }
 
     /**
@@ -161,23 +246,27 @@
         }
 
         CellIdentityWcdma o = (CellIdentityWcdma) other;
-        return mMcc == o.mMcc &&
-                mMnc == o.mMnc &&
-                mLac == o.mLac &&
+        return mLac == o.mLac &&
                 mCid == o.mCid &&
                 mPsc == o.mPsc &&
-                mUarfcn == o.mUarfcn;
+                mUarfcn == o.mUarfcn &&
+                TextUtils.equals(mMccStr, o.mMccStr) &&
+                TextUtils.equals(mMncStr, o.mMncStr) &&
+                TextUtils.equals(mAlphaLong, o.mAlphaLong) &&
+                TextUtils.equals(mAlphaShort, o.mAlphaShort);
     }
 
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("CellIdentityWcdma:{");
-        sb.append(" mMcc=").append(mMcc);
-        sb.append(" mMnc=").append(mMnc);
         sb.append(" mLac=").append(mLac);
         sb.append(" mCid=").append(mCid);
         sb.append(" mPsc=").append(mPsc);
         sb.append(" mUarfcn=").append(mUarfcn);
+        sb.append(" mMcc=").append(mMccStr);
+        sb.append(" mMnc=").append(mMncStr);
+        sb.append(" mAlphaLong=").append(mAlphaLong);
+        sb.append(" mAlphaShort=").append(mAlphaShort);
         sb.append("}");
 
         return sb.toString();
@@ -193,22 +282,21 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         if (DBG) log("writeToParcel(Parcel, int): " + toString());
-        dest.writeInt(mMcc);
-        dest.writeInt(mMnc);
         dest.writeInt(mLac);
         dest.writeInt(mCid);
         dest.writeInt(mPsc);
         dest.writeInt(mUarfcn);
+        dest.writeString(mMccStr);
+        dest.writeString(mMncStr);
+        dest.writeString(mAlphaLong);
+        dest.writeString(mAlphaShort);
     }
 
     /** Construct from Parcel, type has already been processed */
     private CellIdentityWcdma(Parcel in) {
-        mMcc = in.readInt();
-        mMnc = in.readInt();
-        mLac = in.readInt();
-        mCid = in.readInt();
-        mPsc = in.readInt();
-        mUarfcn = in.readInt();
+        this(in.readInt(), in.readInt(), in.readInt(), in.readInt(), in.readString(),
+                in.readString(), in.readString(), in.readString());
+
         if (DBG) log("CellIdentityWcdma(Parcel): " + toString());
     }
 
@@ -216,16 +304,16 @@
     @SuppressWarnings("hiding")
     public static final Creator<CellIdentityWcdma> CREATOR =
             new Creator<CellIdentityWcdma>() {
-        @Override
-        public CellIdentityWcdma createFromParcel(Parcel in) {
-            return new CellIdentityWcdma(in);
-        }
+                @Override
+                public CellIdentityWcdma createFromParcel(Parcel in) {
+                    return new CellIdentityWcdma(in);
+                }
 
-        @Override
-        public CellIdentityWcdma[] newArray(int size) {
-            return new CellIdentityWcdma[size];
-        }
-    };
+                @Override
+                public CellIdentityWcdma[] newArray(int size) {
+                    return new CellIdentityWcdma[size];
+                }
+            };
 
     /**
      * log
@@ -233,4 +321,4 @@
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
-}
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 98fb653..c3a2ceb 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -273,6 +273,13 @@
      * {@hide}
      */
     public static final int EMERGENCY_PERM_FAILURE = 64;
+
+    /**
+     * This cause is used to report a normal event only when no other cause in the normal class
+     * applies.
+     * {@hide}
+     */
+    public static final int NORMAL_UNSPECIFIED = 65;
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Update toString() with the newly added disconnect type.
@@ -413,6 +420,8 @@
             return "EMERGENCY_TEMP_FAILURE";
         case EMERGENCY_PERM_FAILURE:
             return "EMERGENCY_PERM_FAILURE";
+        case NORMAL_UNSPECIFIED:
+            return "NORMAL_UNSPECIFIED";
         default:
             return "INVALID: " + cause;
         }
diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java
index ebac041..f392570 100644
--- a/telephony/java/android/telephony/MbmsDownloadSession.java
+++ b/telephony/java/android/telephony/MbmsDownloadSession.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -74,11 +75,20 @@
             "android.telephony.action.EmbmsDownload";
 
     /**
+     * Metadata key that specifies the component name of the service to bind to for file-download.
+     * @hide
+     */
+    @TestApi
+    public static final String MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA =
+            "mbms-download-service-override";
+
+    /**
      * Integer extra that Android will attach to the intent supplied via
      * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}
      * Indicates the result code of the download. One of
-     * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED}, or
-     * {@link #RESULT_IO_ERROR}.
+     * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, {@link #RESULT_CANCELLED},
+     * {@link #RESULT_IO_ERROR}, {@link #RESULT_DOWNLOAD_FAILURE}, {@link #RESULT_OUT_OF_STORAGE},
+     * {@link #RESULT_SERVICE_ID_NOT_DEFINED}, or {@link #RESULT_FILE_ROOT_UNREACHABLE}.
      *
      * This extra may also be used by the middleware when it is sending intents to the app.
      */
@@ -142,11 +152,41 @@
 
     /**
      * Indicates that the download will not be completed due to an I/O error incurred while
-     * writing to temp files. This commonly indicates that the device is out of storage space,
-     * but may indicate other conditions as well (such as an SD card being removed).
+     * writing to temp files.
+     *
+     * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+     * the download again.
      */
     public static final int RESULT_IO_ERROR = 4;
-    // TODO - more results!
+
+    /**
+     * Indicates that the Service ID specified in the {@link DownloadRequest} is incorrect due to
+     * the Id being incorrect, stale, expired, or similar.
+     */
+    public static final int RESULT_SERVICE_ID_NOT_DEFINED = 5;
+
+    /**
+     * Indicates that there was an error while processing downloaded files, such as a file repair or
+     * file decoding error and is not due to a file I/O error.
+     *
+     * This is likely a transient error and another {@link DownloadRequest} should be sent to try
+     * the download again.
+     */
+    public static final int RESULT_DOWNLOAD_FAILURE = 6;
+
+    /**
+     * Indicates that the file system is full and the {@link DownloadRequest} can not complete.
+     * Either space must be made on the current file system or the temp file root location must be
+     * changed to a location that is not full to download the temp files.
+     */
+    public static final int RESULT_OUT_OF_STORAGE = 7;
+
+    /**
+     * Indicates that the file root that was set is currently unreachable. This can happen if the
+     * temp files are set to be stored on external storage and the SD card was removed, for example.
+     * The temp file root should be changed before sending another DownloadRequest.
+     */
+    public static final int RESULT_FILE_ROOT_UNREACHABLE = 8;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -522,8 +562,7 @@
      * @param handler The {@link Handler} on which calls to {@code callback} should be enqueued on.
      */
     public void registerStateCallback(@NonNull DownloadRequest request,
-            @NonNull DownloadStateCallback callback,
-            @NonNull Handler handler) {
+            @NonNull DownloadStateCallback callback, @NonNull Handler handler) {
         IMbmsDownloadService downloadService = mService.get();
         if (downloadService == null) {
             throw new IllegalStateException("Middleware not yet bound");
@@ -533,7 +572,8 @@
                 new InternalDownloadStateCallback(callback, handler);
 
         try {
-            int result = downloadService.registerStateCallback(request, internalCallback);
+            int result = downloadService.registerStateCallback(request, internalCallback,
+                    callback.getCallbackFilterFlags());
             if (result != MbmsErrors.SUCCESS) {
                 if (result == MbmsErrors.DownloadErrors.ERROR_UNKNOWN_DOWNLOAD_REQUEST) {
                     throw new IllegalArgumentException("Unknown download request.");
diff --git a/telephony/java/android/telephony/MbmsStreamingSession.java b/telephony/java/android/telephony/MbmsStreamingSession.java
index a8c4607..fb2ff7b 100644
--- a/telephony/java/android/telephony/MbmsStreamingSession.java
+++ b/telephony/java/android/telephony/MbmsStreamingSession.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ServiceConnection;
@@ -62,6 +63,14 @@
     public static final String MBMS_STREAMING_SERVICE_ACTION =
             "android.telephony.action.EmbmsStreaming";
 
+    /**
+     * Metadata key that specifies the component name of the service to bind to for file-download.
+     * @hide
+     */
+    @TestApi
+    public static final String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA =
+            "mbms-streaming-service-override";
+
     private static AtomicBoolean sIsInitialized = new AtomicBoolean(false);
 
     private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null);
diff --git a/telephony/java/android/telephony/NetworkScanRequest.java b/telephony/java/android/telephony/NetworkScanRequest.java
index d2aef20..9674c93 100644
--- a/telephony/java/android/telephony/NetworkScanRequest.java
+++ b/telephony/java/android/telephony/NetworkScanRequest.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /**
@@ -38,6 +39,20 @@
     public static final int MAX_BANDS = 8;
     /** @hide */
     public static final int MAX_CHANNELS = 32;
+    /** @hide */
+    public static final int MAX_MCC_MNC_LIST_SIZE = 20;
+    /** @hide */
+    public static final int MIN_SEARCH_PERIODICITY_SEC = 5;
+    /** @hide */
+    public static final int MAX_SEARCH_PERIODICITY_SEC = 300;
+    /** @hide */
+    public static final int MIN_SEARCH_MAX_SEC = 60;
+    /** @hide */
+    public static final int MAX_SEARCH_MAX_SEC = 3600;
+    /** @hide */
+    public static final int MIN_INCREMENTAL_PERIODICITY_SEC = 1;
+    /** @hide */
+    public static final int MAX_INCREMENTAL_PERIODICITY_SEC = 10;
 
     /** Performs the scan only once */
     public static final int SCAN_TYPE_ONE_SHOT = 0;
@@ -46,24 +61,84 @@
      *
      * The modem will start new scans periodically, and the interval between two scans is usually
      * multiple minutes.
-     * */
+     */
     public static final int SCAN_TYPE_PERIODIC = 1;
 
     /** Defines the type of the scan. */
     public int scanType;
 
+    /**
+     * Search periodicity (in seconds).
+     * Expected range for the input is [5s - 300s]
+     * This value must be less than or equal to maxSearchTime
+     */
+    public int searchPeriodicity;
+
+    /**
+     * Maximum duration of the periodic search (in seconds).
+     * Expected range for the input is [60s - 3600s]
+     * If the search lasts this long, it will be terminated.
+     */
+    public int maxSearchTime;
+
+    /**
+     * Indicates whether the modem should report incremental
+     * results of the network scan to the client.
+     * FALSE – Incremental results are not reported.
+     * TRUE (default) – Incremental results are reported
+     */
+    public boolean incrementalResults;
+
+    /**
+     * Indicates the periodicity with which the modem should
+     * report incremental results to the client (in seconds).
+     * Expected range for the input is [1s - 10s]
+     * This value must be less than or equal to maxSearchTime
+     */
+    public int incrementalResultsPeriodicity;
+
     /** Describes the radio access technologies with bands or channels that need to be scanned. */
     public RadioAccessSpecifier[] specifiers;
 
     /**
+     * Describes the List of PLMN ids (MCC-MNC)
+     * If any PLMN of this list is found, search should end at that point and
+     * results with all PLMN found till that point should be sent as response.
+     * If list not sent, search to be completed till end and all PLMNs found to be reported.
+     * Max size of array is MAX_MCC_MNC_LIST_SIZE
+     */
+    public ArrayList<String> mccMncs;
+
+    /**
      * Creates a new NetworkScanRequest with scanType and network specifiers
      *
      * @param scanType The type of the scan
      * @param specifiers the radio network with bands / channels to be scanned
+     * @param searchPeriodicity Search periodicity (in seconds)
+     * @param maxSearchTime Maximum duration of the periodic search (in seconds)
+     * @param incrementalResults Indicates whether the modem should report incremental
+     *                           results of the network scan to the client
+     * @param incrementalResultsPeriodicity Indicates the periodicity with which the modem should
+     *                                      report incremental results to the client (in seconds)
+     * @param mccMncs Describes the List of PLMN ids (MCC-MNC)
      */
-    public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) {
+    public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers,
+                    int searchPeriodicity,
+                    int maxSearchTime,
+                    boolean incrementalResults,
+                    int incrementalResultsPeriodicity,
+                    ArrayList<String> mccMncs) {
         this.scanType = scanType;
         this.specifiers = specifiers;
+        this.searchPeriodicity = searchPeriodicity;
+        this.maxSearchTime = maxSearchTime;
+        this.incrementalResults = incrementalResults;
+        this.incrementalResultsPeriodicity = incrementalResultsPeriodicity;
+        if (mccMncs != null) {
+            this.mccMncs = mccMncs;
+        } else {
+            this.mccMncs = new ArrayList<>();
+        }
     }
 
     @Override
@@ -75,6 +150,11 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(scanType);
         dest.writeParcelableArray(specifiers, flags);
+        dest.writeInt(searchPeriodicity);
+        dest.writeInt(maxSearchTime);
+        dest.writeBoolean(incrementalResults);
+        dest.writeInt(incrementalResultsPeriodicity);
+        dest.writeStringList(mccMncs);
     }
 
     private NetworkScanRequest(Parcel in) {
@@ -82,6 +162,12 @@
         specifiers = (RadioAccessSpecifier[]) in.readParcelableArray(
                 Object.class.getClassLoader(),
                 RadioAccessSpecifier.class);
+        searchPeriodicity = in.readInt();
+        maxSearchTime = in.readInt();
+        incrementalResults = in.readBoolean();
+        incrementalResultsPeriodicity = in.readInt();
+        mccMncs = new ArrayList<>();
+        in.readStringList(mccMncs);
     }
 
     @Override
@@ -99,13 +185,24 @@
         }
 
         return (scanType == nsr.scanType
-                && Arrays.equals(specifiers, nsr.specifiers));
+                && Arrays.equals(specifiers, nsr.specifiers)
+                && searchPeriodicity == nsr.searchPeriodicity
+                && maxSearchTime == nsr.maxSearchTime
+                && incrementalResults == nsr.incrementalResults
+                && incrementalResultsPeriodicity == nsr.incrementalResultsPeriodicity
+                && (((mccMncs != null)
+                && mccMncs.equals(nsr.mccMncs))));
     }
 
     @Override
     public int hashCode () {
         return ((scanType * 31)
-                + (Arrays.hashCode(specifiers)) * 37);
+                + (Arrays.hashCode(specifiers)) * 37
+                + (searchPeriodicity * 41)
+                + (maxSearchTime * 43)
+                + ((incrementalResults == true? 1 : 0) * 47)
+                + (incrementalResultsPeriodicity * 53)
+                + (mccMncs.hashCode() * 59));
     }
 
     public static final Creator<NetworkScanRequest> CREATOR =
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index e448fb2..116e711 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1197,15 +1197,6 @@
         }
     }
 
-    /**
-     * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType}
-     * @hide
-     */
-    public int getNetworkType() {
-        Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******");
-        return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology);
-    }
-
     /** @hide */
     public int getDataNetworkType() {
         return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 9e02399..c8b4776 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -19,7 +19,6 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.telephony.Rlog;
 import android.util.Log;
 import android.content.res.Resources;
 
@@ -429,6 +428,15 @@
     }
 
     /**
+     * Fix {@link #isGsm} based on the signal strength data.
+     *
+     * @hide
+     */
+    public void fixType() {
+        isGsm = getCdmaRelatedSignalStrength() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+    }
+
+    /**
      * @param true - Gsm, Lte phones
      *        false - Cdma phones
      *
@@ -541,30 +549,7 @@
      *     while 4 represents a very strong signal strength.
      */
     public int getLevel() {
-        int level = 0;
-
-        if (isGsm) {
-            level = getLteLevel();
-            if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                level = getTdScdmaLevel();
-                if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                    level = getGsmLevel();
-                }
-            }
-        } else {
-            int cdmaLevel = getCdmaLevel();
-            int evdoLevel = getEvdoLevel();
-            if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                /* We don't know evdo, use cdma */
-                level = cdmaLevel;
-            } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
-                /* We don't know cdma, use evdo */
-                level = evdoLevel;
-            } else {
-                /* We know both, use the lowest level */
-                level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
-            }
-        }
+        int level = isGsm ? getGsmRelatedSignalStrength() : getCdmaRelatedSignalStrength();
         if (DBG) log("getLevel=" + level);
         return level;
     }
@@ -1049,6 +1034,36 @@
                 + " " + (isGsm ? "gsm|lte" : "cdma"));
     }
 
+    /** Returns the signal strength related to GSM. */
+    private int getGsmRelatedSignalStrength() {
+        int level = getLteLevel();
+        if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            level = getTdScdmaLevel();
+            if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+                level = getGsmLevel();
+            }
+        }
+        return level;
+    }
+
+    /** Returns the signal strength related to CDMA. */
+    private int getCdmaRelatedSignalStrength() {
+        int level;
+        int cdmaLevel = getCdmaLevel();
+        int evdoLevel = getEvdoLevel();
+        if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            /* We don't know evdo, use cdma */
+            level = cdmaLevel;
+        } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
+            /* We don't know cdma, use evdo */
+            level = evdoLevel;
+        } else {
+            /* We know both, use the lowest level */
+            level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel;
+        }
+        return level;
+    }
+
     /**
      * Set SignalStrength based on intent notifier map
      *
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 6029995..924f0de 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -387,23 +387,132 @@
     }
 
     /**
+     * Send a text based SMS with messaging options.
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *  the current default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is successfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK</code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     *
+     * @throws IllegalArgumentException if destinationAddress or text are empty
+     * {@hide}
+     */
+    public void sendTextMessage(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                true /* persistMessage*/, priority, expectMore, validityPeriod);
+    }
+
+    private void sendTextMessageInternal(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+            int priority, boolean expectMore, int validityPeriod) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+
+        if (TextUtils.isEmpty(text)) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (priority < 0x00 || priority > 0x03) {
+            throw new IllegalArgumentException("Invalid priority");
+        }
+
+        if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+            throw new IllegalArgumentException("Invalid validity period");
+        }
+
+        try {
+             ISms iccISms = getISmsServiceOrThrow();
+            if (iccISms != null) {
+                iccISms.sendTextForSubscriberWithOptions(getSubscriptionId(),
+                        ActivityThread.currentPackageName(), destinationAddress, scAddress, text,
+                        sentIntent, deliveryIntent, persistMessage,  priority, expectMore,
+                        validityPeriod);
+            }
+        } catch (RemoteException ex) {
+            // ignore it
+        }
+    }
+
+    /**
+     * Send a text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendTextMessage(String, String, String, PendingIntent,
+     * PendingIntent, int, boolean, int)
+     * @hide
+     */
+    public void sendTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, int priority,
+            boolean expectMore, int validityPeriod) {
+        sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+                false /* persistMessage */, priority, expectMore, validityPeriod);
+    }
+
+    /**
+     *
      * Inject an SMS PDU into the android application framework.
      *
      * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier
-     * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     * privileges per {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
      *
      * @param pdu is the byte array of pdu to be injected into android application framework
-     * @param format is the format of SMS pdu (3gpp or 3gpp2)
+     * @param format is the format of SMS pdu ({@link SmsMessage#FORMAT_3GPP} or
+     *  {@link SmsMessage#FORMAT_3GPP2})
      * @param receivedIntent if not NULL this <code>PendingIntent</code> is
      *  broadcast when the message is successfully received by the
      *  android application framework, or failed. This intent is broadcasted at
      *  the same time an SMS received from radio is acknowledged back.
-     *  The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
-     *  <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+     *  The result code will be {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_HANDLED}
+     *  for success, or {@link android.provider.Telephony.Sms.Intents#RESULT_SMS_GENERIC_ERROR} for
+     *  error.
      *
-     * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+     * @throws IllegalArgumentException if the format is invalid.
      */
-    public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+    public void injectSmsPdu(
+            byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
         if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
             // Format must be either 3gpp or 3gpp2.
             throw new IllegalArgumentException(
@@ -541,6 +650,140 @@
     }
 
     /**
+     * Send a multi-part text based SMS with messaging options. The callee should have already
+     * divided the message into correctly sized parts by calling
+     * <code>divideMessage</code>.
+     *
+     * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+     * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+     *
+     * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+     * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+     * writes messages sent using this method to the SMS Provider (the default SMS app is always
+     * responsible for writing its sent messages to the SMS Provider). For information about
+     * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+     *
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK</code> for success,
+     *   or one of these errors:<br>
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *   <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *   <code>RESULT_ERROR_NULL_PDU</code><br>
+     *   For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+     *   the extra "errorCode" containing a radio technology specific value,
+     *   generally only useful for troubleshooting.<br>
+     *   The per-application based SMS control checks sentIntent. If sentIntent
+     *   is NULL the caller will be checked against all unknown applications,
+     *   which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending messages through same link or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     *
+     * @throws IllegalArgumentException if destinationAddress or data are empty
+     * {@hide}
+     */
+    public void sendMultipartTextMessage(
+            String destinationAddress, String scAddress, ArrayList<String> parts,
+            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, true /* persistMessage*/);
+    }
+
+    private void sendMultipartTextMessageInternal(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
+        if (TextUtils.isEmpty(destinationAddress)) {
+            throw new IllegalArgumentException("Invalid destinationAddress");
+        }
+        if (parts == null || parts.size() < 1) {
+            throw new IllegalArgumentException("Invalid message body");
+        }
+
+        if (priority < 0x00 || priority > 0x03) {
+            throw new IllegalArgumentException("Invalid priority");
+        }
+
+        if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) {
+            throw new IllegalArgumentException("Invalid validity period");
+        }
+
+        if (parts.size() > 1) {
+            try {
+                 ISms iccISms = getISmsServiceOrThrow();
+                if (iccISms != null) {
+                    iccISms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
+                            ActivityThread.currentPackageName(), destinationAddress, scAddress,
+                            parts, sentIntents, deliveryIntents, persistMessage, priority,
+                            expectMore, validityPeriod);
+                }
+            } catch (RemoteException ex) {
+                // ignore it
+            }
+        } else {
+            PendingIntent sentIntent = null;
+            PendingIntent deliveryIntent = null;
+            if (sentIntents != null && sentIntents.size() > 0) {
+                sentIntent = sentIntents.get(0);
+            }
+            if (deliveryIntents != null && deliveryIntents.size() > 0) {
+                deliveryIntent = deliveryIntents.get(0);
+            }
+            sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+                    sentIntent, deliveryIntent, persistMessage, priority, expectMore,
+                    validityPeriod);
+        }
+    }
+
+    /**
+     * Send a multi-part text based SMS without writing it into the SMS Provider.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+     * privileges.
+     * </p>
+     *
+     * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList,
+     * ArrayList, int, boolean, int)
+     * @hide
+     **/
+    public void sendMultipartTextMessageWithoutPersisting(
+            String destinationAddress, String scAddress, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            int priority, boolean expectMore, int validityPeriod) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, false /* persistMessage*/, priority, expectMore,
+                validityPeriod);
+    }
+
+   /**
      * Send a data based SMS to a specific application port.
      *
      * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
@@ -1003,7 +1246,7 @@
      *   <code>getAllMessagesFromIcc</code>
      * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
      */
-    private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+    private ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
         ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
         if (records != null) {
             int count = records.size();
@@ -1011,7 +1254,8 @@
                 SmsRawData data = records.get(i);
                 // List contains all records, including "free" records (null)
                 if (data != null) {
-                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+                    SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes(),
+                            getSubscriptionId());
                     if (sms != null) {
                         messages.add(sms);
                     }
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index dcdda86..a5d67c6 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,24 +16,25 @@
 
 package android.telephony;
 
-import android.os.Binder;
-import android.os.Parcel;
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+import android.annotation.StringDef;
 import android.content.res.Resources;
+import android.os.Binder;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
 import com.android.internal.telephony.SmsConstants;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
-import com.android.internal.telephony.Sms7BitEncodingTranslator;
 
-import java.lang.Math;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 
-import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
-
 
 /**
  * A Short Message Service message.
@@ -81,15 +82,18 @@
      */
     public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
 
+    /** @hide */
+    @StringDef({FORMAT_3GPP, FORMAT_3GPP2})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Format {}
+
     /**
      * Indicates a 3GPP format SMS message.
-     * @hide pending API council approval
      */
     public static final String FORMAT_3GPP = "3gpp";
 
     /**
      * Indicates a 3GPP2 format SMS message.
-     * @hide pending API council approval
      */
     public static final String FORMAT_3GPP2 = "3gpp2";
 
@@ -267,6 +271,31 @@
     }
 
     /**
+     * Create an SmsMessage from an SMS EF record.
+     *
+     * @param index Index of SMS record. This should be index in ArrayList
+     *              returned by SmsManager.getAllMessagesFromSim + 1.
+     * @param data Record data.
+     * @param subId Subscription Id of the SMS
+     * @return An SmsMessage representing the record.
+     *
+     * @hide
+     */
+    public static SmsMessage createFromEfRecord(int index, byte[] data, int subId) {
+        SmsMessageBase wrappedMessage;
+
+        if (isCdmaVoice(subId)) {
+            wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+                    index, data);
+        } else {
+            wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+                    index, data);
+        }
+
+        return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+    }
+
+    /**
      * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
      * length in bytes (not hex chars) less the SMSC header
      *
@@ -818,6 +847,7 @@
          int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId);
          return (PHONE_TYPE_CDMA == activePhone);
    }
+
     /**
      * Decide if the carrier supports long SMS.
      * {@hide}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cde0bdf..67f6849 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -107,8 +107,6 @@
     public static final String MODEM_ACTIVITY_RESULT_KEY =
             BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY;
 
-    private static ITelephonyRegistry sRegistry;
-
     /**
      * The allowed states of Wi-Fi calling.
      *
@@ -179,11 +177,6 @@
             mContext = context;
         }
         mSubscriptionManager = SubscriptionManager.from(mContext);
-
-        if (sRegistry == null) {
-            sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
-                    "telephony.registry"));
-        }
     }
 
     /** @hide */
@@ -960,6 +953,27 @@
      */
     public static final int USSD_ERROR_SERVICE_UNAVAIL = -2;
 
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which leaves the roaming
+     * mode set to the radio default or to the user's preference if they've indicated one.
+     */
+    public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1;
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which only permits
+     * connections on home networks.
+     */
+    public static final int CDMA_ROAMING_MODE_HOME = 0;
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+     * affiliated networks.
+     */
+    public static final int CDMA_ROAMING_MODE_AFFILIATED = 1;
+    /**
+     * Value for {@link CarrierConfigManager#KEY_CDMA_ROAMING_MODE_INT} which permits roaming on
+     * any network.
+     */
+    public static final int CDMA_ROAMING_MODE_ANY = 2;
+
     //
     //
     // Device Info
@@ -3513,6 +3527,10 @@
         return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
     }
 
+    private ITelephonyRegistry getTelephonyRegistry() {
+        return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+    }
+
     //
     //
     // PhoneStateListener
@@ -3552,12 +3570,16 @@
             if (listener.mSubId == null) {
                 listener.mSubId = mSubId;
             }
-            sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(),
-                    listener.callback, events, notifyNow);
+
+            ITelephonyRegistry registry = getTelephonyRegistry();
+            if (registry != null) {
+                registry.listenForSubscriber(listener.mSubId, getOpPackageName(),
+                        listener.callback, events, notifyNow);
+            } else {
+                Rlog.w(TAG, "telephony registry not ready.");
+            }
         } catch (RemoteException ex) {
             // system process dead
-        } catch (NullPointerException ex) {
-            // system process dead
         }
     }
 
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index 5a57f32..f0d60b6 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -16,6 +16,7 @@
 
 package android.telephony.mbms;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Intent;
 import android.net.Uri;
@@ -26,7 +27,6 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.File;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -71,6 +71,19 @@
         private String appIntent;
         private int version = CURRENT_VERSION;
 
+
+        /**
+         * Builds a new DownloadRequest.
+         * @param sourceUri the source URI for the DownloadRequest to be built. This URI should
+         *     never be null.
+         */
+        public Builder(@NonNull Uri sourceUri) {
+            if (sourceUri == null) {
+                throw new IllegalArgumentException("Source URI must be non-null.");
+            }
+            source = sourceUri;
+        }
+
         /**
          * Sets the service from which the download request to be built will download from.
          * @param serviceInfo
@@ -92,15 +105,6 @@
         }
 
         /**
-         * Sets the source URI for the download request to be built.
-         * @param source
-         */
-        public Builder setSource(Uri source) {
-            this.source = source;
-            return this;
-        }
-
-        /**
          * Set the subscription ID on which the file(s) should be downloaded.
          * @param subscriptionId
          */
@@ -316,9 +320,11 @@
             throw new RuntimeException("Could not get sha256 hash object");
         }
         if (version >= 1) {
-            // Hash the source URI, destination URI, and the app intent
+            // Hash the source URI and the app intent
             digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8));
-            digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+            if (serializedResultIntentForApp != null) {
+                digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8));
+            }
         }
         // Add updates for future versions here
         return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP);
diff --git a/telephony/java/android/telephony/mbms/DownloadStateCallback.java b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
index 86920bd..892fbf0 100644
--- a/telephony/java/android/telephony/mbms/DownloadStateCallback.java
+++ b/telephony/java/android/telephony/mbms/DownloadStateCallback.java
@@ -16,8 +16,12 @@
 
 package android.telephony.mbms;
 
+import android.annotation.IntDef;
 import android.telephony.MbmsDownloadSession;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A optional listener class used by download clients to track progress. Apps should extend this
  * class and pass an instance into
@@ -29,6 +33,71 @@
 public class DownloadStateCallback {
 
     /**
+     * Bitmask flags used for filtering out callback methods. Used when constructing the
+     * DownloadStateCallback as an optional parameter.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALL_UPDATES, PROGRESS_UPDATES, STATE_UPDATES})
+    public @interface FilterFlag {}
+
+    /**
+     * Receive all callbacks.
+     * Default value.
+     */
+    public static final int ALL_UPDATES = 0x00;
+    /**
+     * Receive callbacks for {@link #onProgressUpdated}.
+     */
+    public static final int PROGRESS_UPDATES = 0x01;
+    /**
+     * Receive callbacks for {@link #onStateUpdated}.
+     */
+    public static final int STATE_UPDATES = 0x02;
+
+    private final int mCallbackFilterFlags;
+
+    /**
+     * Creates a DownloadStateCallback that will receive all callbacks.
+     */
+    public DownloadStateCallback() {
+        mCallbackFilterFlags = ALL_UPDATES;
+    }
+
+    /**
+     * Creates a DownloadStateCallback that will only receive callbacks for the methods specified
+     * via the filterFlags parameter.
+     * @param filterFlags A bitmask of filter flags that will specify which callback this instance
+     *     is interested in.
+     */
+    public DownloadStateCallback(int filterFlags) {
+        mCallbackFilterFlags = filterFlags;
+    }
+
+    /**
+     * Return the currently set filter flags.
+     * @return An integer containing the bitmask of flags that this instance is interested in.
+     * @hide
+     */
+    public int getCallbackFilterFlags() {
+        return mCallbackFilterFlags;
+    }
+
+    /**
+     * Returns true if a filter flag is set for a particular callback method. If the flag is set,
+     * the callback will be delivered to the listening process.
+     * @param flag A filter flag specifying whether or not a callback method is registered to
+     *     receive callbacks.
+     * @return true if registered to receive callbacks in the listening process, false if not.
+     */
+    public final boolean isFilterFlagSet(@FilterFlag int flag) {
+        if (mCallbackFilterFlags == ALL_UPDATES) {
+            return true;
+        }
+        return (mCallbackFilterFlags & flag) > 0;
+    }
+
+    /**
      * Called when the middleware wants to report progress for a file in a {@link DownloadRequest}.
      *
      * @param request a {@link DownloadRequest}, indicating which download is being referenced.
diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
index 61415b5..9af1eb9 100644
--- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
+++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java
@@ -165,6 +165,12 @@
                 Log.w(LOG_TAG, "Download result did not include a result code. Ignoring.");
                 return false;
             }
+            // We do not need to verify below extras if the result is not success.
+            if (MbmsDownloadSession.RESULT_SUCCESSFUL !=
+                    intent.getIntExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_RESULT,
+                    MbmsDownloadSession.RESULT_CANCELLED)) {
+                return true;
+            }
             if (!intent.hasExtra(MbmsDownloadSession.EXTRA_MBMS_DOWNLOAD_REQUEST)) {
                 Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring.");
                 return false;
@@ -281,7 +287,7 @@
             return;
         }
 
-        List<Uri> tempFiles = intent.getParcelableExtra(VendorUtils.EXTRA_TEMP_LIST);
+        List<Uri> tempFiles = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_TEMP_LIST);
         if (tempFiles == null) {
             return;
         }
@@ -303,7 +309,7 @@
             return;
         }
         int fdCount = intent.getIntExtra(VendorUtils.EXTRA_FD_COUNT, 0);
-        List<Uri> pausedList = intent.getParcelableExtra(VendorUtils.EXTRA_PAUSED_LIST);
+        List<Uri> pausedList = intent.getParcelableArrayListExtra(VendorUtils.EXTRA_PAUSED_LIST);
 
         if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) {
             Log.i(LOG_TAG, "No temp files actually requested. Ending.");
@@ -486,9 +492,14 @@
         } catch (PackageManager.NameNotFoundException e) {
             throw new RuntimeException("Package manager couldn't find " + context.getPackageName());
         }
+        if (appInfo.metaData == null) {
+            throw new RuntimeException("App must declare the file provider authority as metadata " +
+                    "in the manifest.");
+        }
         String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY);
         if (authority == null) {
-            throw new RuntimeException("Must declare the file provider authority as meta data");
+            throw new RuntimeException("App must declare the file provider authority as metadata " +
+                    "in the manifest.");
         }
         return authority;
     }
diff --git a/telephony/java/android/telephony/mbms/MbmsUtils.java b/telephony/java/android/telephony/mbms/MbmsUtils.java
index d38d8a7..b4ad1d7 100644
--- a/telephony/java/android/telephony/mbms/MbmsUtils.java
+++ b/telephony/java/android/telephony/mbms/MbmsUtils.java
@@ -22,6 +22,8 @@
 import android.content.ServiceConnection;
 import android.content.pm.*;
 import android.content.pm.ServiceInfo;
+import android.telephony.MbmsDownloadSession;
+import android.telephony.MbmsStreamingSession;
 import android.util.Log;
 
 import java.io.File;
@@ -48,24 +50,64 @@
         return new ComponentName(ci.packageName, ci.name);
     }
 
+    private static ComponentName getOverrideServiceName(Context context, String serviceAction) {
+        String metaDataKey = null;
+        switch (serviceAction) {
+            case MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_ACTION:
+                metaDataKey = MbmsDownloadSession.MBMS_DOWNLOAD_SERVICE_OVERRIDE_METADATA;
+                break;
+            case MbmsStreamingSession.MBMS_STREAMING_SERVICE_ACTION:
+                metaDataKey = MbmsStreamingSession.MBMS_STREAMING_SERVICE_OVERRIDE_METADATA;
+                break;
+        }
+        if (metaDataKey == null) {
+            return null;
+        }
+
+        ApplicationInfo appInfo;
+        try {
+            appInfo = context.getPackageManager()
+                    .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+        if (appInfo.metaData == null) {
+            return null;
+        }
+        String serviceComponent = appInfo.metaData.getString(metaDataKey);
+        if (serviceComponent == null) {
+            return null;
+        }
+        return ComponentName.unflattenFromString(serviceComponent);
+    }
+
     public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) {
         // Query for the proper service
         PackageManager packageManager = context.getPackageManager();
         Intent queryIntent = new Intent();
         queryIntent.setAction(serviceAction);
-        List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent,
-                PackageManager.MATCH_SYSTEM_ONLY);
 
-        if (downloadServices == null || downloadServices.size() == 0) {
-            Log.w(LOG_TAG, "No download services found, cannot get service info");
+        ComponentName overrideService = getOverrideServiceName(context, serviceAction);
+        List<ResolveInfo> services;
+        if (overrideService == null) {
+            services = packageManager.queryIntentServices(queryIntent,
+                    PackageManager.MATCH_SYSTEM_ONLY);
+        } else {
+            queryIntent.setComponent(overrideService);
+            services = packageManager.queryIntentServices(queryIntent,
+                    PackageManager.MATCH_ALL);
+        }
+
+        if (services == null || services.size() == 0) {
+            Log.w(LOG_TAG, "No MBMS services found, cannot get service info");
             return null;
         }
 
-        if (downloadServices.size() > 1) {
-            Log.w(LOG_TAG, "More than one download service found, cannot get unique service");
+        if (services.size() > 1) {
+            Log.w(LOG_TAG, "More than one MBMS service found, cannot get unique service");
             return null;
         }
-        return downloadServices.get(0).serviceInfo;
+        return services.get(0).serviceInfo;
     }
 
     public static int startBinding(Context context, String serviceAction,
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index 9a01ed0..8529f52 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
@@ -62,12 +63,6 @@
             throw new RuntimeException("bad locales length " + newLocales.size());
         }
 
-        for (Locale l : newLocales) {
-            if (!newNames.containsKey(l)) {
-                throw new IllegalArgumentException("A name must be provided for each locale");
-            }
-        }
-
         names = new HashMap(newNames.size());
         names.putAll(newNames);
         className = newClassName;
@@ -127,7 +122,7 @@
      * Get the user-displayable name for this cell-broadcast service corresponding to the
      * provided {@link Locale}.
      * @param locale The {@link Locale} in which you want the name of the service. This must be a
-     *               value from the list returned by {@link #getLocales()} -- an
+     *               value from the set returned by {@link #getNamedContentLocales()} -- an
      *               {@link java.util.NoSuchElementException} may be thrown otherwise.
      * @return The {@link CharSequence} providing the name of the service in the given
      *         {@link Locale}
@@ -140,6 +135,17 @@
     }
 
     /**
+     * Return an unmodifiable set of the current {@link Locale}s that have a user-displayable name
+     * associated with them. The user-displayable name associated with any {@link Locale} in this
+     * set can be retrieved with {@link #getNameForLocale(Locale)}.
+     * @return An unmodifiable set of {@link Locale} objects corresponding to a user-displayable
+     * content name in that locale.
+     */
+    public @NonNull Set<Locale> getNamedContentLocales() {
+        return Collections.unmodifiableSet(names.keySet());
+    }
+
+    /**
      * The class name for this service - used to categorize and filter
      */
     public String getServiceClassName() {
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
index c704f34..ef2a14a 100644
--- a/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/StreamingServiceInfo.java
@@ -17,6 +17,7 @@
 package android.telephony.mbms;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -41,6 +42,7 @@
      * @hide
      */
     @SystemApi
+    @TestApi
     public StreamingServiceInfo(Map<Locale, String> names, String className,
             List<Locale> locales, String serviceId, Date start, Date end) {
         super(names, className, locales, serviceId, start, end);
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
index ed5e826..cb93542 100755
--- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
+++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
@@ -36,7 +36,8 @@
 
     int download(in DownloadRequest downloadRequest);
 
-    int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener);
+    int registerStateCallback(in DownloadRequest downloadRequest, IDownloadStateCallback listener,
+        int flags);
 
     int unregisterStateCallback(in DownloadRequest downloadRequest,
         IDownloadStateCallback listener);
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
index d845a57..9ccdd56 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java
@@ -46,6 +46,47 @@
     private final Map<IBinder, DownloadStateCallback> mDownloadCallbackBinderMap = new HashMap<>();
     private final Map<IBinder, DeathRecipient> mDownloadCallbackDeathRecipients = new HashMap<>();
 
+
+    // Filters the DownloadStateCallbacks by its configuration from the app.
+    private abstract static class FilteredDownloadStateCallback extends DownloadStateCallback {
+
+        private final IDownloadStateCallback mCallback;
+        public FilteredDownloadStateCallback(IDownloadStateCallback callback, int callbackFlags) {
+            super(callbackFlags);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo,
+                int currentDownloadSize, int fullDownloadSize, int currentDecodedSize,
+                int fullDecodedSize) {
+            if (!isFilterFlagSet(PROGRESS_UPDATES)) {
+                return;
+            }
+            try {
+                mCallback.onProgressUpdated(request, fileInfo, currentDownloadSize,
+                        fullDownloadSize, currentDecodedSize, fullDecodedSize);
+            } catch (RemoteException e) {
+                onRemoteException(e);
+            }
+        }
+
+        @Override
+        public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
+                @MbmsDownloadSession.DownloadStatus int state) {
+            if (!isFilterFlagSet(STATE_UPDATES)) {
+                return;
+            }
+            try {
+                mCallback.onStateUpdated(request, fileInfo, state);
+            } catch (RemoteException e) {
+                onRemoteException(e);
+            }
+        }
+
+        protected abstract void onRemoteException(RemoteException e);
+    }
+
     /**
      * Initialize the download service for this app and subId, registering the listener.
      *
@@ -72,15 +113,13 @@
     @Override
     public final int initialize(final int subscriptionId,
             final IMbmsDownloadSessionCallback callback) throws RemoteException {
-        final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
-        return initialize(subscriptionId, new MbmsDownloadSessionCallback() {
+        final int uid = Binder.getCallingUid();
+
+        int result = initialize(subscriptionId, new MbmsDownloadSessionCallback() {
             @Override
             public void onError(int errorCode, String message) {
                 try {
@@ -108,6 +147,17 @@
                 }
             }
         });
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
     /**
@@ -196,49 +246,40 @@
      * @hide
      */
     @Override
-    public final int registerStateCallback(
-            final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
-            throws RemoteException {
+    public final int registerStateCallback(final DownloadRequest downloadRequest,
+            final IDownloadStateCallback callback, int flags) throws RemoteException {
         final int uid = Binder.getCallingUid();
-        DeathRecipient deathRecipient = new DeathRecipient() {
+        if (downloadRequest == null) {
+            throw new NullPointerException("Download request must not be null");
+        }
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
+
+        DownloadStateCallback exposedCallback = new FilteredDownloadStateCallback(callback, flags) {
             @Override
-            public void binderDied() {
+            protected void onRemoteException(RemoteException e) {
                 onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                mDownloadCallbackBinderMap.remove(callback.asBinder());
-                mDownloadCallbackDeathRecipients.remove(callback.asBinder());
-            }
-        };
-        mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
-        callback.asBinder().linkToDeath(deathRecipient, 0);
-
-        DownloadStateCallback exposedCallback = new DownloadStateCallback() {
-            @Override
-            public void onProgressUpdated(DownloadRequest request, FileInfo fileInfo, int
-                    currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int
-                    fullDecodedSize) {
-                try {
-                    callback.onProgressUpdated(request, fileInfo, currentDownloadSize,
-                            fullDownloadSize,
-                            currentDecodedSize, fullDecodedSize);
-                } catch (RemoteException e) {
-                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                }
-            }
-
-            @Override
-            public void onStateUpdated(DownloadRequest request, FileInfo fileInfo,
-                    @MbmsDownloadSession.DownloadStatus int state) {
-                try {
-                    callback.onStateUpdated(request, fileInfo, state);
-                } catch (RemoteException e) {
-                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
-                }
             }
         };
 
-        mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+        int result = registerStateCallback(downloadRequest, exposedCallback);
 
-        return registerStateCallback(downloadRequest, exposedCallback);
+        if (result == MbmsErrors.SUCCESS) {
+            DeathRecipient deathRecipient = new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, downloadRequest.getSubscriptionId());
+                    mDownloadCallbackBinderMap.remove(callback.asBinder());
+                    mDownloadCallbackDeathRecipients.remove(callback.asBinder());
+                }
+            };
+            mDownloadCallbackDeathRecipients.put(callback.asBinder(), deathRecipient);
+            callback.asBinder().linkToDeath(deathRecipient, 0);
+            mDownloadCallbackBinderMap.put(callback.asBinder(), exposedCallback);
+        }
+
+        return result;
     }
 
     /**
@@ -270,6 +311,13 @@
     public final int unregisterStateCallback(
             final DownloadRequest downloadRequest, final IDownloadStateCallback callback)
             throws RemoteException {
+        if (downloadRequest == null) {
+            throw new NullPointerException("Download request must not be null");
+        }
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
+
         DeathRecipient deathRecipient =
                 mDownloadCallbackDeathRecipients.remove(callback.asBinder());
         if (deathRecipient == null) {
diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
index f8f370a..db177c0 100644
--- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
+++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Binder;
@@ -38,6 +39,7 @@
  * @hide
  */
 @SystemApi
+@TestApi
 public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub {
     /**
      * Initialize streaming service for this app and subId, registering the listener.
@@ -65,15 +67,13 @@
     @Override
     public final int initialize(final IMbmsStreamingSessionCallback callback,
             final int subscriptionId) throws RemoteException {
-        final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
-        return initialize(new MbmsStreamingSessionCallback() {
+        final int uid = Binder.getCallingUid();
+
+        int result = initialize(new MbmsStreamingSessionCallback() {
             @Override
             public void onError(final int errorCode, final String message) {
                 try {
@@ -101,6 +101,17 @@
                 }
             }
         }, subscriptionId);
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
 
@@ -152,15 +163,13 @@
     @Override
     public int startStreaming(final int subscriptionId, String serviceId,
             final IStreamingServiceCallback callback) throws RemoteException {
-        final int uid = Binder.getCallingUid();
-        callback.asBinder().linkToDeath(new DeathRecipient() {
-            @Override
-            public void binderDied() {
-                onAppCallbackDied(uid, subscriptionId);
-            }
-        }, 0);
+        if (callback == null) {
+            throw new NullPointerException("Callback must not be null");
+        }
 
-        return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
+        final int uid = Binder.getCallingUid();
+
+        int result = startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() {
             @Override
             public void onError(final int errorCode, final String message) {
                 try {
@@ -207,6 +216,17 @@
                 }
             }
         });
+
+        if (result == MbmsErrors.SUCCESS) {
+            callback.asBinder().linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    onAppCallbackDied(uid, subscriptionId);
+                }
+            }, 0);
+        }
+
+        return result;
     }
 
     /**
diff --git a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
index 8fb27b2..a43f122 100644
--- a/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
+++ b/telephony/java/android/telephony/mbms/vendor/VendorUtils.java
@@ -38,8 +38,9 @@
 
     /**
      * The MBMS middleware should send this when a download of single file has completed or
-     * failed. Mandatory extras are
+     * failed. The only mandatory extra is
      * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_RESULT}
+     * and the following are required when the download has completed:
      * {@link MbmsDownloadSession#EXTRA_MBMS_FILE_INFO}
      * {@link MbmsDownloadSession#EXTRA_MBMS_DOWNLOAD_REQUEST}
      * {@link #EXTRA_TEMP_LIST}
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index fe37531..a4eb424 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -187,6 +187,57 @@
             in PendingIntent deliveryIntent, in boolean persistMessage);
 
     /**
+     * Send an SMS with options using Subscription Id.
+     *
+     * @param subId the subId on which the SMS has to be sent.
+     * @param destAddr the address to send the message to
+     * @param scAddr the SMSC to send the message through, or NULL for the
+     *  default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is sucessfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param persistMessageForNonDefaultSmsApp whether the sent message should
+     *   be automatically persisted in the SMS db. It only affects messages sent
+     *   by a non-default SMS app. Currently only the carrier app can set this
+     *   parameter to false to skip auto message persistence.
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending message is multi segmented or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     */
+    void sendTextForSubscriberWithOptions(in int subId, String callingPkg, in String destAddr,
+            in String scAddr, in String text, in PendingIntent sentIntent,
+            in PendingIntent deliveryIntent, in boolean persistMessageForNonDefaultSmsApp,
+            in int priority, in boolean expectMore, in int validityPeriod);
+
+    /**
      * Inject an SMS PDU into the android platform.
      *
      * @param subId the subId on which the SMS has to be injected.
@@ -234,6 +285,56 @@
             in List<PendingIntent> deliveryIntents, in boolean persistMessageForNonDefaultSmsApp);
 
     /**
+     * Send a multi-part text based SMS with options using Subscription Id.
+     *
+     * @param subId the subId on which the SMS has to be sent.
+     * @param destinationAddress the address to send the message to
+     * @param scAddress is the service center address or null to use
+     *   the current default SMSC
+     * @param parts an <code>ArrayList</code> of strings that, in order,
+     *   comprise the original message
+     * @param sentIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been sent.
+     *   The result code will be <code>Activity.RESULT_OK<code> for success,
+     *   or one of these errors:
+     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
+     *   <code>RESULT_ERROR_RADIO_OFF</code>
+     *   <code>RESULT_ERROR_NULL_PDU</code>.
+     * @param deliveryIntents if not null, an <code>ArrayList</code> of
+     *   <code>PendingIntent</code>s (one for each message part) that is
+     *   broadcast when the corresponding message part has been delivered
+     *   to the recipient.  The raw pdu of the status report is in the
+     *   extended data ("pdu").
+     * @param persistMessageForNonDefaultSmsApp whether the sent message should
+     *   be automatically persisted in the SMS db. It only affects messages sent
+     *   by a non-default SMS app. Currently only the carrier app can set this
+     *   parameter to false to skip auto message persistence.
+     * @param priority Priority level of the message
+     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
+     *  ---------------------------------
+     *  PRIORITY      | Level of Priority
+     *  ---------------------------------
+     *      '00'      |     Normal
+     *      '01'      |     Interactive
+     *      '10'      |     Urgent
+     *      '11'      |     Emergency
+     *  ----------------------------------
+     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
+     * @param expectMore is a boolean to indicate the sending message is multi segmented or not.
+     * @param validityPeriod Validity Period of the message in mins.
+     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     *  Validity Period(Minimum) -> 5 mins
+     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
+     *  Any Other values included Negative considered as Invalid Validity Period of the message.
+     */
+    void sendMultipartTextForSubscriberWithOptions(in int subId, String callingPkg,
+            in String destinationAddress, in String scAddress, in List<String> parts,
+            in List<PendingIntent> sentIntents, in List<PendingIntent> deliveryIntents,
+            in boolean persistMessageForNonDefaultSmsApp, in int priority, in boolean expectMore,
+            in int validityPeriod);
+
+    /**
      * Enable reception of cell broadcast (SMS-CB) messages with the given
      * message identifier and RAN type. The RAN type specify this message ID
      * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 7a53ef6..14c5f4b 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -99,6 +99,15 @@
     private static final int RETURN_NO_ACK  = 0;
     private static final int RETURN_ACK     = 1;
 
+    /**
+     * Supported priority modes for CDMA SMS messages
+     * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+     */
+    private static final int PRIORITY_NORMAL        = 0x0;
+    private static final int PRIORITY_INTERACTIVE   = 0x1;
+    private static final int PRIORITY_URGENT        = 0x2;
+    private static final int PRIORITY_EMERGENCY     = 0x3;
+
     private SmsEnvelope mEnvelope;
     private BearerData mBearerData;
 
@@ -211,6 +220,26 @@
      */
     public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
             boolean statusReportRequested, SmsHeader smsHeader) {
+        return getSubmitPdu(scAddr, destAddr, message, statusReportRequested, smsHeader, -1);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddr                Service Centre address.  Null means use default.
+     * @param destAddr              Address of the recipient.
+     * @param message               String representation of the message payload.
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param smsHeader             Array containing the data for the User Data Header, preceded
+     *                              by the Element Identifiers.
+     * @param priority              Priority level of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+            boolean statusReportRequested, SmsHeader smsHeader, int priority) {
 
         /**
          * TODO(cleanup): Do we really want silent failure like this?
@@ -224,7 +253,7 @@
         UserData uData = new UserData();
         uData.payloadStr = message;
         uData.userDataHeader = smsHeader;
-        return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+        return privateGetSubmitPdu(destAddr, statusReportRequested, uData, priority);
     }
 
     /**
@@ -282,6 +311,22 @@
     }
 
     /**
+     * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
+     *
+     * @param destAddr the address of the destination for the message
+     * @param userData the data for the message
+     * @param statusReportRequested Indicates whether a report is requested for this message.
+     * @param priority Priority level of the message
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+            boolean statusReportRequested, int priority) {
+        return privateGetSubmitPdu(destAddr, statusReportRequested, userData, priority);
+    }
+
+    /**
      * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
      */
     @Override
@@ -764,6 +809,15 @@
      */
     private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
             UserData userData) {
+        return privateGetSubmitPdu(destAddrStr, statusReportRequested, userData, -1);
+    }
+
+    /**
+     * Creates BearerData and Envelope from parameters for a Submit SMS.
+     * @return byte stream for SubmitPdu.
+     */
+    private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+            UserData userData, int priority) {
 
         /**
          * TODO(cleanup): give this function a more meaningful name.
@@ -792,6 +846,10 @@
         bearerData.userAckReq = false;
         bearerData.readAckReq = false;
         bearerData.reportReq = false;
+        if (priority >= PRIORITY_NORMAL && priority <= PRIORITY_EMERGENCY) {
+            bearerData.priorityIndicatorSet = true;
+            bearerData.priority = priority;
+        }
 
         bearerData.userData = userData;
 
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 1ca19e0..4f5bfa9 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -89,6 +89,18 @@
 
     private int mVoiceMailCount = 0;
 
+    private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00;
+    private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01;
+    private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02;
+    private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03;
+
+    //Validity Period min - 5 mins
+    private static final int VALIDITY_PERIOD_MIN = 5;
+    //Validity Period max - 63 weeks
+    private static final int VALIDITY_PERIOD_MAX = 635040;
+
+    private static final int INVALID_VALIDITY_PERIOD = -1;
+
     public static class SubmitPdu extends SubmitPduBase {
     }
 
@@ -202,6 +214,45 @@
     }
 
     /**
+     * Get Encoded Relative Validty Period Value from Validity period in mins.
+     *
+     * @param validityPeriod Validity period in mins.
+     *
+     * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
+     * ||relValidityPeriod (TP-VP)  ||                 ||  validityPeriod   ||
+     *
+     *      0 to 143                            --->       (TP-VP + 1) x 5 minutes
+     *
+     *      144 to 167                         --->        12 hours + ((TP-VP -143) x 30 minutes)
+     *
+     *      168 to 196                         --->        (TP-VP - 166) x 1 day
+     *
+     *      197 to 255                         --->        (TP-VP - 192) x 1 week
+     *
+     * @return relValidityPeriod Encoded Relative Validity Period Value.
+     * @hide
+     */
+    public static int getRelativeValidityPeriod(int validityPeriod) {
+        int relValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+        if (validityPeriod < VALIDITY_PERIOD_MIN  || validityPeriod > VALIDITY_PERIOD_MAX) {
+            Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod);
+            return relValidityPeriod;
+        }
+
+        if (validityPeriod <= 720) {
+            relValidityPeriod = (validityPeriod  / 5) - 1;
+        } else if (validityPeriod <= 1440) {
+            relValidityPeriod = ((validityPeriod - 720) / 30) + 143;
+        } else if (validityPeriod <= 43200) {
+            relValidityPeriod = (validityPeriod  / 1440) + 166;
+        } else if (validityPeriod <= 635040) {
+            relValidityPeriod = (validityPeriod  / 10080) + 192;
+        }
+        return relValidityPeriod;
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a destination address and a message
      *
      * @param scAddress Service Centre address.  Null means use default.
@@ -236,6 +287,29 @@
             String destinationAddress, String message,
             boolean statusReportRequested, byte[] header, int encoding,
             int languageTable, int languageShiftTable) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+            header, encoding, languageTable, languageShiftTable, -1);
+    }
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message using the
+     * specified encoding.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param encoding Encoding defined by constants in
+     *        com.android.internal.telephony.SmsConstants.ENCODING_*
+     * @param languageTable
+     * @param languageShiftTable
+     * @param validityPeriod Validity Period of the message in Minutes.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, byte[] header, int encoding,
+            int languageTable, int languageShiftTable, int validityPeriod) {
 
         // Perform null parameter checks.
         if (message == null || destinationAddress == null) {
@@ -272,8 +346,19 @@
         }
 
         SubmitPdu ret = new SubmitPdu();
-        // MTI = SMS-SUBMIT, UDHI = header != null
-        byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+
+        int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE;
+        int relativeValidityPeriod = INVALID_VALIDITY_PERIOD;
+
+        // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3
+        //bit 4:3 = 10 - TP-VP field present - relative format
+        if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) {
+            validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE;
+        }
+
+        byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) |
+                (header != null ? 0x40 : 0x00));
+
         ByteArrayOutputStream bo = getSubmitPduHead(
                 scAddress, destinationAddress, mtiByte,
                 statusReportRequested, ret);
@@ -338,7 +423,11 @@
             bo.write(0x08);
         }
 
-        // (no TP-Validity-Period)
+        if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) {
+            // ( TP-Validity-Period - relative format)
+            bo.write(relativeValidityPeriod);
+        }
+
         bo.write(userData, 0, userData.length);
         ret.encodedMessage = bo.toByteArray();
         return ret;
@@ -388,6 +477,24 @@
     }
 
     /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param destinationAddress the address of the destination for the message
+     * @param statusReportRequested staus report of the message Requested
+     * @param validityPeriod Validity Period of the message in Minutes.
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, int validityPeriod) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested,
+                null, ENCODING_UNKNOWN, 0, 0, validityPeriod);
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a data message to a destination address &amp; port
      *
      * @param scAddress Service Centre address. null == use default
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 060a518..29a95e6 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -106,9 +106,11 @@
     android.test.mock.stubs \
 
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SDK_VERSION := current
 
 # Make sure to run droiddoc first to generate the stub source files.
 LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_runner_api_gen_stamp)
+android_test_runner_api_gen_stamp :=
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -203,6 +205,7 @@
 
 # Make sure to run droiddoc first to generate the stub source files.
 LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
+android_test_mock_gen_stamp :=
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -246,6 +249,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := android.test.mock.sdk
+LOCAL_SDK_VERSION := current
 
 LOCAL_STATIC_JAVA_LIBRARIES := android.test.mock.stubs
 
diff --git a/tests/CantSaveState1/Android.mk b/tests/CantSaveState1/Android.mk
new file mode 100644
index 0000000..6e9db6e
--- /dev/null
+++ b/tests/CantSaveState1/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CantSaveState1
+
+include $(BUILD_PACKAGE)
diff --git a/tests/CantSaveState1/AndroidManifest.xml b/tests/CantSaveState1/AndroidManifest.xml
new file mode 100644
index 0000000..fadcaeb
--- /dev/null
+++ b/tests/CantSaveState1/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.cantsavestate1">
+    <application android:label="Can't Save 1" android:cantSaveState="true">
+        <activity android:name="CantSave1Activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/CantSaveState1/res/layout/cant_save_1_activity.xml b/tests/CantSaveState1/res/layout/cant_save_1_activity.xml
new file mode 100644
index 0000000..c5bf657
--- /dev/null
+++ b/tests/CantSaveState1/res/layout/cant_save_1_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="25dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="This app #1 can't save its state"
+    />
+
+</LinearLayout>
diff --git a/tests/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java b/tests/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
new file mode 100644
index 0000000..8879ed0
--- /dev/null
+++ b/tests/CantSaveState1/src/com/android/test/cantsavestate2/CantSave1Activity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.test.cantsavestate1;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CantSave1Activity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.cant_save_1_activity);
+    }
+}
diff --git a/tests/CantSaveState2/Android.mk b/tests/CantSaveState2/Android.mk
new file mode 100644
index 0000000..add9214
--- /dev/null
+++ b/tests/CantSaveState2/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := CantSaveState2
+
+include $(BUILD_PACKAGE)
diff --git a/tests/CantSaveState2/AndroidManifest.xml b/tests/CantSaveState2/AndroidManifest.xml
new file mode 100644
index 0000000..8f4f01d
--- /dev/null
+++ b/tests/CantSaveState2/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.test.cantsavestate2">
+    <application android:label="Can't Save 2" android:cantSaveState="true">
+        <activity android:name="CantSave2Activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/CantSaveState2/res/layout/cant_save_2_activity.xml b/tests/CantSaveState2/res/layout/cant_save_2_activity.xml
new file mode 100644
index 0000000..c5b8e3d
--- /dev/null
+++ b/tests/CantSaveState2/res/layout/cant_save_2_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="25dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="This app #2 can't save its state"
+    />
+
+</LinearLayout>
diff --git a/tests/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java b/tests/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java
new file mode 100644
index 0000000..3ce63c7
--- /dev/null
+++ b/tests/CantSaveState2/src/com/android/test/cantsavestate2/CantSave2Activity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.test.cantsavestate2;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CantSave2Activity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.cant_save_2_activity);
+    }
+}
diff --git a/tests/Compatibility/Android.mk b/tests/Compatibility/Android.mk
index feeae02..82e2126 100644
--- a/tests/Compatibility/Android.mk
+++ b/tests/Compatibility/Android.mk
@@ -17,9 +17,7 @@
 
 # We only want this apk build for tests.
 LOCAL_MODULE_TAGS := tests
-
-LOCAL_JAVA_LIBRARIES := legacy-android-test
-LOCAL_STATIC_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 # Include all test java files.
 LOCAL_SRC_FILES := \
 	$(call all-java-files-under, src)
diff --git a/tests/Compatibility/AndroidManifest.xml b/tests/Compatibility/AndroidManifest.xml
index 7017431..5d1317e 100644
--- a/tests/Compatibility/AndroidManifest.xml
+++ b/tests/Compatibility/AndroidManifest.xml
@@ -15,12 +15,12 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.compatibilitytest" >
+    package="com.android.compatibilitytest"
+    android:sharedUserId="android.uid.system">
     <uses-sdk android:minSdkVersion="21"
               android:targetSdkVersion="21" />
-    <application >
-        <uses-library android:name="android.test.runner" />
-    </application>
+    <application />
+    <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.REAL_GET_TASKS" />
     <instrumentation
         android:name=".AppCompatibilityRunner"
diff --git a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
index f81b001..95eb5c9 100644
--- a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
+++ b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibility.java
@@ -17,62 +17,93 @@
 package com.android.compatibilitytest;
 
 import android.app.ActivityManager;
-import android.app.UiAutomation;
-import android.app.UiModeManager;
 import android.app.ActivityManager.ProcessErrorStateInfo;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IActivityController;
+import android.app.IActivityManager;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.app.UiModeManager;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.os.Bundle;
-import android.test.InstrumentationTestCase;
+import android.os.DropBoxManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
-import junit.framework.Assert;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Application Compatibility Test that launches an application and detects
  * crashes.
  */
-public class AppCompatibility extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AppCompatibility {
 
     private static final String TAG = AppCompatibility.class.getSimpleName();
     private static final String PACKAGE_TO_LAUNCH = "package_to_launch";
     private static final String APP_LAUNCH_TIMEOUT_MSECS = "app_launch_timeout_ms";
     private static final String WORKSPACE_LAUNCH_TIMEOUT_MSECS = "workspace_launch_timeout_ms";
+    private static final Set<String> DROPBOX_TAGS = new HashSet<>();
+    static {
+        DROPBOX_TAGS.add("SYSTEM_TOMBSTONE");
+        DROPBOX_TAGS.add("system_app_anr");
+        DROPBOX_TAGS.add("system_app_native_crash");
+        DROPBOX_TAGS.add("system_app_crash");
+        DROPBOX_TAGS.add("data_app_anr");
+        DROPBOX_TAGS.add("data_app_native_crash");
+        DROPBOX_TAGS.add("data_app_crash");
+    }
+    private static final int MAX_CRASH_SNIPPET_LINES = 20;
+    private static final int MAX_NUM_CRASH_SNIPPET = 3;
 
+    // time waiting for app to launch
     private int mAppLaunchTimeout = 7000;
+    // time waiting for launcher home screen to show up
     private int mWorkspaceLaunchTimeout = 2000;
 
     private Context mContext;
     private ActivityManager mActivityManager;
     private PackageManager mPackageManager;
-    private AppCompatibilityRunner mRunner;
     private Bundle mArgs;
+    private Instrumentation mInstrumentation;
+    private String mLauncherPackageName;
+    private IActivityController mCrashSupressor = new CrashSuppressor();
+    private Map<String, List<String>> mAppErrors = new HashMap<>();
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-        mRunner = (AppCompatibilityRunner) getInstrumentation();
-        assertNotNull("Could not fetch InstrumentationTestRunner.", mRunner);
-
-        mContext = mRunner.getTargetContext();
-        Assert.assertNotNull("Could not get the Context", mContext);
-
-        mActivityManager = (ActivityManager)
-                mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        Assert.assertNotNull("Could not get Activity Manager", mActivityManager);
-
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = InstrumentationRegistry.getTargetContext();
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mPackageManager = mContext.getPackageManager();
-        Assert.assertNotNull("Missing Package Manager", mPackageManager);
+        mArgs = InstrumentationRegistry.getArguments();
 
-        mArgs = mRunner.getBundle();
+        // resolve launcher package name
+        Intent intent = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME);
+        ResolveInfo resolveInfo = mPackageManager.resolveActivity(
+                intent, PackageManager.MATCH_DEFAULT_ONLY);
+        mLauncherPackageName = resolveInfo.activityInfo.packageName;
+        Assert.assertNotNull("failed to resolve package name for launcher", mLauncherPackageName);
+        Log.v(TAG, "Using launcher package name: " + mLauncherPackageName);
 
         // Parse optional inputs.
         String appLaunchTimeoutMsecs = mArgs.getString(APP_LAUNCH_TIMEOUT_MSECS);
@@ -83,13 +114,20 @@
         if (workspaceLaunchTimeoutMsecs != null) {
             mWorkspaceLaunchTimeout = Integer.parseInt(workspaceLaunchTimeoutMsecs);
         }
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
+        mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_FREEZE_0);
+
+        // set activity controller to suppress crash dialogs and collects them by process name
+        mAppErrors.clear();
+        IActivityManager.Stub.asInterface(ServiceManager.checkService(Context.ACTIVITY_SERVICE))
+            .setActivityController(mCrashSupressor, false);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        getInstrumentation().getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
+        // unset activity controller
+        IActivityManager.Stub.asInterface(ServiceManager.checkService(Context.ACTIVITY_SERVICE))
+            .setActivityController(null, false);
+        mInstrumentation.getUiAutomation().setRotation(UiAutomation.ROTATION_UNFREEZE);
     }
 
     /**
@@ -98,6 +136,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testAppStability() throws Exception {
         String packageName = mArgs.getString(PACKAGE_TO_LAUNCH);
         if (packageName != null) {
@@ -107,13 +146,30 @@
                 Log.w(TAG, String.format("Skipping %s; no launch intent", packageName));
                 return;
             }
-            ProcessErrorStateInfo err = launchActivity(packageName, intent);
-            // Make sure there are no errors when launching the application,
-            // otherwise raise an
-            // exception with the first error encountered.
-            assertNull(getStackTrace(err), err);
+            long startTime = System.currentTimeMillis();
+            launchActivity(packageName, intent);
             try {
-                assertTrue("App crashed after launch.", processStillUp(packageName));
+                checkDropbox(startTime, packageName);
+                if (mAppErrors.containsKey(packageName)) {
+                    StringBuilder message = new StringBuilder("Error(s) detected for package: ")
+                            .append(packageName);
+                    List<String> errors = mAppErrors.get(packageName);
+                    for (int i = 0; i < MAX_NUM_CRASH_SNIPPET && i < errors.size(); i++) {
+                        String err = errors.get(i);
+                        message.append("\n\n");
+                        // limit the size of each crash snippet
+                        message.append(truncate(err, MAX_CRASH_SNIPPET_LINES));
+                    }
+                    if (errors.size() > MAX_NUM_CRASH_SNIPPET) {
+                        message.append(String.format("\n... %d more errors omitted ...",
+                                errors.size() - MAX_NUM_CRASH_SNIPPET));
+                    }
+                    Assert.fail(message.toString());
+                }
+                // last check: see if app process is still running
+                Assert.assertTrue("app package \"" + packageName + "\" no longer found in running "
+                    + "tasks, but no explicit crashes were detected; check logcat for details",
+                    processStillUp(packageName));
             } finally {
                 returnHome();
             }
@@ -124,31 +180,52 @@
     }
 
     /**
-     * Gets the stack trace for the error.
-     *
-     * @param in {@link ProcessErrorStateInfo} to parse.
-     * @return {@link String} the long message of the error.
+     * Truncate the text to at most the specified number of lines, and append a marker at the end
+     * when truncated
+     * @param text
+     * @param maxLines
+     * @return
      */
-    private String getStackTrace(ProcessErrorStateInfo in) {
-        if (in == null) {
-            return null;
-        } else {
-            return in.stackTrace;
+    private static String truncate(String text, int maxLines) {
+        String[] lines = text.split("\\r?\\n");
+        StringBuilder ret = new StringBuilder();
+        for (int i = 0; i < maxLines && i < lines.length; i++) {
+            ret.append(lines[i]);
+            ret.append('\n');
         }
+        if (lines.length > maxLines) {
+            ret.append("... ");
+            ret.append(lines.length - maxLines);
+            ret.append(" more lines truncated ...\n");
+        }
+        return ret.toString();
     }
 
     /**
-     * Returns the process name that the package is going to use.
-     *
-     * @param packageName name of the package
-     * @return process name of the package
+     * Check dropbox for entries of interest regarding the specified process
+     * @param startTime if not 0, only check entries with timestamp later than the start time
+     * @param processName the process name to check for
      */
-    private String getProcessName(String packageName) {
-        try {
-            PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
-            return pi.applicationInfo.processName;
-        } catch (NameNotFoundException e) {
-            return packageName;
+    private void checkDropbox(long startTime, String processName) {
+        DropBoxManager dropbox = (DropBoxManager) mContext
+                .getSystemService(Context.DROPBOX_SERVICE);
+        DropBoxManager.Entry entry = null;
+        while (null != (entry = dropbox.getNextEntry(null, startTime))) {
+            try {
+                // only check entries with tag that's of interest
+                String tag = entry.getTag();
+                if (DROPBOX_TAGS.contains(tag)) {
+                    String content = entry.getText(4096);
+                    if (content != null) {
+                        if (content.contains(processName)) {
+                            addProcessError(processName, "dropbox:" + tag, content);
+                        }
+                    }
+                }
+                startTime = entry.getTimeMillis();
+            } finally {
+                entry.close();
+            }
         }
     }
 
@@ -166,8 +243,7 @@
     }
 
     private Intent getLaunchIntentForPackage(String packageName) {
-        UiModeManager umm = (UiModeManager)
-                getInstrumentation().getContext().getSystemService(Context.UI_MODE_SERVICE);
+        UiModeManager umm = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
         boolean isLeanback = umm.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION;
         Intent intent = null;
         if (isLeanback) {
@@ -186,35 +262,32 @@
      * @return {@link Collection} of {@link ProcessErrorStateInfo} detected
      *         during the app launch.
      */
-    private ProcessErrorStateInfo launchActivity(String packageName, Intent intent) {
+    private void launchActivity(String packageName, Intent intent) {
         Log.d(TAG, String.format("launching package \"%s\" with intent: %s",
                 packageName, intent.toString()));
 
-        String processName = getProcessName(packageName);
-
         // Launch Activity
         mContext.startActivity(intent);
 
         try {
+            // artificial delay: in case app crashes after doing some work during launch
             Thread.sleep(mAppLaunchTimeout);
         } catch (InterruptedException e) {
             // ignore
         }
+    }
 
-        // See if there are any errors. We wait until down here to give ANRs as much time as
-        // possible to occur.
-        final Collection<ProcessErrorStateInfo> postErr =
-                mActivityManager.getProcessesInErrorState();
-
-        if (postErr == null) {
-            return null;
+    private void addProcessError(String processName, String errorType, String errorInfo) {
+        // parse out the package name if necessary, for apps with multiple proceses
+        String pkgName = processName.split(":", 2)[0];
+        List<String> errors;
+        if (mAppErrors.containsKey(pkgName)) {
+            errors = mAppErrors.get(pkgName);
+        }  else {
+            errors = new ArrayList<>();
         }
-        for (ProcessErrorStateInfo error : postErr) {
-            if (error.processName.equals(processName)) {
-                return error;
-            }
-        }
-        return null;
+        errors.add(String.format("### Type: %s, Details:\n%s", errorType, errorInfo));
+        mAppErrors.put(pkgName, errors);
     }
 
     /**
@@ -233,4 +306,55 @@
         }
         return false;
     }
+
+    /**
+     * An {@link IActivityController} that instructs framework to kill processes hitting crashes
+     * directly without showing crash dialogs
+     *
+     */
+    private class CrashSuppressor extends IActivityController.Stub {
+
+        @Override
+        public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
+            Log.d(TAG, "activity starting: " + intent.getComponent().toShortString());
+            return true;
+        }
+
+        @Override
+        public boolean activityResuming(String pkg) throws RemoteException {
+            Log.d(TAG, "activity resuming: " + pkg);
+            return true;
+        }
+
+        @Override
+        public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
+                long timeMillis, String stackTrace) throws RemoteException {
+            Log.d(TAG, "app crash: " + processName);
+            addProcessError(processName, "crash", stackTrace);
+            // don't show dialog
+            return false;
+        }
+
+        @Override
+        public int appEarlyNotResponding(String processName, int pid, String annotation)
+                throws RemoteException {
+            // ignore
+            return 0;
+        }
+
+        @Override
+        public int appNotResponding(String processName, int pid, String processStats)
+                throws RemoteException {
+            Log.d(TAG, "app ANR: " + processName);
+            addProcessError(processName, "ANR", processStats);
+            // don't show dialog
+            return -1;
+        }
+
+        @Override
+        public int systemNotResponding(String msg) throws RemoteException {
+            // ignore
+            return -1;
+        }
+    }
 }
diff --git a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibilityRunner.java b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibilityRunner.java
index 258937f..b61ec34 100644
--- a/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibilityRunner.java
+++ b/tests/Compatibility/src/com/android/compatibilitytest/AppCompatibilityRunner.java
@@ -16,20 +16,7 @@
 
 package com.android.compatibilitytest;
 
-import android.os.Bundle;
-import android.test.InstrumentationTestRunner;
+import android.support.test.runner.AndroidJUnitRunner;
 
-public class AppCompatibilityRunner extends InstrumentationTestRunner {
-
-    private Bundle mArgs;
-
-    @Override
-    public void onCreate(Bundle args) {
-        super.onCreate(args);
-        mArgs = args;
-    }
-
-    public Bundle getBundle() {
-        return mArgs;
-    }
-}
+// empty subclass to maintain backwards compatibility on host-side harness
+public class AppCompatibilityRunner extends AndroidJUnitRunner {}
diff --git a/tests/FeatureSplit/base/Android.mk b/tests/FeatureSplit/base/Android.mk
index 93f6d7a..6da1b38 100644
--- a/tests/FeatureSplit/base/Android.mk
+++ b/tests/FeatureSplit/base/Android.mk
@@ -17,6 +17,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_USE_AAPT2 := true
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_PACKAGE_NAME := FeatureSplitBase
 LOCAL_EXPORT_PACKAGE_RESOURCES := true
diff --git a/tests/FeatureSplit/feature1/Android.mk b/tests/FeatureSplit/feature1/Android.mk
index e6ba5c2..b3ea97b 100644
--- a/tests/FeatureSplit/feature1/Android.mk
+++ b/tests/FeatureSplit/feature1/Android.mk
@@ -26,6 +26,6 @@
 LOCAL_RES_LIBRARIES := FeatureSplitBase
 
 LOCAL_AAPT_FLAGS += --package-id 0x80
-LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.one
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.test.split.feature
 
 include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml
index b87361f..4e7d151 100644
--- a/tests/FeatureSplit/feature1/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature1/AndroidManifest.xml
@@ -15,13 +15,13 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.split.feature"
+    package="com.android.test.split.feature.one"
     featureSplit="feature1">
 
     <uses-sdk android:minSdkVersion="21" />
 
     <application>
-        <activity android:name=".one.One" android:label="Feature One">
+        <activity android:name=".One" android:label="Feature One">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml
index 10dbd97..0e3e73c 100644
--- a/tests/FeatureSplit/feature1/res/values/values.xml
+++ b/tests/FeatureSplit/feature1/res/values/values.xml
@@ -20,7 +20,7 @@
     <integer name="test_integer2">200</integer>
     <color name="test_color2">#00ff00</color>
     <string-array name="string_array2">
-        <item>@*string/app_title</item>
+        <item>@*com.android.test.split.feature:string/app_title</item>
     </string-array>
 </resources>
 
diff --git a/tests/FeatureSplit/feature2/Android.mk b/tests/FeatureSplit/feature2/Android.mk
index c8e8609..e2fd903 100644
--- a/tests/FeatureSplit/feature2/Android.mk
+++ b/tests/FeatureSplit/feature2/Android.mk
@@ -26,6 +26,6 @@
 LOCAL_RES_LIBRARIES := FeatureSplitBase
 
 LOCAL_AAPT_FLAGS += --package-id 0x81
-LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.two
+LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.test.split.feature
 
 include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature2/AndroidManifest.xml b/tests/FeatureSplit/feature2/AndroidManifest.xml
index abd0b5e..bfe6f38 100644
--- a/tests/FeatureSplit/feature2/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature2/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.test.split.feature"
+    package="com.android.test.split.feature.two"
     featureSplit="feature2">
 
     <uses-sdk android:minSdkVersion="21" />
diff --git a/tests/FeatureSplit/feature2/res/values/values.xml b/tests/FeatureSplit/feature2/res/values/values.xml
index af5ed1b..2fa6f90 100644
--- a/tests/FeatureSplit/feature2/res/values/values.xml
+++ b/tests/FeatureSplit/feature2/res/values/values.xml
@@ -18,7 +18,7 @@
     <integer name="test_integer3">300</integer>
     <color name="test_color3">#0000ff</color>
     <string-array name="string_array3">
-        <item>@string/app_title</item>
+        <item>@*com.android.test.split.feature:string/app_title</item>
     </string-array>
 </resources>
 
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 25bfa53..047be16 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -17,6 +17,7 @@
 package android.security.net.config;
 
 import android.app.Activity;
+import android.content.pm.ApplicationInfo;
 import android.os.Build;
 import android.test.ActivityUnitTestCase;
 import android.util.ArraySet;
@@ -227,7 +228,8 @@
     public void testConfigBuilderUsesParents() throws Exception {
         // Check that a builder with a parent uses the parent's values when non is set.
         NetworkSecurityConfig config = new NetworkSecurityConfig.Builder()
-                .setParent(NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1))
+                .setParent(NetworkSecurityConfig
+                        .getDefaultBuilder(TestUtils.makeApplicationInfo()))
                 .build();
         assert(!config.getTrustAnchors().isEmpty());
     }
@@ -268,11 +270,22 @@
             // Install the test CA.
             store.installCertificate(TEST_CA_CERT);
             NetworkSecurityConfig preNConfig =
-                    NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.M, 1).build();
+                    NetworkSecurityConfig
+                    .getDefaultBuilder(TestUtils.makeApplicationInfo(Build.VERSION_CODES.M))
+                    .build();
             NetworkSecurityConfig nConfig =
-                    NetworkSecurityConfig.getDefaultBuilder(Build.VERSION_CODES.N, 1).build();
+                    NetworkSecurityConfig
+                    .getDefaultBuilder(TestUtils.makeApplicationInfo(Build.VERSION_CODES.N))
+                    .build();
+            ApplicationInfo privInfo = TestUtils.makeApplicationInfo(Build.VERSION_CODES.M);
+            privInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+            NetworkSecurityConfig privConfig =
+                    NetworkSecurityConfig
+                    .getDefaultBuilder(privInfo)
+                    .build();
             Set<TrustAnchor> preNAnchors = preNConfig.getTrustAnchors();
             Set<TrustAnchor> nAnchors = nConfig.getTrustAnchors();
+            Set<TrustAnchor> privAnchors = privConfig.getTrustAnchors();
             Set<X509Certificate> preNCerts = new HashSet<X509Certificate>();
             for (TrustAnchor anchor : preNAnchors) {
                 preNCerts.add(anchor.certificate);
@@ -281,8 +294,13 @@
             for (TrustAnchor anchor : nAnchors) {
                 nCerts.add(anchor.certificate);
             }
+            Set<X509Certificate> privCerts = new HashSet<X509Certificate>();
+            for (TrustAnchor anchor : privAnchors) {
+                privCerts.add(anchor.certificate);
+            }
             assertTrue(preNCerts.contains(TEST_CA_CERT));
             assertFalse(nCerts.contains(TEST_CA_CERT));
+            assertFalse(privCerts.contains(TEST_CA_CERT));
         } finally {
             // Delete the user added CA. We don't know the alias so just delete them all.
             for (String alias : store.aliases()) {
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
index f7590fd..9dec21b 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -16,6 +16,8 @@
 
 package android.security.net.config;
 
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
 import java.net.Socket;
 import java.net.URL;
 import javax.net.ssl.HttpsURLConnection;
@@ -77,4 +79,17 @@
         context.init(null, tmf.getTrustManagers(), null);
         return context;
     }
+
+    public static ApplicationInfo makeApplicationInfo() {
+        ApplicationInfo info = new ApplicationInfo();
+        info.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+        info.targetSandboxVersion = 1;
+        return info;
+    }
+
+    public static ApplicationInfo makeApplicationInfo(int targetSdkVersion) {
+        ApplicationInfo info = makeApplicationInfo();
+        info.targetSdkVersion = targetSdkVersion;
+        return info;
+    }
 }
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index f7066a6..4b7a014 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -17,6 +17,7 @@
 package android.security.net.config;
 
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.util.ArraySet;
@@ -44,7 +45,8 @@
     private final static String DEBUG_CA_SUBJ = "O=AOSP, CN=Test debug CA";
 
     public void testEmptyConfigFile() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_config,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -63,7 +65,8 @@
     }
 
     public void testEmptyAnchors() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.empty_trust,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -81,7 +84,8 @@
     }
 
     public void testBasicDomainConfig() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.domain1,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -117,7 +121,8 @@
     }
 
     public void testBasicPinning() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.pins1,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -132,7 +137,8 @@
     }
 
     public void testExpiredPin() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.expired_pin,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -146,7 +152,8 @@
     }
 
     public void testOverridesPins() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_pins,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -160,7 +167,8 @@
     }
 
     public void testBadPin() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -175,7 +183,8 @@
     }
 
     public void testMultipleDomains() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_domains,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -196,7 +205,8 @@
     }
 
     public void testMultipleDomainConfigs() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.multiple_configs,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Should be two different config objects
@@ -211,7 +221,8 @@
     }
 
     public void testIncludeSubdomains() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.subdomains,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Try connections.
@@ -224,7 +235,8 @@
     }
 
     public void testAttributes() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.attributes,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
@@ -233,7 +245,8 @@
     }
 
     public void testResourcePemCertificateSource() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_pem,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         // Check android.com.
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -249,7 +262,8 @@
     }
 
     public void testResourceDerCertificateSource() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.resource_anchors_der,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         // Check android.com.
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
@@ -265,7 +279,8 @@
     }
 
     public void testNestedDomainConfigs() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
@@ -283,7 +298,8 @@
     }
 
     public void testNestedDomainConfigsOverride() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.nested_domains_override,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig parent = appConfig.getConfigForHostname("android.com");
@@ -294,7 +310,8 @@
     }
 
     public void testDebugOverridesDisabled() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, false);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -305,7 +322,9 @@
     }
 
     public void testBasicDebugOverrides() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_basic, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -319,7 +338,9 @@
     }
 
     public void testDebugOverridesWithDomain() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -337,7 +358,9 @@
     }
 
     public void testDebugInherit() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.debug_domain, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
         Set<TrustAnchor> anchors = config.getTrustAnchors();
@@ -357,7 +380,8 @@
 
     private void testBadConfig(int configId) throws Exception {
         try {
-            XmlConfigSource source = new XmlConfigSource(getContext(), configId);
+            XmlConfigSource source = new XmlConfigSource(getContext(), configId,
+                    TestUtils.makeApplicationInfo());
             ApplicationConfig appConfig = new ApplicationConfig(source);
             appConfig.getConfigForHostname("android.com");
             fail("Bad config " + getContext().getResources().getResourceName(configId)
@@ -393,7 +417,8 @@
     }
 
     public void testTrustManagerKeystore() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin, true);
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.bad_pin,
+                TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         Provider provider = new NetworkSecurityConfigProvider();
         TrustManagerFactory tmf =
@@ -415,7 +440,9 @@
     }
 
     public void testDebugDedup() throws Exception {
-        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertTrue(appConfig.hasPerDomainConfigs());
         // Check android.com.
@@ -433,15 +460,18 @@
     }
 
     public void testExtraDebugResource() throws Exception {
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
         XmlConfigSource source =
-                new XmlConfigSource(getContext(), R.xml.extra_debug_resource, true);
+                new XmlConfigSource(getContext(), R.xml.extra_debug_resource, info);
         ApplicationConfig appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         NetworkSecurityConfig config = appConfig.getConfigForHostname("");
         MoreAsserts.assertNotEmpty(config.getTrustAnchors());
 
         // Check that the _debug file is ignored if debug is false.
-        source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource, false);
+        source = new XmlConfigSource(getContext(), R.xml.extra_debug_resource,
+                TestUtils.makeApplicationInfo());
         appConfig = new ApplicationConfig(source);
         assertFalse(appConfig.hasPerDomainConfigs());
         config = appConfig.getConfigForHostname("");
@@ -451,12 +481,15 @@
     public void testExtraDebugResourceIgnored() throws Exception {
         // Verify that parsing the extra debug config resource fails only when debugging is true.
         XmlConfigSource source =
-                new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, false);
+                new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource,
+                        TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         // Force parsing the config file.
         appConfig.getConfigForHostname("");
 
-        source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, true);
+        ApplicationInfo info = TestUtils.makeApplicationInfo();
+        info.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        source = new XmlConfigSource(getContext(), R.xml.bad_extra_debug_resource, info);
         appConfig = new ApplicationConfig(source);
         try {
             appConfig.getConfigForHostname("");
@@ -467,7 +500,8 @@
 
     public void testDomainWhitespaceTrimming() throws Exception {
         XmlConfigSource source =
-                new XmlConfigSource(getContext(), R.xml.domain_whitespace, false);
+                new XmlConfigSource(getContext(), R.xml.domain_whitespace,
+                        TestUtils.makeApplicationInfo());
         ApplicationConfig appConfig = new ApplicationConfig(source);
         NetworkSecurityConfig defaultConfig = appConfig.getConfigForHostname("");
         MoreAsserts.assertNotEqual(defaultConfig, appConfig.getConfigForHostname("developer.android.com"));
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 8210403..163250d 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -296,6 +296,7 @@
                     Notification n = new Notification.Builder(NotificationTestList.this, "min")
                             .setSmallIcon(R.drawable.icon2)
                             .setContentTitle("Min priority")
+                            .setTicker("Min priority")
                             .build();
                     mNM.notify("min", 7000, n);
                 }
@@ -306,6 +307,7 @@
                     Notification n = new Notification.Builder(NotificationTestList.this, "low")
                             .setSmallIcon(R.drawable.icon2)
                             .setContentTitle("Low priority")
+                            .setTicker("Low priority")
                             .build();
                     mNM.notify("low", 7002, n);
                 }
@@ -326,6 +328,7 @@
                     Notification n = new Notification.Builder(NotificationTestList.this, "high")
                             .setSmallIcon(R.drawable.icon2)
                             .setContentTitle("High priority")
+                            .setTicker("High priority")
                             .build();
                     mNM.notify("high", 7006, n);
                 }
diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java
new file mode 100644
index 0000000..1b4bef5
--- /dev/null
+++ b/tests/net/java/android/net/IpSecConfigTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link IpSecConfig}. */
+@SmallTest
+@RunWith(JUnit4.class)
+public class IpSecConfigTest {
+
+    @Test
+    public void testDefaults() throws Exception {
+        IpSecConfig c = new IpSecConfig();
+        assertEquals(IpSecTransform.MODE_TRANSPORT, c.getMode());
+        assertEquals("", c.getLocalAddress());
+        assertEquals("", c.getRemoteAddress());
+        assertNull(c.getNetwork());
+        assertEquals(IpSecTransform.ENCAP_NONE, c.getEncapType());
+        assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getEncapSocketResourceId());
+        assertEquals(0, c.getEncapRemotePort());
+        assertEquals(0, c.getNattKeepaliveInterval());
+        for (int direction :
+                new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN}) {
+            assertNull(c.getEncryption(direction));
+            assertNull(c.getAuthentication(direction));
+            assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId(direction));
+        }
+    }
+
+    @Test
+    public void testParcelUnparcel() throws Exception {
+        assertParcelingIsLossless(new IpSecConfig());
+
+        IpSecConfig c = new IpSecConfig();
+        c.setMode(IpSecTransform.MODE_TUNNEL);
+        c.setLocalAddress("0.0.0.0");
+        c.setRemoteAddress("1.2.3.4");
+        c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP);
+        c.setEncapSocketResourceId(7);
+        c.setEncapRemotePort(22);
+        c.setNattKeepaliveInterval(42);
+        c.setEncryption(
+                IpSecTransform.DIRECTION_OUT,
+                new IpSecAlgorithm(
+                        IpSecAlgorithm.CRYPT_AES_CBC,
+                        new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
+        c.setAuthentication(
+                IpSecTransform.DIRECTION_OUT,
+                new IpSecAlgorithm(
+                        IpSecAlgorithm.AUTH_HMAC_SHA1,
+                        new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0}));
+        c.setSpiResourceId(IpSecTransform.DIRECTION_OUT, 1984);
+        c.setEncryption(
+                IpSecTransform.DIRECTION_IN,
+                new IpSecAlgorithm(
+                        IpSecAlgorithm.CRYPT_AES_CBC,
+                        new byte[] {2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}));
+        c.setAuthentication(
+                IpSecTransform.DIRECTION_IN,
+                new IpSecAlgorithm(
+                        IpSecAlgorithm.AUTH_HMAC_SHA1,
+                        new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 1}));
+        c.setSpiResourceId(IpSecTransform.DIRECTION_IN, 99);
+        assertParcelingIsLossless(c);
+    }
+
+    private void assertParcelingIsLossless(IpSecConfig ci) throws Exception {
+        Parcel p = Parcel.obtain();
+        ci.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        IpSecConfig co = IpSecConfig.CREATOR.createFromParcel(p);
+        assertTrue(IpSecConfig.equals(co, ci));
+    }
+}
diff --git a/tests/net/java/android/net/IpSecManagerTest.java b/tests/net/java/android/net/IpSecManagerTest.java
index 9f31d27..ccb0f3b 100644
--- a/tests/net/java/android/net/IpSecManagerTest.java
+++ b/tests/net/java/android/net/IpSecManagerTest.java
@@ -31,19 +31,21 @@
 import static org.mockito.Mockito.when;
 
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.system.Os;
-import android.test.AndroidTestCase;
+
 import com.android.server.IpSecService;
+
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 /** Unit tests for {@link IpSecManager}. */
 @SmallTest
-@RunWith(JUnit4.class)
+@RunWith(AndroidJUnit4.class)
 public class IpSecManagerTest {
 
     private static final int TEST_UDP_ENCAP_PORT = 34567;
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
new file mode 100644
index 0000000..52da79a
--- /dev/null
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright (C) 2010 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.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.LinkProperties.CompareResult;
+import android.net.LinkProperties.ProvisioningChange;
+import android.net.RouteInfo;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.system.OsConstants;
+import android.util.ArraySet;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LinkPropertiesTest {
+    private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
+    private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
+            "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+    private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
+    private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
+    private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
+    private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
+    private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
+    private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
+    private static InetAddress GATEWAY62 = NetworkUtils.numericToInetAddress("fe80::6:2222");
+    private static String NAME = "qmi0";
+    private static int MTU = 1500;
+
+    private static LinkAddress LINKADDRV4 = new LinkAddress(ADDRV4, 32);
+    private static LinkAddress LINKADDRV6 = new LinkAddress(ADDRV6, 128);
+    private static LinkAddress LINKADDRV6LINKLOCAL = new LinkAddress("fe80::1/64");
+
+    // TODO: replace all calls to NetworkUtils.numericToInetAddress with calls to this method.
+    private InetAddress Address(String addrString) {
+        return NetworkUtils.numericToInetAddress(addrString);
+    }
+
+    public void assertLinkPropertiesEqual(LinkProperties source, LinkProperties target) {
+        // Check implementation of equals(), element by element.
+        assertTrue(source.isIdenticalInterfaceName(target));
+        assertTrue(target.isIdenticalInterfaceName(source));
+
+        assertTrue(source.isIdenticalAddresses(target));
+        assertTrue(target.isIdenticalAddresses(source));
+
+        assertTrue(source.isIdenticalDnses(target));
+        assertTrue(target.isIdenticalDnses(source));
+
+        assertTrue(source.isIdenticalRoutes(target));
+        assertTrue(target.isIdenticalRoutes(source));
+
+        assertTrue(source.isIdenticalHttpProxy(target));
+        assertTrue(target.isIdenticalHttpProxy(source));
+
+        assertTrue(source.isIdenticalStackedLinks(target));
+        assertTrue(target.isIdenticalStackedLinks(source));
+
+        assertTrue(source.isIdenticalMtu(target));
+        assertTrue(target.isIdenticalMtu(source));
+
+        // Check result of equals().
+        assertTrue(source.equals(target));
+        assertTrue(target.equals(source));
+
+        // Check hashCode.
+        assertEquals(source.hashCode(), target.hashCode());
+    }
+
+    @Test
+    public void testEqualsNull() {
+        LinkProperties source = new LinkProperties();
+        LinkProperties target = new LinkProperties();
+
+        assertFalse(source == target);
+        assertLinkPropertiesEqual(source, target);
+    }
+
+    @Test
+    public void testEqualsSameOrder() throws Exception {
+        LinkProperties source = new LinkProperties();
+        source.setInterfaceName(NAME);
+        // set 2 link addresses
+        source.addLinkAddress(LINKADDRV4);
+        source.addLinkAddress(LINKADDRV6);
+        // set 2 dnses
+        source.addDnsServer(DNS1);
+        source.addDnsServer(DNS2);
+        // set 2 gateways
+        source.addRoute(new RouteInfo(GATEWAY1));
+        source.addRoute(new RouteInfo(GATEWAY2));
+        source.setMtu(MTU);
+
+        LinkProperties target = new LinkProperties();
+
+        // All fields are same
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(DNS1);
+        target.addDnsServer(DNS2);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+
+        assertLinkPropertiesEqual(source, target);
+
+        target.clear();
+        // change Interface Name
+        target.setInterfaceName("qmi1");
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(DNS1);
+        target.addDnsServer(DNS2);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+        assertFalse(source.equals(target));
+
+        target.clear();
+        target.setInterfaceName(NAME);
+        // change link addresses
+        target.addLinkAddress(new LinkAddress(
+                NetworkUtils.numericToInetAddress("75.208.6.2"), 32));
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(DNS1);
+        target.addDnsServer(DNS2);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+        assertFalse(source.equals(target));
+
+        target.clear();
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        // change dnses
+        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(DNS2);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+        assertFalse(source.equals(target));
+
+        target.clear();
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(DNS1);
+        target.addDnsServer(DNS2);
+        // change gateway
+        target.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress("75.208.8.2")));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+        assertFalse(source.equals(target));
+
+        target.clear();
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(DNS1);
+        target.addDnsServer(DNS2);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        // change mtu
+        target.setMtu(1440);
+        assertFalse(source.equals(target));
+    }
+
+    @Test
+    public void testEqualsDifferentOrder() throws Exception {
+        LinkProperties source = new LinkProperties();
+        source.setInterfaceName(NAME);
+        // set 2 link addresses
+        source.addLinkAddress(LINKADDRV4);
+        source.addLinkAddress(LINKADDRV6);
+        // set 2 dnses
+        source.addDnsServer(DNS1);
+        source.addDnsServer(DNS2);
+        // set 2 gateways
+        source.addRoute(new RouteInfo(GATEWAY1));
+        source.addRoute(new RouteInfo(GATEWAY2));
+        source.setMtu(MTU);
+
+        LinkProperties target = new LinkProperties();
+        // Exchange order
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV6);
+        target.addLinkAddress(LINKADDRV4);
+        target.addDnsServer(DNS2);
+        target.addDnsServer(DNS1);
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.setMtu(MTU);
+
+        assertLinkPropertiesEqual(source, target);
+    }
+
+    @Test
+    public void testEqualsDuplicated() throws Exception {
+        LinkProperties source = new LinkProperties();
+        // set 3 link addresses, eg, [A, A, B]
+        source.addLinkAddress(LINKADDRV4);
+        source.addLinkAddress(LINKADDRV4);
+        source.addLinkAddress(LINKADDRV6);
+
+        LinkProperties target = new LinkProperties();
+        // set 3 link addresses, eg, [A, B, B]
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addLinkAddress(LINKADDRV6);
+
+        assertLinkPropertiesEqual(source, target);
+    }
+
+    private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) {
+        for (RouteInfo r : lp.getRoutes()) {
+            assertEquals(iface, r.getInterface());
+        }
+    }
+
+    @Test
+    public void testRouteInterfaces() {
+        LinkAddress prefix = new LinkAddress(
+            NetworkUtils.numericToInetAddress("2001:db8::"), 32);
+        InetAddress address = ADDRV6;
+
+        // Add a route with no interface to a LinkProperties with no interface. No errors.
+        LinkProperties lp = new LinkProperties();
+        RouteInfo r = new RouteInfo(prefix, address, null);
+        assertTrue(lp.addRoute(r));
+        assertEquals(1, lp.getRoutes().size());
+        assertAllRoutesHaveInterface(null, lp);
+
+        // Adding the same route twice has no effect.
+        assertFalse(lp.addRoute(r));
+        assertEquals(1, lp.getRoutes().size());
+
+        // Add a route with an interface. Expect an exception.
+        r = new RouteInfo(prefix, address, "wlan0");
+        try {
+          lp.addRoute(r);
+          fail("Adding wlan0 route to LP with no interface, expect exception");
+        } catch (IllegalArgumentException expected) {}
+
+        // Change the interface name. All the routes should change their interface name too.
+        lp.setInterfaceName("rmnet0");
+        assertAllRoutesHaveInterface("rmnet0", lp);
+
+        // Now add a route with the wrong interface. This causes an exception too.
+        try {
+          lp.addRoute(r);
+          fail("Adding wlan0 route to rmnet0 LP, expect exception");
+        } catch (IllegalArgumentException expected) {}
+
+        // If the interface name matches, the route is added.
+        r = new RouteInfo(prefix, null, "wlan0");
+        lp.setInterfaceName("wlan0");
+        lp.addRoute(r);
+        assertEquals(2, lp.getRoutes().size());
+        assertAllRoutesHaveInterface("wlan0", lp);
+
+        // Routes with null interfaces are converted to wlan0.
+        r = RouteInfo.makeHostRoute(ADDRV6, null);
+        lp.addRoute(r);
+        assertEquals(3, lp.getRoutes().size());
+        assertAllRoutesHaveInterface("wlan0", lp);
+
+        // Check comparisons work.
+        LinkProperties lp2 = new LinkProperties(lp);
+        assertAllRoutesHaveInterface("wlan0", lp);
+        assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+        assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+
+        lp2.setInterfaceName("p2p0");
+        assertAllRoutesHaveInterface("p2p0", lp2);
+        assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+        assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+    }
+
+    @Test
+    public void testStackedInterfaces() {
+        LinkProperties rmnet0 = new LinkProperties();
+        rmnet0.setInterfaceName("rmnet0");
+        rmnet0.addLinkAddress(LINKADDRV6);
+
+        LinkProperties clat4 = new LinkProperties();
+        clat4.setInterfaceName("clat4");
+        clat4.addLinkAddress(LINKADDRV4);
+
+        assertEquals(0, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(1, rmnet0.getAllAddresses().size());
+        assertEquals(1, rmnet0.getAllLinkAddresses().size());
+
+        rmnet0.addStackedLink(clat4);
+        assertEquals(1, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(2, rmnet0.getAllAddresses().size());
+        assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
+        rmnet0.addStackedLink(clat4);
+        assertEquals(1, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(2, rmnet0.getAllAddresses().size());
+        assertEquals(2, rmnet0.getAllLinkAddresses().size());
+
+        assertEquals(0, clat4.getStackedLinks().size());
+
+        // Modify an item in the returned collection to see what happens.
+        for (LinkProperties link : rmnet0.getStackedLinks()) {
+            if (link.getInterfaceName().equals("clat4")) {
+               link.setInterfaceName("newname");
+            }
+        }
+        for (LinkProperties link : rmnet0.getStackedLinks()) {
+            assertFalse("newname".equals(link.getInterfaceName()));
+        }
+
+        assertTrue(rmnet0.removeStackedLink("clat4"));
+        assertEquals(0, rmnet0.getStackedLinks().size());
+        assertEquals(1, rmnet0.getAddresses().size());
+        assertEquals(1, rmnet0.getLinkAddresses().size());
+        assertEquals(1, rmnet0.getAllAddresses().size());
+        assertEquals(1, rmnet0.getAllLinkAddresses().size());
+
+        assertFalse(rmnet0.removeStackedLink("clat4"));
+    }
+
+    private LinkAddress getFirstLinkAddress(LinkProperties lp) {
+        return lp.getLinkAddresses().iterator().next();
+    }
+
+    @Test
+    public void testAddressMethods() {
+        LinkProperties lp = new LinkProperties();
+
+        // No addresses.
+        assertFalse(lp.hasIPv4Address());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        // Addresses on stacked links don't count.
+        LinkProperties stacked = new LinkProperties();
+        stacked.setInterfaceName("stacked");
+        lp.addStackedLink(stacked);
+        stacked.addLinkAddress(LINKADDRV4);
+        stacked.addLinkAddress(LINKADDRV6);
+        assertTrue(stacked.hasIPv4Address());
+        assertTrue(stacked.hasGlobalIPv6Address());
+        assertFalse(lp.hasIPv4Address());
+        assertFalse(lp.hasGlobalIPv6Address());
+        lp.removeStackedLink("stacked");
+        assertFalse(lp.hasIPv4Address());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        // Addresses on the base link.
+        // Check the return values of hasIPvXAddress and ensure the add/remove methods return true
+        // iff something changes.
+        assertEquals(0, lp.getLinkAddresses().size());
+        assertTrue(lp.addLinkAddress(LINKADDRV6));
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertFalse(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.removeLinkAddress(LINKADDRV6));
+        assertEquals(0, lp.getLinkAddresses().size());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV6LINKLOCAL));
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV4));
+        assertEquals(2, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertFalse(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.addLinkAddress(LINKADDRV6));
+        assertEquals(3, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
+
+        assertTrue(lp.removeLinkAddress(LINKADDRV6LINKLOCAL));
+        assertEquals(2, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.hasGlobalIPv6Address());
+
+        // Adding an address twice has no effect.
+        // Removing an address that's not present has no effect.
+        assertFalse(lp.addLinkAddress(LINKADDRV4));
+        assertEquals(2, lp.getLinkAddresses().size());
+        assertTrue(lp.hasIPv4Address());
+        assertTrue(lp.removeLinkAddress(LINKADDRV4));
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertFalse(lp.hasIPv4Address());
+        assertFalse(lp.removeLinkAddress(LINKADDRV4));
+        assertEquals(1, lp.getLinkAddresses().size());
+
+        // Adding an address that's already present but with different properties causes the
+        // existing address to be updated and returns true.
+        // Start with only LINKADDRV6.
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertEquals(LINKADDRV6, getFirstLinkAddress(lp));
+
+        // Create a LinkAddress object for the same address, but with different flags.
+        LinkAddress deprecated = new LinkAddress(ADDRV6, 128,
+                OsConstants.IFA_F_DEPRECATED, OsConstants.RT_SCOPE_UNIVERSE);
+        assertTrue(deprecated.isSameAddressAs(LINKADDRV6));
+        assertFalse(deprecated.equals(LINKADDRV6));
+
+        // Check that adding it updates the existing address instead of adding a new one.
+        assertTrue(lp.addLinkAddress(deprecated));
+        assertEquals(1, lp.getLinkAddresses().size());
+        assertEquals(deprecated, getFirstLinkAddress(lp));
+        assertFalse(LINKADDRV6.equals(getFirstLinkAddress(lp)));
+
+        // Removing LINKADDRV6 removes deprecated, because removing addresses ignores properties.
+        assertTrue(lp.removeLinkAddress(LINKADDRV6));
+        assertEquals(0, lp.getLinkAddresses().size());
+    }
+
+    @Test
+    public void testSetLinkAddresses() {
+        LinkProperties lp = new LinkProperties();
+        lp.addLinkAddress(LINKADDRV4);
+        lp.addLinkAddress(LINKADDRV6);
+
+        LinkProperties lp2 = new LinkProperties();
+        lp2.addLinkAddress(LINKADDRV6);
+
+        assertFalse(lp.equals(lp2));
+
+        lp2.setLinkAddresses(lp.getLinkAddresses());
+        assertTrue(lp.equals(lp));
+    }
+
+    @Test
+    public void testIsProvisioned() {
+        LinkProperties lp4 = new LinkProperties();
+        assertFalse("v4only:empty", lp4.isProvisioned());
+        lp4.addLinkAddress(LINKADDRV4);
+        assertFalse("v4only:addr-only", lp4.isProvisioned());
+        lp4.addDnsServer(DNS1);
+        assertFalse("v4only:addr+dns", lp4.isProvisioned());
+        lp4.addRoute(new RouteInfo(GATEWAY1));
+        assertTrue("v4only:addr+dns+route", lp4.isProvisioned());
+        assertTrue("v4only:addr+dns+route", lp4.isIPv4Provisioned());
+        assertFalse("v4only:addr+dns+route", lp4.isIPv6Provisioned());
+
+        LinkProperties lp6 = new LinkProperties();
+        assertFalse("v6only:empty", lp6.isProvisioned());
+        lp6.addLinkAddress(LINKADDRV6LINKLOCAL);
+        assertFalse("v6only:fe80-only", lp6.isProvisioned());
+        lp6.addDnsServer(DNS6);
+        assertFalse("v6only:fe80+dns", lp6.isProvisioned());
+        lp6.addRoute(new RouteInfo(GATEWAY61));
+        assertFalse("v6only:fe80+dns+route", lp6.isProvisioned());
+        lp6.addLinkAddress(LINKADDRV6);
+        assertTrue("v6only:fe80+global+dns+route", lp6.isIPv6Provisioned());
+        assertTrue("v6only:fe80+global+dns+route", lp6.isProvisioned());
+        lp6.removeLinkAddress(LINKADDRV6LINKLOCAL);
+        assertFalse("v6only:global+dns+route", lp6.isIPv4Provisioned());
+        assertTrue("v6only:global+dns+route", lp6.isIPv6Provisioned());
+        assertTrue("v6only:global+dns+route", lp6.isProvisioned());
+
+        LinkProperties lp46 = new LinkProperties();
+        lp46.addLinkAddress(LINKADDRV4);
+        lp46.addLinkAddress(LINKADDRV6);
+        lp46.addDnsServer(DNS1);
+        lp46.addDnsServer(DNS6);
+        assertFalse("dualstack:missing-routes", lp46.isProvisioned());
+        lp46.addRoute(new RouteInfo(GATEWAY1));
+        assertTrue("dualstack:v4-provisioned", lp46.isIPv4Provisioned());
+        assertFalse("dualstack:v4-provisioned", lp46.isIPv6Provisioned());
+        assertTrue("dualstack:v4-provisioned", lp46.isProvisioned());
+        lp46.addRoute(new RouteInfo(GATEWAY61));
+        assertTrue("dualstack:both-provisioned", lp46.isIPv4Provisioned());
+        assertTrue("dualstack:both-provisioned", lp46.isIPv6Provisioned());
+        assertTrue("dualstack:both-provisioned", lp46.isProvisioned());
+
+        // A link with an IPv6 address and default route, but IPv4 DNS server.
+        LinkProperties mixed = new LinkProperties();
+        mixed.addLinkAddress(LINKADDRV6);
+        mixed.addDnsServer(DNS1);
+        mixed.addRoute(new RouteInfo(GATEWAY61));
+        assertFalse("mixed:addr6+route6+dns4", mixed.isIPv4Provisioned());
+        assertFalse("mixed:addr6+route6+dns4", mixed.isIPv6Provisioned());
+        assertFalse("mixed:addr6+route6+dns4", mixed.isProvisioned());
+    }
+
+    @Test
+    public void testCompareProvisioning() {
+        LinkProperties v4lp = new LinkProperties();
+        v4lp.addLinkAddress(LINKADDRV4);
+        v4lp.addRoute(new RouteInfo(GATEWAY1));
+        v4lp.addDnsServer(DNS1);
+        assertTrue(v4lp.isProvisioned());
+
+        LinkProperties v4r = new LinkProperties(v4lp);
+        v4r.removeDnsServer(DNS1);
+        assertFalse(v4r.isProvisioned());
+
+        assertEquals(ProvisioningChange.STILL_NOT_PROVISIONED,
+                LinkProperties.compareProvisioning(v4r, v4r));
+        assertEquals(ProvisioningChange.LOST_PROVISIONING,
+                LinkProperties.compareProvisioning(v4lp, v4r));
+        assertEquals(ProvisioningChange.GAINED_PROVISIONING,
+                LinkProperties.compareProvisioning(v4r, v4lp));
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v4lp, v4lp));
+
+        // Check that losing IPv4 provisioning on a dualstack network is
+        // seen as a total loss of provisioning.
+        LinkProperties v6lp = new LinkProperties();
+        v6lp.addLinkAddress(LINKADDRV6);
+        v6lp.addRoute(new RouteInfo(GATEWAY61));
+        v6lp.addDnsServer(DNS6);
+        assertFalse(v6lp.isIPv4Provisioned());
+        assertTrue(v6lp.isIPv6Provisioned());
+        assertTrue(v6lp.isProvisioned());
+
+        LinkProperties v46lp = new LinkProperties(v6lp);
+        v46lp.addLinkAddress(LINKADDRV4);
+        v46lp.addRoute(new RouteInfo(GATEWAY1));
+        v46lp.addDnsServer(DNS1);
+        assertTrue(v46lp.isIPv4Provisioned());
+        assertTrue(v46lp.isIPv6Provisioned());
+        assertTrue(v46lp.isProvisioned());
+
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v4lp, v46lp));
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v6lp, v46lp));
+        assertEquals(ProvisioningChange.LOST_PROVISIONING,
+                LinkProperties.compareProvisioning(v46lp, v6lp));
+        assertEquals(ProvisioningChange.LOST_PROVISIONING,
+                LinkProperties.compareProvisioning(v46lp, v4lp));
+
+        // Check that losing and gaining a secondary router does not change
+        // the provisioning status.
+        LinkProperties v6lp2 = new LinkProperties(v6lp);
+        v6lp2.addRoute(new RouteInfo(GATEWAY62));
+        assertTrue(v6lp2.isProvisioned());
+
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v6lp2, v6lp));
+        assertEquals(ProvisioningChange.STILL_PROVISIONED,
+                LinkProperties.compareProvisioning(v6lp, v6lp2));
+    }
+
+    @Test
+    public void testIsReachable() {
+        final LinkProperties v4lp = new LinkProperties();
+        assertFalse(v4lp.isReachable(DNS1));
+        assertFalse(v4lp.isReachable(DNS2));
+
+        // Add an on-link route, making the on-link DNS server reachable,
+        // but there is still no IPv4 address.
+        assertTrue(v4lp.addRoute(new RouteInfo(
+                new IpPrefix(NetworkUtils.numericToInetAddress("75.208.0.0"), 16))));
+        assertFalse(v4lp.isReachable(DNS1));
+        assertFalse(v4lp.isReachable(DNS2));
+
+        // Adding an IPv4 address (right now, any IPv4 address) means we use
+        // the routes to compute likely reachability.
+        assertTrue(v4lp.addLinkAddress(new LinkAddress(ADDRV4, 16)));
+        assertTrue(v4lp.isReachable(DNS1));
+        assertFalse(v4lp.isReachable(DNS2));
+
+        // Adding a default route makes the off-link DNS server reachable.
+        assertTrue(v4lp.addRoute(new RouteInfo(GATEWAY1)));
+        assertTrue(v4lp.isReachable(DNS1));
+        assertTrue(v4lp.isReachable(DNS2));
+
+        final LinkProperties v6lp = new LinkProperties();
+        final InetAddress kLinkLocalDns = NetworkUtils.numericToInetAddress("fe80::6:1");
+        final InetAddress kLinkLocalDnsWithScope = NetworkUtils.numericToInetAddress("fe80::6:2%43");
+        final InetAddress kOnLinkDns = NetworkUtils.numericToInetAddress("2001:db8:85a3::53");
+        assertFalse(v6lp.isReachable(kLinkLocalDns));
+        assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
+        assertFalse(v6lp.isReachable(kOnLinkDns));
+        assertFalse(v6lp.isReachable(DNS6));
+
+        // Add a link-local route, making the link-local DNS servers reachable. Because
+        // we assume the presence of an IPv6 link-local address, link-local DNS servers
+        // are considered reachable, but only those with a non-zero scope identifier.
+        assertTrue(v6lp.addRoute(new RouteInfo(
+                new IpPrefix(NetworkUtils.numericToInetAddress("fe80::"), 64))));
+        assertFalse(v6lp.isReachable(kLinkLocalDns));
+        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+        assertFalse(v6lp.isReachable(kOnLinkDns));
+        assertFalse(v6lp.isReachable(DNS6));
+
+        // Add a link-local address--nothing changes.
+        assertTrue(v6lp.addLinkAddress(LINKADDRV6LINKLOCAL));
+        assertFalse(v6lp.isReachable(kLinkLocalDns));
+        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+        assertFalse(v6lp.isReachable(kOnLinkDns));
+        assertFalse(v6lp.isReachable(DNS6));
+
+        // Add a global route on link, but no global address yet. DNS servers reachable
+        // via a route that doesn't require a gateway: give them the benefit of the
+        // doubt and hope the link-local source address suffices for communication.
+        assertTrue(v6lp.addRoute(new RouteInfo(
+                new IpPrefix(NetworkUtils.numericToInetAddress("2001:db8:85a3::"), 64))));
+        assertFalse(v6lp.isReachable(kLinkLocalDns));
+        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+        assertTrue(v6lp.isReachable(kOnLinkDns));
+        assertFalse(v6lp.isReachable(DNS6));
+
+        // Add a global address; the on-link global address DNS server is (still)
+        // presumed reachable.
+        assertTrue(v6lp.addLinkAddress(new LinkAddress(ADDRV6, 64)));
+        assertFalse(v6lp.isReachable(kLinkLocalDns));
+        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+        assertTrue(v6lp.isReachable(kOnLinkDns));
+        assertFalse(v6lp.isReachable(DNS6));
+
+        // Adding a default route makes the off-link DNS server reachable.
+        assertTrue(v6lp.addRoute(new RouteInfo(GATEWAY62)));
+        assertFalse(v6lp.isReachable(kLinkLocalDns));
+        assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+        assertTrue(v6lp.isReachable(kOnLinkDns));
+        assertTrue(v6lp.isReachable(DNS6));
+
+        // Check isReachable on stacked links. This requires that the source IP address be assigned
+        // on the interface returned by the route lookup.
+        LinkProperties stacked = new LinkProperties();
+
+        // Can't add a stacked link without an interface name.
+        stacked.setInterfaceName("v4-test0");
+        v6lp.addStackedLink(stacked);
+
+        InetAddress stackedAddress = Address("192.0.0.4");
+        LinkAddress stackedLinkAddress = new LinkAddress(stackedAddress, 32);
+        assertFalse(v6lp.isReachable(stackedAddress));
+        stacked.addLinkAddress(stackedLinkAddress);
+        assertFalse(v6lp.isReachable(stackedAddress));
+        stacked.addRoute(new RouteInfo(stackedLinkAddress));
+        assertTrue(stacked.isReachable(stackedAddress));
+        assertTrue(v6lp.isReachable(stackedAddress));
+
+        assertFalse(v6lp.isReachable(DNS1));
+        stacked.addRoute(new RouteInfo((IpPrefix) null, stackedAddress));
+        assertTrue(v6lp.isReachable(DNS1));
+    }
+
+    @Test
+    public void testLinkPropertiesEnsureDirectlyConnectedRoutes() {
+        // IPv4 case: no route added initially
+        LinkProperties rmnet0 = new LinkProperties();
+        rmnet0.setInterfaceName("rmnet0");
+        rmnet0.addLinkAddress(new LinkAddress("10.0.0.2/8"));
+        RouteInfo directRoute0 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
+                rmnet0.getInterfaceName());
+
+        // Since no routes is added explicitly, getAllRoutes() should return empty.
+        assertTrue(rmnet0.getAllRoutes().isEmpty());
+        rmnet0.ensureDirectlyConnectedRoutes();
+        // ensureDirectlyConnectedRoutes() should have added the missing local route.
+        assertEqualRoutes(Collections.singletonList(directRoute0), rmnet0.getAllRoutes());
+
+        // IPv4 case: both direct and default routes added initially
+        LinkProperties rmnet1 = new LinkProperties();
+        rmnet1.setInterfaceName("rmnet1");
+        rmnet1.addLinkAddress(new LinkAddress("10.0.0.3/8"));
+        RouteInfo defaultRoute1 = new RouteInfo((IpPrefix) null,
+                NetworkUtils.numericToInetAddress("10.0.0.1"), rmnet1.getInterfaceName());
+        RouteInfo directRoute1 = new RouteInfo(new IpPrefix("10.0.0.0/8"), null,
+                rmnet1.getInterfaceName());
+        rmnet1.addRoute(defaultRoute1);
+        rmnet1.addRoute(directRoute1);
+
+        // Check added routes
+        assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
+        // ensureDirectlyConnectedRoutes() shouldn't change the routes since direct connected
+        // route is already part of the configuration.
+        rmnet1.ensureDirectlyConnectedRoutes();
+        assertEqualRoutes(Arrays.asList(defaultRoute1, directRoute1), rmnet1.getAllRoutes());
+
+        // IPv6 case: only default routes added initially
+        LinkProperties rmnet2 = new LinkProperties();
+        rmnet2.setInterfaceName("rmnet2");
+        rmnet2.addLinkAddress(new LinkAddress("fe80::cafe/64"));
+        rmnet2.addLinkAddress(new LinkAddress("2001:db8::2/64"));
+        RouteInfo defaultRoute2 = new RouteInfo((IpPrefix) null,
+                NetworkUtils.numericToInetAddress("2001:db8::1"), rmnet2.getInterfaceName());
+        RouteInfo directRoute2 = new RouteInfo(new IpPrefix("2001:db8::/64"), null,
+                rmnet2.getInterfaceName());
+        RouteInfo linkLocalRoute2 = new RouteInfo(new IpPrefix("fe80::/64"), null,
+                rmnet2.getInterfaceName());
+        rmnet2.addRoute(defaultRoute2);
+
+        assertEqualRoutes(Arrays.asList(defaultRoute2), rmnet2.getAllRoutes());
+        rmnet2.ensureDirectlyConnectedRoutes();
+        assertEqualRoutes(Arrays.asList(defaultRoute2, directRoute2, linkLocalRoute2),
+                rmnet2.getAllRoutes());
+
+        // Corner case: no interface name
+        LinkProperties rmnet3 = new LinkProperties();
+        rmnet3.addLinkAddress(new LinkAddress("192.168.0.2/24"));
+        RouteInfo directRoute3 = new RouteInfo(new IpPrefix("192.168.0.0/24"), null,
+                rmnet3.getInterfaceName());
+
+        assertTrue(rmnet3.getAllRoutes().isEmpty());
+        rmnet3.ensureDirectlyConnectedRoutes();
+        assertEqualRoutes(Collections.singletonList(directRoute3), rmnet3.getAllRoutes());
+
+    }
+
+    @Test
+    public void testCompareResult() {
+        // Either adding or removing items
+        compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
+                Arrays.asList(2, 3, 4), new ArrayList<>());
+        compareResult(Arrays.asList(1, 2), Arrays.asList(3, 2, 1, 4),
+                new ArrayList<>(), Arrays.asList(3, 4));
+
+
+        // adding and removing items at the same time
+        compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(2, 3, 4, 5),
+                Arrays.asList(1), Arrays.asList(5));
+        compareResult(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6),
+                Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6));
+
+        // null cases
+        compareResult(Arrays.asList(1, 2, 3), null, Arrays.asList(1, 2, 3), new ArrayList<>());
+        compareResult(null, Arrays.asList(3, 2, 1), new ArrayList<>(), Arrays.asList(1, 2, 3));
+        compareResult(null, null, new ArrayList<>(), new ArrayList<>());
+    }
+
+    private void assertEqualRoutes(Collection<RouteInfo> expected, Collection<RouteInfo> actual) {
+        Set<RouteInfo> expectedSet = new ArraySet<>(expected);
+        Set<RouteInfo> actualSet = new ArraySet<>(actual);
+        // Duplicated entries in actual routes are considered failures
+        assertEquals(actual.size(), actualSet.size());
+
+        assertEquals(expectedSet, actualSet);
+    }
+
+    private <T> void compareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved,
+            List<T> expectAdded) {
+        CompareResult<T> result = new CompareResult<>(oldItems, newItems);
+        assertEquals(new ArraySet<>(expectAdded), new ArraySet<>(result.added));
+        assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
+    }
+}
diff --git a/tests/net/java/android/net/NetworkStatsHistoryTest.java b/tests/net/java/android/net/NetworkStatsHistoryTest.java
index 1c0c14e..301d04d 100644
--- a/tests/net/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/net/java/android/net/NetworkStatsHistoryTest.java
@@ -32,9 +32,14 @@
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 import static android.text.format.DateUtils.YEAR_IN_MILLIS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
@@ -46,25 +51,31 @@
 import java.io.DataOutputStream;
 import java.util.Random;
 
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class NetworkStatsHistoryTest extends AndroidTestCase {
+public class NetworkStatsHistoryTest {
     private static final String TAG = "NetworkStatsHistoryTest";
 
     private static final long TEST_START = 1194220800000L;
 
     private NetworkStatsHistory stats;
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         if (stats != null) {
             assertConsistent(stats);
         }
     }
 
+    @Test
     public void testReadOriginalVersion() throws Exception {
-        final DataInputStream in = new DataInputStream(
-                getContext().getResources().openRawResource(R.raw.history_v1));
+        final Context context = InstrumentationRegistry.getContext();
+        final DataInputStream in =
+                new DataInputStream(context.getResources().openRawResource(R.raw.history_v1));
 
         NetworkStatsHistory.Entry entry = null;
         try {
@@ -88,6 +99,7 @@
         }
     }
 
+    @Test
     public void testRecordSingleBucket() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -100,6 +112,7 @@
         assertValues(stats, 0, SECOND_IN_MILLIS, 1024L, 10L, 2048L, 20L, 2L);
     }
 
+    @Test
     public void testRecordEqualBuckets() throws Exception {
         final long bucketDuration = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(bucketDuration);
@@ -114,6 +127,7 @@
         assertValues(stats, 1, HOUR_IN_MILLIS / 2, 512L, 5L, 64L, 1L, 1L);
     }
 
+    @Test
     public void testRecordTouchingBuckets() throws Exception {
         final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -134,6 +148,7 @@
         assertValues(stats, 2, 4 * MINUTE_IN_MILLIS, 200L, 400L, 1000L, 2000L, 20L);
     }
 
+    @Test
     public void testRecordGapBuckets() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -165,6 +180,7 @@
         assertValues(stats, 3, SECOND_IN_MILLIS, 64L, 1L, 512L, 8L, 2L);
     }
 
+    @Test
     public void testRecordOverlapBuckets() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -182,6 +198,7 @@
         assertValues(stats, 1, (HOUR_IN_MILLIS / 2), 512L, 5L, 512L, 5L, 5L);
     }
 
+    @Test
     public void testRecordEntireGapIdentical() throws Exception {
         // first, create two separate histories far apart
         final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
@@ -206,6 +223,7 @@
         assertValues(stats, 3, 500L, 250L);
     }
 
+    @Test
     public void testRecordEntireOverlapVaryingBuckets() throws Exception {
         // create history just over hour bucket boundary
         final NetworkStatsHistory stats1 = new NetworkStatsHistory(HOUR_IN_MILLIS);
@@ -247,6 +265,7 @@
         assertValues(stats, 3, 150L, 150L);
     }
 
+    @Test
     public void testRemove() throws Exception {
         stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
 
@@ -280,6 +299,7 @@
         assertEquals(0, stats.size());
     }
 
+    @Test
     public void testTotalData() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -304,7 +324,7 @@
 
     }
 
-    @Suppress
+    @Test
     public void testFuzzing() throws Exception {
         try {
             // fuzzing with random events, looking for crashes
@@ -341,6 +361,7 @@
         return value < 0 ? -value : value;
     }
 
+    @Test
     public void testIgnoreFields() throws Exception {
         final NetworkStatsHistory history = new NetworkStatsHistory(
                 MINUTE_IN_MILLIS, 0, FIELD_RX_BYTES | FIELD_TX_BYTES);
@@ -353,6 +374,7 @@
         assertFullValues(history, UNKNOWN, 1026L, UNKNOWN, 2050L, UNKNOWN, UNKNOWN);
     }
 
+    @Test
     public void testIgnoreFieldsRecordIn() throws Exception {
         final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
         final NetworkStatsHistory partial = new NetworkStatsHistory(
@@ -365,6 +387,7 @@
         assertFullValues(partial, UNKNOWN, UNKNOWN, 10L, UNKNOWN, UNKNOWN, 4L);
     }
 
+    @Test
     public void testIgnoreFieldsRecordOut() throws Exception {
         final NetworkStatsHistory full = new NetworkStatsHistory(MINUTE_IN_MILLIS, 0, FIELD_ALL);
         final NetworkStatsHistory partial = new NetworkStatsHistory(
@@ -377,6 +400,7 @@
         assertFullValues(full, MINUTE_IN_MILLIS, 0L, 10L, 0L, 0L, 4L);
     }
 
+    @Test
     public void testSerialize() throws Exception {
         final NetworkStatsHistory before = new NetworkStatsHistory(MINUTE_IN_MILLIS, 40, FIELD_ALL);
         before.recordData(0, 4 * MINUTE_IN_MILLIS,
@@ -396,6 +420,7 @@
         assertFullValues(after, 5 * MINUTE_IN_MILLIS, 1034L, 30L, 2078L, 60L, 54L);
     }
 
+    @Test
     public void testVarLong() throws Exception {
         assertEquals(0L, performVarLong(0L));
         assertEquals(-1L, performVarLong(-1L));
@@ -409,6 +434,7 @@
         assertEquals(Long.MAX_VALUE - 40, performVarLong(Long.MAX_VALUE - 40));
     }
 
+    @Test
     public void testIndexBeforeAfter() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -451,6 +477,7 @@
         assertIndexBeforeAfter(stats, 4, 4, Long.MAX_VALUE);
     }
 
+    @Test
     public void testIntersects() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
         stats = new NetworkStatsHistory(BUCKET_SIZE);
@@ -485,6 +512,7 @@
         assertTrue(stats.intersects(Long.MIN_VALUE, TEST_START + 1));
     }
 
+    @Test
     public void testSetValues() throws Exception {
         stats = new NetworkStatsHistory(HOUR_IN_MILLIS);
         stats.recordData(TEST_START, TEST_START + 1,
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index eb85eb4..25289ba 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -30,23 +30,30 @@
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 
 import com.google.android.collect.Sets;
 
-import junit.framework.TestCase;
-
 import java.util.HashSet;
 
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class NetworkStatsTest extends TestCase {
+public class NetworkStatsTest {
 
     private static final String TEST_IFACE = "test0";
     private static final String TEST_IFACE2 = "test2";
     private static final int TEST_UID = 1001;
     private static final long TEST_START = 1194220800000L;
 
+    @Test
     public void testFindIndex() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 5)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
@@ -74,6 +81,7 @@
                 ROAMING_NO));
     }
 
+    @Test
     public void testFindIndexHinted() {
         final NetworkStats stats = new NetworkStats(TEST_START, 3)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 1024L,
@@ -116,6 +124,7 @@
         }
     }
 
+    @Test
     public void testAddEntryGrow() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 4);
 
@@ -168,6 +177,7 @@
                 ROAMING_YES, 7L, 70L, 5L, 50L, 11);
     }
 
+    @Test
     public void testCombineExisting() throws Exception {
         final NetworkStats stats = new NetworkStats(TEST_START, 10);
 
@@ -190,6 +200,7 @@
                 256L, 2L, 256L, 2L, 6);
     }
 
+    @Test
     public void testSubtractIdenticalData() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
@@ -208,6 +219,7 @@
                 0L, 0L, 0L, 0);
     }
 
+    @Test
     public void testSubtractIdenticalRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
@@ -226,6 +238,7 @@
                 1L, 4L, 1L, 8);
     }
 
+    @Test
     public void testSubtractNewRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
@@ -247,6 +260,7 @@
                 1024L, 8L, 1024L, 8L, 20);
     }
 
+    @Test
     public void testSubtractMissingRows() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 2)
                 .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
@@ -264,6 +278,7 @@
         assertEquals(4L, result.getTotalBytes());
     }
 
+    @Test
     public void testTotalBytes() throws Exception {
         final NetworkStats iface = new NetworkStats(TEST_START, 2)
                 .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
@@ -304,6 +319,7 @@
         assertEquals(96L, uidRoaming.getTotalBytes());
     }
 
+    @Test
     public void testGroupedByIfaceEmpty() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3);
         final NetworkStats grouped = uidStats.groupedByIface();
@@ -312,6 +328,7 @@
         assertEquals(0, grouped.size());
     }
 
+    @Test
     public void testGroupedByIfaceAll() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
                 .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L, 0L,
@@ -329,6 +346,7 @@
                 384L, 24L, 0L, 6L, 0L);
     }
 
+    @Test
     public void testGroupedByIface() throws Exception {
         final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
@@ -357,6 +375,7 @@
                 1024L, 64L, 0L, 0L, 0L);
     }
 
+    @Test
     public void testAddAllValues() {
         final NetworkStats first = new NetworkStats(TEST_START, 5)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO, 32L, 0L,
@@ -387,6 +406,7 @@
                 32L, 0L, 0L, 0L, 0L);
     }
 
+    @Test
     public void testGetTotal() {
         final NetworkStats stats = new NetworkStats(TEST_START, 7)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, 128L, 8L,
@@ -415,6 +435,7 @@
         assertValues(stats.getTotal(null, ifaces), 1024L, 64L, 0L, 0L, 0L);
     }
 
+    @Test
     public void testWithoutUid() throws Exception {
         final NetworkStats before = new NetworkStats(TEST_START, 3)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
@@ -433,6 +454,7 @@
                 8L, 0L, 0L, 0L);
     }
 
+    @Test
     public void testClone() throws Exception {
         final NetworkStats original = new NetworkStats(TEST_START, 5)
                 .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
@@ -449,6 +471,7 @@
         assertEquals(128L + 512L, clone.getTotalBytes());
     }
 
+    @Test
     public void testAddWhenEmpty() throws Exception {
         final NetworkStats red = new NetworkStats(TEST_START, -1);
         final NetworkStats blue = new NetworkStats(TEST_START, 5)
@@ -459,6 +482,7 @@
         red.combineAllValues(blue);
     }
 
+    @Test
     public void testMigrateTun() throws Exception {
         final int tunUid = 10030;
         final String tunIface = "tun0";
@@ -556,6 +580,7 @@
     // interface by the vpn app before it's sent out of the underlying interface. The VPN app should
     // not be charged for the echoed data but it should still be charged for any extra data it sends
     // via the underlying interface.
+    @Test
     public void testMigrateTun_VpnAsLoopback() {
         final int tunUid = 10030;
         final String tunIface = "tun0";
diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java
new file mode 100644
index 0000000..bacf986
--- /dev/null
+++ b/tests/net/java/android/net/NetworkTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.Network;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Inet6Address;
+import java.net.SocketException;
+import java.util.Objects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkTest {
+    final Network mNetwork = new Network(99);
+
+    @Test
+    public void testBindSocketOfInvalidFdThrows() throws Exception {
+
+        final FileDescriptor fd = new FileDescriptor();
+        assertFalse(fd.valid());
+
+        try {
+            mNetwork.bindSocket(fd);
+            fail("SocketException not thrown");
+        } catch (SocketException expected) {}
+    }
+
+    @Test
+    public void testBindSocketOfNonSocketFdThrows() throws Exception {
+        final File devNull = new File("/dev/null");
+        assertTrue(devNull.canRead());
+
+        final FileInputStream fis = new FileInputStream(devNull);
+        assertTrue(null != fis.getFD());
+        assertTrue(fis.getFD().valid());
+
+        try {
+            mNetwork.bindSocket(fis.getFD());
+            fail("SocketException not thrown");
+        } catch (SocketException expected) {}
+    }
+
+    @Test
+    public void testBindSocketOfConnectedDatagramSocketThrows() throws Exception {
+        final DatagramSocket mDgramSocket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY);
+        mDgramSocket.connect((InetAddress) Inet6Address.LOOPBACK, 53);
+        assertTrue(mDgramSocket.isConnected());
+
+        try {
+            mNetwork.bindSocket(mDgramSocket);
+            fail("SocketException not thrown");
+        } catch (SocketException expected) {}
+    }
+
+    @Test
+    public void testBindSocketOfLocalSocketThrows() throws Exception {
+        final LocalSocket mLocalClient = new LocalSocket();
+        mLocalClient.bind(new LocalSocketAddress("testClient"));
+        assertTrue(mLocalClient.getFileDescriptor().valid());
+
+        try {
+            mNetwork.bindSocket(mLocalClient.getFileDescriptor());
+            fail("SocketException not thrown");
+        } catch (SocketException expected) {}
+
+        final LocalServerSocket mLocalServer = new LocalServerSocket("testServer");
+        mLocalClient.connect(mLocalServer.getLocalSocketAddress());
+        assertTrue(mLocalClient.isConnected());
+
+        try {
+            mNetwork.bindSocket(mLocalClient.getFileDescriptor());
+            fail("SocketException not thrown");
+        } catch (SocketException expected) {}
+    }
+
+    @Test
+    public void testZeroIsObviousForDebugging() {
+        Network zero = new Network(0);
+        assertEquals(0, zero.hashCode());
+        assertEquals(0, zero.getNetworkHandle());
+        assertEquals("0", zero.toString());
+    }
+
+    @Test
+    public void testGetNetworkHandle() {
+        Network one = new Network(1);
+        Network two = new Network(2);
+        Network three = new Network(3);
+
+        // None of the hashcodes are zero.
+        assertNotEqual(0, one.hashCode());
+        assertNotEqual(0, two.hashCode());
+        assertNotEqual(0, three.hashCode());
+
+        // All the hashcodes are distinct.
+        assertNotEqual(one.hashCode(), two.hashCode());
+        assertNotEqual(one.hashCode(), three.hashCode());
+        assertNotEqual(two.hashCode(), three.hashCode());
+
+        // None of the handles are zero.
+        assertNotEqual(0, one.getNetworkHandle());
+        assertNotEqual(0, two.getNetworkHandle());
+        assertNotEqual(0, three.getNetworkHandle());
+
+        // All the handles are distinct.
+        assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle());
+        assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle());
+        assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle());
+
+        // The handles are not equal to the hashcodes.
+        assertNotEqual(one.hashCode(), one.getNetworkHandle());
+        assertNotEqual(two.hashCode(), two.getNetworkHandle());
+        assertNotEqual(three.hashCode(), three.getNetworkHandle());
+
+        // Adjust as necessary to test an implementation's specific constants.
+        // When running with runtest, "adb logcat -s TestRunner" can be useful.
+        assertEquals(4311403230L, one.getNetworkHandle());
+        assertEquals(8606370526L, two.getNetworkHandle());
+        assertEquals(12901337822L, three.getNetworkHandle());
+    }
+
+    private static <T> void assertNotEqual(T t1, T t2) {
+        assertFalse(Objects.equals(t1, t2));
+    }
+}
diff --git a/tests/net/java/android/net/StaticIpConfigurationTest.java b/tests/net/java/android/net/StaticIpConfigurationTest.java
new file mode 100644
index 0000000..5bb5734
--- /dev/null
+++ b/tests/net/java/android/net/StaticIpConfigurationTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.Objects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StaticIpConfigurationTest {
+
+    private static final String ADDRSTR = "192.0.2.2/25";
+    private static final LinkAddress ADDR = new LinkAddress(ADDRSTR);
+    private static final InetAddress GATEWAY = IpAddress("192.0.2.1");
+    private static final InetAddress OFFLINKGATEWAY = IpAddress("192.0.2.129");
+    private static final InetAddress DNS1 = IpAddress("8.8.8.8");
+    private static final InetAddress DNS2 = IpAddress("8.8.4.4");
+    private static final InetAddress DNS3 = IpAddress("4.2.2.2");
+    private static final String IFACE = "eth0";
+
+    private static InetAddress IpAddress(String addr) {
+        return InetAddress.parseNumericAddress(addr);
+    }
+
+    private void checkEmpty(StaticIpConfiguration s) {
+        assertNull(s.ipAddress);
+        assertNull(s.gateway);
+        assertNull(s.domains);
+        assertEquals(0, s.dnsServers.size());
+    }
+
+    private static <T> void assertNotEquals(T t1, T t2) {
+        assertFalse(Objects.equals(t1, t2));
+    }
+
+    private StaticIpConfiguration makeTestObject() {
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        s.ipAddress = ADDR;
+        s.gateway = GATEWAY;
+        s.dnsServers.add(DNS1);
+        s.dnsServers.add(DNS2);
+        s.dnsServers.add(DNS3);
+        s.domains = "google.com";
+        return s;
+    }
+
+    @Test
+    public void testConstructor() {
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        checkEmpty(s);
+    }
+
+    @Test
+    public void testCopyAndClear() {
+        StaticIpConfiguration empty = new StaticIpConfiguration((StaticIpConfiguration) null);
+        checkEmpty(empty);
+
+        StaticIpConfiguration s1 = makeTestObject();
+        StaticIpConfiguration s2 = new StaticIpConfiguration(s1);
+        assertEquals(s1, s2);
+        s2.clear();
+        assertEquals(empty, s2);
+    }
+
+    @Test
+    public void testHashCodeAndEquals() {
+        HashSet<Integer> hashCodes = new HashSet();
+        hashCodes.add(0);
+
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        // Check that this hash code is nonzero and different from all the ones seen so far.
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.ipAddress = ADDR;
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.gateway = GATEWAY;
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.dnsServers.add(DNS1);
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.dnsServers.add(DNS2);
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.dnsServers.add(DNS3);
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        s.domains = "example.com";
+        assertTrue(hashCodes.add(s.hashCode()));
+
+        assertFalse(s.equals(null));
+        assertEquals(s, s);
+
+        StaticIpConfiguration s2 = new StaticIpConfiguration(s);
+        assertEquals(s, s2);
+
+        s.ipAddress = new LinkAddress(DNS1, 32);
+        assertNotEquals(s, s2);
+
+        s2 = new StaticIpConfiguration(s);
+        s.domains = "foo";
+        assertNotEquals(s, s2);
+
+        s2 = new StaticIpConfiguration(s);
+        s.gateway = DNS2;
+        assertNotEquals(s, s2);
+
+        s2 = new StaticIpConfiguration(s);
+        s.dnsServers.add(DNS3);
+        assertNotEquals(s, s2);
+    }
+
+    @Test
+    public void testToLinkProperties() {
+        LinkProperties expected = new LinkProperties();
+        expected.setInterfaceName(IFACE);
+
+        StaticIpConfiguration s = new StaticIpConfiguration();
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        final RouteInfo connectedRoute = new RouteInfo(new IpPrefix(ADDRSTR), null, IFACE);
+        s.ipAddress = ADDR;
+        expected.addLinkAddress(ADDR);
+        expected.addRoute(connectedRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.gateway = GATEWAY;
+        RouteInfo defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), GATEWAY, IFACE);
+        expected.addRoute(defaultRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.gateway = OFFLINKGATEWAY;
+        expected.removeRoute(defaultRoute);
+        defaultRoute = new RouteInfo(new IpPrefix("0.0.0.0/0"), OFFLINKGATEWAY, IFACE);
+        expected.addRoute(defaultRoute);
+
+        RouteInfo gatewayRoute = new RouteInfo(new IpPrefix("192.0.2.129/32"), null, IFACE);
+        expected.addRoute(gatewayRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.dnsServers.add(DNS1);
+        expected.addDnsServer(DNS1);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.dnsServers.add(DNS2);
+        s.dnsServers.add(DNS3);
+        expected.addDnsServer(DNS2);
+        expected.addDnsServer(DNS3);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.domains = "google.com";
+        expected.setDomains("google.com");
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        s.gateway = null;
+        expected.removeRoute(defaultRoute);
+        expected.removeRoute(gatewayRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+
+        // Without knowing the IP address, we don't have a directly-connected route, so we can't
+        // tell if the gateway is off-link or not and we don't add a host route. This isn't a real
+        // configuration, but we should at least not crash.
+        s.gateway = OFFLINKGATEWAY;
+        s.ipAddress = null;
+        expected.removeLinkAddress(ADDR);
+        expected.removeRoute(connectedRoute);
+        expected.addRoute(defaultRoute);
+        assertEquals(expected, s.toLinkProperties(IFACE));
+    }
+
+    private StaticIpConfiguration passThroughParcel(StaticIpConfiguration s) {
+        Parcel p = Parcel.obtain();
+        StaticIpConfiguration s2 = null;
+        try {
+            s.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            s2 = StaticIpConfiguration.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+        assertNotNull(s2);
+        return s2;
+    }
+
+    @Test
+    public void testParceling() {
+        StaticIpConfiguration s = makeTestObject();
+        StaticIpConfiguration s2 = passThroughParcel(s);
+        assertEquals(s, s2);
+    }
+}
diff --git a/tests/net/java/android/net/UidRangeTest.java b/tests/net/java/android/net/UidRangeTest.java
index 0a56e1b..1d1013e 100644
--- a/tests/net/java/android/net/UidRangeTest.java
+++ b/tests/net/java/android/net/UidRangeTest.java
@@ -16,14 +16,20 @@
 
 package android.net;
 
-import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
-public class UidRangeTest extends TestCase {
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UidRangeTest {
 
     static {
         System.loadLibrary("frameworksnettestsjni");
@@ -33,7 +39,7 @@
     private static native int getStart(byte[] inParcel);
     private static native int getStop(byte[] inParcel);
 
-    @SmallTest
+    @Test
     public void testNativeParcelUnparcel() {
         UidRange original = new UidRange(1234, Integer.MAX_VALUE);
 
@@ -45,7 +51,7 @@
         assertArrayEquals(inParcel, outParcel);
     }
 
-    @SmallTest
+    @Test
     public void testIndividualNativeFields() {
         UidRange original = new UidRange(0x11115678, 0x22224321);
         byte[] originalBytes = marshall(original);
@@ -54,14 +60,14 @@
         assertEquals(original.stop, getStop(originalBytes));
     }
 
-    @SmallTest
+    @Test
     public void testSingleItemUidRangeAllowed() {
         new UidRange(123, 123);
         new UidRange(0, 0);
         new UidRange(Integer.MAX_VALUE, Integer.MAX_VALUE);
     }
 
-    @SmallTest
+    @Test
     public void testNegativeUidsDisallowed() {
         try {
             new UidRange(-2, 100);
@@ -76,7 +82,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testStopLessThanStartDisallowed() {
         final int x = 4195000;
         try {
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 5008a41..99a2ad9 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -16,6 +16,16 @@
 
 package android.net.apf;
 
+import static android.system.OsConstants.*;
+import static com.android.internal.util.BitUtils.bytesToBEInt;
+import static com.android.internal.util.BitUtils.put;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
@@ -30,23 +40,22 @@
 import android.os.ConditionVariable;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.system.ErrnoException;
 import android.system.Os;
-import android.test.AndroidTestCase;
 import android.text.format.DateUtils;
-import android.test.suitebuilder.annotation.SmallTest;
-import static android.system.OsConstants.*;
 
 import com.android.frameworks.tests.net.R;
 import com.android.internal.util.HexDump;
-import static com.android.internal.util.BitUtils.bytesToBEInt;
-import static com.android.internal.util.BitUtils.put;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -69,14 +78,15 @@
  * Build, install and run with:
  *  runtest frameworks-net -c android.net.apf.ApfTest
  */
-public class ApfTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ApfTest {
     private static final int TIMEOUT_MS = 500;
 
     @Mock IpConnectivityLog mLog;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         MockitoAnnotations.initMocks(this);
         // Load up native shared library containing APF interpreter exposed via JNI.
         System.loadLibrary("frameworksnettestsjni");
@@ -161,7 +171,7 @@
      * generating bytecode for that program and running it through the
      * interpreter to verify it functions correctly.
      */
-    @SmallTest
+    @Test
     public void testApfInstructions() throws IllegalInstructionException {
         // Empty program should pass because having the program counter reach the
         // location immediately after the program indicates the packet should be
@@ -569,7 +579,7 @@
      * Generate some BPF programs, translate them to APF, then run APF and BPF programs
      * over packet traces and verify both programs filter out the same packets.
      */
-    @SmallTest
+    @Test
     public void testApfAgainstBpf() throws Exception {
         String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
                 "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
@@ -739,7 +749,7 @@
     private static final byte[] ANOTHER_IPV4_ADDR        = {10, 0, 0, 2};
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
-    @SmallTest
+    @Test
     public void testApfFilterIPv4() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
@@ -796,7 +806,7 @@
         apfFilter.shutdown();
     }
 
-    @SmallTest
+    @Test
     public void testApfFilterIPv6() throws Exception {
         final int[] ethTypeBlackList = {};
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
@@ -830,7 +840,7 @@
         apfFilter.shutdown();
     }
 
-    @SmallTest
+    @Test
     public void testApfFilterMulticast() throws Exception {
         final byte[] unicastIpv4Addr   = {(byte)192,0,2,63};
         final byte[] broadcastIpv4Addr = {(byte)192,0,2,(byte)255};
@@ -922,7 +932,7 @@
         apfFilter.shutdown();
     }
 
-    @SmallTest
+    @Test
     public void testApfFilter802_3() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
@@ -973,7 +983,7 @@
         apfFilter.shutdown();
     }
 
-    @SmallTest
+    @Test
     public void testApfFilterEthTypeBL() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
@@ -1058,7 +1068,7 @@
         assertDrop(program, garpReply());
     }
 
-    @SmallTest
+    @Test
     public void testApfFilterArp() throws Exception {
         final int[] ethTypeBlackList = {};
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
@@ -1136,7 +1146,7 @@
 
     // Test that when ApfFilter is shown the given packet, it generates a program to filter it
     // for the given lifetime.
-    private void testRaLifetime(TestApfFilter apfFilter, MockIpManagerCallback ipManagerCallback,
+    private void verifyRaLifetime(TestApfFilter apfFilter, MockIpManagerCallback ipManagerCallback,
             ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
         // Verify new program generated if ApfFilter witnesses RA
         ipManagerCallback.resetApfProgramWait();
@@ -1181,7 +1191,7 @@
         ipManagerCallback.assertNoProgramUpdate();
     }
 
-    @SmallTest
+    @Test
     public void testApfFilterRa() throws Exception {
         MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
         final int[] ethTypeBlackList = {};
@@ -1212,7 +1222,7 @@
         basePacket.put(IPV6_ALL_NODES_ADDRESS);
         assertPass(program, basePacket.array());
 
-        testRaLifetime(apfFilter, ipManagerCallback, basePacket, ROUTER_LIFETIME);
+        verifyRaLifetime(apfFilter, ipManagerCallback, basePacket, ROUTER_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, -1));
 
         ByteBuffer newFlowLabelPacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
@@ -1247,7 +1257,8 @@
         prefixOptionPacket.putInt(
                 ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
                 PREFIX_VALID_LIFETIME);
-        testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
+        verifyRaLifetime(
+                apfFilter, ipManagerCallback, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME);
         verifyRaEvent(new RaEvent(
                 ROUTER_LIFETIME, PREFIX_VALID_LIFETIME, PREFIX_PREFERRED_LIFETIME, -1, -1, -1));
 
@@ -1259,7 +1270,7 @@
         rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
         rdnssOptionPacket.putInt(
                 ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, RDNSS_LIFETIME);
-        testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, RDNSS_LIFETIME);
+        verifyRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, RDNSS_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1));
 
         ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
@@ -1270,7 +1281,7 @@
         routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
         routeInfoOptionPacket.putInt(
                 ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, ROUTE_LIFETIME);
-        testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
+        verifyRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, ROUTE_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, ROUTE_LIFETIME, -1, -1));
 
         ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
@@ -1281,7 +1292,7 @@
         dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
         dnsslOptionPacket.putInt(
                 ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, DNSSL_LIFETIME);
-        testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, ROUTER_LIFETIME);
+        verifyRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, ROUTER_LIFETIME);
         verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, -1, DNSSL_LIFETIME));
 
         // Verify that current program filters all five RAs:
@@ -1301,12 +1312,12 @@
      * copy that resource into the app's data directory and return the path to it.
      */
     private String stageFile(int rawId) throws Exception {
-        File file = new File(getContext().getFilesDir(), "staged_file");
+        File file = new File(InstrumentationRegistry.getContext().getFilesDir(), "staged_file");
         new File(file.getParent()).mkdirs();
         InputStream in = null;
         OutputStream out = null;
         try {
-            in = getContext().getResources().openRawResource(rawId);
+            in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
             out = new FileOutputStream(file);
             Streams.copy(in, out);
         } finally {
@@ -1323,7 +1334,7 @@
         buffer.position(original);
     }
 
-    @SmallTest
+    @Test
     public void testRaParsing() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
@@ -1343,7 +1354,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testRaProcessing() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
@@ -1383,7 +1394,7 @@
     private native static boolean compareBpfApf(String filter, String pcap_filename,
             byte[] apf_program);
 
-    @SmallTest
+    @Test
     public void testBroadcastAddress() throws Exception {
         assertEqualsIp("255.255.255.255", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 0));
         assertEqualsIp("0.0.0.0", ApfFilter.ipv4BroadcastAddress(IPV4_ANY_HOST_ADDR, 32));
diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
index d79c312..050183c 100644
--- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java
+++ b/tests/net/java/android/net/dhcp/DhcpPacketTest.java
@@ -16,23 +16,36 @@
 
 package android.net.dhcp;
 
+import static android.net.dhcp.DhcpPacket.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.net.DhcpResults;
 import android.net.LinkAddress;
 import android.net.NetworkUtils;
 import android.net.metrics.DhcpErrorEvent;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.system.OsConstants;
-import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.internal.util.HexDump;
+
 import java.net.Inet4Address;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Random;
-import junit.framework.TestCase;
 
-import static android.net.dhcp.DhcpPacket.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class DhcpPacketTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DhcpPacketTest {
 
     private static Inet4Address SERVER_ADDR = v4Address("192.0.2.1");
     private static Inet4Address CLIENT_ADDR = v4Address("192.0.2.234");
@@ -46,6 +59,7 @@
         return (Inet4Address) NetworkUtils.numericToInetAddress(addrString);
     }
 
+    @Before
     public void setUp() {
         DhcpPacket.testOverrideVendorId = "android-dhcp-???";
         DhcpPacket.testOverrideHostname = "android-01234567890abcde";
@@ -131,7 +145,7 @@
         assertEquals(expectedVendorInfo, offerPacket.mVendorInfo);
     }
 
-    @SmallTest
+    @Test
     public void testDomainName() throws Exception {
         byte[] nullByte = new byte[] { 0x00 };
         byte[] twoNullBytes = new byte[] { 0x00, 0x00 };
@@ -186,7 +200,7 @@
         assertEquals(leaseTimeMillis, offerPacket.getLeaseTimeMillis());
     }
 
-    @SmallTest
+    @Test
     public void testLeaseTime() throws Exception {
         byte[] noLease = null;
         byte[] tooShortLease = new byte[] { 0x00, 0x00 };
@@ -234,7 +248,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testIpAddress() throws Exception {
         byte[] slash11Netmask = new byte[] { (byte) 0xff, (byte) 0xe0, 0x00, 0x00 };
         byte[] slash24Netmask = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x00 };
@@ -278,11 +292,11 @@
         assertEquals(mtu, dhcpResults.mtu);
     }
 
-    @SmallTest
+    @Test
     public void testOffer1() throws Exception {
-        // TODO: Turn all of these into golden files. This will probably require modifying
-        // Android.mk appropriately, making this into an AndroidTestCase, and adding code to read
-        // the golden files from the test APK's assets via mContext.getAssets().
+        // TODO: Turn all of these into golden files. This will probably require using
+        // android.support.test.InstrumentationRegistry for obtaining a Context object
+        // to read such golden files, along with an appropriate Android.mk.
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
             "451001480000000080118849c0a89003c0a89ff7" +
@@ -311,7 +325,7 @@
                 null, "192.168.144.3", null, 7200, false, 0, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testOffer2() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
@@ -343,7 +357,7 @@
         assertTrue(dhcpResults.hasMeteredHint());
     }
 
-    @SmallTest
+    @Test
     public void testBadIpPacket() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -358,7 +372,7 @@
         fail("Dhcp packet parsing should have failed");
     }
 
-    @SmallTest
+    @Test
     public void testBadDhcpPacket() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -377,7 +391,7 @@
         fail("Dhcp packet parsing should have failed");
     }
 
-    @SmallTest
+    @Test
     public void testBadTruncatedOffer() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -406,7 +420,7 @@
         fail("Dhcp packet parsing should have failed");
     }
 
-    @SmallTest
+    @Test
     public void testBadOfferWithoutACookie() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -437,7 +451,7 @@
         fail("Dhcp packet parsing should have failed");
     }
 
-    @SmallTest
+    @Test
     public void testOfferWithBadCookie() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -473,7 +487,7 @@
         assertEquals(Integer.toHexString(expected), Integer.toHexString(got));
     }
 
-    @SmallTest
+    @Test
     public void testTruncatedOfferPackets() throws Exception {
         final byte[] packet = HexDump.hexStringToByteArray(
             // IP header.
@@ -507,7 +521,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testRandomPackets() throws Exception {
         final int maxRandomPacketSize = 512;
         final Random r = new Random();
@@ -547,7 +561,7 @@
                 null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testMtu() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
@@ -583,7 +597,7 @@
         checkMtu(packet, 0, mtuBytes(-1));
     }
 
-    @SmallTest
+    @Test
     public void testBadHwaddrLength() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
@@ -652,7 +666,7 @@
         assertEquals(expectedClientMac, HexDump.toHexString(offerPacket.getClientMac()));
     }
 
-    @SmallTest
+    @Test
     public void testPadAndOverloadedOptionsOffer() throws Exception {
         // A packet observed in the real world that is interesting for two reasons:
         //
@@ -691,7 +705,7 @@
                 null, "1.1.1.1", null, 43200, false, 0, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testBug2111() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
@@ -721,7 +735,7 @@
                 "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testBug2136() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
@@ -754,7 +768,7 @@
                 "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testUdpServerAnySourcePort() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
@@ -789,7 +803,7 @@
                 "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testUdpInvalidDstPort() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
@@ -821,7 +835,7 @@
         } catch (ParseException expected) {}
     }
 
-    @SmallTest
+    @Test
     public void testMultipleRouters() throws Exception {
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
@@ -854,7 +868,7 @@
                 null, "192.171.189.2", null, 28800, false, 0, dhcpResults);
     }
 
-    @SmallTest
+    @Test
     public void testDiscoverPacket() throws Exception {
         short secs = 7;
         int transactionId = 0xdeadbeef;
diff --git a/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java b/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
index 5deba27..6647760 100644
--- a/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkErrorMessageTest.java
@@ -19,20 +19,30 @@
 import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
 import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkErrorMessage;
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.StructNlMsgErr;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.util.Log;
+
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import junit.framework.TestCase;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 import libcore.util.HexEncoding;
 
 
-public class NetlinkErrorMessageTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkErrorMessageTest {
     private final String TAG = "NetlinkErrorMessageTest";
 
     // Hexadecimal representation of packet capture.
@@ -54,7 +64,7 @@
     public static final byte[] NLM_ERROR_OK =
             HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false);
 
-    @SmallTest
+    @Test
     public void testParseNlmErrorOk() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
diff --git a/tests/net/java/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
index 78b3b70..bd36bac8 100644
--- a/tests/net/java/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
@@ -16,25 +16,35 @@
 
 package android.net.netlink;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.net.netlink.NetlinkSocket;
 import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNdMsg;
 import android.net.netlink.StructNlMsgHdr;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
 import android.util.Log;
+
 import java.io.InterruptedIOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import junit.framework.TestCase;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 
-public class NetlinkSocketTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkSocketTest {
     private final String TAG = "NetlinkSocketTest";
 
-    @SmallTest
+    @Test
     public void testBasicWorkingGetNeighborsQuery() throws Exception {
         NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
         assertNotNull(s);
@@ -93,7 +103,7 @@
         s.close();
     }
 
-    @SmallTest
+    @Test
     public void testRepeatedCloseCallsAreQuiet() throws Exception {
         // Create a working NetlinkSocket.
         NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
diff --git a/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
index 029758e..c9fd74f 100644
--- a/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ b/tests/net/java/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -16,15 +16,19 @@
 
 package android.net.netlink;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNdMsg;
 import android.net.netlink.StructNlMsgHdr;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.system.OsConstants;
 import android.util.Log;
-import libcore.util.HexEncoding;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -32,10 +36,15 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
-import junit.framework.TestCase;
 
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
-public class RtNetlinkNeighborMessageTest extends TestCase {
+import libcore.util.HexEncoding;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkNeighborMessageTest {
     private final String TAG = "RtNetlinkNeighborMessageTest";
 
     // Hexadecimal representation of packet capture.
@@ -136,7 +145,7 @@
     public static final byte[] RTM_GETNEIGH_RESPONSE =
             HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false);
 
-    @SmallTest
+    @Test
     public void testParseRtmDelNeigh() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
@@ -163,7 +172,7 @@
         assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination);
     }
 
-    @SmallTest
+    @Test
     public void testParseRtmNewNeigh() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
@@ -190,7 +199,7 @@
         assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination);
     }
 
-    @SmallTest
+    @Test
     public void testParseRtmGetNeighResponse() {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
@@ -215,7 +224,7 @@
         assertEquals(14, messageCount);
     }
 
-    @SmallTest
+    @Test
     public void testCreateRtmNewNeighMessage() {
         final int seqNo = 2635;
         final int ifIndex = 14;
diff --git a/tests/net/java/android/net/util/BlockingSocketReaderTest.java b/tests/net/java/android/net/util/BlockingSocketReaderTest.java
index e03350f..29dfa4c 100644
--- a/tests/net/java/android/net/util/BlockingSocketReaderTest.java
+++ b/tests/net/java/android/net/util/BlockingSocketReaderTest.java
@@ -16,17 +16,25 @@
 
 package android.net.util;
 
+import static android.net.util.BlockingSocketReader.DEFAULT_RECV_BUF_SIZE;
 import static android.system.OsConstants.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructTimeval;
 
-import libcore.io.IoBridge;
-
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
 import java.net.Inet6Address;
@@ -37,15 +45,21 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
+import libcore.io.IoBridge;
 
 /**
  * Tests for BlockingSocketReader.
  *
  * @hide
  */
-public class BlockingSocketReaderTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BlockingSocketReaderTest {
     static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
     static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
 
@@ -53,61 +67,83 @@
     protected FileDescriptor mLocalSocket;
     protected InetSocketAddress mLocalSockName;
     protected byte[] mLastRecvBuf;
-    protected boolean mExited;
+    protected boolean mStopped;
+    protected HandlerThread mHandlerThread;
     protected BlockingSocketReader mReceiver;
 
-    @Override
+    class UdpLoopbackReader extends BlockingSocketReader {
+        public UdpLoopbackReader(Handler h) {
+            super(h);
+        }
+
+        @Override
+        protected FileDescriptor createFd() {
+            FileDescriptor s = null;
+            try {
+                s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+                Os.bind(s, LOOPBACK6, 0);
+                mLocalSockName = (InetSocketAddress) Os.getsockname(s);
+                Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
+            } catch (ErrnoException|SocketException e) {
+                closeFd(s);
+                fail();
+                return null;
+            }
+
+            mLocalSocket = s;
+            return s;
+        }
+
+        @Override
+        protected void handlePacket(byte[] recvbuf, int length) {
+            mLastRecvBuf = Arrays.copyOf(recvbuf, length);
+            mLatch.countDown();
+        }
+
+        @Override
+        protected void onStart() {
+            mStopped = false;
+            mLatch.countDown();
+        }
+
+        @Override
+        protected void onStop() {
+            mStopped = true;
+            mLatch.countDown();
+        }
+    };
+
+    @Before
     public void setUp() {
         resetLatch();
         mLocalSocket = null;
         mLocalSockName = null;
         mLastRecvBuf = null;
-        mExited = false;
+        mStopped = false;
 
-        mReceiver = new BlockingSocketReader() {
-            @Override
-            protected FileDescriptor createSocket() {
-                FileDescriptor s = null;
-                try {
-                    s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
-                    Os.bind(s, LOOPBACK6, 0);
-                    mLocalSockName = (InetSocketAddress) Os.getsockname(s);
-                    Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
-                } catch (ErrnoException|SocketException e) {
-                    closeSocket(s);
-                    fail();
-                    return null;
-                }
-
-                mLocalSocket = s;
-                return s;
-            }
-
-            @Override
-            protected void handlePacket(byte[] recvbuf, int length) {
-                mLastRecvBuf = Arrays.copyOf(recvbuf, length);
-                mLatch.countDown();
-            }
-
-            @Override
-            protected void onExit() {
-                mExited = true;
-                mLatch.countDown();
-            }
-        };
+        mHandlerThread = new HandlerThread(BlockingSocketReaderTest.class.getSimpleName());
+        mHandlerThread.start();
     }
 
-    @Override
-    public void tearDown() {
-        if (mReceiver != null) mReceiver.stop();
+    @After
+    public void tearDown() throws Exception {
+        if (mReceiver != null) {
+            mHandlerThread.getThreadHandler().post(() -> { mReceiver.stop(); });
+            waitForActivity();
+        }
         mReceiver = null;
+        mHandlerThread.quit();
+        mHandlerThread = null;
     }
 
     void resetLatch() { mLatch = new CountDownLatch(1); }
 
     void waitForActivity() throws Exception {
-        assertTrue(mLatch.await(500, TimeUnit.MILLISECONDS));
-        resetLatch();
+        try {
+            mLatch.await(1000, TimeUnit.MILLISECONDS);
+        } finally {
+            resetLatch();
+        }
     }
 
     void sendPacket(byte[] contents) throws Exception {
@@ -117,32 +153,57 @@
         sender.close();
     }
 
+    @Test
     public void testBasicWorking() throws Exception {
-        assertTrue(mReceiver.start());
+        final Handler h = mHandlerThread.getThreadHandler();
+        mReceiver = new UdpLoopbackReader(h);
+
+        h.post(() -> { mReceiver.start(); });
+        waitForActivity();
         assertTrue(mLocalSockName != null);
         assertEquals(LOOPBACK6, mLocalSockName.getAddress());
         assertTrue(0 < mLocalSockName.getPort());
         assertTrue(mLocalSocket != null);
-        assertFalse(mExited);
+        assertFalse(mStopped);
 
         final byte[] one = "one 1".getBytes("UTF-8");
         sendPacket(one);
         waitForActivity();
         assertEquals(1, mReceiver.numPacketsReceived());
         assertTrue(Arrays.equals(one, mLastRecvBuf));
-        assertFalse(mExited);
+        assertFalse(mStopped);
 
         final byte[] two = "two 2".getBytes("UTF-8");
         sendPacket(two);
         waitForActivity();
         assertEquals(2, mReceiver.numPacketsReceived());
         assertTrue(Arrays.equals(two, mLastRecvBuf));
-        assertFalse(mExited);
+        assertFalse(mStopped);
 
         mReceiver.stop();
         waitForActivity();
         assertEquals(2, mReceiver.numPacketsReceived());
         assertTrue(Arrays.equals(two, mLastRecvBuf));
-        assertTrue(mExited);
+        assertTrue(mStopped);
+        mReceiver = null;
+    }
+
+    class NullBlockingSocketReader extends BlockingSocketReader {
+        public NullBlockingSocketReader(Handler h, int recvbufsize) {
+            super(h, recvbufsize);
+        }
+
+        @Override
+        public FileDescriptor createFd() { return null; }
+    }
+
+    @Test
+    public void testMinimalRecvBufSize() throws Exception {
+        final Handler h = mHandlerThread.getThreadHandler();
+
+        for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
+            final BlockingSocketReader b = new NullBlockingSocketReader(h, i);
+            assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
+        }
     }
 }
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
index dd679bc..38d3d74 100644
--- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
+++ b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
@@ -17,18 +17,25 @@
 package android.net.util;
 
 import static android.net.util.NetworkConstants.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 import libcore.util.HexEncoding;
 
-import junit.framework.TestCase;
-
-
 /**
  * Tests for ConnectivityPacketSummary.
  *
  * @hide
  */
-public class ConnectivityPacketSummaryTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityPacketSummaryTest {
     private static final byte[] MYHWADDR = {
         asByte(0x80), asByte(0x7a), asByte(0xbf), asByte(0x6f), asByte(0x48), asByte(0xf3)
     };
@@ -39,6 +46,7 @@
         return ConnectivityPacketSummary.summarize(MYHWADDR, bytes);
     }
 
+    @Test
     public void testParseICMPv6DADProbe() {
         final String packet =
                 // Ethernet
@@ -60,6 +68,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseICMPv6RS() {
         final String packet =
                 // Ethernet
@@ -81,6 +90,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseICMPv6RA() {
         final String packet =
                 // Ethernet
@@ -113,6 +123,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseICMPv6NS() {
         final String packet =
                 // Ethernet
@@ -135,6 +146,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testInvalidICMPv6NDLength() {
         final String packet =
                 // Ethernet
@@ -159,6 +171,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseICMPv6NA() {
         final String packet =
                 // Ethernet
@@ -179,6 +192,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseARPRequest() {
         final String packet =
                 // Ethernet
@@ -197,6 +211,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseARPReply() {
         final String packet =
                 // Ethernet
@@ -217,6 +232,7 @@
         assertEquals(expected, getSummary(packet));
     }
 
+    @Test
     public void testParseDHCPv4Discover() {
         final String packet =
                 // Ethernet
@@ -262,6 +278,7 @@
         assertTrue(getSummary(packet).startsWith(expectedPrefix));
     }
 
+    @Test
     public void testParseDHCPv4Offer() {
         final String packet =
                 // Ethernet
@@ -307,6 +324,7 @@
         assertTrue(getSummary(packet).startsWith(expectedPrefix));
     }
 
+    @Test
     public void testParseDHCPv4Request() {
         final String packet =
                 // Ethernet
@@ -354,6 +372,7 @@
         assertTrue(getSummary(packet).startsWith(expectedPrefix));
     }
 
+    @Test
     public void testParseDHCPv4Ack() {
         final String packet =
                 // Ethernet
diff --git a/tests/net/java/android/net/util/IpUtilsTest.java b/tests/net/java/android/net/util/IpUtilsTest.java
index c2d1608..8903bf9 100644
--- a/tests/net/java/android/net/util/IpUtilsTest.java
+++ b/tests/net/java/android/net/util/IpUtilsTest.java
@@ -16,15 +16,19 @@
 
 package android.net.util;
 
-import android.net.util.IpUtils;
-import android.test.suitebuilder.annotation.SmallTest;
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 
 import java.nio.ByteBuffer;
 
-import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
-
-public class IpUtilsTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpUtilsTest {
 
     private static final int IPV4_HEADER_LENGTH = 20;
     private static final int IPV6_HEADER_LENGTH = 40;
@@ -67,7 +71,7 @@
     //           "hello")
     // print JavaPacketDefinition(str(packet))
 
-    @SmallTest
+    @Test
     public void testIpv6TcpChecksum() throws Exception {
         // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) /
         //           scapy.TCP(sport=12345, dport=7,
@@ -115,7 +119,7 @@
         assertEquals(0, IpUtils.tcpChecksum(packet, 0, IPV6_HEADER_LENGTH, transportLen));
     }
 
-    @SmallTest
+    @Test
     public void testIpv4UdpChecksum() {
         // packet = (scapy.IP(src="192.0.2.1", dst="192.0.2.2", tos=0x40) /
         //           scapy.UDP(sport=32012, dport=4500) /
diff --git a/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
new file mode 100644
index 0000000..39f59f1
--- /dev/null
+++ b/tests/net/java/android/net/util/VersionedBroadcastListenerTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.reset;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VersionedBroadcastListenerTest {
+    private static final String TAG = VersionedBroadcastListenerTest.class.getSimpleName();
+    private static final String ACTION_TEST = "action.test.happy.broadcasts";
+
+    @Mock private Context mContext;
+    private BroadcastInterceptingContext mServiceContext;
+    private Handler mHandler;
+    private VersionedBroadcastListener mListener;
+    private int mCallbackCount;
+
+    private void doCallback() { mCallbackCount++; }
+
+    private class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+    }
+
+    @BeforeClass
+    public static void setUpBeforeClass() throws Exception {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        reset(mContext);
+        mServiceContext = new MockContext(mContext);
+        mHandler = new Handler(Looper.myLooper());
+        mCallbackCount = 0;
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_TEST);
+        mListener = new VersionedBroadcastListener(
+                TAG, mServiceContext, mHandler, filter, (Intent intent) -> doCallback());
+    }
+
+    @After public void tearDown() throws Exception {
+        if (mListener != null) {
+            mListener.stopListening();
+            mListener = null;
+        }
+    }
+
+    private void sendBroadcast() {
+        final Intent intent = new Intent(ACTION_TEST);
+        mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    @Test
+    public void testBasicListening() {
+        assertEquals(0, mCallbackCount);
+        mListener.startListening();
+        for (int i = 0; i < 5; i++) {
+            sendBroadcast();
+            assertEquals(i+1, mCallbackCount);
+        }
+        mListener.stopListening();
+    }
+
+    @Test
+    public void testBroadcastsBeforeStartAreIgnored() {
+        assertEquals(0, mCallbackCount);
+        for (int i = 0; i < 5; i++) {
+            sendBroadcast();
+            assertEquals(0, mCallbackCount);
+        }
+
+        mListener.startListening();
+        sendBroadcast();
+        assertEquals(1, mCallbackCount);
+    }
+
+    @Test
+    public void testBroadcastsAfterStopAreIgnored() {
+        mListener.startListening();
+        sendBroadcast();
+        assertEquals(1, mCallbackCount);
+        mListener.stopListening();
+
+        for (int i = 0; i < 5; i++) {
+            sendBroadcast();
+            assertEquals(1, mCallbackCount);
+        }
+    }
+}
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
index a423c2a..fb2bd79 100644
--- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
@@ -24,12 +24,15 @@
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 import android.content.res.Resources;
 import android.net.NetworkStats;
 import android.net.TrafficStats;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.tests.net.R;
 
@@ -42,19 +45,23 @@
 import libcore.io.IoUtils;
 import libcore.io.Streams;
 
+import org.junit.runner.RunWith;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 /**
  * Tests for {@link NetworkStatsFactory}.
  */
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class NetworkStatsFactoryTest extends AndroidTestCase {
+public class NetworkStatsFactoryTest {
     private File mTestProc;
     private NetworkStatsFactory mFactory;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-
-        mTestProc = new File(getContext().getFilesDir(), "proc");
+        mTestProc = new File(InstrumentationRegistry.getContext().getFilesDir(), "proc");
         if (mTestProc.exists()) {
             IoUtils.deleteContents(mTestProc);
         }
@@ -62,17 +69,16 @@
         mFactory = new NetworkStatsFactory(mTestProc);
     }
 
-    @Override
+    @After
     public void tearDown() throws Exception {
         mFactory = null;
 
         if (mTestProc.exists()) {
             IoUtils.deleteContents(mTestProc);
         }
-
-        super.tearDown();
     }
 
+    @Test
     public void testNetworkStatsDetail() throws Exception {
         final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
 
@@ -84,6 +90,7 @@
         assertStatsEntry(stats, "rmnet2", 10001, SET_DEFAULT, 0x0, 1125899906842624L, 984L);
     }
 
+    @Test
     public void testKernelTags() throws Exception {
         assertEquals(0, kernelToTag("0x0000000000000000"));
         assertEquals(0x32, kernelToTag("0x0000003200000000"));
@@ -98,6 +105,7 @@
         assertEquals(TrafficStats.TAG_SYSTEM_DOWNLOAD, kernelToTag("0xffffff0100000000"));
     }
 
+    @Test
     public void testNetworkStatsWithSet() throws Exception {
         final NetworkStats stats = parseDetailedStats(R.raw.xt_qtaguid_typical);
         assertEquals(70, stats.size());
@@ -106,6 +114,7 @@
         assertStatsEntry(stats, "rmnet1", 10021, SET_FOREGROUND, 0x30100000, 742L, 3L, 1265L, 3L);
     }
 
+    @Test
     public void testNetworkStatsSingle() throws Exception {
         stageFile(R.raw.xt_qtaguid_iface_typical, file("net/xt_qtaguid/iface_stat_all"));
 
@@ -116,6 +125,7 @@
         assertStatsEntry(stats, "test2", UID_ALL, SET_ALL, TAG_NONE, 1L, 2L, 3L, 4L);
     }
 
+    @Test
     public void testNetworkStatsXt() throws Exception {
         stageFile(R.raw.xt_qtaguid_iface_fmt_typical, file("net/xt_qtaguid/iface_stat_fmt"));
 
@@ -127,6 +137,7 @@
         assertStatsEntry(stats, "rmnet2", UID_ALL, SET_ALL, TAG_NONE, 4968L, 35L, 3081L, 39L);
     }
 
+    @Test
     public void testDoubleClatAccounting() throws Exception {
         NetworkStatsFactory.noteStackedIface("v4-wlan0", "wlan0");
 
@@ -161,6 +172,7 @@
         NetworkStatsFactory.noteStackedIface("v4-wlan0", null);
     }
 
+    @Test
     public void testDoubleClatAccounting100MBDownload() throws Exception {
         // Downloading 100mb from an ipv4 only destination in a foreground activity
 
@@ -197,7 +209,7 @@
         InputStream in = null;
         OutputStream out = null;
         try {
-            in = getContext().getResources().openRawResource(rawId);
+            in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
             out = new FileOutputStream(file);
             Streams.copy(in, out);
         } finally {
@@ -251,5 +263,4 @@
         assertEquals("unexpected txBytes", txBytes, entry.txBytes);
         assertEquals("unexpected txPackets", txPackets, entry.txPackets);
     }
-
 }
diff --git a/tests/net/java/com/android/internal/util/BitUtilsTest.java b/tests/net/java/com/android/internal/util/BitUtilsTest.java
index 0ad8a21..f4dc12a 100644
--- a/tests/net/java/com/android/internal/util/BitUtilsTest.java
+++ b/tests/net/java/com/android/internal/util/BitUtilsTest.java
@@ -56,6 +56,25 @@
     }
 
     @Test
+    public void testUnsignedShortComposition() {
+        byte b0 = 0;
+        byte b1 = 1;
+        byte b2 = 2;
+        byte b10 = 10;
+        byte b16 = 16;
+        byte b128 = -128;
+        byte b224 = -32;
+        byte b255 = -1;
+        assertEquals(0x0000, uint16(b0, b0));
+        assertEquals(0xffff, uint16(b255, b255));
+        assertEquals(0x0a01, uint16(b10, b1));
+        assertEquals(0x8002, uint16(b128, b2));
+        assertEquals(0x01ff, uint16(b1, b255));
+        assertEquals(0x80ff, uint16(b128, b255));
+        assertEquals(0xe010, uint16(b224, b16));
+    }
+
+    @Test
     public void testUnsignedIntWideningConversions() {
         assertEquals(0, uint32(0));
         assertEquals(1, uint32(1));
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
new file mode 100644
index 0000000..7a2344317
--- /dev/null
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.internal.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.Objects;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RingBufferTest {
+
+    @Test
+    public void testEmptyRingBuffer() {
+        RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);
+
+        assertArraysEqual(new String[0], buffer.toArray());
+    }
+
+    @Test
+    public void testIncorrectConstructorArguments() {
+        try {
+            RingBuffer<String> buffer = new RingBuffer<>(String.class, -10);
+            fail("Should not be able to create a negative capacity RingBuffer");
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            RingBuffer<String> buffer = new RingBuffer<>(String.class, 0);
+            fail("Should not be able to create a 0 capacity RingBuffer");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testRingBufferWithNoWrapping() {
+        RingBuffer<String> buffer = new RingBuffer<>(String.class, 100);
+
+        buffer.append("a");
+        buffer.append("b");
+        buffer.append("c");
+        buffer.append("d");
+        buffer.append("e");
+
+        String[] expected = {"a", "b", "c", "d", "e"};
+        assertArraysEqual(expected, buffer.toArray());
+    }
+
+    @Test
+    public void testRingBufferWithCapacity1() {
+        RingBuffer<String> buffer = new RingBuffer<>(String.class, 1);
+
+        buffer.append("a");
+        assertArraysEqual(new String[]{"a"}, buffer.toArray());
+
+        buffer.append("b");
+        assertArraysEqual(new String[]{"b"}, buffer.toArray());
+
+        buffer.append("c");
+        assertArraysEqual(new String[]{"c"}, buffer.toArray());
+
+        buffer.append("d");
+        assertArraysEqual(new String[]{"d"}, buffer.toArray());
+
+        buffer.append("e");
+        assertArraysEqual(new String[]{"e"}, buffer.toArray());
+    }
+
+    @Test
+    public void testRingBufferWithWrapping() {
+        int capacity = 100;
+        RingBuffer<String> buffer = new RingBuffer<>(String.class, capacity);
+
+        buffer.append("a");
+        buffer.append("b");
+        buffer.append("c");
+        buffer.append("d");
+        buffer.append("e");
+
+        String[] expected1 = {"a", "b", "c", "d", "e"};
+        assertArraysEqual(expected1, buffer.toArray());
+
+        String[] expected2 = new String[capacity];
+        int firstIndex = 0;
+        int lastIndex = capacity - 1;
+
+        expected2[firstIndex] = "e";
+        for (int i = 1; i < capacity; i++) {
+            buffer.append("x");
+            expected2[i] = "x";
+        }
+        assertArraysEqual(expected2, buffer.toArray());
+
+        buffer.append("x");
+        expected2[firstIndex] = "x";
+        assertArraysEqual(expected2, buffer.toArray());
+
+        for (int i = 0; i < 10; i++) {
+            for (String s : expected2) {
+                buffer.append(s);
+            }
+        }
+        assertArraysEqual(expected2, buffer.toArray());
+
+        buffer.append("a");
+        expected2[lastIndex] = "a";
+        assertArraysEqual(expected2, buffer.toArray());
+    }
+
+    static <T> void assertArraysEqual(T[] expected, T[] got) {
+        if (expected.length != got.length) {
+            fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
+                    + " did not have the same length");
+        }
+
+        for (int i = 0; i < expected.length; i++) {
+            if (!Objects.equals(expected[i], got[i])) {
+                fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
+                        + " were not equal");
+            }
+        }
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index a814738..27a29b6 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -19,13 +19,20 @@
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
+import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.NetworkCapabilities.*;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 import static com.android.internal.util.TestUtils.waitForIdleHandler;
-
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -85,9 +92,10 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.test.mock.MockContentResolver;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -96,6 +104,8 @@
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.DefaultNetworkMetrics;
+import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkMonitor;
@@ -103,7 +113,11 @@
 import com.android.server.net.NetworkPinner;
 import com.android.server.net.NetworkPolicyManagerInternal;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
@@ -119,16 +133,18 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.BooleanSupplier;
 import java.util.function.Predicate;
 
+
 /**
  * Tests for {@link ConnectivityService}.
  *
  * Build, install and run with:
  *  runtest frameworks-net -c com.android.server.ConnectivityServiceTest
  */
-public class ConnectivityServiceTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConnectivityServiceTest {
     private static final String TAG = "ConnectivityServiceTest";
 
     private static final int TIMEOUT_MS = 500;
@@ -140,6 +156,10 @@
     private MockNetworkAgent mWiFiNetworkAgent;
     private MockNetworkAgent mCellNetworkAgent;
     private MockNetworkAgent mEthernetNetworkAgent;
+    private Context mContext;
+
+    @Mock IpConnectivityMetrics.Logger mMetricsService;
+    @Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
 
     // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
     // do not go through ConnectivityService but talk to netd directly, so they don't automatically
@@ -241,7 +261,7 @@
         waitForIdle(TIMEOUT_MS);
     }
 
-    @SmallTest
+    @Test
     public void testWaitForIdle() {
         final int attempts = 50;  // Causes the test to take about 200ms on bullhead-eng.
 
@@ -742,7 +762,8 @@
 
                 // Don't overlap test NetIDs with real NetIDs as binding sockets to real networks
                 // can have odd side-effects, like network validations succeeding.
-                final Network[] networks = ConnectivityManager.from(getContext()).getAllNetworks();
+                Context context = InstrumentationRegistry.getContext();
+                final Network[] networks = ConnectivityManager.from(context).getAllNetworks();
                 boolean overlaps = false;
                 for (Network network : networks) {
                     if (netId == network.netId) {
@@ -782,6 +803,18 @@
             return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
         }
 
+        @Override
+        public boolean hasService(String name) {
+            // Currenty, the only relevant service that ConnectivityService checks for is
+            // ETHERNET_SERVICE.
+            return Context.ETHERNET_SERVICE.equals(name);
+        }
+
+        @Override
+        protected IpConnectivityMetrics.Logger metricsLogger() {
+            return mMetricsService;
+        }
+
         public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
             return mLastCreatedNetworkMonitor;
         }
@@ -806,9 +839,12 @@
         fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
     }
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
+        mContext = InstrumentationRegistry.getContext();
+
+        MockitoAnnotations.initMocks(this);
+        when(mMetricsService.defaultNetworkMetrics()).thenReturn(mDefaultNetworkMetrics);
 
         // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
         // http://b/25897652 .
@@ -816,7 +852,7 @@
             Looper.prepare();
         }
 
-        mServiceContext = new MockContext(getContext());
+        mServiceContext = new MockContext(InstrumentationRegistry.getContext());
         LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
         LocalServices.addService(
                 NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
@@ -827,7 +863,7 @@
                 mock(IpConnectivityLog.class));
 
         mService.systemReady();
-        mCm = new WrappedConnectivityManager(getContext(), mService);
+        mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService);
         mCm.bindProcessToNetwork(null);
 
         // Ensure that the default setting for Captive Portals is used for most tests
@@ -835,6 +871,7 @@
         setMobileDataAlwaysOn(false);
     }
 
+    @After
     public void tearDown() throws Exception {
         setMobileDataAlwaysOn(false);
         if (mCellNetworkAgent != null) {
@@ -849,7 +886,6 @@
             mEthernetNetworkAgent.disconnect();
             mEthernetNetworkAgent = null;
         }
-        super.tearDown();
     }
 
     private static int transportToLegacyType(int transport) {
@@ -923,15 +959,23 @@
         return cv;
     }
 
+    @Test
     public void testNetworkTypes() {
         // Ensure that our mocks for the networkAttributes config variable work as expected. If they
         // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
         // will fail. Failing here is much easier to debug.
         assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
         assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
+        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
+        assertFalse(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
+
+        // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
+        // mocks, this assert exercises the ConnectivityService code path that ensures that
+        // TYPE_ETHERNET is supported if the ethernet service is running.
+        assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET));
     }
 
-    @SmallTest
+    @Test
     public void testLingering() throws Exception {
         verifyNoNetwork();
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -972,7 +1016,7 @@
         verifyNoNetwork();
     }
 
-    @SmallTest
+    @Test
     public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
         // Test bringing up unvalidated WiFi
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1007,7 +1051,7 @@
         verifyNoNetwork();
     }
 
-    @SmallTest
+    @Test
     public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception {
         // Test bringing up unvalidated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1033,7 +1077,7 @@
         verifyNoNetwork();
     }
 
-    @SmallTest
+    @Test
     public void testUnlingeringDoesNotValidate() throws Exception {
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1061,7 +1105,7 @@
                 NET_CAPABILITY_VALIDATED));
     }
 
-    @SmallTest
+    @Test
     public void testCellularOutscoresWeakWifi() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1087,7 +1131,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @SmallTest
+    @Test
     public void testReapingNetwork() throws Exception {
         // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
         // Expect it to be torn down immediately because it satisfies no requests.
@@ -1120,7 +1164,7 @@
         waitFor(cv);
     }
 
-    @SmallTest
+    @Test
     public void testCellularFallback() throws Exception {
         // Test bringing up validated cellular.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1158,7 +1202,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @SmallTest
+    @Test
     public void testWiFiFallback() throws Exception {
         // Test bringing up unvalidated WiFi.
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
@@ -1372,7 +1416,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testStateChangeNetworkCallbacks() throws Exception {
         final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
         final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
@@ -1462,7 +1506,7 @@
         assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
     }
 
-    @SmallTest
+    @Test
     public void testMultipleLingering() {
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED)
@@ -1690,7 +1734,7 @@
         mCm.unregisterNetworkCallback(trackDefaultCallback);
     }
 
-    @SmallTest
+    @Test
     public void testExplicitlySelected() {
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
@@ -1857,7 +1901,7 @@
         handlerThread.quit();
     }
 
-    @SmallTest
+    @Test
     public void testNetworkFactoryRequests() throws Exception {
         tryNetworkFactoryRequests(NET_CAPABILITY_MMS);
         tryNetworkFactoryRequests(NET_CAPABILITY_SUPL);
@@ -1877,7 +1921,7 @@
         // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
     }
 
-    @SmallTest
+    @Test
     public void testNoMutableNetworkRequests() throws Exception {
         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
         NetworkRequest request1 = new NetworkRequest.Builder()
@@ -1894,7 +1938,7 @@
         assertException(() -> { mCm.requestNetwork(request2, pendingIntent); }, expected);
     }
 
-    @SmallTest
+    @Test
     public void testMMSonWiFi() throws Exception {
         // Test bringing up cellular without MMS NetworkRequest gets reaped
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1933,7 +1977,7 @@
         verifyActiveNetwork(TRANSPORT_WIFI);
     }
 
-    @SmallTest
+    @Test
     public void testMMSonCell() throws Exception {
         // Test bringing up cellular without MMS
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -1962,7 +2006,7 @@
         verifyActiveNetwork(TRANSPORT_CELLULAR);
     }
 
-    @SmallTest
+    @Test
     public void testCaptivePortal() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -2013,7 +2057,7 @@
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
     }
 
-    @SmallTest
+    @Test
     public void testCaptivePortalApp() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -2059,7 +2103,7 @@
         mCm.unregisterNetworkCallback(captivePortalCallback);
     }
 
-    @SmallTest
+    @Test
     public void testAvoidOrIgnoreCaptivePortals() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -2104,7 +2148,7 @@
         return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
     }
 
-    @SmallTest
+    @Test
     public void testNetworkSpecifier() {
         NetworkRequest rEmpty1 = newWifiRequestBuilder().build();
         NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier((String) null).build();
@@ -2165,7 +2209,7 @@
         assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cFoo, cBar);
     }
 
-    @SmallTest
+    @Test
     public void testInvalidNetworkSpecifier() {
         try {
             NetworkRequest.Builder builder = new NetworkRequest.Builder();
@@ -2226,7 +2270,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testNetworkSpecifierUidSpoofSecurityException() {
         class UidAwareNetworkSpecifier extends NetworkSpecifier implements Parcelable {
             @Override
@@ -2260,7 +2304,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testRegisterDefaultNetworkCallback() throws Exception {
         final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
         mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -2309,7 +2353,7 @@
         defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
     }
 
-    @SmallTest
+    @Test
     public void testAdditionalStateCallbacks() throws Exception {
         // File a network request for mobile.
         final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -2370,7 +2414,7 @@
         return nc.hasCapability(NET_CAPABILITY_FOREGROUND);
     }
 
-    @SmallTest
+    @Test
     public void testBackgroundNetworks() throws Exception {
         // Create a background request. We can't do this ourselves because ConnectivityService
         // doesn't have an API for it. So just turn on mobile data always on.
@@ -2539,7 +2583,7 @@
         return false;
     }
 
-    @SmallTest
+    @Test
     public void testMobileDataAlwaysOn() throws Exception {
         final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
         final NetworkRequest cellRequest = new NetworkRequest.Builder()
@@ -2604,7 +2648,7 @@
         handlerThread.quit();
     }
 
-    @SmallTest
+    @Test
     public void testAvoidBadWifiSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
         final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
@@ -2642,7 +2686,7 @@
         assertTrue(tracker.shouldNotifyWifiUnvalidated());
     }
 
-    @SmallTest
+    @Test
     public void testAvoidBadWifi() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
         final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
@@ -2769,7 +2813,7 @@
         mCm.unregisterNetworkCallback(defaultCallback);
     }
 
-    @SmallTest
+    @Test
     public void testMeteredMultipathPreferenceSetting() throws Exception {
         final ContentResolver cr = mServiceContext.getContentResolver();
         final WrappedMultinetworkPolicyTracker tracker = mService.getMultinetworkPolicyTracker();
@@ -2793,7 +2837,7 @@
      * Validate that a satisfied network request does not trigger onUnavailable() once the
      * time-out period expires.
      */
-    @SmallTest
+    @Test
     public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -2813,7 +2857,7 @@
      * Validate that a satisfied network request followed by a disconnected (lost) network does
      * not trigger onUnavailable() once the time-out period expires.
      */
-    @SmallTest
+    @Test
     public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -2837,7 +2881,7 @@
      * callback is called when time-out expires. Then validate that if network request is
      * (somehow) satisfied - the callback isn't called later.
      */
-    @SmallTest
+    @Test
     public void testTimedoutNetworkRequest() {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -2858,7 +2902,7 @@
      * Validate that when a network request is unregistered (cancelled), no posterior event can
      * trigger the callback.
      */
-    @SmallTest
+    @Test
     public void testNoCallbackAfterUnregisteredNetworkRequest() {
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -2966,7 +3010,7 @@
         return mWiFiNetworkAgent.getNetwork();
     }
 
-    @SmallTest
+    @Test
     public void testPacketKeepalives() throws Exception {
         InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
         InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
@@ -3090,7 +3134,7 @@
         callback3.expectStopped();
     }
 
-    @SmallTest
+    @Test
     public void testGetCaptivePortalServerUrl() throws Exception {
         String url = mCm.getCaptivePortalServerUrl();
         assertEquals("http://connectivitycheck.gstatic.com/generate_204", url);
@@ -3135,7 +3179,7 @@
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
     }
 
-    @SmallTest
+    @Test
     public void testNetworkPinner() {
         NetworkRequest wifiRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI)
@@ -3195,69 +3239,69 @@
         assertPinnedToWifiWithCellDefault();
     }
 
-    @SmallTest
-    public void testNetworkRequestMaximum() {
+    @Test
+    public void testNetworkCallbackMaximum() {
         final int MAX_REQUESTS = 100;
-        // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added.
+        final int CALLBACKS = 90;
+        final int INTENTS = 10;
+        assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
+
         NetworkRequest networkRequest = new NetworkRequest.Builder().build();
-        ArrayList<NetworkCallback> networkCallbacks = new ArrayList<NetworkCallback>();
-        try {
-            for (int i = 0; i < MAX_REQUESTS; i++) {
-                NetworkCallback networkCallback = new NetworkCallback();
-                mCm.requestNetwork(networkRequest, networkCallback);
-                networkCallbacks.add(networkCallback);
-            }
-            fail("Registering " + MAX_REQUESTS + " NetworkRequests did not throw exception");
-        } catch (TooManyRequestsException expected) {}
-        for (NetworkCallback networkCallback : networkCallbacks) {
-            mCm.unregisterNetworkCallback(networkCallback);
-        }
-        networkCallbacks.clear();
+        ArrayList<Object> registered = new ArrayList<>();
 
-        try {
-            for (int i = 0; i < MAX_REQUESTS; i++) {
-                NetworkCallback networkCallback = new NetworkCallback();
-                mCm.registerNetworkCallback(networkRequest, networkCallback);
-                networkCallbacks.add(networkCallback);
-            }
-            fail("Registering " + MAX_REQUESTS + " NetworkCallbacks did not throw exception");
-        } catch (TooManyRequestsException expected) {}
-        for (NetworkCallback networkCallback : networkCallbacks) {
-            mCm.unregisterNetworkCallback(networkCallback);
+        int j = 0;
+        while (j++ < CALLBACKS / 2) {
+            NetworkCallback cb = new NetworkCallback();
+            mCm.requestNetwork(networkRequest, cb);
+            registered.add(cb);
         }
-        networkCallbacks.clear();
+        while (j++ < CALLBACKS) {
+            NetworkCallback cb = new NetworkCallback();
+            mCm.registerNetworkCallback(networkRequest, cb);
+            registered.add(cb);
+        }
+        j = 0;
+        while (j++ < INTENTS / 2) {
+            PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, new Intent("a" + j), 0);
+            mCm.requestNetwork(networkRequest, pi);
+            registered.add(pi);
+        }
+        while (j++ < INTENTS) {
+            PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, new Intent("b" + j), 0);
+            mCm.registerNetworkCallback(networkRequest, pi);
+            registered.add(pi);
+        }
 
-        ArrayList<PendingIntent> pendingIntents = new ArrayList<PendingIntent>();
+        // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added.
         try {
-            for (int i = 0; i < MAX_REQUESTS + 1; i++) {
-                PendingIntent pendingIntent =
-                        PendingIntent.getBroadcast(mContext, 0, new Intent("a" + i), 0);
-                mCm.requestNetwork(networkRequest, pendingIntent);
-                pendingIntents.add(pendingIntent);
-            }
-            fail("Registering " + MAX_REQUESTS +
-                    " PendingIntent NetworkRequests did not throw exception");
+            mCm.requestNetwork(networkRequest, new NetworkCallback());
+            fail("Registering " + MAX_REQUESTS + " network requests did not throw exception");
         } catch (TooManyRequestsException expected) {}
-        for (PendingIntent pendingIntent : pendingIntents) {
-            mCm.unregisterNetworkCallback(pendingIntent);
-        }
-        pendingIntents.clear();
+        try {
+            mCm.registerNetworkCallback(networkRequest, new NetworkCallback());
+            fail("Registering " + MAX_REQUESTS + " network callbacks did not throw exception");
+        } catch (TooManyRequestsException expected) {}
+        try {
+            mCm.requestNetwork(networkRequest,
+                PendingIntent.getBroadcast(mContext, 0, new Intent("c"), 0));
+            fail("Registering " + MAX_REQUESTS + " PendingIntent requests did not throw exception");
+        } catch (TooManyRequestsException expected) {}
+        try {
+            mCm.registerNetworkCallback(networkRequest,
+                PendingIntent.getBroadcast(mContext, 0, new Intent("d"), 0));
+            fail("Registering " + MAX_REQUESTS
+                    + " PendingIntent callbacks did not throw exception");
+        } catch (TooManyRequestsException expected) {}
 
-        try {
-            for (int i = 0; i < MAX_REQUESTS + 1; i++) {
-                PendingIntent pendingIntent =
-                        PendingIntent.getBroadcast(mContext, 0, new Intent("a" + i), 0);
-                mCm.registerNetworkCallback(networkRequest, pendingIntent);
-                pendingIntents.add(pendingIntent);
+        for (Object o : registered) {
+            if (o instanceof NetworkCallback) {
+                mCm.unregisterNetworkCallback((NetworkCallback)o);
             }
-            fail("Registering " + MAX_REQUESTS +
-                    " PendingIntent NetworkCallbacks did not throw exception");
-        } catch (TooManyRequestsException expected) {}
-        for (PendingIntent pendingIntent : pendingIntents) {
-            mCm.unregisterNetworkCallback(pendingIntent);
+            if (o instanceof PendingIntent) {
+                mCm.unregisterNetworkCallback((PendingIntent)o);
+            }
         }
-        pendingIntents.clear();
-        waitForIdle(5000);
+        waitForIdle();
 
         // Test that the limit is not hit when MAX_REQUESTS requests are added and removed.
         for (int i = 0; i < MAX_REQUESTS; i++) {
@@ -3266,28 +3310,31 @@
             mCm.unregisterNetworkCallback(networkCallback);
         }
         waitForIdle();
+
         for (int i = 0; i < MAX_REQUESTS; i++) {
             NetworkCallback networkCallback = new NetworkCallback();
             mCm.registerNetworkCallback(networkRequest, networkCallback);
             mCm.unregisterNetworkCallback(networkCallback);
         }
         waitForIdle();
+
         for (int i = 0; i < MAX_REQUESTS; i++) {
             PendingIntent pendingIntent =
-                    PendingIntent.getBroadcast(mContext, 0, new Intent("b" + i), 0);
+                    PendingIntent.getBroadcast(mContext, 0, new Intent("e" + i), 0);
             mCm.requestNetwork(networkRequest, pendingIntent);
             mCm.unregisterNetworkCallback(pendingIntent);
         }
         waitForIdle();
+
         for (int i = 0; i < MAX_REQUESTS; i++) {
             PendingIntent pendingIntent =
-                    PendingIntent.getBroadcast(mContext, 0, new Intent("c" + i), 0);
+                    PendingIntent.getBroadcast(mContext, 0, new Intent("f" + i), 0);
             mCm.registerNetworkCallback(networkRequest, pendingIntent);
             mCm.unregisterNetworkCallback(pendingIntent);
         }
     }
 
-    @SmallTest
+    @Test
     public void testNetworkInfoOfTypeNone() {
         ConditionVariable broadcastCV = waitForConnectivityBroadcasts(1);
 
@@ -3327,7 +3374,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testDeprecatedAndUnsupportedOperations() throws Exception {
         final int TYPE_NONE = ConnectivityManager.TYPE_NONE;
         assertNull(mCm.getNetworkInfo(TYPE_NONE));
@@ -3348,7 +3395,7 @@
         assertException(() -> { mCm.requestRouteToHostAddress(TYPE_NONE, null); }, unsupported);
     }
 
-    @SmallTest
+    @Test
     public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() {
         final NetworkRequest networkRequest = new NetworkRequest.Builder()
                 .addTransportType(TRANSPORT_WIFI).build();
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
new file mode 100644
index 0000000..9e97d84b
--- /dev/null
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -0,0 +1,415 @@
+/*
+ * 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.server;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.INetd;
+import android.net.IpSecAlgorithm;
+import android.net.IpSecConfig;
+import android.net.IpSecManager;
+import android.net.IpSecSpiResponse;
+import android.net.IpSecTransform;
+import android.net.IpSecTransformResponse;
+import android.net.NetworkUtils;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.support.test.filters.SmallTest;
+
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/** Unit tests for {@link IpSecService}. */
+@SmallTest
+@RunWith(Parameterized.class)
+public class IpSecServiceParameterizedTest {
+
+    private static final int TEST_SPI_OUT = 0xD1201D;
+    private static final int TEST_SPI_IN = TEST_SPI_OUT + 1;
+
+    private final String mRemoteAddr;
+
+    @Parameterized.Parameters
+    public static Collection ipSecConfigs() {
+        return Arrays.asList(new Object[][] {{"8.8.4.4"}, {"2601::10"}});
+    }
+
+    private static final byte[] CRYPT_KEY = {
+        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+        0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
+    };
+    private static final byte[] AUTH_KEY = {
+        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
+        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
+    };
+
+    Context mMockContext;
+    INetd mMockNetd;
+    IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
+    IpSecService mIpSecService;
+
+    private static final IpSecAlgorithm AUTH_ALGO =
+            new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4);
+    private static final IpSecAlgorithm CRYPT_ALGO =
+            new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
+    private static final IpSecAlgorithm AEAD_ALGO =
+            new IpSecAlgorithm(IpSecAlgorithm.AUTH_CRYPT_AES_GCM, CRYPT_KEY, CRYPT_KEY.length * 4);
+
+    private static final int[] DIRECTIONS =
+            new int[] {IpSecTransform.DIRECTION_IN, IpSecTransform.DIRECTION_OUT};
+
+    public IpSecServiceParameterizedTest(String remoteAddr) {
+        mRemoteAddr = remoteAddr;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mMockNetd = mock(INetd.class);
+        mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
+        mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
+
+        // Injecting mock netd
+        when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+    }
+
+    @Test
+    public void testIpSecServiceReserveSpi() throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        eq(mRemoteAddr),
+                        eq(TEST_SPI_OUT)))
+                .thenReturn(TEST_SPI_OUT);
+
+        IpSecSpiResponse spiResp =
+                mIpSecService.reserveSecurityParameterIndex(
+                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+        assertEquals(IpSecManager.Status.OK, spiResp.status);
+        assertEquals(TEST_SPI_OUT, spiResp.spi);
+    }
+
+    @Test
+    public void testReleaseSecurityParameterIndex() throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        eq(mRemoteAddr),
+                        eq(TEST_SPI_OUT)))
+                .thenReturn(TEST_SPI_OUT);
+
+        IpSecSpiResponse spiResp =
+                mIpSecService.reserveSecurityParameterIndex(
+                        IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT, new Binder());
+
+        mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
+
+        verify(mMockNetd)
+                .ipSecDeleteSecurityAssociation(
+                        eq(spiResp.resourceId),
+                        anyInt(),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI_OUT));
+    }
+
+    private int getNewSpiResourceId(int direction, String remoteAddress, int returnSpi)
+            throws Exception {
+        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
+                .thenReturn(returnSpi);
+
+        IpSecSpiResponse spi =
+                mIpSecService.reserveSecurityParameterIndex(
+                        direction,
+                        NetworkUtils.numericToInetAddress(remoteAddress).getHostAddress(),
+                        IpSecManager.INVALID_SECURITY_PARAMETER_INDEX,
+                        new Binder());
+        return spi.resourceId;
+    }
+
+    private void addDefaultSpisAndRemoteAddrToIpSecConfig(IpSecConfig config) throws Exception {
+        config.setSpiResourceId(
+                IpSecTransform.DIRECTION_OUT,
+                getNewSpiResourceId(IpSecTransform.DIRECTION_OUT, mRemoteAddr, TEST_SPI_OUT));
+        config.setSpiResourceId(
+                IpSecTransform.DIRECTION_IN,
+                getNewSpiResourceId(IpSecTransform.DIRECTION_IN, mRemoteAddr, TEST_SPI_IN));
+        config.setRemoteAddress(mRemoteAddr);
+    }
+
+    private void addAuthAndCryptToIpSecConfig(IpSecConfig config) throws Exception {
+        for (int direction : DIRECTIONS) {
+            config.setEncryption(direction, CRYPT_ALGO);
+            config.setAuthentication(direction, AUTH_ALGO);
+        }
+    }
+
+    @Test
+    public void testCreateTransportModeTransform() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(TEST_SPI_OUT),
+                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
+                        eq(AUTH_KEY),
+                        anyInt(),
+                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        eq(""),
+                        eq(new byte[] {}),
+                        eq(0),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(TEST_SPI_IN),
+                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
+                        eq(AUTH_KEY),
+                        anyInt(),
+                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        eq(""),
+                        eq(new byte[] {}),
+                        eq(0),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+    }
+
+    @Test
+    public void testCreateTransportModeTransformAead() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_OUT, AEAD_ALGO);
+        ipSecConfig.setAuthenticatedEncryption(IpSecTransform.DIRECTION_IN, AEAD_ALGO);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        assertEquals(IpSecManager.Status.OK, createTransformResp.status);
+
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(TEST_SPI_OUT),
+                        eq(""),
+                        eq(new byte[] {}),
+                        eq(0),
+                        eq(""),
+                        eq(new byte[] {}),
+                        eq(0),
+                        eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+        verify(mMockNetd)
+                .ipSecAddSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        anyInt(),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        anyLong(),
+                        eq(TEST_SPI_IN),
+                        eq(""),
+                        eq(new byte[] {}),
+                        eq(0),
+                        eq(""),
+                        eq(new byte[] {}),
+                        eq(0),
+                        eq(IpSecAlgorithm.AUTH_CRYPT_AES_GCM),
+                        eq(CRYPT_KEY),
+                        anyInt(),
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+    }
+
+    @Test
+    public void testCreateInvalidConfigAeadWithAuth() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        for (int direction : DIRECTIONS) {
+            ipSecConfig.setAuthentication(direction, AUTH_ALGO);
+            ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+        }
+
+        try {
+            mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+            fail(
+                    "IpSecService should have thrown an error on authentication being"
+                            + " enabled with authenticated encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testCreateInvalidConfigAeadWithCrypt() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        for (int direction : DIRECTIONS) {
+            ipSecConfig.setEncryption(direction, CRYPT_ALGO);
+            ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+        }
+
+        try {
+            mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+            fail(
+                    "IpSecService should have thrown an error on encryption being"
+                            + " enabled with authenticated encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testCreateInvalidConfigAeadWithAuthAndCrypt() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+
+        for (int direction : DIRECTIONS) {
+            ipSecConfig.setAuthentication(direction, AUTH_ALGO);
+            ipSecConfig.setEncryption(direction, CRYPT_ALGO);
+            ipSecConfig.setAuthenticatedEncryption(direction, AEAD_ALGO);
+        }
+
+        try {
+            mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+            fail(
+                    "IpSecService should have thrown an error on authentication and encryption being"
+                            + " enabled with authenticated encryption");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testDeleteTransportModeTransform() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        mIpSecService.deleteTransportModeTransform(createTransformResp.resourceId);
+
+        verify(mMockNetd)
+                .ipSecDeleteSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI_OUT));
+        verify(mMockNetd)
+                .ipSecDeleteSecurityAssociation(
+                        eq(createTransformResp.resourceId),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI_IN));
+    }
+
+    @Test
+    public void testApplyTransportModeTransform() throws Exception {
+        IpSecConfig ipSecConfig = new IpSecConfig();
+        addDefaultSpisAndRemoteAddrToIpSecConfig(ipSecConfig);
+        addAuthAndCryptToIpSecConfig(ipSecConfig);
+
+        IpSecTransformResponse createTransformResp =
+                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+
+        int resourceId = createTransformResp.resourceId;
+        mIpSecService.applyTransportModeTransform(pfd, resourceId);
+
+        verify(mMockNetd)
+                .ipSecApplyTransportModeTransform(
+                        eq(pfd.getFileDescriptor()),
+                        eq(resourceId),
+                        eq(IpSecTransform.DIRECTION_OUT),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI_OUT));
+        verify(mMockNetd)
+                .ipSecApplyTransportModeTransform(
+                        eq(pfd.getFileDescriptor()),
+                        eq(resourceId),
+                        eq(IpSecTransform.DIRECTION_IN),
+                        anyString(),
+                        anyString(),
+                        eq(TEST_SPI_IN));
+    }
+
+    @Test
+    public void testRemoveTransportModeTransform() throws Exception {
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
+        mIpSecService.removeTransportModeTransform(pfd, 1);
+
+        verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
+    }
+}
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index 23fee28..7b07038 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -21,54 +21,44 @@
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.net.INetd;
-import android.net.IpSecAlgorithm;
-import android.net.IpSecConfig;
 import android.net.IpSecManager;
 import android.net.IpSecSpiResponse;
 import android.net.IpSecTransform;
-import android.net.IpSecTransformResponse;
 import android.net.IpSecUdpEncapResponse;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.system.ErrnoException;
 import android.system.Os;
+
 import java.io.FileDescriptor;
 import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.UnknownHostException;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 /** Unit tests for {@link IpSecService}. */
 @SmallTest
-@RunWith(JUnit4.class)
+@RunWith(AndroidJUnit4.class)
 public class IpSecServiceTest {
 
     private static final int DROID_SPI = 0xD1201D;
-    private static final int DROID_SPI2 = DROID_SPI + 1;
     private static final int TEST_UDP_ENCAP_INVALID_PORT = 100;
     private static final int TEST_UDP_ENCAP_PORT_OUT_RANGE = 100000;
-    private static final int TEST_UDP_ENCAP_PORT = 34567;
-
-    private static final String IPV4_LOOPBACK = "127.0.0.1";
-    private static final String IPV4_ADDR = "192.168.0.2";
 
     private static final InetAddress INADDR_ANY;
 
@@ -80,21 +70,6 @@
         }
     }
 
-    private static final int[] DIRECTIONS =
-            new int[] {IpSecTransform.DIRECTION_OUT, IpSecTransform.DIRECTION_IN};
-    private static final byte[] CRYPT_KEY = {
-        0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
-        0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
-        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
-        0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
-    };
-    private static final byte[] AUTH_KEY = {
-        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
-        0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F
-    };
-
     Context mMockContext;
     INetd mMockNetd;
     IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
@@ -118,44 +93,6 @@
     }
 
     @Test
-    public void testIpSecServiceReserveSpi() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(IPV4_LOOPBACK),
-                        eq(DROID_SPI)))
-                .thenReturn(DROID_SPI);
-
-        IpSecSpiResponse spiResp =
-                mIpSecService.reserveSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, IPV4_LOOPBACK, DROID_SPI, new Binder());
-        assertEquals(IpSecManager.Status.OK, spiResp.status);
-        assertEquals(DROID_SPI, spiResp.spi);
-    }
-
-    @Test
-    public void testReleaseSecurityParameterIndex() throws Exception {
-        when(mMockNetd.ipSecAllocateSpi(
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        eq(IPV4_LOOPBACK),
-                        eq(DROID_SPI)))
-                .thenReturn(DROID_SPI);
-
-        IpSecSpiResponse spiResp =
-                mIpSecService.reserveSecurityParameterIndex(
-                        IpSecTransform.DIRECTION_OUT, IPV4_LOOPBACK, DROID_SPI, new Binder());
-
-        mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId);
-
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(spiResp.resourceId), anyInt(), anyString(), anyString(), eq(DROID_SPI));
-    }
-
-    @Test
     public void testReleaseInvalidSecurityParameterIndex() throws Exception {
         try {
             mIpSecService.releaseSecurityParameterIndex(1);
@@ -238,6 +175,7 @@
                 mIpSecService.openUdpEncapsulationSocket(0, new Binder());
         assertNotNull(udpEncapResp);
         assertEquals(IpSecManager.Status.OK, udpEncapResp.status);
+        assertNotEquals(0, udpEncapResp.port);
         mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
         udpEncapResp.fileDescriptor.close();
     }
@@ -285,108 +223,6 @@
         }
     }
 
-    IpSecConfig buildIpSecConfig() throws Exception {
-        IpSecManager ipSecManager = new IpSecManager(mIpSecService);
-
-        // Mocking the netd to allocate SPI
-        when(mMockNetd.ipSecAllocateSpi(anyInt(), anyInt(), anyString(), anyString(), anyInt()))
-                .thenReturn(DROID_SPI)
-                .thenReturn(DROID_SPI2);
-
-        IpSecAlgorithm encryptAlgo = new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY);
-        IpSecAlgorithm authAlgo =
-                new IpSecAlgorithm(IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 8);
-
-        InetAddress localAddr = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
-
-        /** Allocate and add SPI records in the IpSecService through IpSecManager interface. */
-        IpSecManager.SecurityParameterIndex outSpi =
-                ipSecManager.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_OUT, localAddr);
-        IpSecManager.SecurityParameterIndex inSpi =
-                ipSecManager.reserveSecurityParameterIndex(IpSecTransform.DIRECTION_IN, localAddr);
-
-        IpSecConfig ipSecConfig =
-                new IpSecTransform.Builder(mMockContext)
-                        .setSpi(IpSecTransform.DIRECTION_OUT, outSpi)
-                        .setSpi(IpSecTransform.DIRECTION_IN, inSpi)
-                        .setEncryption(IpSecTransform.DIRECTION_OUT, encryptAlgo)
-                        .setAuthentication(IpSecTransform.DIRECTION_OUT, authAlgo)
-                        .setEncryption(IpSecTransform.DIRECTION_IN, encryptAlgo)
-                        .setAuthentication(IpSecTransform.DIRECTION_IN, authAlgo)
-                        .getIpSecConfig();
-        return ipSecConfig;
-    }
-
-    @Test
-    public void testCreateTransportModeTransform() throws Exception {
-        IpSecConfig ipSecConfig = buildIpSecConfig();
-
-        IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
-        assertEquals(IpSecManager.Status.OK, createTransformResp.status);
-
-        verify(mMockNetd)
-                .ipSecAddSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        anyLong(),
-                        eq(DROID_SPI),
-                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
-                        eq(AUTH_KEY),
-                        anyInt(),
-                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
-                        eq(CRYPT_KEY),
-                        anyInt(),
-                        anyInt(),
-                        anyInt(),
-                        anyInt());
-        verify(mMockNetd)
-                .ipSecAddSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        anyInt(),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        anyLong(),
-                        eq(DROID_SPI2),
-                        eq(IpSecAlgorithm.AUTH_HMAC_SHA256),
-                        eq(AUTH_KEY),
-                        anyInt(),
-                        eq(IpSecAlgorithm.CRYPT_AES_CBC),
-                        eq(CRYPT_KEY),
-                        anyInt(),
-                        anyInt(),
-                        anyInt(),
-                        anyInt());
-    }
-
-    @Test
-    public void testDeleteTransportModeTransform() throws Exception {
-        IpSecConfig ipSecConfig = buildIpSecConfig();
-
-        IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
-        mIpSecService.deleteTransportModeTransform(createTransformResp.resourceId);
-
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        eq(DROID_SPI));
-        verify(mMockNetd)
-                .ipSecDeleteSecurityAssociation(
-                        eq(createTransformResp.resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(DROID_SPI2));
-    }
-
     @Test
     public void testDeleteInvalidTransportModeTransform() throws Exception {
         try {
@@ -397,39 +233,31 @@
     }
 
     @Test
-    public void testApplyTransportModeTransform() throws Exception {
-        IpSecConfig ipSecConfig = buildIpSecConfig();
-
-        IpSecTransformResponse createTransformResp =
-                mIpSecService.createTransportModeTransform(ipSecConfig, new Binder());
-        ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
-
-        int resourceId = createTransformResp.resourceId;
-        mIpSecService.applyTransportModeTransform(pfd, resourceId);
-
-        verify(mMockNetd)
-                .ipSecApplyTransportModeTransform(
-                        eq(pfd.getFileDescriptor()),
-                        eq(resourceId),
-                        eq(IpSecTransform.DIRECTION_OUT),
-                        anyString(),
-                        anyString(),
-                        eq(DROID_SPI));
-        verify(mMockNetd)
-                .ipSecApplyTransportModeTransform(
-                        eq(pfd.getFileDescriptor()),
-                        eq(resourceId),
-                        eq(IpSecTransform.DIRECTION_IN),
-                        anyString(),
-                        anyString(),
-                        eq(DROID_SPI2));
-    }
-
-    @Test
     public void testRemoveTransportModeTransform() throws Exception {
         ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(new Socket());
         mIpSecService.removeTransportModeTransform(pfd, 1);
 
         verify(mMockNetd).ipSecRemoveTransportModeTransform(pfd.getFileDescriptor());
     }
+
+    @Test
+    public void testValidateIpAddresses() throws Exception {
+        String[] invalidAddresses =
+                new String[] {"www.google.com", "::", "2001::/64", "0.0.0.0", ""};
+        for (String address : invalidAddresses) {
+            try {
+                IpSecSpiResponse spiResp =
+                        mIpSecService.reserveSecurityParameterIndex(
+                                IpSecTransform.DIRECTION_OUT, address, DROID_SPI, new Binder());
+                fail("Invalid address was passed through IpSecService validation: " + address);
+            } catch (IllegalArgumentException e) {
+            } catch (Exception e) {
+                fail(
+                        "Invalid InetAddress was not caught in validation: "
+                                + address
+                                + ", Exception: "
+                                + e);
+            }
+        }
+    }
 }
diff --git a/tests/net/java/com/android/server/NsdServiceTest.java b/tests/net/java/com/android/server/NsdServiceTest.java
index 2e49c98..b88c784 100644
--- a/tests/net/java/com/android/server/NsdServiceTest.java
+++ b/tests/net/java/com/android/server/NsdServiceTest.java
@@ -77,7 +77,10 @@
 
     @After
     public void tearDown() throws Exception {
-        mThread.quit();
+        if (mThread != null) {
+            mThread.quit();
+            mThread = null;
+        }
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 2624176..ad6ebf9 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
@@ -198,21 +198,20 @@
 
     @Test
     public void testDefaultNetworkEventSerialization() {
-        ConnectivityMetricsEvent ev = describeIpEvent(
-                aType(DefaultNetworkEvent.class),
-                anInt(102),
-                anIntArray(1, 2, 3),
-                anInt(101),
-                aBool(true),
-                aBool(false));
+        DefaultNetworkEvent ev = new DefaultNetworkEvent();
+        ev.netId = 102;
+        ev.prevNetId = 101;
+        ev.transportTypes = new int[]{1, 2, 3};
+        ev.prevIPv4 = true;
+        ev.prevIPv6 = true;
 
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
                 "  if_name: \"\"",
                 "  link_layer: 0",
-                "  network_id: 0",
-                "  time_ms: 1",
+                "  network_id: 102",
+                "  time_ms: 0",
                 "  transports: 0",
                 "  default_network_event <",
                 "    default_network_duration_ms: 0",
@@ -226,7 +225,7 @@
                 "    previous_network_id <",
                 "      network_id: 101",
                 "    >",
-                "    previous_network_ip_support: 1",
+                "    previous_network_ip_support: 3",
                 "    transport_types: 1",
                 "    transport_types: 2",
                 "    transport_types: 3",
@@ -234,7 +233,7 @@
                 ">",
                 "version: 2\n");
 
-        verifySerialization(want, ev);
+        verifySerialization(want, IpConnectivityEventBuilder.toProto(ev));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index a395c48..6c1decc 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -18,6 +18,7 @@
 
 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -30,6 +31,10 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityMetricsEvent;
 import android.net.IIpConnectivityMetrics;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.metrics.ApfProgramEvent;
@@ -41,18 +46,22 @@
 import android.net.metrics.IpReachabilityEvent;
 import android.net.metrics.RaEvent;
 import android.net.metrics.ValidationProbeEvent;
-import android.system.OsConstants;
 import android.os.Parcelable;
 import android.support.test.runner.AndroidJUnit4;
+import android.system.OsConstants;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
+
+import com.android.internal.util.BitUtils;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -162,6 +171,144 @@
     }
 
     @Test
+    public void testDefaultNetworkEvents() throws Exception {
+        final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+        final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
+
+        NetworkAgentInfo[][] defaultNetworks = {
+            // nothing -> cell
+            {null, makeNai(100, 10, false, true, cell)},
+            // cell -> wifi
+            {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)},
+            // wifi -> nothing
+            {makeNai(101, 60, true, false, wifi), null},
+            // nothing -> cell
+            {null, makeNai(102, 10, true, true, cell)},
+            // cell -> wifi
+            {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
+        };
+
+        for (NetworkAgentInfo[] pair : defaultNetworks) {
+            mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(pair[1], pair[0]);
+        }
+
+        String want = String.join("\n",
+                "dropped_events: 0",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 100",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 0",
+                "    >",
+                "    previous_network_ip_support: 0",
+                "    transport_types: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 101",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 100",
+                "    >",
+                "    previous_network_ip_support: 3",
+                "    transport_types: 1",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 0",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 0",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 101",
+                "    >",
+                "    previous_network_ip_support: 1",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 102",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 102",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 0",
+                "    >",
+                "    previous_network_ip_support: 0",
+                "    transport_types: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 103",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 103",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 102",
+                "    >",
+                "    previous_network_ip_support: 3",
+                "    transport_types: 1",
+                "  >",
+                ">",
+                "version: 2\n");
+
+        verifySerialization(want, getdump("flush"));
+    }
+
+    @Test
     public void testEndToEndLogging() throws Exception {
         // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
@@ -194,7 +341,6 @@
         Parcelable[] events = {
             new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
             new DhcpClientEvent("SomeState", 192),
-            new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
             new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
             validationEv,
             apfStats,
@@ -233,6 +379,13 @@
         wakeupEvent("wlan0", 10008);
         wakeupEvent("rmnet0", 1000);
 
+        final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+        final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
+        NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
+        NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
+        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(cellNai, null);
+        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(wifiNai, cellNai);
+
         String want = String.join("\n",
                 "dropped_events: 0",
                 "events <",
@@ -264,30 +417,6 @@
                 "  network_id: 0",
                 "  time_ms: 300",
                 "  transports: 0",
-                "  default_network_event <",
-                "    default_network_duration_ms: 0",
-                "    final_score: 0",
-                "    initial_score: 0",
-                "    ip_support: 0",
-                "    network_id <",
-                "      network_id: 102",
-                "    >",
-                "    no_default_network_duration_ms: 0",
-                "    previous_network_id <",
-                "      network_id: 101",
-                "    >",
-                "    previous_network_ip_support: 1",
-                "    transport_types: 1",
-                "    transport_types: 2",
-                "    transport_types: 3",
-                "  >",
-                ">",
-                "events <",
-                "  if_name: \"\"",
-                "  link_layer: 4",
-                "  network_id: 0",
-                "  time_ms: 400",
-                "  transports: 0",
                 "  ip_provisioning_event <",
                 "    event_type: 1",
                 "    if_name: \"\"",
@@ -298,7 +427,7 @@
                 "  if_name: \"\"",
                 "  link_layer: 4",
                 "  network_id: 0",
-                "  time_ms: 500",
+                "  time_ms: 400",
                 "  transports: 0",
                 "  validation_probe_event <",
                 "    latency_ms: 40730",
@@ -310,7 +439,7 @@
                 "  if_name: \"\"",
                 "  link_layer: 4",
                 "  network_id: 0",
-                "  time_ms: 600",
+                "  time_ms: 500",
                 "  transports: 0",
                 "  apf_statistics <",
                 "    dropped_ras: 2",
@@ -331,7 +460,7 @@
                 "  if_name: \"\"",
                 "  link_layer: 4",
                 "  network_id: 0",
-                "  time_ms: 700",
+                "  time_ms: 600",
                 "  transports: 0",
                 "  ra_event <",
                 "    dnssl_lifetime: -1",
@@ -344,6 +473,50 @@
                 ">",
                 "events <",
                 "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 100",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 100",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 0",
+                "    >",
+                "    previous_network_ip_support: 0",
+                "    transport_types: 0",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
+                "  link_layer: 0",
+                "  network_id: 101",
+                "  time_ms: 0",
+                "  transports: 0",
+                "  default_network_event <",
+                "    default_network_duration_ms: 0",
+                "    final_score: 0",
+                "    initial_score: 0",
+                "    ip_support: 0",
+                "    network_id <",
+                "      network_id: 101",
+                "    >",
+                "    no_default_network_duration_ms: 0",
+                "    previous_network_id <",
+                "      network_id: 100",
+                "    >",
+                "    previous_network_ip_support: 2",
+                "    transport_types: 1",
+                "  >",
+                ">",
+                "events <",
+                "  if_name: \"\"",
                 "  link_layer: 4",
                 "  network_id: 100",
                 "  time_ms: 0",
@@ -471,6 +644,26 @@
         mNetdListener.onWakeupEvent(prefix, uid, uid, 0);
     }
 
+    NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) {
+        NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
+        when(nai.network()).thenReturn(new Network(netId));
+        when(nai.getCurrentScore()).thenReturn(score);
+        nai.linkProperties = new LinkProperties();
+        nai.networkCapabilities = new NetworkCapabilities();
+        for (int t : BitUtils.unpackBits(transports)) {
+            nai.networkCapabilities.addTransportType(t);
+        }
+        if (ipv4) {
+            nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24"));
+            nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
+        }
+        if (ipv6) {
+            nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64"));
+            nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0")));
+        }
+        return nai;
+    }
+
     List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
         ArgumentCaptor<ConnectivityMetricsEvent> captor =
                 ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 77956be..354cf2f 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -16,24 +16,8 @@
 
 package com.android.server.connectivity;
 
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkMisc;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.text.format.DateUtils;
-import com.android.internal.R;
-import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkNotificationManager;
-import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
-import junit.framework.TestCase;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -44,7 +28,32 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.reset;
 
-public class LingerMonitorTest extends TestCase {
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkMisc;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
+import android.text.format.DateUtils;
+
+import com.android.internal.R;
+import com.android.server.ConnectivityService;
+import com.android.server.connectivity.NetworkNotificationManager;
+import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LingerMonitorTest {
     static final String CELLULAR = "CELLULAR";
     static final String WIFI     = "WIFI";
 
@@ -62,6 +71,7 @@
     @Mock NetworkNotificationManager mNotifier;
     @Mock Resources mResources;
 
+    @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mCtx.getResources()).thenReturn(mResources);
@@ -71,7 +81,7 @@
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
     }
 
-    @SmallTest
+    @Test
     public void testTransitions() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         NetworkAgentInfo nai1 = wifiNai(100);
@@ -81,7 +91,7 @@
         assertFalse(mMonitor.isNotificationEnabled(nai2, nai1));
     }
 
-    @SmallTest
+    @Test
     public void testNotificationOnLinger() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -92,7 +102,7 @@
         verifyNotification(from, to);
     }
 
-    @SmallTest
+    @Test
     public void testToastOnLinger() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
@@ -103,7 +113,7 @@
         verifyToast(from, to);
     }
 
-    @SmallTest
+    @Test
     public void testNotificationClearedAfterDisconnect() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -117,7 +127,7 @@
         verify(mNotifier, times(1)).clearNotification(100);
     }
 
-    @SmallTest
+    @Test
     public void testNotificationClearedAfterSwitchingBack() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -131,7 +141,7 @@
         verify(mNotifier, times(1)).clearNotification(100);
     }
 
-    @SmallTest
+    @Test
     public void testUniqueToast() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
@@ -149,7 +159,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testMultipleNotifications() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -168,7 +178,7 @@
         verifyNotification(wifi2, cell);
     }
 
-    @SmallTest
+    @Test
     public void testRateLimiting() throws InterruptedException {
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT);
 
@@ -194,7 +204,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testDailyLimiting() throws InterruptedException {
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT);
 
@@ -221,7 +231,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testUniqueNotification() {
         setNotificationSwitch(transition(WIFI, CELLULAR));
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION);
@@ -238,7 +248,7 @@
         verifyNotification(from, to);
     }
 
-    @SmallTest
+    @Test
     public void testIgnoreNeverValidatedNetworks() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -250,7 +260,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testIgnoreCurrentlyValidatedNetworks() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -262,7 +272,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testNoNotificationType() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch();
@@ -273,7 +283,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testNoTransitionToNotify() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE);
         setNotificationSwitch(transition(WIFI, CELLULAR));
@@ -284,7 +294,7 @@
         verifyNoNotifications();
     }
 
-    @SmallTest
+    @Test
     public void testDifferentTransitionToNotify() {
         setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST);
         setNotificationSwitch(transition(CELLULAR, WIFI));
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index de28de6..dfe31bd 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -126,7 +126,6 @@
         mLooper.dispatchNext();
 
         verify(mNms).getInterfaceConfig(eq(STACKED_IFACE));
-        verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(false));
         verify(mConnectivity).handleUpdateLinkProperties(eq(mNai), c.capture());
         assertFalse(c.getValue().getStackedLinks().isEmpty());
         assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -136,7 +135,6 @@
         nat.stop();
 
         verify(mNms).stopClatd(eq(BASE_IFACE));
-        verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(true));
 
         // Stacked interface removed notification arrives.
         nat.interfaceRemoved(STACKED_IFACE);
@@ -167,7 +165,6 @@
         mLooper.dispatchNext();
 
         verify(mNms).getInterfaceConfig(eq(STACKED_IFACE));
-        verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(false));
         verify(mConnectivity, times(1)).handleUpdateLinkProperties(eq(mNai), c.capture());
         assertFalse(c.getValue().getStackedLinks().isEmpty());
         assertTrue(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
@@ -179,7 +176,6 @@
 
         verify(mNms).unregisterObserver(eq(nat));
         verify(mNms).stopClatd(eq(BASE_IFACE));
-        verify(mNms).setInterfaceIpv6NdOffload(eq(BASE_IFACE), eq(true));
         verify(mConnectivity, times(2)).handleUpdateLinkProperties(eq(mNai), c.capture());
         assertTrue(c.getValue().getStackedLinks().isEmpty());
         assertFalse(c.getValue().getAllInterfaceNames().contains(STACKED_IFACE));
diff --git a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 911347c..125fe72 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -34,20 +34,29 @@
 import android.content.res.Resources;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import junit.framework.TestCase;
+
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-public class NetworkNotificationManagerTest extends TestCase {
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkNotificationManagerTest {
 
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
@@ -71,6 +80,7 @@
 
     NetworkNotificationManager mManager;
 
+    @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mCaptor = ArgumentCaptor.forClass(Notification.class);
@@ -87,7 +97,7 @@
         mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager);
     }
 
-    @SmallTest
+    @Test
     public void testNotificationsShownAndCleared() {
         final int NETWORK_ID_BASE = 100;
         List<NotificationType> types = Arrays.asList(NotificationType.values());
@@ -117,7 +127,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testNoInternetNotificationsNotShownForCellular() {
         mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false);
         mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false);
@@ -131,7 +141,7 @@
         verify(mNotificationManager, times(1)).notifyAsUser(eq(tag), eq(eventId), any(), any());
     }
 
-    @SmallTest
+    @Test
     public void testNotificationsNotShownIfNoInternetCapability() {
         mWifiNai.networkCapabilities = new NetworkCapabilities();
         mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
@@ -142,7 +152,7 @@
         verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any());
     }
 
-    @SmallTest
+    @Test
     public void testDuplicatedNotificationsNoInternetThenSignIn() {
         final int id = 101;
         final String tag = NetworkNotificationManager.tagFor(id);
@@ -164,7 +174,7 @@
         verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(SIGN_IN.eventId), any());
     }
 
-    @SmallTest
+    @Test
     public void testDuplicatedNotificationsSignInThenNoInternet() {
         final int id = 101;
         final String tag = NetworkNotificationManager.tagFor(id);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 4f18da7..fe396c3 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -20,6 +20,9 @@
 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
 import static android.content.pm.UserInfo.FLAG_PRIMARY;
 import static android.content.pm.UserInfo.FLAG_RESTRICTED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.AdditionalMatchers.*;
 import static org.mockito.Mockito.*;
 
@@ -42,14 +45,17 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import com.android.internal.R;
 import com.android.internal.net.VpnConfig;
 
+import org.junit.runner.RunWith;
+import org.junit.Before;
+import org.junit.Test;
 import org.mockito.Answers;
 import org.mockito.InOrder;
 import org.mockito.Mock;
@@ -61,13 +67,16 @@
 import java.util.Map;
 import java.util.Set;
 
+
 /**
  * Tests for {@link Vpn}.
  *
  * Build, install and run with:
- *  runtest --path java/com/android/server/connectivity/VpnTest.java
+ *  runtest frameworks-net -c com.android.server.connectivity.VpnTest
  */
-public class VpnTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class VpnTest {
     private static final String TAG = "VpnTest";
 
     // Mock users
@@ -106,7 +115,7 @@
     @Mock private NotificationManager mNotificationManager;
     @Mock private Vpn.SystemServices mSystemServices;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
@@ -130,7 +139,7 @@
         doNothing().when(mNetService).registerObserver(any());
     }
 
-    @SmallTest
+    @Test
     public void testRestrictedProfilesAreAddedToVpn() {
         setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
 
@@ -144,7 +153,7 @@
         })), ranges);
     }
 
-    @SmallTest
+    @Test
     public void testManagedProfilesAreNotAddedToVpn() {
         setMockedUsers(primaryUser, managedProfileA);
 
@@ -157,7 +166,7 @@
         })), ranges);
     }
 
-    @SmallTest
+    @Test
     public void testAddUserToVpnOnlyAddsOneUser() {
         setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
 
@@ -170,7 +179,7 @@
         })), ranges);
     }
 
-    @SmallTest
+    @Test
     public void testUidWhiteAndBlacklist() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
@@ -195,7 +204,7 @@
         })), disallow);
     }
 
-    @SmallTest
+    @Test
     public void testLockdownChangingPackage() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         final UidRange user = UidRange.createForUser(primaryUser.id);
@@ -230,7 +239,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[3]);
     }
 
-    @SmallTest
+    @Test
     public void testLockdownAddingAProfile() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
         setMockedUsers(primaryUser);
@@ -270,7 +279,7 @@
         }));
     }
 
-    @SmallTest
+    @Test
     public void testLockdownRuleRepeatability() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
 
@@ -293,7 +302,7 @@
         verify(mNetService, times(2)).setAllowOnlyVpnForUids(anyBoolean(), any(UidRange[].class));
     }
 
-    @SmallTest
+    @Test
     public void testLockdownRuleReversibility() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
 
@@ -322,7 +331,7 @@
         order.verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(entireUser));
     }
 
-    @SmallTest
+    @Test
     public void testIsAlwaysOnPackageSupported() throws Exception {
         final Vpn vpn = createVpn(primaryUser.id);
 
@@ -356,7 +365,7 @@
         assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
     }
 
-    @SmallTest
+    @Test
     public void testNotificationShownForAlwaysOnApp() {
         final UserHandle userHandle = UserHandle.of(primaryUser.id);
         final Vpn vpn = createVpn(primaryUser.id);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
index b5d333b..f58ea7e 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/SimChangeListenerTest.java
@@ -48,8 +48,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SimChangeListenerTest {
-    private static final int EVENT_UNM_UPDATE = 1;
-
     @Mock private Context mContext;
     private BroadcastInterceptingContext mServiceContext;
     private Handler mHandler;
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 9c10264..dbaf8e6 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -26,6 +26,9 @@
 import static android.os.Process.myUid;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import static com.android.server.net.NetworkStatsCollection.multiplySafe;
 
@@ -37,11 +40,12 @@
 import android.net.NetworkTemplate;
 import android.os.Process;
 import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telephony.SubscriptionPlan;
 import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.format.DateUtils;
 import android.util.RecurrenceRule;
 
@@ -64,11 +68,17 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Tests for {@link NetworkStatsCollection}.
  */
+@RunWith(AndroidJUnit4.class)
 @SmallTest
-public class NetworkStatsCollectionTest extends AndroidTestCase {
+public class NetworkStatsCollectionTest {
 
     private static final String TEST_FILE = "test.bin";
     private static final String TEST_IMSI = "310260000000000";
@@ -79,18 +89,15 @@
 
     private static Clock sOriginalClock;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
         sOriginalClock = RecurrenceRule.sClock;
-
         // ignore any device overlay while testing
         NetworkTemplate.forceAllNetworkTypes();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    @After
+    public void tearDown() throws Exception {
         RecurrenceRule.sClock = sOriginalClock;
     }
 
@@ -98,8 +105,10 @@
         RecurrenceRule.sClock = Clock.fixed(instant, ZoneId.systemDefault());
     }
 
+    @Test
     public void testReadLegacyNetwork() throws Exception {
-        final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+        final File testFile =
+                new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
         stageFile(R.raw.netstats_v1, testFile);
 
         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
@@ -124,8 +133,10 @@
                 636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
     }
 
+    @Test
     public void testReadLegacyUid() throws Exception {
-        final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+        final File testFile =
+                new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
         stageFile(R.raw.netstats_uid_v4, testFile);
 
         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
@@ -150,8 +161,10 @@
                 637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
     }
 
+    @Test
     public void testReadLegacyUidTags() throws Exception {
-        final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+        final File testFile =
+                new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
         stageFile(R.raw.netstats_uid_v4, testFile);
 
         final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
@@ -176,6 +189,7 @@
                 77017831L, 100995L, 35436758L, 92344L);
     }
 
+    @Test
     public void testStartEndAtomicBuckets() throws Exception {
         final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
 
@@ -190,6 +204,7 @@
         assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
     }
 
+    @Test
     public void testAccessLevels() throws Exception {
         final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
         final NetworkStats.Entry entry = new NetworkStats.Entry();
@@ -250,8 +265,10 @@
                 0, NetworkStatsAccess.Level.DEVICE);
     }
 
+    @Test
     public void testAugmentPlan() throws Exception {
-        final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+        final File testFile =
+                new File(InstrumentationRegistry.getContext().getFilesDir(), TEST_FILE);
         stageFile(R.raw.netstats_v1, testFile);
 
         final NetworkStatsCollection emptyCollection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
@@ -439,6 +456,7 @@
         }
     }
 
+    @Test
     public void testAugmentPlanGigantic() throws Exception {
         // We're in the future, but not that far off
         setClock(Instant.parse("2012-06-01T00:00:00.00Z"));
@@ -461,6 +479,7 @@
         assertEquals(4_939_212_386L, getHistory(large, plan, TIME_A, TIME_C).getTotalBytes());
     }
 
+    @Test
     public void testRounding() throws Exception {
         final NetworkStatsCollection coll = new NetworkStatsCollection(HOUR_IN_MILLIS);
 
@@ -482,6 +501,7 @@
         assertEquals(TIME_A - HOUR_IN_MILLIS, coll.roundDown(TIME_A - 1));
     }
 
+    @Test
     public void testMultiplySafe() {
         assertEquals(25, multiplySafe(50, 1, 2));
         assertEquals(100, multiplySafe(50, 2, 1));
@@ -510,7 +530,7 @@
         InputStream in = null;
         OutputStream out = null;
         try {
-            in = getContext().getResources().openRawResource(rawId);
+            in = InstrumentationRegistry.getContext().getResources().openRawResource(rawId);
             out = new FileOutputStream(file);
             Streams.copy(in, out);
         } finally {
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 814a626..7f1bc5b 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -85,8 +85,8 @@
 import android.os.PowerManager;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.SmallTest;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 import android.util.TrustedTime;
 
@@ -201,7 +201,6 @@
               ArgumentCaptor.forClass(INetworkManagementEventObserver.class);
         verify(mNetManager).registerObserver(networkObserver.capture());
         mNetworkObserver = networkObserver.getValue();
-
     }
 
     @After
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 4aeae70..9c4da1f 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -24,7 +24,6 @@
 import android.view.IWindowManager;
 import junit.framework.TestCase;
 
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 /**
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 5e85802..cb87737 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -739,12 +739,8 @@
 
     AssetManager assets;
     int32_t assetsCookie;
-    if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
-        fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
-        return 1;
-    }
 
-    // Now add any dependencies passed in.
+    // Add any dependencies passed in.
     for (size_t i = 0; i < bundle->getPackageIncludes().size(); i++) {
       const String8& assetPath = bundle->getPackageIncludes()[i];
       if (!assets.addAssetPath(assetPath, NULL)) {
@@ -753,6 +749,11 @@
       }
     }
 
+    if (!assets.addAssetPath(String8(filename), &assetsCookie)) {
+        fprintf(stderr, "ERROR: dump failed because assets could not be loaded\n");
+        return 1;
+    }
+
     // Make a dummy config for retrieving resources...  we need to supply
     // non-default values for some configs so that we can retrieve resources
     // in the app that don't have a default.  The most important of these is
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 5f586a1..627a231 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -1246,7 +1246,7 @@
         if (kIsDebug) {
             printf("Adding 9-patch info...\n");
         }
-        strcpy((char*)unknowns[p_index].name, "npTc");
+        memcpy((char*)unknowns[p_index].name, "npTc", 5);
         unknowns[p_index].data = (png_byte*)imageInfo.serialize9patch();
         unknowns[p_index].size = imageInfo.info9Patch.serializedSize();
         // TODO: remove the check below when everything works
@@ -1254,7 +1254,7 @@
 
         // automatically generated 9 patch outline data
         int chunk_size = sizeof(png_uint_32) * 6;
-        strcpy((char*)unknowns[o_index].name, "npOl");
+        memcpy((char*)unknowns[o_index].name, "npOl", 5);
         unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1);
         png_byte outputData[chunk_size];
         memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32));
@@ -1266,7 +1266,7 @@
         // optional optical inset / layout bounds data
         if (imageInfo.haveLayoutBounds) {
             int chunk_size = sizeof(png_uint_32) * 4;
-            strcpy((char*)unknowns[b_index].name, "npLb");
+            memcpy((char*)unknowns[b_index].name, "npLb", 5);
             unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1);
             memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size);
             unknowns[b_index].size = chunk_size;
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 52b93a9..669afe1 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -4847,6 +4847,7 @@
     const String16 animatedVector16("animated-vector");
     const String16 pathInterpolator16("pathInterpolator");
     const String16 objectAnimator16("objectAnimator");
+    const String16 gradient16("gradient");
 
     const int minSdk = getMinSdkVersion(bundle);
     if (minSdk >= SDK_LOLLIPOP_MR1) {
@@ -4874,7 +4875,8 @@
         if (bundle->getNoVersionVectors() && (node->getElementName() == vector16 ||
                     node->getElementName() == animatedVector16 ||
                     node->getElementName() == objectAnimator16 ||
-                    node->getElementName() == pathInterpolator16)) {
+                    node->getElementName() == pathInterpolator16 ||
+                    node->getElementName() == gradient16)) {
             // We were told not to version vector tags, so skip the children here.
             continue;
         }
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 43918da..058504d 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -83,14 +83,19 @@
         "configuration/ConfigurationParser.cpp",
         "filter/AbiFilter.cpp",
         "filter/ConfigFilter.cpp",
-        "flatten/Archive.cpp",
-        "flatten/TableFlattener.cpp",
-        "flatten/XmlFlattener.cpp",
-        "io/BigBufferStreams.cpp",
+        "format/Archive.cpp",
+        "format/Container.cpp",
+        "format/binary/BinaryResourceParser.cpp",
+        "format/binary/ResChunkPullParser.cpp",
+        "format/binary/TableFlattener.cpp",
+        "format/binary/XmlFlattener.cpp",
+        "format/proto/ProtoDeserialize.cpp",
+        "format/proto/ProtoSerialize.cpp",
+        "io/BigBufferStream.cpp",
         "io/File.cpp",
-        "io/FileInputStream.cpp",
+        "io/FileStream.cpp",
         "io/FileSystem.cpp",
-        "io/StringInputStream.cpp",
+        "io/StringStream.cpp",
         "io/Util.cpp",
         "io/ZipArchive.cpp",
         "link/AutoVersioner.cpp",
@@ -106,14 +111,9 @@
         "optimize/ResourceDeduper.cpp",
         "optimize/VersionCollapser.cpp",
         "process/SymbolTable.cpp",
-        "proto/ProtoHelpers.cpp",
-        "proto/TableProtoDeserializer.cpp",
-        "proto/TableProtoSerializer.cpp",
         "split/TableSplitter.cpp",
         "text/Unicode.cpp",
         "text/Utf8Iterator.cpp",
-        "unflatten/BinaryResourceParser.cpp",
-        "unflatten/ResChunkPullParser.cpp",
         "util/BigBuffer.cpp",
         "util/Files.cpp",
         "util/Util.cpp",
@@ -139,6 +139,7 @@
         "xml/XmlDom.cpp",
         "xml/XmlPullParser.cpp",
         "xml/XmlUtil.cpp",
+        "Configuration.proto",
         "Resources.proto",
         "ResourcesInternal.proto",
     ],
@@ -167,7 +168,7 @@
         "test/Builders.cpp",
         "test/Common.cpp",
         "**/*_test.cpp",
-    ],
+    ] + toolSources,
     static_libs: [
         "libaapt2",
         "libgmock",
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index a9278c1..59a6e12 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -876,6 +876,12 @@
   return copy;
 }
 
+std::string ConfigDescription::GetBcp47LanguageTag(bool canonicalize) const {
+  char locale[RESTABLE_MAX_LOCALE_LEN];
+  getBcp47Locale(locale, canonicalize);
+  return std::string(locale);
+}
+
 bool ConfigDescription::Dominates(const ConfigDescription& o) const {
   if (*this == o) {
     return true;
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 65c9617..c1d0e10 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -61,6 +61,9 @@
 
   ConfigDescription CopyWithoutSdkVersion() const;
 
+  // Returns the BCP-47 language tag of this configuration's locale.
+  std::string GetBcp47LanguageTag(bool canonicalize = false) const;
+
   /**
    * A configuration X dominates another configuration Y, if X has at least the
    * precedence of Y and X is strictly more general than Y: for any type defined
diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto
new file mode 100644
index 0000000..fc636a4
--- /dev/null
+++ b/tools/aapt2/Configuration.proto
@@ -0,0 +1,207 @@
+/*
+ * 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 = "proto3";
+
+package aapt.pb;
+
+option java_package = "com.android.aapt";
+option optimize_for = LITE_RUNTIME;
+
+// A description of the requirements a device must have in order for a
+// resource to be matched and selected.
+message Configuration {
+  enum LayoutDirection {
+    LAYOUT_DIRECTION_UNSET = 0;
+    LAYOUT_DIRECTION_LTR = 1;
+    LAYOUT_DIRECTION_RTL = 2;
+  }
+
+  enum ScreenLayoutSize {
+    SCREEN_LAYOUT_SIZE_UNSET = 0;
+    SCREEN_LAYOUT_SIZE_SMALL = 1;
+    SCREEN_LAYOUT_SIZE_NORMAL = 2;
+    SCREEN_LAYOUT_SIZE_LARGE = 3;
+    SCREEN_LAYOUT_SIZE_XLARGE = 4;
+  }
+
+  enum ScreenLayoutLong {
+    SCREEN_LAYOUT_LONG_UNSET = 0;
+    SCREEN_LAYOUT_LONG_LONG = 1;
+    SCREEN_LAYOUT_LONG_NOTLONG = 2;
+  }
+
+  enum ScreenRound {
+    SCREEN_ROUND_UNSET = 0;
+    SCREEN_ROUND_ROUND = 1;
+    SCREEN_ROUND_NOTROUND = 2;
+  }
+
+  enum WideColorGamut {
+    WIDE_COLOR_GAMUT_UNSET = 0;
+    WIDE_COLOR_GAMUT_WIDECG = 1;
+    WIDE_COLOR_GAMUT_NOWIDECG = 2;
+  }
+
+  enum Hdr {
+    HDR_UNSET = 0;
+    HDR_HIGHDR = 1;
+    HDR_LOWDR = 2;
+  }
+
+  enum Orientation {
+    ORIENTATION_UNSET = 0;
+    ORIENTATION_PORT = 1;
+    ORIENTATION_LAND = 2;
+    ORIENTATION_SQUARE = 3;
+  }
+
+  enum UiModeType {
+    UI_MODE_TYPE_UNSET = 0;
+    UI_MODE_TYPE_NORMAL = 1;
+    UI_MODE_TYPE_DESK = 2;
+    UI_MODE_TYPE_CAR = 3;
+    UI_MODE_TYPE_TELEVISION = 4;
+    UI_MODE_TYPE_APPLIANCE = 5;
+    UI_MODE_TYPE_WATCH = 6;
+    UI_MODE_TYPE_VRHEADSET = 7;
+  }
+
+  enum UiModeNight {
+    UI_MODE_NIGHT_UNSET = 0;
+    UI_MODE_NIGHT_NIGHT = 1;
+    UI_MODE_NIGHT_NOTNIGHT = 2;
+  }
+
+  enum Touchscreen {
+    TOUCHSCREEN_UNSET = 0;
+    TOUCHSCREEN_NOTOUCH = 1;
+    TOUCHSCREEN_STYLUS = 2;
+    TOUCHSCREEN_FINGER = 3;
+  }
+
+  enum KeysHidden {
+    KEYS_HIDDEN_UNSET = 0;
+    KEYS_HIDDEN_KEYSEXPOSED = 1;
+    KEYS_HIDDEN_KEYSHIDDEN = 2;
+    KEYS_HIDDEN_KEYSSOFT = 3;
+  }
+
+  enum Keyboard {
+    KEYBOARD_UNSET = 0;
+    KEYBOARD_NOKEYS = 1;
+    KEYBOARD_QWERTY = 2;
+    KEYBOARD_TWELVEKEY = 3;
+  }
+
+  enum NavHidden {
+    NAV_HIDDEN_UNSET = 0;
+    NAV_HIDDEN_NAVEXPOSED = 1;
+    NAV_HIDDEN_NAVHIDDEN = 2;
+  }
+
+  enum Navigation {
+    NAVIGATION_UNSET = 0;
+    NAVIGATION_NONAV = 1;
+    NAVIGATION_DPAD = 2;
+    NAVIGATION_TRACKBALL = 3;
+    NAVIGATION_WHEEL = 4;
+  }
+
+  //
+  // Axis/dimensions that are understood by the runtime.
+  //
+
+  // Mobile country code.
+  uint32 mcc = 1;
+
+  // Mobile network code.
+  uint32 mnc = 2;
+
+  // BCP-47 locale tag.
+  string locale = 3;
+
+  // Left-to-right, right-to-left...
+  LayoutDirection layout_direction = 4;
+
+  // Screen width in pixels. Prefer screen_width_dp.
+  uint32 screen_width = 5;
+
+  // Screen height in pixels. Prefer screen_height_dp.
+  uint32 screen_height = 6;
+
+  // Screen width in density independent pixels (dp).
+  uint32 screen_width_dp = 7;
+
+  // Screen height in density independent pixels (dp).
+  uint32 screen_height_dp = 8;
+
+  // The smallest screen dimension, regardless of orientation, in dp.
+  uint32 smallest_screen_width_dp = 9;
+
+  // Whether the device screen is classified as small, normal, large, xlarge.
+  ScreenLayoutSize screen_layout_size = 10;
+
+  // Whether the device screen is long.
+  ScreenLayoutLong screen_layout_long = 11;
+
+  // Whether the screen is round (Android Wear).
+  ScreenRound screen_round = 12;
+
+  // Whether the screen supports wide color gamut.
+  WideColorGamut wide_color_gamut = 13;
+
+  // Whether the screen has high dynamic range.
+  Hdr hdr = 14;
+
+  // Which orientation the device is in (portrait, landscape).
+  Orientation orientation = 15;
+
+  // Which type of UI mode the device is in (television, car, etc.).
+  UiModeType ui_mode_type = 16;
+
+  // Whether the device is in night mode.
+  UiModeNight ui_mode_night = 17;
+
+  // The device's screen density in dots-per-inch (dpi).
+  uint32 density = 18;
+
+  // Whether a touchscreen exists, supports a stylus, or finger.
+  Touchscreen touchscreen = 19;
+
+  // Whether the keyboard hardware keys are currently hidden, exposed, or
+  // if the keyboard is a software keyboard.
+  KeysHidden keys_hidden = 20;
+
+  // The type of keyboard present (none, QWERTY, 12-key).
+  Keyboard keyboard = 21;
+
+  // Whether the navigation is exposed or hidden.
+  NavHidden nav_hidden = 22;
+
+  // The type of navigation present on the device
+  // (trackball, wheel, dpad, etc.).
+  Navigation navigation = 23;
+
+  // The minimum SDK version of the device.
+  uint32 sdk_version = 24;
+
+  //
+  // Build-time only dimensions.
+  //
+
+  string product = 25;
+}
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index dba9cb5..61c304b 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -35,11 +35,11 @@
 
 namespace {
 
-class PrintVisitor : public ValueVisitor {
+class PrintVisitor : public ConstValueVisitor {
  public:
-  using ValueVisitor::Visit;
+  using ConstValueVisitor::Visit;
 
-  void Visit(Attribute* attr) override {
+  void Visit(const Attribute* attr) override {
     std::cout << "(attr) type=";
     attr->PrintMask(&std::cout);
     static constexpr uint32_t kMask =
@@ -55,7 +55,7 @@
     }
   }
 
-  void Visit(Style* style) override {
+  void Visit(const Style* style) override {
     std::cout << "(style)";
     if (style->parent) {
       const Reference& parent_ref = style->parent.value();
@@ -90,15 +90,15 @@
     }
   }
 
-  void Visit(Array* array) override {
+  void Visit(const Array* array) override {
     array->Print(&std::cout);
   }
 
-  void Visit(Plural* plural) override {
+  void Visit(const Plural* plural) override {
     plural->Print(&std::cout);
   }
 
-  void Visit(Styleable* styleable) override {
+  void Visit(const Styleable* styleable) override {
     std::cout << "(styleable)";
     for (const auto& attr : styleable->entries) {
       std::cout << "\n        ";
@@ -116,17 +116,17 @@
     }
   }
 
-  void VisitItem(Item* item) override {
+  void VisitItem(const Item* item) override {
     item->Print(&std::cout);
   }
 };
 
 }  // namespace
 
-void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) {
+void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options) {
   PrintVisitor visitor;
 
-  for (auto& package : table->packages) {
+  for (const auto& package : table.packages) {
     std::cout << "Package name=" << package->name;
     if (package->id) {
       std::cout << " id=" << std::hex << (int)package->id.value() << std::dec;
@@ -261,11 +261,11 @@
 
 namespace {
 
-class XmlPrinter : public xml::Visitor {
+class XmlPrinter : public xml::ConstVisitor {
  public:
-  using xml::Visitor::Visit;
+  using xml::ConstVisitor::Visit;
 
-  void Visit(xml::Element* el) override {
+  void Visit(const xml::Element* el) override {
     const size_t previous_size = prefix_.size();
 
     for (const xml::NamespaceDecl& decl : el->namespace_decls) {
@@ -301,11 +301,11 @@
     }
 
     prefix_ += "  ";
-    xml::Visitor::Visit(el);
+    xml::ConstVisitor::Visit(el);
     prefix_.resize(previous_size);
   }
 
-  void Visit(xml::Text* text) override {
+  void Visit(const xml::Text* text) override {
     std::cerr << prefix_ << "T: '" << text->text << "'\n";
   }
 
@@ -315,9 +315,9 @@
 
 }  // namespace
 
-void Debug::DumpXml(xml::XmlResource* doc) {
+void Debug::DumpXml(const xml::XmlResource& doc) {
   XmlPrinter printer;
-  doc->root->Accept(&printer);
+  doc.root->Accept(&printer);
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index e2456c7..296d04b 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -31,13 +31,11 @@
 };
 
 struct Debug {
-  static void PrintTable(ResourceTable* table,
-                         const DebugPrintTableOptions& options = {});
+  static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options = {});
   static void PrintStyleGraph(ResourceTable* table,
                               const ResourceName& target_style);
   static void DumpHex(const void* data, size_t len);
-  static void DumpXml(xml::XmlResource* doc);
-  static std::string ToString(xml::XmlResource* doc);
+  static void DumpXml(const xml::XmlResource& doc);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index b80780e..ae32ee9 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -18,13 +18,17 @@
 
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-#include "flatten/Archive.h"
-#include "flatten/TableFlattener.h"
-#include "io/BigBufferInputStream.h"
+#include "format/Archive.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
+using xml::XmlResource;
+
 std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
                                                       const android::StringPiece& path) {
   Source source(path);
@@ -52,6 +56,7 @@
   if (!parser.Parse()) {
     return {};
   }
+
   return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
 }
 
@@ -63,7 +68,7 @@
 
 bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
                                const TableFlattenerOptions& options, FilterChain* filters,
-                               IArchiveWriter* writer) {
+                               IArchiveWriter* writer, XmlResource* manifest) {
   std::set<std::string> referenced_resources;
   // List the files being referenced in the resource table.
   for (auto& pkg : split_table->packages) {
@@ -119,6 +124,20 @@
         return false;
       }
 
+    } else if (manifest != nullptr && path == "AndroidManifest.xml") {
+      BigBuffer buffer(8192);
+      XmlFlattener xml_flattener(&buffer, {});
+      if (!xml_flattener.Consume(context, manifest)) {
+        context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
+        return false;
+      }
+
+      uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+      io::BigBufferInputStream manifest_buffer_in(&buffer);
+      if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
+                                        writer)) {
+        return false;
+      }
     } else {
       uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
       if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) {
@@ -129,4 +148,26 @@
   return true;
 }
 
+std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
+  IDiagnostics* diag = context->GetDiagnostics();
+
+  io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
+  if (manifest_file == nullptr) {
+    diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
+    return {};
+  }
+
+  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+  if (manifest_data == nullptr) {
+    diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
+    return {};
+  }
+
+  std::unique_ptr<xml::XmlResource> manifest =
+      xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
+  if (manifest == nullptr) {
+    diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
+  }
+  return manifest;
+}
 }  // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index dacd0c2..d2dd5cf 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -21,14 +21,15 @@
 
 #include "ResourceTable.h"
 #include "filter/Filter.h"
-#include "flatten/Archive.h"
-#include "flatten/TableFlattener.h"
+#include "format/Archive.h"
+#include "format/binary/BinaryResourceParser.h"
+#include "format/binary/TableFlattener.h"
 #include "io/ZipArchive.h"
-#include "unflatten/BinaryResourceParser.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
-/** Info about an APK loaded in memory. */
+// Info about an APK loaded in memory.
 class LoadedApk {
  public:
   LoadedApk(
@@ -36,12 +37,19 @@
       std::unique_ptr<io::IFileCollection> apk,
       std::unique_ptr<ResourceTable> table)
       : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
+  virtual ~LoadedApk() = default;
 
-  io::IFileCollection* GetFileCollection() { return apk_.get(); }
+  io::IFileCollection* GetFileCollection() {
+    return apk_.get();
+  }
 
-  ResourceTable* GetResourceTable() { return table_.get(); }
+  ResourceTable* GetResourceTable() {
+    return table_.get();
+  }
 
-  const Source& GetSource() { return source_; }
+  const Source& GetSource() {
+    return source_;
+  }
 
   /**
    * Writes the APK on disk at the given path, while also removing the resource
@@ -51,23 +59,30 @@
                               IArchiveWriter* writer);
 
   /**
-   * Writes the APK on disk at the given path, while also removing the resource
-   * files that are not referenced in the resource table. The provided filter
-   * chain is applied to each entry in the APK file.
+   * Writes the APK on disk at the given path, while also removing the resource files that are not
+   * referenced in the resource table. The provided filter chain is applied to each entry in the APK
+   * file.
+   *
+   * If the manifest is also provided, it will be written to the new APK file, otherwise the
+   * original manifest will be written. The manifest is only required if the contents of the new APK
+   * have been modified in a way that require the AndroidManifest.xml to also be modified.
    */
   virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table,
                               const TableFlattenerOptions& options, FilterChain* filters,
-                              IArchiveWriter* writer);
+                              IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
+
+  /** Inflates the AndroidManifest.xml file from the APK. */
+  std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
 
   static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
                                                     const android::StringPiece& path);
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(LoadedApk);
+
   Source source_;
   std::unique_ptr<io::IFileCollection> apk_;
   std::unique_ptr<ResourceTable> table_;
-
-  DISALLOW_COPY_AND_ASSIGN(LoadedApk);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index 7664fac..d81921f 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -24,9 +24,10 @@
 
 #include "util/Util.h"
 
-namespace aapt {
+using ::android::ResTable_config;
+using ::android::StringPiece;
 
-using android::ResTable_config;
+namespace aapt {
 
 void LocaleValue::set_language(const char* language_chars) {
   size_t i = 0;
@@ -72,7 +73,7 @@
   return std::all_of(std::begin(str), std::end(str), ::isdigit);
 }
 
-bool LocaleValue::InitFromFilterString(const android::StringPiece& str) {
+bool LocaleValue::InitFromFilterString(const StringPiece& str) {
   // A locale (as specified in the filter) is an underscore separated name such
   // as "en_US", "en_Latn_US", or "en_US_POSIX".
   std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
@@ -138,6 +139,71 @@
   return true;
 }
 
+bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+  return InitFromBcp47TagImpl(bcp47tag, '-');
+}
+
+bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+  std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
+  if (subtags.size() == 1) {
+    set_language(subtags[0].c_str());
+  } else if (subtags.size() == 2) {
+    set_language(subtags[0].c_str());
+
+    // The second tag can either be a region, a variant or a script.
+    switch (subtags[1].size()) {
+      case 2:
+      case 3:
+        set_region(subtags[1].c_str());
+        break;
+      case 4:
+        if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+          // This is a variant: fall through
+        } else {
+          set_script(subtags[1].c_str());
+          break;
+        }
+      case 5:
+      case 6:
+      case 7:
+      case 8:
+        set_variant(subtags[1].c_str());
+        break;
+      default:
+        return false;
+    }
+  } else if (subtags.size() == 3) {
+    // The language is always the first subtag.
+    set_language(subtags[0].c_str());
+
+    // The second subtag can either be a script or a region code.
+    // If its size is 4, it's a script code, else it's a region code.
+    if (subtags[1].size() == 4) {
+      set_script(subtags[1].c_str());
+    } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
+      set_region(subtags[1].c_str());
+    } else {
+      return false;
+    }
+
+    // The third tag can either be a region code (if the second tag was
+    // a script), else a variant code.
+    if (subtags[2].size() >= 4) {
+      set_variant(subtags[2].c_str());
+    } else {
+      set_region(subtags[2].c_str());
+    }
+  } else if (subtags.size() == 4) {
+    set_language(subtags[0].c_str());
+    set_script(subtags[1].c_str());
+    set_region(subtags[2].c_str());
+    set_variant(subtags[3].c_str());
+  } else {
+    return false;
+  }
+  return true;
+}
+
 ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
                                    std::vector<std::string>::iterator end) {
   const std::vector<std::string>::iterator start_iter = iter;
@@ -145,71 +211,13 @@
   std::string& part = *iter;
   if (part[0] == 'b' && part[1] == '+') {
     // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
-    // except that the separator is "+" and not "-".
-    std::vector<std::string> subtags = util::SplitAndLowercase(part, '+');
-    subtags.erase(subtags.begin());
-    if (subtags.size() == 1) {
-      set_language(subtags[0].c_str());
-    } else if (subtags.size() == 2) {
-      set_language(subtags[0].c_str());
-
-      // The second tag can either be a region, a variant or a script.
-      switch (subtags[1].size()) {
-        case 2:
-        case 3:
-          set_region(subtags[1].c_str());
-          break;
-        case 4:
-          if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
-            // This is a variant: fall through
-          } else {
-            set_script(subtags[1].c_str());
-            break;
-          }
-        case 5:
-        case 6:
-        case 7:
-        case 8:
-          set_variant(subtags[1].c_str());
-          break;
-        default:
-          return -1;
-      }
-    } else if (subtags.size() == 3) {
-      // The language is always the first subtag.
-      set_language(subtags[0].c_str());
-
-      // The second subtag can either be a script or a region code.
-      // If its size is 4, it's a script code, else it's a region code.
-      if (subtags[1].size() == 4) {
-        set_script(subtags[1].c_str());
-      } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
-        set_region(subtags[1].c_str());
-      } else {
-        return -1;
-      }
-
-      // The third tag can either be a region code (if the second tag was
-      // a script), else a variant code.
-      if (subtags[2].size() >= 4) {
-        set_variant(subtags[2].c_str());
-      } else {
-        set_region(subtags[2].c_str());
-      }
-    } else if (subtags.size() == 4) {
-      set_language(subtags[0].c_str());
-      set_script(subtags[1].c_str());
-      set_region(subtags[2].c_str());
-      set_variant(subtags[3].c_str());
-    } else {
+    // except that the separator is "+" and not "-". Skip the prefix 'b+'.
+    if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
       return -1;
     }
-
     ++iter;
-
   } else {
-    if ((part.length() == 2 || part.length() == 3) && is_alpha(part) &&
-        part != "car") {
+    if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
       set_language(part.c_str());
       ++iter;
 
@@ -222,7 +230,6 @@
       }
     }
   }
-
   return static_cast<ssize_t>(iter - start_iter);
 }
 
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index 3d73b2e..6d8b598 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -41,6 +41,9 @@
    */
   bool InitFromFilterString(const android::StringPiece& config);
 
+  // Initializes this LocaleValue from a BCP-47 locale tag.
+  bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+
   /**
    * Initialize this LocaleValue from parts of a vector.
    */
@@ -67,6 +70,8 @@
   inline bool operator>(const LocaleValue& o) const;
 
  private:
+  bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+
   void set_language(const char* language);
   void set_region(const char* language);
   void set_script(const char* script);
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index cbcc8fb..87b9867 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -157,12 +157,22 @@
 };
 
 struct ResourceFile {
+  enum class Type {
+    kUnknown,
+    kPng,
+    kBinaryXml,
+    kProtoXml,
+  };
+
   // Name
   ResourceName name;
 
   // Configuration
   ConfigDescription config;
 
+  // Type
+  Type type;
+
   // Source
   Source source;
 
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index f08b03e..9a5cd3e 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -22,7 +22,7 @@
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Test.h"
 #include "xml/XmlPullParser.h"
 
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index f193fe0..24187d9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -23,12 +23,12 @@
 
 #include "NameMangler.h"
 #include "SdkConstants.h"
-#include "flatten/ResourceTypeExtensions.h"
+#include "format/binary/ResourceTypeExtensions.h"
 #include "util/Files.h"
 #include "util/Util.h"
 
-using android::StringPiece;
-using android::StringPiece16;
+using ::android::StringPiece;
+using ::android::StringPiece16;
 
 namespace aapt {
 namespace ResourceUtils {
@@ -704,8 +704,15 @@
       } else {
         if (type != ResourceType::kString && util::StartsWith(str, "res/")) {
           // This must be a FileReference.
-          return util::make_unique<FileReference>(dst_pool->MakeRef(
-              str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+          std::unique_ptr<FileReference> file_ref =
+              util::make_unique<FileReference>(dst_pool->MakeRef(
+                  str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+          if (util::EndsWith(*file_ref->path, ".xml")) {
+            file_ref->type = ResourceFile::Type::kBinaryXml;
+          } else if (util::EndsWith(*file_ref->path, ".png")) {
+            file_ref->type = ResourceFile::Type::kPng;
+          }
+          return std::move(file_ref);
         }
 
         // There are no styles associated with this string, so treat it as a simple string.
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 1cba194..082fd86 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -35,15 +35,25 @@
 }
 
 template <typename Derived>
-void BaseValue<Derived>::Accept(RawValueVisitor* visitor) {
+void BaseValue<Derived>::Accept(ValueVisitor* visitor) {
   visitor->Visit(static_cast<Derived*>(this));
 }
 
 template <typename Derived>
-void BaseItem<Derived>::Accept(RawValueVisitor* visitor) {
+void BaseValue<Derived>::Accept(ConstValueVisitor* visitor) const {
+  visitor->Visit(static_cast<const Derived*>(this));
+}
+
+template <typename Derived>
+void BaseItem<Derived>::Accept(ValueVisitor* visitor) {
   visitor->Visit(static_cast<Derived*>(this));
 }
 
+template <typename Derived>
+void BaseItem<Derived>::Accept(ConstValueVisitor* visitor) const {
+  visitor->Visit(static_cast<const Derived*>(this));
+}
+
 RawString::RawString(const StringPool::Ref& ref) : value(ref) {}
 
 bool RawString::Equals(const Value* value) const {
@@ -55,7 +65,7 @@
 }
 
 RawString* RawString::Clone(StringPool* new_pool) const {
-  RawString* rs = new RawString(new_pool->MakeRef(*value));
+  RawString* rs = new RawString(new_pool->MakeRef(value));
   rs->comment_ = comment_;
   rs->source_ = source_;
   return rs;
@@ -197,7 +207,7 @@
 }
 
 String* String::Clone(StringPool* new_pool) const {
-  String* str = new String(new_pool->MakeRef(*value));
+  String* str = new String(new_pool->MakeRef(value));
   str->comment_ = comment_;
   str->source_ = source_;
   str->untranslatable_sections = untranslatable_sections;
@@ -280,8 +290,9 @@
 }
 
 FileReference* FileReference::Clone(StringPool* new_pool) const {
-  FileReference* fr = new FileReference(new_pool->MakeRef(*path));
+  FileReference* fr = new FileReference(new_pool->MakeRef(path));
   fr->file = file;
+  fr->type = type;
   fr->comment_ = comment_;
   fr->source_ = source_;
   return fr;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 275864b..fd242a1 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -33,7 +33,8 @@
 
 namespace aapt {
 
-struct RawValueVisitor;
+class ValueVisitor;
+class ConstValueVisitor;
 
 // A resource value. This is an all-encompassing representation
 // of Item and Map and their subclasses. The way to do
@@ -45,36 +46,58 @@
   virtual ~Value() = default;
 
   // Whether this value is weak and can be overridden without warning or error. Default is false.
-  bool IsWeak() const { return weak_; }
+  bool IsWeak() const {
+    return weak_;
+  }
 
-  void SetWeak(bool val) { weak_ = val; }
+  void SetWeak(bool val) {
+    weak_ = val;
+  }
 
-  // Whether the value is marked as translatable.
-  // This does not persist when flattened.
+  // Whether the value is marked as translatable. This does not persist when flattened to binary.
   // It is only used during compilation phase.
-  void SetTranslatable(bool val) { translatable_ = val; }
+  void SetTranslatable(bool val) {
+    translatable_ = val;
+  }
 
   // Default true.
-  bool IsTranslatable() const { return translatable_; }
+  bool IsTranslatable() const {
+    return translatable_;
+  }
 
   // Returns the source where this value was defined.
-  const Source& GetSource() const { return source_; }
+  const Source& GetSource() const {
+    return source_;
+  }
 
-  void SetSource(const Source& source) { source_ = source; }
+  void SetSource(const Source& source) {
+    source_ = source;
+  }
 
-  void SetSource(Source&& source) { source_ = std::move(source); }
+  void SetSource(Source&& source) {
+    source_ = std::move(source);
+  }
 
   // Returns the comment that was associated with this resource.
-  const std::string& GetComment() const { return comment_; }
+  const std::string& GetComment() const {
+    return comment_;
+  }
 
-  void SetComment(const android::StringPiece& str) { comment_ = str.to_string(); }
+  void SetComment(const android::StringPiece& str) {
+    comment_ = str.to_string();
+  }
 
-  void SetComment(std::string&& str) { comment_ = std::move(str); }
+  void SetComment(std::string&& str) {
+    comment_ = std::move(str);
+  }
 
   virtual bool Equals(const Value* value) const = 0;
 
   // Calls the appropriate overload of ValueVisitor.
-  virtual void Accept(RawValueVisitor* visitor) = 0;
+  virtual void Accept(ValueVisitor* visitor) = 0;
+
+  // Calls the appropriate overload of ConstValueVisitor.
+  virtual void Accept(ConstValueVisitor* visitor) const = 0;
 
   // Clone the value. `new_pool` is the new StringPool that
   // any resources with strings should use when copying their string.
@@ -95,7 +118,8 @@
 // Inherit from this to get visitor accepting implementations for free.
 template <typename Derived>
 struct BaseValue : public Value {
-  void Accept(RawValueVisitor* visitor) override;
+  void Accept(ValueVisitor* visitor) override;
+  void Accept(ConstValueVisitor* visitor) const override;
 };
 
 // A resource item with a single value. This maps to android::ResTable_entry.
@@ -111,7 +135,8 @@
 // Inherit from this to get visitor accepting implementations for free.
 template <typename Derived>
 struct BaseItem : public Item {
-  void Accept(RawValueVisitor* visitor) override;
+  void Accept(ValueVisitor* visitor) override;
+  void Accept(ConstValueVisitor* visitor) const override;
 };
 
 // A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
@@ -144,7 +169,10 @@
 
 // An ID resource. Has no real value, just a place holder.
 struct Id : public BaseItem<Id> {
-  Id() { weak_ = true; }
+  Id() {
+    weak_ = true;
+  }
+
   bool Equals(const Value* value) const override;
   bool Flatten(android::Res_value* out) const override;
   Id* Clone(StringPool* new_pool) const override;
@@ -221,6 +249,10 @@
   // This field is NOT persisted in any format. It is transient.
   io::IFile* file = nullptr;
 
+  // FileType of the file pointed to by `file`. This is used to know how to inflate the file,
+  // or if to inflate at all (just copy).
+  ResourceFile::Type type = ResourceFile::Type::kUnknown;
+
   FileReference() = default;
   explicit FileReference(const StringPool::Ref& path);
 
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index 10f9b55..a80a9dc 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -18,6 +18,10 @@
 
 #include "test/Test.h"
 
+using ::testing::Eq;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
 namespace aapt {
 
 TEST(ResourceValuesTest, PluralEquals) {
@@ -148,6 +152,22 @@
   EXPECT_TRUE(a->Equals(b.get()));
 }
 
+TEST(ResourcesValuesTest, StringClones) {
+  StringPool pool_a;
+  StringPool pool_b;
+
+  String str_a(pool_a.MakeRef("hello", StringPool::Context(test::ParseConfigOrDie("en"))));
+
+  ASSERT_THAT(pool_a, SizeIs(1u));
+  EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
+  EXPECT_THAT(pool_a.strings()[0]->value, StrEq("hello"));
+
+  std::unique_ptr<String> str_b(str_a.Clone(&pool_b));
+  ASSERT_THAT(pool_b, SizeIs(1u));
+  EXPECT_THAT(pool_b.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en")));
+  EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello"));
+}
+
 TEST(ResourceValuesTest, StyleMerges) {
   StringPool pool_a;
   StringPool pool_b;
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 71f33b0..7e7c86d 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -14,77 +14,80 @@
  * limitations under the License.
  */
 
-// Keep proto2 syntax because we require the distinction between fields that
-// are set and unset.
-syntax = "proto2";
+syntax = "proto3";
+
+import "frameworks/base/tools/aapt2/Configuration.proto";
+
+package aapt.pb;
 
 option java_package = "com.android.aapt";
 option optimize_for = LITE_RUNTIME;
 
-package aapt.pb;
-
-// A configuration description that wraps the binary form of the C++ class
-// aapt::ConfigDescription, with an added product definition.
-// TODO(adamlesinski): Flesh this out to be represented in proto.
-message ConfigDescription {
-  optional bytes data = 1;
-  optional string product = 2;
-}
-
 // A string pool that wraps the binary form of the C++ class android::ResStringPool.
 message StringPool {
-  optional bytes data = 1;
+  bytes data = 1;
 }
 
 // The position of a declared entity within a file.
 message SourcePosition {
-  optional uint32 line_number = 1;
-  optional uint32 column_number = 2;
+  uint32 line_number = 1;
+  uint32 column_number = 2;
 }
 
 // Developer friendly source file information for an entity in the resource table.
 message Source {
   // The index of the string path within the source string pool of a ResourceTable.
-  optional uint32 path_idx = 1;
-  optional SourcePosition position = 2;
+  uint32 path_idx = 1;
+  SourcePosition position = 2;
 }
 
 // Top level message representing a resource table.
 message ResourceTable {
   // The string pool containing source paths referenced throughout the resource table. This does
   // not end up in the final binary ARSC file.
-  optional StringPool source_pool = 1;
+  StringPool source_pool = 1;
 
   // Resource definitions corresponding to an Android package.
   repeated Package package = 2;
 }
 
+// A package ID in the range [0x00, 0xff].
+message PackageId {
+  uint32 id = 1;
+}
+
 // Defines resources for an Android package.
 message Package {
   // The package ID of this package, in the range [0x00, 0xff].
-  // The ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time.
-  // The ID 0x01 is reserved for the 'android' package (framework).
-  // The ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time.
-  // The ID 0x7f is reserved for the application package.
-  // IDs > 0x7f are reserved for the application as well and are treated as feature splits.
-  optional uint32 package_id = 1;
+  // - ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time.
+  // - ID 0x01 is reserved for the 'android' package (framework).
+  // - ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time.
+  // - ID 0x7f is reserved for the application package.
+  // - IDs > 0x7f are reserved for the application as well and are treated as feature splits.
+  // This may not be set if no ID was assigned.
+  PackageId package_id = 1;
 
   // The Java compatible Android package name of the app.
-  optional string package_name = 2;
+  string package_name = 2;
 
   // The series of types defined by the package.
   repeated Type type = 3;
 }
 
+// A type ID in the range [0x01, 0xff].
+message TypeId {
+  uint32 id = 1;
+}
+
 // A set of resources grouped under a common type. Such types include string, layout, xml, dimen,
 // attr, etc. This maps to the second part of a resource identifier in Java (R.type.entry).
 message Type {
-  // The ID of the type. This may be 0, which indicates no ID is set.
-  optional uint32 id = 1;
+  // The ID of the type. This may not be set if no ID was assigned.
+  TypeId type_id = 1;
 
   // The name of the type. This corresponds to the 'type' part of a full resource name of the form
   // package:type/entry. The set of legal type names is listed in Resource.cpp.
-  optional string name = 2;
+  string name = 2;
 
   // The entries defined for this type.
   repeated Entry entry = 3;
@@ -112,17 +115,22 @@
     PUBLIC = 2;
   }
 
-  optional Visibility visibility = 1;
+  Visibility visibility = 1;
 
   // The path at which this entry's visibility was defined (eg. public.xml).
-  optional Source source = 2;
+  Source source = 2;
 
   // The comment associated with the <public> tag.
-  optional string comment = 3;
+  string comment = 3;
 
   // Whether the symbol can be merged into another resource table without there being an existing
   // definition to override. Used for overlays and set to true when <add-resource> is specified.
-  optional bool allow_new = 4;
+  bool allow_new = 4;
+}
+
+// An entry ID in the range [0x0000, 0xffff].
+message EntryId {
+  uint32 id = 1;
 }
 
 // An entry declaration. An entry has a full resource ID that is the combination of package ID,
@@ -132,14 +140,15 @@
   // The ID of this entry. Together with the package ID and type ID, this forms a full resource ID
   // of the form 0xPPTTEEEE, where PP is the package ID, TT is the type ID, and EEEE is the entry
   // ID.
-  optional uint32 id = 1;
+  // This may not be set if no ID was assigned.
+  EntryId entry_id = 1;
 
   // The name of this entry. This corresponds to the 'entry' part of a full resource name of the
   // form package:type/entry.
-  optional string name = 2;
+  string name = 2;
 
   // The symbol status of this entry, which includes visibility information.
-  optional SymbolStatus symbol_status = 3;
+  SymbolStatus symbol_status = 3;
 
   // The set of values defined for this entry, each corresponding to a different
   // configuration/variant.
@@ -148,50 +157,54 @@
 
 // A Configuration/Value pair.
 message ConfigValue {
-  optional ConfigDescription config = 1;
-  optional Value value = 2;
+  Configuration config = 1;
+  Value value = 2;
 }
 
 // The generic meta-data for every value in a resource table.
 message Value {
   // Where the value was defined.
-  optional Source source = 1;
+  Source source = 1;
 
   // Any comment associated with the value.
-  optional string comment = 2;
+  string comment = 2;
 
   // Whether the value can be overridden.
-  optional bool weak = 3;
+  bool weak = 3;
 
-  // If the value is an Item, this is set.
-  optional Item item = 4;
-
-  // If the value is a CompoundValue, this is set.
-  optional CompoundValue compound_value = 5;
+  // The value is either an Item or a CompoundValue.
+  oneof value {
+    Item item = 4;
+    CompoundValue compound_value = 5;
+  }
 }
 
 // An Item is an abstract type. It represents a value that can appear inline in many places, such
 // as XML attribute values or on the right hand side of style attribute definitions. The concrete
 // type is one of the types below. Only one can be set.
 message Item {
-  optional Reference ref = 1;
-  optional String str = 2;
-  optional RawString raw_str = 3;
-  optional StyledString styled_str = 4;
-  optional FileReference file = 5;
-  optional Id id = 6;
-  optional Primitive prim = 7;
+  oneof value {
+    Reference ref = 1;
+    String str = 2;
+    RawString raw_str = 3;
+    StyledString styled_str = 4;
+    FileReference file = 5;
+    Id id = 6;
+    Primitive prim = 7;
+  }
 }
 
 // A CompoundValue is an abstract type. It represents a value that is a made of other values.
 // These can only usually appear as top-level resources. The concrete type is one of the types
 // below. Only one can be set.
 message CompoundValue {
-  optional Attribute attr = 1;
-  optional Style style = 2;
-  optional Styleable styleable = 3;
-  optional Array array = 4;
-  optional Plural plural = 5;
+  oneof value {
+    Attribute attr = 1;
+    Style style = 2;
+    Styleable styleable = 3;
+    Array array = 4;
+    Plural plural = 5;
+  }
 }
 
 // A value that is a reference to another resource. This reference can be by name or resource ID.
@@ -204,16 +217,16 @@
     ATTRIBUTE = 1;
   }
 
-  optional Type type = 1;
+  Type type = 1;
 
-  // The resource ID (0xPPTTEEEE) of the resource being referred.
-  optional uint32 id = 2;
+  // The resource ID (0xPPTTEEEE) of the resource being referred. This is optional.
+  uint32 id = 2;
 
-  // The optional resource name.
-  optional string name = 3;
+  // The name of the resource being referred. This is optional if the resource ID is set.
+  string name = 3;
 
   // Whether this reference is referencing a private resource (@*package:type/entry).
-  optional bool private = 4;
+  bool private = 4;
 }
 
 // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
@@ -223,32 +236,32 @@
 
 // A value that is a string.
 message String {
-  optional string value = 1;
+  string value = 1;
 }
 
 // A value that is a raw string, which is unescaped/uninterpreted. This is typically used to
 // represent the value of a style attribute before the attribute is compiled and the set of
 // allowed values is known.
 message RawString {
-  optional string value = 1;
+  string value = 1;
 }
 
 // A string with styling information, like html tags that specify boldness, italics, etc.
 message StyledString {
   // The raw text of the string.
-  optional string value = 1;
+  string value = 1;
 
   // A Span marks a region of the string text that is styled.
   message Span {
     // The name of the tag, and its attributes, encoded as follows:
     // tag_name;attr1=value1;attr2=value2;[...]
-    optional string tag = 1;
+    string tag = 1;
 
     // The first character position this span applies to, in UTF-16 offset.
-    optional uint32 first_char = 2;
+    uint32 first_char = 2;
 
     // The last character position this span applies to, in UTF-16 offset.
-    optional uint32 last_char = 3;
+    uint32 last_char = 3;
   }
 
   repeated Span span = 2;
@@ -256,15 +269,26 @@
 
 // A value that is a reference to an external entity, like an XML file or a PNG.
 message FileReference {
+  enum Type {
+    UNKNOWN = 0;
+    PNG = 1;
+    BINARY_XML = 2;
+    PROTO_XML = 3;
+  }
+
   // Path to a file within the APK (typically res/type-config/entry.ext).
-  optional string path = 1;
+  string path = 1;
+
+  // The type of file this path points to. For UAM bundle, this cannot be
+  // BINARY_XML.
+  Type type = 2;
 }
 
 // A value that represents a primitive data type (float, int, boolean, etc.).
 // Corresponds to the fields (type/data) of the C struct android::Res_value.
 message Primitive {
-  optional uint32 type = 1;
-  optional uint32 data = 2;
+  uint32 type = 1;
+  uint32 data = 2;
 }
 
 // A value that represents an XML attribute and what values it accepts.
@@ -272,21 +296,22 @@
   // A Symbol used to represent an enum or a flag.
   message Symbol {
     // Where the enum/flag item was defined.
-    optional Source source = 1;
+    Source source = 1;
 
     // Any comments associated with the enum or flag.
-    optional string comment = 2;
+    string comment = 2;
 
     // The name of the enum/flag as a reference. Enums/flag items are generated as ID resource
     // values.
-    optional Reference name = 3;
+    Reference name = 3;
 
     // The value of the enum/flag.
-    optional uint32 value = 4;
+    uint32 value = 4;
   }
 
   // Bitmask of formats allowed for an attribute.
   enum FormatFlags {
+    NONE = 0x0;          // Proto3 requires a default of 0.
     ANY = 0x0000ffff;    // Allows any type except ENUM and FLAGS.
     REFERENCE = 0x01;    // Allows Reference values.
     STRING = 0x02;       // Allows String/StyledString values.
@@ -304,15 +329,15 @@
 
   // A bitmask of types that this XML attribute accepts. Corresponds to the flags in the
   // enum FormatFlags.
-  optional uint32 format_flags = 1;
+  uint32 format_flags = 1;
 
   // The smallest integer allowed for this XML attribute. Only makes sense if the format includes
   // FormatFlags::INTEGER.
-  optional int32 min_int = 2;
+  int32 min_int = 2;
 
   // The largest integer allowed for this XML attribute. Only makes sense if the format includes
   // FormatFlags::INTEGER.
-  optional int32 max_int = 3;
+  int32 max_int = 3;
 
   // The set of enums/flags defined in this attribute. Only makes sense if the format includes
   // either FormatFlags::ENUM or FormatFlags::FLAGS. Having both is an error.
@@ -324,23 +349,23 @@
   // An XML attribute/value pair defined in the style.
   message Entry {
     // Where the entry was defined.
-    optional Source source = 1;
+    Source source = 1;
 
     // Any comments associated with the entry.
-    optional string comment = 2;
+    string comment = 2;
 
     // A reference to the XML attribute.
-    optional Reference key = 3;
+    Reference key = 3;
 
     // The Item defined for this XML attribute.
-    optional Item item = 4;
+    Item item = 4;
   }
 
   // The optinal style from which this style inherits attributes.
-  optional Reference parent = 1;
+  Reference parent = 1;
 
   // The source file information of the parent inheritance declaration.
-  optional Source parent_source = 2;
+  Source parent_source = 2;
 
   // The set of XML attribute/value pairs for this style.
   repeated Entry entry = 3;
@@ -352,13 +377,13 @@
   // An attribute defined for this styleable.
   message Entry {
     // Where the attribute was defined within the <declare-styleable> block.
-    optional Source source = 1;
+    Source source = 1;
 
     // Any comments associated with the declaration.
-    optional string comment = 2;
+    string comment = 2;
 
     // The reference to the attribute.
-    optional Reference attr = 3;
+    Reference attr = 3;
   }
 
   // The set of attribute declarations.
@@ -370,13 +395,13 @@
   // A single element of the array.
   message Element {
     // Where the element was defined.
-    optional Source source = 1;
+    Source source = 1;
 
     // Any comments associated with the element.
-    optional string comment = 2;
+    string comment = 2;
 
     // The value assigned to this element.
-    optional Item item = 3;
+    Item item = 3;
   }
 
   // The list of array elements.
@@ -398,16 +423,16 @@
   // The plural value for a given arity.
   message Entry {
     // Where the plural was defined.
-    optional Source source = 1;
+    Source source = 1;
 
     // Any comments associated with the plural.
-    optional string comment = 2;
+    string comment = 2;
 
     // The arity of the plural.
-    optional Arity arity = 3;
+    Arity arity = 3;
 
     // The value assigned to this plural.
-    optional Item item = 4;
+    Item item = 4;
   }
 
   // The set of arity/plural mappings.
@@ -417,14 +442,13 @@
 // Defines an abstract XmlNode that must be either an XmlElement, or
 // a text node represented by a string.
 message XmlNode {
-  // If set, this node is an element/tag.
-  optional XmlElement element = 1;
-
-  // If set, this node is a chunk of text.
-  optional string text = 2;
+  oneof node {
+    XmlElement element = 1;
+    string text = 2;
+  }
 
   // Source line and column info.
-  optional SourcePosition source = 3;
+  SourcePosition source = 3;
 }
 
 // An <element> in an XML document.
@@ -433,10 +457,10 @@
   repeated XmlNamespace namespace_declaration = 1;
 
   // The namespace URI of this element.
-  optional string namespace_uri = 2;
+  string namespace_uri = 2;
 
   // The name of this element.
-  optional string name = 3;
+  string name = 3;
 
   // The attributes of this element.
   repeated XmlAttribute attribute = 4;
@@ -447,25 +471,25 @@
 
 // A namespace declaration on an XmlElement (xmlns:android="http://...").
 message XmlNamespace {
-  optional string prefix = 1;
-  optional string uri = 2;
+  string prefix = 1;
+  string uri = 2;
 
   // Source line and column info.
-  optional SourcePosition source = 3;
+  SourcePosition source = 3;
 }
 
 // An attribute defined on an XmlElement (android:text="...").
 message XmlAttribute {
-  optional string namespace_uri = 1;
-  optional string name = 2;
-  optional string value = 3;
+  string namespace_uri = 1;
+  string name = 2;
+  string value = 3;
 
   // Source line and column info.
-  optional SourcePosition source = 4;
+  SourcePosition source = 4;
 
-  // The resource ID (0xPPTTEEEE) of the attribute.
-  optional uint32 resource_id = 5;
+  // The optional resource ID (0xPPTTEEEE) of the attribute.
+  uint32 resource_id = 5;
 
-  // The interpreted/compiled version of the `value` string.
-  optional Item compiled_item = 6;
+  // The optional interpreted/compiled version of the `value` string.
+  Item compiled_item = 6;
 }
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index 3117917..520b242 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -14,39 +14,40 @@
  * limitations under the License.
  */
 
-syntax = "proto2";
+syntax = "proto3";
 
-option java_package = "android.aapt.pb.internal";
-option optimize_for = LITE_RUNTIME;
-
+import "frameworks/base/tools/aapt2/Configuration.proto";
 import "frameworks/base/tools/aapt2/Resources.proto";
 
 package aapt.pb.internal;
 
+option java_package = "android.aapt.pb.internal";
+option optimize_for = LITE_RUNTIME;
+
 // The top level message representing an external resource file (layout XML, PNG, etc).
 // This is used to represent a compiled file before it is linked. Only useful to aapt2.
 message CompiledFile {
   message Symbol {
     // The name of the symbol (in the form package:type/name).
-    optional string resource_name = 1;
+    string resource_name = 1;
 
     // The position in the file at which this symbol is defined. For debug use.
-    optional aapt.pb.SourcePosition source = 2;
+    aapt.pb.SourcePosition source = 2;
   }
 
   // The name of the resource (in the form package:type/name).
-  optional string resource_name = 1;
+  string resource_name = 1;
 
   // The configuration for which the resource is defined.
-  optional aapt.pb.ConfigDescription config = 2;
+  aapt.pb.Configuration config = 2;
+
+  // The type of the file.
+  aapt.pb.FileReference.Type type = 3;
 
   // The filesystem path to where the source file originated.
   // Mainly used to display helpful error messages.
-  optional string source_path = 3;
+  string source_path = 4;
 
   // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
-  repeated Symbol exported_symbol = 4;
-
-  // If this is a compiled XML file, this is the root node.
-  optional aapt.pb.XmlNode xml_root = 5;
+  repeated Symbol exported_symbol = 5;
 }
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 041cb4f..8ebde75 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -25,8 +25,8 @@
 
 namespace aapt {
 
-static const char* sDevelopmentSdkCodeName = "O";
-static ApiVersion sDevelopmentSdkLevel = 26;
+static const char* sDevelopmentSdkCodeName = "P";
+static ApiVersion sDevelopmentSdkLevel = 28;
 
 static const std::vector<std::pair<uint16_t, ApiVersion>> sAttrIdMap = {
     {0x021c, 1},
@@ -53,6 +53,7 @@
     {0x0527, SDK_NOUGAT},
     {0x0530, SDK_NOUGAT_MR1},
     {0x0568, SDK_O},
+    {0x056d, SDK_O_MR1},
 };
 
 static bool less_entry_id(const std::pair<uint16_t, ApiVersion>& p, uint16_t entryId) {
@@ -70,680 +71,6 @@
   return iter->second;
 }
 
-static const std::unordered_map<std::string, ApiVersion> sAttrMap = {
-    {"marqueeRepeatLimit", 2},
-    {"windowNoDisplay", 3},
-    {"backgroundDimEnabled", 3},
-    {"inputType", 3},
-    {"isDefault", 3},
-    {"windowDisablePreview", 3},
-    {"privateImeOptions", 3},
-    {"editorExtras", 3},
-    {"settingsActivity", 3},
-    {"fastScrollEnabled", 3},
-    {"reqTouchScreen", 3},
-    {"reqKeyboardType", 3},
-    {"reqHardKeyboard", 3},
-    {"reqNavigation", 3},
-    {"windowSoftInputMode", 3},
-    {"imeFullscreenBackground", 3},
-    {"noHistory", 3},
-    {"headerDividersEnabled", 3},
-    {"footerDividersEnabled", 3},
-    {"candidatesTextStyleSpans", 3},
-    {"smoothScrollbar", 3},
-    {"reqFiveWayNav", 3},
-    {"keyBackground", 3},
-    {"keyTextSize", 3},
-    {"labelTextSize", 3},
-    {"keyTextColor", 3},
-    {"keyPreviewLayout", 3},
-    {"keyPreviewOffset", 3},
-    {"keyPreviewHeight", 3},
-    {"verticalCorrection", 3},
-    {"popupLayout", 3},
-    {"state_long_pressable", 3},
-    {"keyWidth", 3},
-    {"keyHeight", 3},
-    {"horizontalGap", 3},
-    {"verticalGap", 3},
-    {"rowEdgeFlags", 3},
-    {"codes", 3},
-    {"popupKeyboard", 3},
-    {"popupCharacters", 3},
-    {"keyEdgeFlags", 3},
-    {"isModifier", 3},
-    {"isSticky", 3},
-    {"isRepeatable", 3},
-    {"iconPreview", 3},
-    {"keyOutputText", 3},
-    {"keyLabel", 3},
-    {"keyIcon", 3},
-    {"keyboardMode", 3},
-    {"isScrollContainer", 3},
-    {"fillEnabled", 3},
-    {"updatePeriodMillis", 3},
-    {"initialLayout", 3},
-    {"voiceSearchMode", 3},
-    {"voiceLanguageModel", 3},
-    {"voicePromptText", 3},
-    {"voiceLanguage", 3},
-    {"voiceMaxResults", 3},
-    {"bottomOffset", 3},
-    {"topOffset", 3},
-    {"allowSingleTap", 3},
-    {"handle", 3},
-    {"content", 3},
-    {"animateOnClick", 3},
-    {"configure", 3},
-    {"hapticFeedbackEnabled", 3},
-    {"innerRadius", 3},
-    {"thickness", 3},
-    {"sharedUserLabel", 3},
-    {"dropDownWidth", 3},
-    {"dropDownAnchor", 3},
-    {"imeOptions", 3},
-    {"imeActionLabel", 3},
-    {"imeActionId", 3},
-    {"imeExtractEnterAnimation", 3},
-    {"imeExtractExitAnimation", 3},
-    {"tension", 4},
-    {"extraTension", 4},
-    {"anyDensity", 4},
-    {"searchSuggestThreshold", 4},
-    {"includeInGlobalSearch", 4},
-    {"onClick", 4},
-    {"targetSdkVersion", 4},
-    {"maxSdkVersion", 4},
-    {"testOnly", 4},
-    {"contentDescription", 4},
-    {"gestureStrokeWidth", 4},
-    {"gestureColor", 4},
-    {"uncertainGestureColor", 4},
-    {"fadeOffset", 4},
-    {"fadeDuration", 4},
-    {"gestureStrokeType", 4},
-    {"gestureStrokeLengthThreshold", 4},
-    {"gestureStrokeSquarenessThreshold", 4},
-    {"gestureStrokeAngleThreshold", 4},
-    {"eventsInterceptionEnabled", 4},
-    {"fadeEnabled", 4},
-    {"backupAgent", 4},
-    {"allowBackup", 4},
-    {"glEsVersion", 4},
-    {"queryAfterZeroResults", 4},
-    {"dropDownHeight", 4},
-    {"smallScreens", 4},
-    {"normalScreens", 4},
-    {"largeScreens", 4},
-    {"progressBarStyleInverse", 4},
-    {"progressBarStyleSmallInverse", 4},
-    {"progressBarStyleLargeInverse", 4},
-    {"searchSettingsDescription", 4},
-    {"textColorPrimaryInverseDisableOnly", 4},
-    {"autoUrlDetect", 4},
-    {"resizeable", 4},
-    {"required", 5},
-    {"accountType", 5},
-    {"contentAuthority", 5},
-    {"userVisible", 5},
-    {"windowShowWallpaper", 5},
-    {"wallpaperOpenEnterAnimation", 5},
-    {"wallpaperOpenExitAnimation", 5},
-    {"wallpaperCloseEnterAnimation", 5},
-    {"wallpaperCloseExitAnimation", 5},
-    {"wallpaperIntraOpenEnterAnimation", 5},
-    {"wallpaperIntraOpenExitAnimation", 5},
-    {"wallpaperIntraCloseEnterAnimation", 5},
-    {"wallpaperIntraCloseExitAnimation", 5},
-    {"supportsUploading", 5},
-    {"killAfterRestore", 5},
-    {"restoreNeedsApplication", 5},
-    {"smallIcon", 5},
-    {"accountPreferences", 5},
-    {"textAppearanceSearchResultSubtitle", 5},
-    {"textAppearanceSearchResultTitle", 5},
-    {"summaryColumn", 5},
-    {"detailColumn", 5},
-    {"detailSocialSummary", 5},
-    {"thumbnail", 5},
-    {"detachWallpaper", 5},
-    {"finishOnCloseSystemDialogs", 5},
-    {"scrollbarFadeDuration", 5},
-    {"scrollbarDefaultDelayBeforeFade", 5},
-    {"fadeScrollbars", 5},
-    {"colorBackgroundCacheHint", 5},
-    {"dropDownHorizontalOffset", 5},
-    {"dropDownVerticalOffset", 5},
-    {"quickContactBadgeStyleWindowSmall", 6},
-    {"quickContactBadgeStyleWindowMedium", 6},
-    {"quickContactBadgeStyleWindowLarge", 6},
-    {"quickContactBadgeStyleSmallWindowSmall", 6},
-    {"quickContactBadgeStyleSmallWindowMedium", 6},
-    {"quickContactBadgeStyleSmallWindowLarge", 6},
-    {"author", 7},
-    {"autoStart", 7},
-    {"expandableListViewWhiteStyle", 8},
-    {"installLocation", 8},
-    {"vmSafeMode", 8},
-    {"webTextViewStyle", 8},
-    {"restoreAnyVersion", 8},
-    {"tabStripLeft", 8},
-    {"tabStripRight", 8},
-    {"tabStripEnabled", 8},
-    {"logo", 9},
-    {"xlargeScreens", 9},
-    {"immersive", 9},
-    {"overScrollMode", 9},
-    {"overScrollHeader", 9},
-    {"overScrollFooter", 9},
-    {"filterTouchesWhenObscured", 9},
-    {"textSelectHandleLeft", 9},
-    {"textSelectHandleRight", 9},
-    {"textSelectHandle", 9},
-    {"textSelectHandleWindowStyle", 9},
-    {"popupAnimationStyle", 9},
-    {"screenSize", 9},
-    {"screenDensity", 9},
-    {"allContactsName", 11},
-    {"windowActionBar", 11},
-    {"actionBarStyle", 11},
-    {"navigationMode", 11},
-    {"displayOptions", 11},
-    {"subtitle", 11},
-    {"customNavigationLayout", 11},
-    {"hardwareAccelerated", 11},
-    {"measureWithLargestChild", 11},
-    {"animateFirstView", 11},
-    {"dropDownSpinnerStyle", 11},
-    {"actionDropDownStyle", 11},
-    {"actionButtonStyle", 11},
-    {"showAsAction", 11},
-    {"previewImage", 11},
-    {"actionModeBackground", 11},
-    {"actionModeCloseDrawable", 11},
-    {"windowActionModeOverlay", 11},
-    {"valueFrom", 11},
-    {"valueTo", 11},
-    {"valueType", 11},
-    {"propertyName", 11},
-    {"ordering", 11},
-    {"fragment", 11},
-    {"windowActionBarOverlay", 11},
-    {"fragmentOpenEnterAnimation", 11},
-    {"fragmentOpenExitAnimation", 11},
-    {"fragmentCloseEnterAnimation", 11},
-    {"fragmentCloseExitAnimation", 11},
-    {"fragmentFadeEnterAnimation", 11},
-    {"fragmentFadeExitAnimation", 11},
-    {"actionBarSize", 11},
-    {"imeSubtypeLocale", 11},
-    {"imeSubtypeMode", 11},
-    {"imeSubtypeExtraValue", 11},
-    {"splitMotionEvents", 11},
-    {"listChoiceBackgroundIndicator", 11},
-    {"spinnerMode", 11},
-    {"animateLayoutChanges", 11},
-    {"actionBarTabStyle", 11},
-    {"actionBarTabBarStyle", 11},
-    {"actionBarTabTextStyle", 11},
-    {"actionOverflowButtonStyle", 11},
-    {"actionModeCloseButtonStyle", 11},
-    {"titleTextStyle", 11},
-    {"subtitleTextStyle", 11},
-    {"iconifiedByDefault", 11},
-    {"actionLayout", 11},
-    {"actionViewClass", 11},
-    {"activatedBackgroundIndicator", 11},
-    {"state_activated", 11},
-    {"listPopupWindowStyle", 11},
-    {"popupMenuStyle", 11},
-    {"textAppearanceLargePopupMen", 11},
-    {"textAppearanceSmallPopupMen", 11},
-    {"breadCrumbTitle", 11},
-    {"breadCrumbShortTitle", 11},
-    {"listDividerAlertDialog", 11},
-    {"textColorAlertDialogListItem", 11},
-    {"loopViews", 11},
-    {"dialogTheme", 11},
-    {"alertDialogTheme", 11},
-    {"dividerVertical", 11},
-    {"homeAsUpIndicator", 11},
-    {"enterFadeDuration", 11},
-    {"exitFadeDuration", 11},
-    {"selectableItemBackground", 11},
-    {"autoAdvanceViewId", 11},
-    {"useIntrinsicSizeAsMinimum", 11},
-    {"actionModeCutDrawable", 11},
-    {"actionModeCopyDrawable", 11},
-    {"actionModePasteDrawable", 11},
-    {"textEditPasteWindowLayout", 11},
-    {"textEditNoPasteWindowLayout", 11},
-    {"textIsSelectable", 11},
-    {"windowEnableSplitTouch", 11},
-    {"indeterminateProgressStyle", 11},
-    {"progressBarPadding", 11},
-    {"animationResolution", 11},
-    {"state_accelerated", 11},
-    {"baseline", 11},
-    {"homeLayout", 11},
-    {"opacity", 11},
-    {"alpha", 11},
-    {"transformPivotX", 11},
-    {"transformPivotY", 11},
-    {"translationX", 11},
-    {"translationY", 11},
-    {"scaleX", 11},
-    {"scaleY", 11},
-    {"rotation", 11},
-    {"rotationX", 11},
-    {"rotationY", 11},
-    {"showDividers", 11},
-    {"dividerPadding", 11},
-    {"borderlessButtonStyle", 11},
-    {"dividerHorizontal", 11},
-    {"itemPadding", 11},
-    {"buttonBarStyle", 11},
-    {"buttonBarButtonStyle", 11},
-    {"segmentedButtonStyle", 11},
-    {"staticWallpaperPreview", 11},
-    {"allowParallelSyncs", 11},
-    {"isAlwaysSyncable", 11},
-    {"verticalScrollbarPosition", 11},
-    {"fastScrollAlwaysVisible", 11},
-    {"fastScrollThumbDrawable", 11},
-    {"fastScrollPreviewBackgroundLeft", 11},
-    {"fastScrollPreviewBackgroundRight", 11},
-    {"fastScrollTrackDrawable", 11},
-    {"fastScrollOverlayPosition", 11},
-    {"customTokens", 11},
-    {"nextFocusForward", 11},
-    {"firstDayOfWeek", 11},
-    {"showWeekNumber", 11},
-    {"minDate", 11},
-    {"maxDate", 11},
-    {"shownWeekCount", 11},
-    {"selectedWeekBackgroundColor", 11},
-    {"focusedMonthDateColor", 11},
-    {"unfocusedMonthDateColor", 11},
-    {"weekNumberColor", 11},
-    {"weekSeparatorLineColor", 11},
-    {"selectedDateVerticalBar", 11},
-    {"weekDayTextAppearance", 11},
-    {"dateTextAppearance", 11},
-    {"solidColor", 11},
-    {"spinnersShown", 11},
-    {"calendarViewShown", 11},
-    {"state_multiline", 11},
-    {"detailsElementBackground", 11},
-    {"textColorHighlightInverse", 11},
-    {"textColorLinkInverse", 11},
-    {"editTextColor", 11},
-    {"editTextBackground", 11},
-    {"horizontalScrollViewStyle", 11},
-    {"layerType", 11},
-    {"alertDialogIcon", 11},
-    {"windowMinWidthMajor", 11},
-    {"windowMinWidthMinor", 11},
-    {"queryHint", 11},
-    {"fastScrollTextColor", 11},
-    {"largeHeap", 11},
-    {"windowCloseOnTouchOutside", 11},
-    {"datePickerStyle", 11},
-    {"calendarViewStyle", 11},
-    {"textEditSidePasteWindowLayout", 11},
-    {"textEditSideNoPasteWindowLayout", 11},
-    {"actionMenuTextAppearance", 11},
-    {"actionMenuTextColor", 11},
-    {"textCursorDrawable", 12},
-    {"resizeMode", 12},
-    {"requiresSmallestWidthDp", 12},
-    {"compatibleWidthLimitDp", 12},
-    {"largestWidthLimitDp", 12},
-    {"state_hovered", 13},
-    {"state_drag_can_accept", 13},
-    {"state_drag_hovered", 13},
-    {"stopWithTask", 13},
-    {"switchTextOn", 13},
-    {"switchTextOff", 13},
-    {"switchPreferenceStyle", 13},
-    {"switchTextAppearance", 13},
-    {"track", 13},
-    {"switchMinWidth", 13},
-    {"switchPadding", 13},
-    {"thumbTextPadding", 13},
-    {"textSuggestionsWindowStyle", 13},
-    {"textEditSuggestionItemLayout", 13},
-    {"rowCount", 13},
-    {"rowOrderPreserved", 13},
-    {"columnCount", 13},
-    {"columnOrderPreserved", 13},
-    {"useDefaultMargins", 13},
-    {"alignmentMode", 13},
-    {"layout_row", 13},
-    {"layout_rowSpan", 13},
-    {"layout_columnSpan", 13},
-    {"actionModeSelectAllDrawable", 13},
-    {"isAuxiliary", 13},
-    {"accessibilityEventTypes", 13},
-    {"packageNames", 13},
-    {"accessibilityFeedbackType", 13},
-    {"notificationTimeout", 13},
-    {"accessibilityFlags", 13},
-    {"canRetrieveWindowContent", 13},
-    {"listPreferredItemHeightLarge", 13},
-    {"listPreferredItemHeightSmall", 13},
-    {"actionBarSplitStyle", 13},
-    {"actionProviderClass", 13},
-    {"backgroundStacked", 13},
-    {"backgroundSplit", 13},
-    {"textAllCaps", 13},
-    {"colorPressedHighlight", 13},
-    {"colorLongPressedHighlight", 13},
-    {"colorFocusedHighlight", 13},
-    {"colorActivatedHighlight", 13},
-    {"colorMultiSelectHighlight", 13},
-    {"drawableStart", 13},
-    {"drawableEnd", 13},
-    {"actionModeStyle", 13},
-    {"minResizeWidth", 13},
-    {"minResizeHeight", 13},
-    {"actionBarWidgetTheme", 13},
-    {"uiOptions", 13},
-    {"subtypeLocale", 13},
-    {"subtypeExtraValue", 13},
-    {"actionBarDivider", 13},
-    {"actionBarItemBackground", 13},
-    {"actionModeSplitBackground", 13},
-    {"textAppearanceListItem", 13},
-    {"textAppearanceListItemSmall", 13},
-    {"targetDescriptions", 13},
-    {"directionDescriptions", 13},
-    {"overridesImplicitlyEnabledSubtype", 13},
-    {"listPreferredItemPaddingLeft", 13},
-    {"listPreferredItemPaddingRight", 13},
-    {"requiresFadingEdge", 13},
-    {"publicKey", 13},
-    {"parentActivityName", 16},
-    {"isolatedProcess", 16},
-    {"importantForAccessibility", 16},
-    {"keyboardLayout", 16},
-    {"fontFamily", 16},
-    {"mediaRouteButtonStyle", 16},
-    {"mediaRouteTypes", 16},
-    {"supportsRtl", 17},
-    {"textDirection", 17},
-    {"textAlignment", 17},
-    {"layoutDirection", 17},
-    {"paddingStart", 17},
-    {"paddingEnd", 17},
-    {"layout_marginStart", 17},
-    {"layout_marginEnd", 17},
-    {"layout_toStartOf", 17},
-    {"layout_toEndOf", 17},
-    {"layout_alignStart", 17},
-    {"layout_alignEnd", 17},
-    {"layout_alignParentStart", 17},
-    {"layout_alignParentEnd", 17},
-    {"listPreferredItemPaddingStart", 17},
-    {"listPreferredItemPaddingEnd", 17},
-    {"singleUser", 17},
-    {"presentationTheme", 17},
-    {"subtypeId", 17},
-    {"initialKeyguardLayout", 17},
-    {"widgetCategory", 17},
-    {"permissionGroupFlags", 17},
-    {"labelFor", 17},
-    {"permissionFlags", 17},
-    {"checkedTextViewStyle", 17},
-    {"showOnLockScreen", 17},
-    {"format12Hour", 17},
-    {"format24Hour", 17},
-    {"timeZone", 17},
-    {"mipMap", 18},
-    {"mirrorForRtl", 18},
-    {"windowOverscan", 18},
-    {"requiredForAllUsers", 18},
-    {"indicatorStart", 18},
-    {"indicatorEnd", 18},
-    {"childIndicatorStart", 18},
-    {"childIndicatorEnd", 18},
-    {"restrictedAccountType", 18},
-    {"requiredAccountType", 18},
-    {"canRequestTouchExplorationMode", 18},
-    {"canRequestEnhancedWebAccessibility", 18},
-    {"canRequestFilterKeyEvents", 18},
-    {"layoutMode", 18},
-    {"keySet", 19},
-    {"targetId", 19},
-    {"fromScene", 19},
-    {"toScene", 19},
-    {"transition", 19},
-    {"transitionOrdering", 19},
-    {"fadingMode", 19},
-    {"startDelay", 19},
-    {"ssp", 19},
-    {"sspPrefix", 19},
-    {"sspPattern", 19},
-    {"addPrintersActivity", 19},
-    {"vendor", 19},
-    {"category", 19},
-    {"isAsciiCapable", 19},
-    {"autoMirrored", 19},
-    {"supportsSwitchingToNextInputMethod", 19},
-    {"requireDeviceUnlock", 19},
-    {"apduServiceBanner", 19},
-    {"accessibilityLiveRegion", 19},
-    {"windowTranslucentStatus", 19},
-    {"windowTranslucentNavigation", 19},
-    {"advancedPrintOptionsActivity", 19},
-    {"banner", 20},
-    {"windowSwipeToDismiss", 20},
-    {"isGame", 20},
-    {"allowEmbedded", 20},
-    {"setupActivity", 20},
-    {"fastScrollStyle", 21},
-    {"windowContentTransitions", 21},
-    {"windowContentTransitionManager", 21},
-    {"translationZ", 21},
-    {"tintMode", 21},
-    {"controlX1", 21},
-    {"controlY1", 21},
-    {"controlX2", 21},
-    {"controlY2", 21},
-    {"transitionName", 21},
-    {"transitionGroup", 21},
-    {"viewportWidth", 21},
-    {"viewportHeight", 21},
-    {"fillColor", 21},
-    {"pathData", 21},
-    {"strokeColor", 21},
-    {"strokeWidth", 21},
-    {"trimPathStart", 21},
-    {"trimPathEnd", 21},
-    {"trimPathOffset", 21},
-    {"strokeLineCap", 21},
-    {"strokeLineJoin", 21},
-    {"strokeMiterLimit", 21},
-    {"colorControlNormal", 21},
-    {"colorControlActivated", 21},
-    {"colorButtonNormal", 21},
-    {"colorControlHighlight", 21},
-    {"persistableMode", 21},
-    {"titleTextAppearance", 21},
-    {"subtitleTextAppearance", 21},
-    {"slideEdge", 21},
-    {"actionBarTheme", 21},
-    {"textAppearanceListItemSecondary", 21},
-    {"colorPrimary", 21},
-    {"colorPrimaryDark", 21},
-    {"colorAccent", 21},
-    {"nestedScrollingEnabled", 21},
-    {"windowEnterTransition", 21},
-    {"windowExitTransition", 21},
-    {"windowSharedElementEnterTransition", 21},
-    {"windowSharedElementExitTransition", 21},
-    {"windowAllowReturnTransitionOverlap", 21},
-    {"windowAllowEnterTransitionOverlap", 21},
-    {"sessionService", 21},
-    {"stackViewStyle", 21},
-    {"switchStyle", 21},
-    {"elevation", 21},
-    {"excludeId", 21},
-    {"excludeClass", 21},
-    {"hideOnContentScroll", 21},
-    {"actionOverflowMenuStyle", 21},
-    {"documentLaunchMode", 21},
-    {"maxRecents", 21},
-    {"autoRemoveFromRecents", 21},
-    {"stateListAnimator", 21},
-    {"toId", 21},
-    {"fromId", 21},
-    {"reversible", 21},
-    {"splitTrack", 21},
-    {"targetName", 21},
-    {"excludeName", 21},
-    {"matchOrder", 21},
-    {"windowDrawsSystemBarBackgrounds", 21},
-    {"statusBarColor", 21},
-    {"navigationBarColor", 21},
-    {"contentInsetStart", 21},
-    {"contentInsetEnd", 21},
-    {"contentInsetLeft", 21},
-    {"contentInsetRight", 21},
-    {"paddingMode", 21},
-    {"layout_rowWeight", 21},
-    {"layout_columnWeight", 21},
-    {"translateX", 21},
-    {"translateY", 21},
-    {"selectableItemBackgroundBorderless", 21},
-    {"elegantTextHeight", 21},
-    {"searchKeyphraseId", 21},
-    {"searchKeyphrase", 21},
-    {"searchKeyphraseSupportedLocales", 21},
-    {"windowTransitionBackgroundFadeDuration", 21},
-    {"overlapAnchor", 21},
-    {"progressTint", 21},
-    {"progressTintMode", 21},
-    {"progressBackgroundTint", 21},
-    {"progressBackgroundTintMode", 21},
-    {"secondaryProgressTint", 21},
-    {"secondaryProgressTintMode", 21},
-    {"indeterminateTint", 21},
-    {"indeterminateTintMode", 21},
-    {"backgroundTint", 21},
-    {"backgroundTintMode", 21},
-    {"foregroundTint", 21},
-    {"foregroundTintMode", 21},
-    {"buttonTint", 21},
-    {"buttonTintMode", 21},
-    {"thumbTint", 21},
-    {"thumbTintMode", 21},
-    {"fullBackupOnly", 21},
-    {"propertyXName", 21},
-    {"propertyYName", 21},
-    {"relinquishTaskIdentity", 21},
-    {"tileModeX", 21},
-    {"tileModeY", 21},
-    {"actionModeShareDrawable", 21},
-    {"actionModeFindDrawable", 21},
-    {"actionModeWebSearchDrawable", 21},
-    {"transitionVisibilityMode", 21},
-    {"minimumHorizontalAngle", 21},
-    {"minimumVerticalAngle", 21},
-    {"maximumAngle", 21},
-    {"searchViewStyle", 21},
-    {"closeIcon", 21},
-    {"goIcon", 21},
-    {"searchIcon", 21},
-    {"voiceIcon", 21},
-    {"commitIcon", 21},
-    {"suggestionRowLayout", 21},
-    {"queryBackground", 21},
-    {"submitBackground", 21},
-    {"buttonBarPositiveButtonStyle", 21},
-    {"buttonBarNeutralButtonStyle", 21},
-    {"buttonBarNegativeButtonStyle", 21},
-    {"popupElevation", 21},
-    {"actionBarPopupTheme", 21},
-    {"multiArch", 21},
-    {"touchscreenBlocksFocus", 21},
-    {"windowElevation", 21},
-    {"launchTaskBehindTargetAnimation", 21},
-    {"launchTaskBehindSourceAnimation", 21},
-    {"restrictionType", 21},
-    {"dayOfWeekBackground", 21},
-    {"dayOfWeekTextAppearance", 21},
-    {"headerMonthTextAppearance", 21},
-    {"headerDayOfMonthTextAppearance", 21},
-    {"headerYearTextAppearance", 21},
-    {"yearListItemTextAppearance", 21},
-    {"yearListSelectorColor", 21},
-    {"calendarTextColor", 21},
-    {"recognitionService", 21},
-    {"timePickerStyle", 21},
-    {"timePickerDialogTheme", 21},
-    {"headerTimeTextAppearance", 21},
-    {"headerAmPmTextAppearance", 21},
-    {"numbersTextColor", 21},
-    {"numbersBackgroundColor", 21},
-    {"numbersSelectorColor", 21},
-    {"amPmTextColor", 21},
-    {"amPmBackgroundColor", 21},
-    {"searchKeyphraseRecognitionFlags", 21},
-    {"checkMarkTint", 21},
-    {"checkMarkTintMode", 21},
-    {"popupTheme", 21},
-    {"toolbarStyle", 21},
-    {"windowClipToOutline", 21},
-    {"datePickerDialogTheme", 21},
-    {"showText", 21},
-    {"windowReturnTransition", 21},
-    {"windowReenterTransition", 21},
-    {"windowSharedElementReturnTransition", 21},
-    {"windowSharedElementReenterTransition", 21},
-    {"resumeWhilePausing", 21},
-    {"datePickerMode", 21},
-    {"timePickerMode", 21},
-    {"inset", 21},
-    {"letterSpacing", 21},
-    {"fontFeatureSettings", 21},
-    {"outlineProvider", 21},
-    {"contentAgeHint", 21},
-    {"country", 21},
-    {"windowSharedElementsUseOverlay", 21},
-    {"reparent", 21},
-    {"reparentWithOverlay", 21},
-    {"ambientShadowAlpha", 21},
-    {"spotShadowAlpha", 21},
-    {"navigationIcon", 21},
-    {"navigationContentDescription", 21},
-    {"fragmentExitTransition", 21},
-    {"fragmentEnterTransition", 21},
-    {"fragmentSharedElementEnterTransition", 21},
-    {"fragmentReturnTransition", 21},
-    {"fragmentSharedElementReturnTransition", 21},
-    {"fragmentReenterTransition", 21},
-    {"fragmentAllowEnterTransitionOverlap", 21},
-    {"fragmentAllowReturnTransitionOverlap", 21},
-    {"patternPathData", 21},
-    {"strokeAlpha", 21},
-    {"fillAlpha", 21},
-    {"windowActivityTransitions", 21},
-    {"colorEdgeEffect", 21}};
-
-ApiVersion FindAttributeSdkLevel(const ResourceName& name) {
-  if (name.package != "android" && name.type != ResourceType::kAttr) {
-    return 0;
-  }
-
-  auto iter = sAttrMap.find(name.entry);
-  if (iter != sAttrMap.end()) {
-    return iter->second;
-  }
-  return SDK_LOLLIPOP_MR1;
-}
-
 std::pair<StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion() {
   return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel);
 }
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 13584c0..5b7be3b 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -57,7 +57,6 @@
 };
 
 ApiVersion FindAttributeSdkLevel(const ResourceId& id);
-ApiVersion FindAttributeSdkLevel(const ResourceName& name);
 std::pair<android::StringPiece, ApiVersion> GetDevelopmentSdkCodeNameAndVersion();
 
 }  // namespace aapt
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 705b1ab..3a1a18c 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -191,6 +191,13 @@
   return Ref(borrow);
 }
 
+StringPool::Ref StringPool::MakeRef(const Ref& ref) {
+  if (ref.entry_->pool_ == this) {
+    return ref;
+  }
+  return MakeRef(ref.entry_->value, ref.entry_->context);
+}
+
 StringPool::StyleRef StringPool::MakeRef(const StyleString& str) {
   return MakeRef(str, Context{});
 }
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 8350d0d..3c1f3dc 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -49,6 +49,8 @@
 // Otherwise, the style data array would have to be sparse and take up more space.
 class StringPool {
  public:
+  using size_type = size_t;
+
   class Context {
    public:
     enum : uint32_t {
@@ -165,6 +167,9 @@
   // when sorting the string pool. Returns a reference to the string in the pool.
   Ref MakeRef(const android::StringPiece& str, const Context& context);
 
+  // Adds a string from another string pool. Returns a reference to the string in the string pool.
+  Ref MakeRef(const Ref& ref);
+
   // Adds a style to the string pool and returns a reference to it.
   StyleRef MakeRef(const StyleString& str);
 
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index eb4fa49..4e74ec3 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -22,12 +22,11 @@
 
 namespace aapt {
 
-/**
- * Visits a value and invokes the appropriate method based on its type. Does not
- * traverse into compound types. Use ValueVisitor for that.
- */
-struct RawValueVisitor {
-  virtual ~RawValueVisitor() = default;
+// Visits a value and invokes the appropriate method based on its type.
+// Does not traverse into compound types. Use ValueVisitor for that.
+class ValueVisitor {
+ public:
+  virtual ~ValueVisitor() = default;
 
   virtual void VisitAny(Value* value) {}
   virtual void VisitItem(Item* value) { VisitAny(value); }
@@ -46,21 +45,67 @@
   virtual void Visit(Styleable* value) { VisitAny(value); }
 };
 
+// Const version of ValueVisitor.
+class ConstValueVisitor {
+ public:
+  virtual ~ConstValueVisitor() = default;
+
+  virtual void VisitAny(const Value* value) {
+  }
+  virtual void VisitItem(const Item* value) {
+    VisitAny(value);
+  }
+  virtual void Visit(const Reference* value) {
+    VisitItem(value);
+  }
+  virtual void Visit(const RawString* value) {
+    VisitItem(value);
+  }
+  virtual void Visit(const String* value) {
+    VisitItem(value);
+  }
+  virtual void Visit(const StyledString* value) {
+    VisitItem(value);
+  }
+  virtual void Visit(const FileReference* value) {
+    VisitItem(value);
+  }
+  virtual void Visit(const Id* value) {
+    VisitItem(value);
+  }
+  virtual void Visit(const BinaryPrimitive* value) {
+    VisitItem(value);
+  }
+
+  virtual void Visit(const Attribute* value) {
+    VisitAny(value);
+  }
+  virtual void Visit(const Style* value) {
+    VisitAny(value);
+  }
+  virtual void Visit(const Array* value) {
+    VisitAny(value);
+  }
+  virtual void Visit(const Plural* value) {
+    VisitAny(value);
+  }
+  virtual void Visit(const Styleable* value) {
+    VisitAny(value);
+  }
+};
+
 // NOLINT, do not add parentheses around T.
 #define DECL_VISIT_COMPOUND_VALUE(T)                   \
   virtual void Visit(T* value) override { /* NOLINT */ \
     VisitSubValues(value);                             \
   }
 
-/**
- * Visits values, and if they are compound values, visits the components as
- * well.
- */
-struct ValueVisitor : public RawValueVisitor {
+// Visits values, and if they are compound values, descends into their components as well.
+struct DescendingValueVisitor : public ValueVisitor {
   // The compiler will think we're hiding an overload, when we actually intend
   // to call into RawValueVisitor. This will expose the visit methods in the
   // super class so the compiler knows we are trying to call them.
-  using RawValueVisitor::Visit;
+  using ValueVisitor::Visit;
 
   void VisitSubValues(Attribute* attribute) {
     for (Attribute::Symbol& symbol : attribute->symbols) {
@@ -106,37 +151,30 @@
   DECL_VISIT_COMPOUND_VALUE(Styleable);
 };
 
-/**
- * Do not use directly. Helper struct for dyn_cast.
- */
+// Do not use directly. Helper struct for dyn_cast.
 template <typename T>
-struct DynCastVisitor : public RawValueVisitor {
-  T* value = nullptr;
+struct DynCastVisitor : public ConstValueVisitor {
+  const T* value = nullptr;
 
-  void Visit(T* v) override { value = v; }
+  void Visit(const T* v) override {
+    value = v;
+  }
 };
 
-/**
- * Specialization that checks if the value is an Item.
- */
+// Specialization that checks if the value is an Item.
 template <>
-struct DynCastVisitor<Item> : public RawValueVisitor {
-  Item* value = nullptr;
+struct DynCastVisitor<Item> : public ConstValueVisitor {
+  const Item* value = nullptr;
 
-  void VisitItem(Item* item) override { value = item; }
+  void VisitItem(const Item* item) override {
+    value = item;
+  }
 };
 
+// Returns a valid pointer to T if the value is an instance of T. Returns nullptr if value is
+// nullptr of if value is not an instance of T.
 template <typename T>
 const T* ValueCast(const Value* value) {
-  return ValueCast<T>(const_cast<Value*>(value));
-}
-
-/**
- * Returns a valid pointer to T if the Value is of subtype T.
- * Otherwise, returns nullptr.
- */
-template <typename T>
-T* ValueCast(Value* value) {
   if (!value) {
     return nullptr;
   }
@@ -145,8 +183,13 @@
   return visitor.value;
 }
 
-inline void VisitAllValuesInPackage(ResourceTablePackage* pkg,
-                                    RawValueVisitor* visitor) {
+// Non-const version of ValueCast.
+template <typename T>
+T* ValueCast(Value* value) {
+  return const_cast<T*>(ValueCast<T>(static_cast<const Value*>(value)));
+}
+
+inline void VisitAllValuesInPackage(ResourceTablePackage* pkg, ValueVisitor* visitor) {
   for (auto& type : pkg->types) {
     for (auto& entry : type->entries) {
       for (auto& config_value : entry->values) {
@@ -156,8 +199,7 @@
   }
 }
 
-inline void VisitAllValuesInTable(ResourceTable* table,
-                                  RawValueVisitor* visitor) {
+inline void VisitAllValuesInTable(ResourceTable* table, ValueVisitor* visitor) {
   for (auto& pkg : table->packages) {
     VisitAllValuesInPackage(pkg.get(), visitor);
   }
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
index eb75b10..5aea77d 100644
--- a/tools/aapt2/ValueVisitor_test.cpp
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -24,16 +24,16 @@
 
 namespace aapt {
 
-struct SingleReferenceVisitor : public ValueVisitor {
-  using ValueVisitor::Visit;
+struct SingleReferenceVisitor : public DescendingValueVisitor {
+  using DescendingValueVisitor::Visit;
 
   Reference* visited = nullptr;
 
   void Visit(Reference* ref) override { visited = ref; }
 };
 
-struct StyleVisitor : public ValueVisitor {
-  using ValueVisitor::Visit;
+struct StyleVisitor : public DescendingValueVisitor {
+  using DescendingValueVisitor::Visit;
 
   std::list<Reference*> visited_refs;
   Style* visited_style = nullptr;
@@ -42,7 +42,7 @@
 
   void Visit(Style* style) override {
     visited_style = style;
-    ValueVisitor::Visit(style);
+    DescendingValueVisitor::Visit(style);
   }
 };
 
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 7f5bbf0..53910af 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -35,12 +35,13 @@
 #include "compile/Png.h"
 #include "compile/PseudolocaleGenerator.h"
 #include "compile/XmlIdCollector.h"
-#include "flatten/Archive.h"
-#include "flatten/XmlFlattener.h"
-#include "io/BigBufferOutputStream.h"
-#include "io/FileInputStream.h"
+#include "format/Archive.h"
+#include "format/Container.h"
+#include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
+#include "io/StringStream.h"
 #include "io/Util.h"
-#include "proto/ProtoSerialize.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
@@ -49,6 +50,7 @@
 
 using ::aapt::io::FileInputStream;
 using ::android::StringPiece;
+using ::android::base::SystemErrorCodeToString;
 using ::google::protobuf::io::CopyingOutputStreamAdaptor;
 
 namespace aapt {
@@ -116,7 +118,7 @@
   bool verbose = false;
 };
 
-static std::string BuildIntermediateFilename(const ResourcePathData& data) {
+static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
   std::stringstream name;
   name << data.resource_dir;
   if (!data.config_str.empty()) {
@@ -141,7 +143,7 @@
   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
   if (!d) {
     context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
-                                     << android::base::SystemErrorCodeToString(errno));
+                                                           << SystemErrorCodeToString(errno));
     return false;
   }
 
@@ -160,7 +162,7 @@
     std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
     if (!subdir) {
       context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
-                                       << android::base::SystemErrorCodeToString(errno));
+                                                                << SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -241,15 +243,15 @@
     return false;
   }
 
-  // Make sure CopyingOutputStreamAdaptor is deleted before we call
-  // writer->FinishEntry().
+  // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
   {
-    // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream interface.
+    // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
+    ContainerWriter container_writer(&copying_adaptor, 1u);
 
-    std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
-    if (!pb_table->SerializeToZeroCopyStream(&copying_adaptor)) {
+    pb::ResourceTable pb_table;
+    SerializeTableToPb(table, &pb_table);
+    if (!container_writer.AddResTableEntry(pb_table)) {
       context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
       return false;
     }
@@ -262,45 +264,8 @@
   return true;
 }
 
-static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
-                                         const BigBuffer& buffer, IArchiveWriter* writer,
-                                         IDiagnostics* diag) {
-  // Start the entry so we can write the header.
-  if (!writer->StartEntry(output_path, 0)) {
-    diag->Error(DiagMessage(output_path) << "failed to open file");
-    return false;
-  }
-
-  // Make sure CopyingOutputStreamAdaptor is deleted before we call
-  // writer->FinishEntry().
-  {
-    // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream interface.
-    CopyingOutputStreamAdaptor copying_adaptor(writer);
-    CompiledFileOutputStream output_stream(&copying_adaptor);
-
-    // Number of CompiledFiles.
-    output_stream.WriteLittleEndian32(1);
-
-    std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
-    output_stream.WriteCompiledFile(compiled_file.get());
-    output_stream.WriteData(&buffer);
-
-    if (output_stream.HadError()) {
-      diag->Error(DiagMessage(output_path) << "failed to write data");
-      return false;
-    }
-  }
-
-  if (!writer->FinishEntry()) {
-    diag->Error(DiagMessage(output_path) << "failed to finish writing data");
-    return false;
-  }
-  return true;
-}
-
-static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
-                                       const android::FileMap& map, IArchiveWriter* writer,
+static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+                                       io::KnownSizeInputStream* in, IArchiveWriter* writer,
                                        IDiagnostics* diag) {
   // Start the entry so we can write the header.
   if (!writer->StartEntry(output_path, 0)) {
@@ -308,23 +273,17 @@
     return false;
   }
 
-  // Make sure CopyingOutputStreamAdaptor is deleted before we call
-  // writer->FinishEntry().
+  // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
   {
-    // Wrap our IArchiveWriter with an adaptor that implements the
-    // ZeroCopyOutputStream interface.
+    // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
-    CompiledFileOutputStream output_stream(&copying_adaptor);
+    ContainerWriter container_writer(&copying_adaptor, 1u);
 
-    // Number of CompiledFiles.
-    output_stream.WriteLittleEndian32(1);
+    pb::internal::CompiledFile pb_compiled_file;
+    SerializeCompiledFileToPb(file, &pb_compiled_file);
 
-    std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
-    output_stream.WriteCompiledFile(compiled_file.get());
-    output_stream.WriteData(map.getDataPtr(), map.getDataLength());
-
-    if (output_stream.HadError()) {
-      diag->Error(DiagMessage(output_path) << "failed to write data");
+    if (!container_writer.AddResFileEntry(pb_compiled_file, in)) {
+      diag->Error(DiagMessage(output_path) << "failed to write entry data");
       return false;
     }
   }
@@ -336,23 +295,19 @@
   return true;
 }
 
-static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
-                                  xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
-  BigBuffer buffer(1024);
-  XmlFlattenerOptions xml_flattener_options;
-  xml_flattener_options.keep_raw_values = true;
-  XmlFlattener flattener(&buffer, xml_flattener_options);
-  if (!flattener.Consume(context, xmlres)) {
-    return false;
-  }
+static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+                                  ContainerWriter* container_writer, IDiagnostics* diag) {
+  pb::internal::CompiledFile pb_compiled_file;
+  SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
 
-  std::unique_ptr<pb::internal::CompiledFile> pb_compiled_file =
-      SerializeCompiledFileToPb(xmlres->file);
-  out->WriteCompiledFile(pb_compiled_file.get());
-  out->WriteData(&buffer);
+  pb::XmlNode pb_xml_node;
+  SerializeXmlToPb(*xmlres.root, &pb_xml_node);
 
-  if (out->HadError()) {
-    context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
+  std::string serialized_xml = pb_xml_node.SerializeAsString();
+  io::StringInputStream serialized_in(serialized_xml);
+
+  if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) {
+    diag->Error(DiagMessage(output_path) << "failed to write entry data");
     return false;
   }
   return true;
@@ -401,6 +356,7 @@
   xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
   xmlres->file.config = path_data.config;
   xmlres->file.source = path_data.source;
+  xmlres->file.type = ResourceFile::Type::kProtoXml;
 
   // Collect IDs that are defined here.
   XmlIdCollector collector;
@@ -420,24 +376,23 @@
     return false;
   }
 
+  std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
+      inline_xml_format_parser.GetExtractedInlineXmlDocuments();
+
   // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
   {
     // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
     CopyingOutputStreamAdaptor copying_adaptor(writer);
-    CompiledFileOutputStream output_stream(&copying_adaptor);
+    ContainerWriter container_writer(&copying_adaptor, 1u + inline_documents.size());
 
-    std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
-        inline_xml_format_parser.GetExtractedInlineXmlDocuments();
-
-    // Number of CompiledFiles.
-    output_stream.WriteLittleEndian32(1 + inline_documents.size());
-
-    if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
+    if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer,
+                               context->GetDiagnostics())) {
       return false;
     }
 
-    for (auto& inline_xml_doc : inline_documents) {
-      if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
+    for (const std::unique_ptr<xml::XmlResource>& inline_xml_doc : inline_documents) {
+      if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer,
+                                 context->GetDiagnostics())) {
         return false;
       }
     }
@@ -462,6 +417,7 @@
   res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
   res_file.config = path_data.config;
   res_file.source = path_data.source;
+  res_file.type = ResourceFile::Type::kPng;
 
   {
     std::string content;
@@ -469,7 +425,7 @@
                                          true /*follow_symlinks*/)) {
       context->GetDiagnostics()->Error(DiagMessage(path_data.source)
                                        << "failed to open file: "
-                                       << android::base::SystemErrorCodeToString(errno));
+                                       << SystemErrorCodeToString(errno));
       return false;
     }
 
@@ -553,8 +509,9 @@
     }
   }
 
-  if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
-                                    context->GetDiagnostics())) {
+  io::BigBufferInputStream buffer_in(&buffer);
+  if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
+                                  context->GetDiagnostics())) {
     return false;
   }
   return true;
@@ -572,6 +529,7 @@
   res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
   res_file.config = path_data.config;
   res_file.source = path_data.source;
+  res_file.type = ResourceFile::Type::kUnknown;
 
   std::string error_str;
   Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
@@ -581,7 +539,8 @@
     return false;
   }
 
-  if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
+  io::MmappedData mmapped_in(std::move(f.value()));
+  if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer,
                                   context->GetDiagnostics())) {
     return false;
   }
@@ -611,7 +570,7 @@
   }
 
   NameMangler* GetNameMangler() override {
-    abort();
+    UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase";
     return nullptr;
   }
 
@@ -625,7 +584,7 @@
   }
 
   SymbolTable* GetExternalSymbols() override {
-    abort();
+    UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase";
     return nullptr;
   }
 
@@ -634,14 +593,13 @@
   }
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(CompileContext);
+
   IDiagnostics* diagnostics_;
   bool verbose_ = false;
 };
 
-/**
- * Entry point for compilation phase. Parses arguments and dispatches to the
- * correct steps.
- */
+// Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
 int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
   CompileContext context(diagnostics);
   CompileOptions options;
@@ -714,50 +672,34 @@
       continue;
     }
 
+    // Determine how to compile the file based on its type.
+    auto compile_func = &CompileFile;
     if (path_data.resource_dir == "values") {
-      // Overwrite the extension.
+      compile_func = &CompileTable;
+      // We use a different extension (not necessary anymore, but avoids altering the existing
+      // build system logic).
       path_data.extension = "arsc";
-
-      const std::string output_filename = BuildIntermediateFilename(path_data);
-      if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
-        error = true;
-      }
-
-    } else {
-      const std::string output_filename = BuildIntermediateFilename(path_data);
-      if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
-        if (*type != ResourceType::kRaw) {
-          if (path_data.extension == "xml") {
-            if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
-              error = true;
-            }
-          } else if (!options.no_png_crunch &&
-                     (path_data.extension == "png" || path_data.extension == "9.png")) {
-            if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
-              error = true;
-            }
-          } else {
-            if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
-              error = true;
-            }
-          }
-        } else {
-          if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
-            error = true;
-          }
+    } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
+      if (*type != ResourceType::kRaw) {
+        if (path_data.extension == "xml") {
+          compile_func = &CompileXml;
+        } else if (!options.no_png_crunch &&
+                   (path_data.extension == "png" || path_data.extension == "9.png")) {
+          compile_func = &CompilePng;
         }
-      } else {
-        context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
-                                                      << "'");
-        error = true;
       }
+    } else {
+      context.GetDiagnostics()->Error(DiagMessage()
+                                      << "invalid file path '" << path_data.source << "'");
+      error = true;
+      continue;
     }
-  }
 
-  if (error) {
-    return 1;
+    // Compile the file.
+    const std::string out_path = BuildIntermediateContainerFilename(path_data);
+    error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path);
   }
-  return 0;
+  return error ? 1 : 0;
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 1a6f348..625c47c 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -22,7 +22,7 @@
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 
@@ -325,9 +325,9 @@
   return diff;
 }
 
-class ZeroingReferenceVisitor : public ValueVisitor {
+class ZeroingReferenceVisitor : public DescendingValueVisitor {
  public:
-  using ValueVisitor::Visit;
+  using DescendingValueVisitor::Visit;
 
   void Visit(Reference* ref) override {
     if (ref->name && ref->id) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 0965910..090c3fb 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -21,132 +21,147 @@
 #include "Debug.h"
 #include "Diagnostics.h"
 #include "Flags.h"
+#include "format/Container.h"
+#include "format/binary/BinaryResourceParser.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "io/FileStream.h"
 #include "io/ZipArchive.h"
 #include "process/IResourceTableConsumer.h"
-#include "proto/ProtoSerialize.h"
-#include "unflatten/BinaryResourceParser.h"
 #include "util/Files.h"
 
 using ::android::StringPiece;
 
 namespace aapt {
 
-bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len,
-                      const Source& source, IAaptContext* context) {
-  std::unique_ptr<ResourceFile> file =
-      DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics());
-  if (!file) {
-    context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
-    return false;
+static const char* ResourceFileTypeToString(const ResourceFile::Type& type) {
+  switch (type) {
+    case ResourceFile::Type::kPng:
+      return "PNG";
+    case ResourceFile::Type::kBinaryXml:
+      return "BINARY_XML";
+    case ResourceFile::Type::kProtoXml:
+      return "PROTO_XML";
+    default:
+      break;
   }
-
-  std::cout << "Resource: " << file->name << "\n"
-            << "Config:   " << file->config << "\n"
-            << "Source:   " << file->source << "\n";
-  return true;
+  return "UNKNOWN";
 }
 
-bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
-  std::unique_ptr<ResourceTable> table;
+static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset,
+                             size_t len) {
+  std::cout << "Resource: " << file.name << "\n"
+            << "Config:   " << file.config << "\n"
+            << "Source:   " << file.source << "\n"
+            << "Type:     " << ResourceFileTypeToString(file.type) << "\n"
+            << "DataOff:  " << offset << "\n"
+            << "DataLen:  " << len << "\n";
+}
+
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+  DebugPrintTableOptions print_options;
+  print_options.show_sources = true;
 
   std::string err;
   std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
   if (zip) {
-    io::IFile* file = zip->FindFile("resources.arsc.flat");
-    if (file) {
+    ResourceTable table;
+    if (io::IFile* file = zip->FindFile("resources.pb")) {
       std::unique_ptr<io::IData> data = file->OpenAsData();
-      if (!data) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                         << "failed to open resources.arsc.flat");
+      if (data == nullptr) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb");
         return false;
       }
 
       pb::ResourceTable pb_table;
       if (!pb_table.ParseFromArray(data->data(), data->size())) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat");
+        context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb");
         return false;
       }
 
-      table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics());
-      if (!table) {
+      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                         << "failed to parse table: " << err);
+        return false;
+      }
+    } else if (io::IFile* file = zip->FindFile("resources.arsc")) {
+      std::unique_ptr<io::IData> data = file->OpenAsData();
+      if (!data) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.arsc");
+        return false;
+      }
+
+      BinaryResourceParser parser(context, &table, Source(file_path), data->data(), data->size());
+      if (!parser.Parse()) {
         return false;
       }
     }
 
-    if (!table) {
-      file = zip->FindFile("resources.arsc");
-      if (file) {
-        std::unique_ptr<io::IData> data = file->OpenAsData();
-        if (!data) {
-          context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                           << "failed to open resources.arsc");
-          return false;
-        }
-
-        table = util::make_unique<ResourceTable>();
-        BinaryResourceParser parser(context, table.get(), Source(file_path), data->data(),
-                                    data->size());
-        if (!parser.Parse()) {
-          return false;
-        }
-      }
-    }
+    Debug::PrintTable(table, print_options);
+    return true;
   }
 
-  if (!table) {
-    Maybe<android::FileMap> file = file::MmapPath(file_path, &err);
-    if (!file) {
-      context->GetDiagnostics()->Error(DiagMessage(file_path) << err);
-      return false;
-    }
+  err.clear();
 
-    android::FileMap* file_map = &file.value();
-
-    // Try as a compiled table.
-    pb::ResourceTable pb_table;
-    if (pb_table.ParseFromArray(file_map->getDataPtr(), file_map->getDataLength())) {
-      table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics());
-    }
-
-    if (!table) {
-      // Try as a compiled file.
-      CompiledFileInputStream input(file_map->getDataPtr(), file_map->getDataLength());
-
-      uint32_t num_files = 0;
-      if (!input.ReadLittleEndian32(&num_files)) {
-        return false;
-      }
-
-      for (uint32_t i = 0; i < num_files; i++) {
-        pb::internal::CompiledFile compiled_file;
-        if (!input.ReadCompiledFile(&compiled_file)) {
-          context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
-          return false;
-        }
-
-        uint64_t offset, len;
-        if (!input.ReadDataMetaData(&offset, &len)) {
-          context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data");
-          return false;
-        }
-
-        const void* data = static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
-        if (!DumpCompiledFile(compiled_file, data, len, Source(file_path), context)) {
-          return false;
-        }
-      }
-    }
+  io::FileInputStream input(file_path);
+  if (input.HadError()) {
+    context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                     << "failed to open file: " << input.GetError());
+    return false;
   }
 
-  if (table) {
-    DebugPrintTableOptions options;
-    options.show_sources = true;
-    Debug::PrintTable(table.get(), options);
+  // Try as a compiled file.
+  ContainerReader reader(&input);
+  if (reader.HadError()) {
+    context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                     << "failed to read container: " << reader.GetError());
+    return false;
   }
 
+  ContainerReaderEntry* entry;
+  while ((entry = reader.Next()) != nullptr) {
+    if (entry->Type() == ContainerEntryType::kResTable) {
+      pb::ResourceTable pb_table;
+      if (!entry->GetResTable(&pb_table)) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                         << "failed to parse proto table: " << entry->GetError());
+        continue;
+      }
+
+      ResourceTable table;
+      err.clear();
+      if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+        context->GetDiagnostics()->Error(DiagMessage(file_path)
+                                         << "failed to parse table: " << err);
+        continue;
+      }
+
+      Debug::PrintTable(table, print_options);
+    } else if (entry->Type() == ContainerEntryType::kResFile) {
+      pb::internal::CompiledFile pb_compiled_file;
+      off64_t offset;
+      size_t length;
+      if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
+        context->GetDiagnostics()->Error(
+            DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError());
+        continue;
+      }
+
+      ResourceFile file;
+      std::string error;
+      if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
+        context->GetDiagnostics()->Warn(DiagMessage(file_path)
+                                        << "failed to parse compiled file: " << error);
+        continue;
+      }
+
+      DumpCompiledFile(file, Source(file_path), offset, length);
+    }
+  }
   return true;
 }
 
+namespace {
+
 class DumpContext : public IAaptContext {
  public:
   PackageType GetPackageType() override {
@@ -159,7 +174,7 @@
   }
 
   NameMangler* GetNameMangler() override {
-    abort();
+    UNIMPLEMENTED(FATAL);
     return nullptr;
   }
 
@@ -173,7 +188,7 @@
   }
 
   SymbolTable* GetExternalSymbols() override {
-    abort();
+    UNIMPLEMENTED(FATAL);
     return nullptr;
   }
 
@@ -194,9 +209,9 @@
   bool verbose_ = false;
 };
 
-/**
- * Entry point for dump command.
- */
+}  // namespace
+
+// Entry point for dump command.
 int Dump(const std::vector<StringPiece>& args) {
   bool verbose = false;
   Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
@@ -212,7 +227,6 @@
       return 1;
     }
   }
-
   return 0;
 }
 
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 3a2faa9..e0dae1b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -25,7 +25,6 @@
 #include "android-base/file.h"
 #include "android-base/stringprintf.h"
 #include "androidfw/StringPiece.h"
-#include "google/protobuf/io/coded_stream.h"
 
 #include "AppInfo.h"
 #include "Debug.h"
@@ -33,14 +32,20 @@
 #include "Locale.h"
 #include "NameMangler.h"
 #include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
 #include "cmd/Util.h"
 #include "compile/IdAssigner.h"
 #include "filter/ConfigFilter.h"
-#include "flatten/Archive.h"
-#include "flatten/TableFlattener.h"
-#include "flatten/XmlFlattener.h"
-#include "io/BigBufferInputStream.h"
-#include "io/FileInputStream.h"
+#include "format/Archive.h"
+#include "format/Container.h"
+#include "format/binary/BinaryResourceParser.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "format/proto/ProtoSerialize.h"
+#include "io/BigBufferStream.h"
+#include "io/FileStream.h"
 #include "io/FileSystem.h"
 #include "io/Util.h"
 #include "io/ZipArchive.h"
@@ -56,9 +61,7 @@
 #include "optimize/VersionCollapser.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
-#include "proto/ProtoSerialize.h"
 #include "split/TableSplitter.h"
-#include "unflatten/BinaryResourceParser.h"
 #include "util/Files.h"
 #include "xml/XmlDom.h"
 
@@ -68,6 +71,14 @@
 
 namespace aapt {
 
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+
+enum class OutputFormat {
+  kApk,
+  kProto,
+};
+
 struct LinkOptions {
   std::string output_path;
   std::string manifest_path;
@@ -76,6 +87,7 @@
   std::vector<std::string> assets_dirs;
   bool output_to_directory = false;
   bool auto_add_overlay = false;
+  OutputFormat output_format = OutputFormat::kApk;
 
   // Java/Proguard options.
   Maybe<std::string> generate_java_class_path;
@@ -84,6 +96,7 @@
   Maybe<std::string> generate_text_symbols_path;
   Maybe<std::string> generate_proguard_rules_path;
   Maybe<std::string> generate_main_dex_proguard_rules_path;
+  bool generate_conditional_proguard_rules = false;
   bool generate_non_final_ids = false;
   std::vector<std::string> javadoc_annotations;
   Maybe<std::string> private_symbols;
@@ -250,25 +263,39 @@
   IAaptContext* context_;
 };
 
-static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path,
-                       bool keep_raw_values, IArchiveWriter* writer) {
-  BigBuffer buffer(1024);
-  XmlFlattenerOptions options = {};
-  options.keep_raw_values = keep_raw_values;
-  XmlFlattener flattener(&buffer, options);
-  if (!flattener.Consume(context, xml_res)) {
-    return false;
-  }
-
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
+                       const StringPiece& path, bool keep_raw_values, bool utf16,
+                       OutputFormat format, IArchiveWriter* writer) {
   if (context->IsVerbose()) {
     context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values="
                                                       << (keep_raw_values ? "true" : "false")
                                                       << ")");
   }
 
-  io::BigBufferInputStream input_stream(&buffer);
-  return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
-                                      ArchiveEntry::kCompress, writer);
+  switch (format) {
+    case OutputFormat::kApk: {
+      BigBuffer buffer(1024);
+      XmlFlattenerOptions options = {};
+      options.keep_raw_values = keep_raw_values;
+      options.use_utf16 = utf16;
+      XmlFlattener flattener(&buffer, options);
+      if (!flattener.Consume(context, &xml_res)) {
+        return false;
+      }
+
+      io::BigBufferInputStream input_stream(&buffer);
+      return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
+                                          ArchiveEntry::kCompress, writer);
+    } break;
+
+    case OutputFormat::kProto: {
+      pb::XmlNode pb_node;
+      SerializeXmlResourceToPb(xml_res, &pb_node);
+      return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
+                                    writer);
+    } break;
+  }
+  return false;
 }
 
 static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
@@ -279,8 +306,10 @@
     return {};
   }
 
-  std::unique_ptr<ResourceTable> table = DeserializeTableFromPb(pb_table, source, diag);
-  if (!table) {
+  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+  std::string error;
+  if (!DeserializeTableFromPb(pb_table, table.get(), &error)) {
+    diag->Error(DiagMessage(source) << "invalid compiled table: " << error);
     return {};
   }
   return table;
@@ -304,6 +333,7 @@
   bool keep_raw_values = false;
   bool do_not_compress_anything = false;
   bool update_proguard_spec = false;
+  OutputFormat output_format = OutputFormat::kApk;
   std::unordered_set<std::string> extensions_to_not_compress;
 };
 
@@ -441,7 +471,7 @@
 
 static bool IsVectorElement(const std::string& name) {
   return name == "vector" || name == "animated-vector" || name == "pathInterpolator" ||
-         name == "objectAnimator";
+         name == "objectAnimator" || name == "gradient";
 }
 
 template <typename T>
@@ -461,12 +491,16 @@
                                      << "linking " << src.path << " (" << doc->file.name << ")");
   }
 
+  // First, strip out any tools namespace attributes. AAPT stripped them out early, which means
+  // that existing projects have out-of-date references which pass compilation.
+  xml::StripAndroidStudioAttributes(doc->root.get());
+
   XmlReferenceLinker xml_linker;
   if (!xml_linker.Consume(context_, doc)) {
     return {};
   }
 
-  if (options_.update_proguard_spec && !proguard::CollectProguardRules(src, doc, keep_set_)) {
+  if (options_.update_proguard_spec && !proguard::CollectProguardRules(doc, keep_set_)) {
     return {};
   }
 
@@ -505,6 +539,8 @@
   bool error = false;
   std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files;
 
+  proguard::CollectResourceReferences(context_, table, keep_set_);
+
   for (auto& pkg : table->packages) {
     CHECK(!pkg->name.empty()) << "Packages must have names when being linked";
 
@@ -537,9 +573,9 @@
           file_op.config = config_value->config;
           file_op.file_to_copy = file;
 
-          const StringPiece src_path = file->GetSource().path;
           if (type->type != ResourceType::kRaw &&
-              (util::EndsWith(src_path, ".xml.flat") || util::EndsWith(src_path, ".xml"))) {
+              (file_ref->type == ResourceFile::Type::kBinaryXml ||
+               file_ref->type == ResourceFile::Type::kProtoXml)) {
             std::unique_ptr<io::IData> data = file->OpenAsData();
             if (!data) {
               context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
@@ -547,11 +583,27 @@
               return false;
             }
 
-            file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
-                                                  context_->GetDiagnostics(), file->GetSource());
+            if (file_ref->type == ResourceFile::Type::kProtoXml) {
+              pb::XmlNode pb_xml_node;
+              if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
+                context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                  << "failed to parse proto xml");
+                return false;
+              }
 
-            if (!file_op.xml_to_flatten) {
-              return false;
+              std::string error;
+              file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
+              if (file_op.xml_to_flatten == nullptr) {
+                context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+                                                  << "failed to deserialize proto xml: " << error);
+                return false;
+              }
+            } else {
+              file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
+                                                    context_->GetDiagnostics(), file->GetSource());
+              if (file_op.xml_to_flatten == nullptr) {
+                return false;
+              }
             }
 
             file_op.xml_to_flatten->file.config = config_value->config;
@@ -601,8 +653,9 @@
                 return false;
               }
             }
-            error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values,
-                                 archive_writer);
+
+            error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
+                                 false /*utf16*/, options_.output_format, archive_writer);
           }
         } else {
           error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path,
@@ -901,22 +954,29 @@
     }
   }
 
-  bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) {
-    BigBuffer buffer(1024);
-    TableFlattener flattener(options_.table_flattener_options, &buffer);
-    if (!flattener.Consume(context_, table)) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
-      return false;
+  bool FlattenTable(ResourceTable* table, OutputFormat format, IArchiveWriter* writer) {
+    switch (format) {
+      case OutputFormat::kApk: {
+        BigBuffer buffer(1024);
+        TableFlattener flattener(options_.table_flattener_options, &buffer);
+        if (!flattener.Consume(context_, table)) {
+          context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table");
+          return false;
+        }
+
+        io::BigBufferInputStream input_stream(&buffer);
+        return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
+                                            ArchiveEntry::kAlign, writer);
+      } break;
+
+      case OutputFormat::kProto: {
+        pb::ResourceTable pb_table;
+        SerializeTableToPb(*table, &pb_table);
+        return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
+                                      ArchiveEntry::kCompress, writer);
+      } break;
     }
-
-    io::BigBufferInputStream input_stream(&buffer);
-    return io::CopyInputStreamToArchive(context_, &input_stream, "resources.arsc",
-                                        ArchiveEntry::kAlign, writer);
-  }
-
-  bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
-    std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table);
-    return io::CopyProtoToArchive(context_, pb_table.get(), "resources.arsc.flat", 0, writer);
+    return false;
   }
 
   bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
@@ -1145,7 +1205,7 @@
   }
 
   std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
-    io::IFile* file = collection->FindFile("resources.arsc.flat");
+    io::IFile* file = collection->FindFile(kProtoResourceTablePath);
     if (!file) {
       return {};
     }
@@ -1194,11 +1254,7 @@
       // Clear the package name, so as to make the resources look like they are coming from the
       // local package.
       pkg->name = "";
-      if (override) {
-        result = table_merger_->MergeOverlay(Source(input), table.get(), collection.get());
-      } else {
-        result = table_merger_->Merge(Source(input), table.get(), collection.get());
-      }
+      result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
 
     } else {
       // This is the proper way to merge libraries, where the package name is
@@ -1234,49 +1290,34 @@
       return false;
     }
 
-    bool result = false;
-    if (override) {
-      result = table_merger_->MergeOverlay(file->GetSource(), table.get());
-    } else {
-      result = table_merger_->Merge(file->GetSource(), table.get());
-    }
-    return result;
+    return table_merger_->Merge(file->GetSource(), table.get(), override);
   }
 
-  bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, bool override) {
+  bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
     if (context_->IsVerbose()) {
-      context_->GetDiagnostics()->Note(DiagMessage() << "merging '" << file_desc->name
-                                                     << "' from compiled file "
-                                                     << file->GetSource());
+      context_->GetDiagnostics()->Note(DiagMessage()
+                                       << "merging '" << compiled_file.name
+                                       << "' from compiled file " << compiled_file.source);
     }
 
-    bool result = false;
-    if (override) {
-      result = table_merger_->MergeFileOverlay(*file_desc, file);
-    } else {
-      result = table_merger_->MergeFile(*file_desc, file);
-    }
-
-    if (!result) {
+    if (!table_merger_->MergeFile(compiled_file, override, file)) {
       return false;
     }
 
     // Add the exports of this file to the table.
-    for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) {
-      if (exported_symbol.name.package.empty()) {
-        exported_symbol.name.package = context_->GetCompilationPackage();
+    for (const SourcedResourceName& exported_symbol : compiled_file.exported_symbols) {
+      ResourceName res_name = exported_symbol.name;
+      if (res_name.package.empty()) {
+        res_name.package = context_->GetCompilationPackage();
       }
 
-      ResourceNameRef res_name = exported_symbol.name;
-
-      Maybe<ResourceName> mangled_name =
-          context_->GetNameMangler()->MangleName(exported_symbol.name);
+      Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name);
       if (mangled_name) {
         res_name = mangled_name.value();
       }
 
       std::unique_ptr<Id> id = util::make_unique<Id>();
-      id->SetSource(file_desc->source.WithLine(exported_symbol.line));
+      id->SetSource(compiled_file.source.WithLine(exported_symbol.line));
       bool result = final_table_.AddResourceAllowMangled(
           res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
           context_->GetDiagnostics());
@@ -1287,15 +1328,11 @@
     return true;
   }
 
-  /**
-   * Takes a path to load as a ZIP file and merges the files within into the
-   * master ResourceTable.
-   * If override is true, conflicting resources are allowed to override each
-   * other, in order of last seen.
-   *
-   * An io::IFileCollection is created from the ZIP file and added to the set of
-   * io::IFileCollections that are open.
-   */
+  // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
+  // If override is true, conflicting resources are allowed to override each other, in order of last
+  // seen.
+  // An io::IFileCollection is created from the ZIP file and added to the set of
+  // io::IFileCollections that are open.
   bool MergeArchive(const std::string& input, bool override) {
     if (context_->IsVerbose()) {
       context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input);
@@ -1321,18 +1358,11 @@
     return !error;
   }
 
-  /**
-   * Takes a path to load and merge into the master ResourceTable. If override
-   * is true,
-   * conflicting resources are allowed to override each other, in order of last
-   * seen.
-   *
-   * If the file path ends with .flata, .jar, .jack, or .zip the file is treated
-   * as ZIP archive
-   * and the files within are merged individually.
-   *
-   * Otherwise the files is processed on its own.
-   */
+  // Takes a path to load and merge into the master ResourceTable. If override is true,
+  // conflicting resources are allowed to override each other, in order of last seen.
+  // If the file path ends with .flata, .jar, .jack, or .zip the file is treated
+  // as ZIP archive and the files within are merged individually.
+  // Otherwise the file is processed on its own.
   bool MergePath(const std::string& path, bool override) {
     if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") ||
         util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) {
@@ -1345,69 +1375,15 @@
     return MergeFile(file, override);
   }
 
-  /**
-   * Takes a file to load and merge into the master ResourceTable. If override
-   * is true,
-   * conflicting resources are allowed to override each other, in order of last
-   * seen.
-   *
-   * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and
-   * merged into the
-   * master ResourceTable. If the file ends with .flat, then it is treated like
-   * a compiled file
-   * and the header data is read and merged into the final ResourceTable.
-   *
-   * All other file types are ignored. This is because these files could be
-   * coming from a zip,
-   * where we could have other files like classes.dex.
-   */
+  // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable.
+  // If override is true, conflicting resources are allowed to override each other, in order of last
+  // seen.
+  // All other file types are ignored. This is because these files could be coming from a zip,
+  // where we could have other files like classes.dex.
   bool MergeFile(io::IFile* file, bool override) {
     const Source& src = file->GetSource();
-    if (util::EndsWith(src.path, ".arsc.flat")) {
-      return MergeResourceTable(file, override);
 
-    } else if (util::EndsWith(src.path, ".flat")) {
-      // Try opening the file and looking for an Export header.
-      std::unique_ptr<io::IData> data = file->OpenAsData();
-      if (!data) {
-        context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open");
-        return false;
-      }
-
-      CompiledFileInputStream input_stream(data->data(), data->size());
-      uint32_t num_files = 0;
-      if (!input_stream.ReadLittleEndian32(&num_files)) {
-        context_->GetDiagnostics()->Error(DiagMessage(src) << "failed read num files");
-        return false;
-      }
-
-      for (uint32_t i = 0; i < num_files; i++) {
-        pb::internal::CompiledFile compiled_file;
-        if (!input_stream.ReadCompiledFile(&compiled_file)) {
-          context_->GetDiagnostics()->Error(DiagMessage(src)
-                                            << "failed to read compiled file header");
-          return false;
-        }
-
-        uint64_t offset, len;
-        if (!input_stream.ReadDataMetaData(&offset, &len)) {
-          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data");
-          return false;
-        }
-
-        std::unique_ptr<ResourceFile> resource_file = DeserializeCompiledFileFromPb(
-            compiled_file, file->GetSource(), context_->GetDiagnostics());
-        if (!resource_file) {
-          return false;
-        }
-
-        if (!MergeCompiledFile(file->CreateFileSegment(offset, len), resource_file.get(),
-                               override)) {
-          return false;
-        }
-      }
-      return true;
-    } else if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) {
+    if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) {
       // Since AAPT compiles these file types and appends .flat to them, seeing
       // their raw extensions is a sign that they weren't compiled.
       const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG";
@@ -1415,11 +1391,71 @@
                                                          << " file passed as argument. Must be "
                                                             "compiled first into .flat file.");
       return false;
+    } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) {
+      if (context_->IsVerbose()) {
+        context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file");
+        return true;
+      }
     }
 
-    // Ignore non .flat files. This could be classes.dex or something else that
-    // happens
-    // to be in an archive.
+    std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream();
+    if (input_stream == nullptr) {
+      context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file");
+      return false;
+    }
+
+    if (input_stream->HadError()) {
+      context_->GetDiagnostics()->Error(DiagMessage(src)
+                                        << "failed to open file: " << input_stream->GetError());
+      return false;
+    }
+
+    ContainerReaderEntry* entry;
+    ContainerReader reader(input_stream.get());
+    while ((entry = reader.Next()) != nullptr) {
+      if (entry->Type() == ContainerEntryType::kResTable) {
+        pb::ResourceTable pb_table;
+        if (!entry->GetResTable(&pb_table)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: "
+                                                             << entry->GetError());
+          return false;
+        }
+
+        ResourceTable table;
+        std::string error;
+        if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src)
+                                            << "failed to deserialize resource table: " << error);
+          return false;
+        }
+
+        if (!table_merger_->Merge(src, &table, override)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table");
+          return false;
+        }
+      } else if (entry->Type() == ContainerEntryType::kResFile) {
+        pb::internal::CompiledFile pb_compiled_file;
+        off64_t offset;
+        size_t len;
+        if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: "
+                                                             << entry->GetError());
+          return false;
+        }
+
+        ResourceFile resource_file;
+        std::string error;
+        if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) {
+          context_->GetDiagnostics()->Error(DiagMessage(src)
+                                            << "failed to read compiled header: " << error);
+          return false;
+        }
+
+        if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) {
+          return false;
+        }
+      }
+    }
     return true;
   }
 
@@ -1463,14 +1499,13 @@
     return true;
   }
 
-  /**
-   * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
-   * the ResourceTable to the IArchiveWriter.
-   */
+  // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
+  // to the IArchiveWriter.
   bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest,
                 ResourceTable* table) {
     const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib;
-    bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values, writer);
+    bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values,
+                             true /*utf16*/, options_.output_format, writer);
     if (!result) {
       return false;
     }
@@ -1485,6 +1520,7 @@
     file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces;
     file_flattener_options.update_proguard_spec =
         static_cast<bool>(options_.generate_proguard_rules_path);
+    file_flattener_options.output_format = options_.output_format;
 
     ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set);
 
@@ -1493,15 +1529,9 @@
       return false;
     }
 
-    if (context_->GetPackageType() == PackageType::kStaticLib) {
-      if (!FlattenTableToPb(table, writer)) {
-        return false;
-      }
-    } else {
-      if (!FlattenTable(table, writer)) {
-        context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc");
-        return false;
-      }
+    if (!FlattenTable(table, options_.output_format, writer)) {
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
+      return false;
     }
     return true;
   }
@@ -1692,7 +1722,8 @@
       }
     }
 
-    proguard::KeepSet proguard_keep_set;
+    proguard::KeepSet proguard_keep_set =
+        proguard::KeepSet(options_.generate_conditional_proguard_rules);
     proguard::KeepSet proguard_main_dex_keep_set;
 
     if (context_->GetPackageType() == PackageType::kStaticLib) {
@@ -1768,14 +1799,12 @@
       XmlReferenceLinker manifest_linker;
       if (manifest_linker.Consume(context_, manifest_xml.get())) {
         if (options_.generate_proguard_rules_path &&
-            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
-                                                       manifest_xml.get(), &proguard_keep_set)) {
+            !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) {
           error = true;
         }
 
         if (options_.generate_main_dex_proguard_rules_path &&
-            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
-                                                       manifest_xml.get(),
+            !proguard::CollectProguardRulesForManifest(manifest_xml.get(),
                                                        &proguard_main_dex_keep_set, true)) {
           error = true;
         }
@@ -1865,6 +1894,7 @@
   bool verbose = false;
   bool shared_lib = false;
   bool static_lib = false;
+  bool proto_format = false;
   Maybe<std::string> stable_id_file_path;
   std::vector<std::string> split_args;
   Flags flags =
@@ -1891,6 +1921,9 @@
           .OptionalFlag("--proguard-main-dex",
                         "Output file for generated Proguard rules for the main dex.",
                         &options.generate_main_dex_proguard_rules_path)
+          .OptionalSwitch("--proguard-conditional-keep-rules",
+                          "Generate conditional Proguard keep rules.",
+                          &options.generate_conditional_proguard_rules)
           .OptionalSwitch("--no-auto-version",
                           "Disables automatic style and layout SDK versioning.",
                           &options.no_auto_version)
@@ -1945,6 +1978,10 @@
           .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.",
                           &shared_lib)
           .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib)
+          .OptionalSwitch("--proto-format",
+                          "Generates compiled resources in Protobuf format.\n"
+                          "Suitable as input to the bundle tool for generating an App Bundle.",
+                          &proto_format)
           .OptionalSwitch("--no-static-lib-packages",
                           "Merge all library resources under the app's package.",
                           &options.no_static_lib_packages)
@@ -2031,21 +2068,25 @@
     context.SetVerbose(verbose);
   }
 
-  if (shared_lib && static_lib) {
-    context.GetDiagnostics()->Error(DiagMessage()
-                                    << "only one of --shared-lib and --static-lib can be defined");
+  if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) {
+    context.GetDiagnostics()->Error(
+        DiagMessage()
+        << "only one of --shared-lib, --static-lib, or --proto_format can be defined");
     return 1;
   }
 
+  // The default build type.
+  context.SetPackageType(PackageType::kApp);
+  context.SetPackageId(kAppPackageId);
+
   if (shared_lib) {
     context.SetPackageType(PackageType::kSharedLib);
     context.SetPackageId(0x00);
   } else if (static_lib) {
     context.SetPackageType(PackageType::kStaticLib);
-    context.SetPackageId(kAppPackageId);
-  } else {
-    context.SetPackageType(PackageType::kApp);
-    context.SetPackageId(kAppPackageId);
+    options.output_format = OutputFormat::kProto;
+  } else if (proto_format) {
+    options.output_format = OutputFormat::kProto;
   }
 
   if (package_id) {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 33a1d3a..44e148e 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "android-base/stringprintf.h"
+
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/StringPiece.h"
 
@@ -30,9 +31,9 @@
 #include "cmd/Util.h"
 #include "configuration/ConfigurationParser.h"
 #include "filter/AbiFilter.h"
-#include "flatten/TableFlattener.h"
-#include "flatten/XmlFlattener.h"
-#include "io/BigBufferInputStream.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "io/BigBufferStream.h"
 #include "io/Util.h"
 #include "optimize/MultiApkGenerator.h"
 #include "optimize/ResourceDeduper.h"
@@ -282,24 +283,8 @@
 
 bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
                                 OptimizeOptions* out_options) {
-  io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
-  if (manifest_file == nullptr) {
-    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
-                                     << "missing AndroidManifest.xml");
-    return false;
-  }
-
-  std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
-  if (data == nullptr) {
-    context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
-                                     << "failed to open file");
-    return false;
-  }
-
-  std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
-      data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
+  std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
   if (manifest == nullptr) {
-    context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
     return false;
   }
 
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index d17858d..708bed8 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -72,7 +72,6 @@
   }
 
   *out_path = parts[0];
-  std::vector<ConfigDescription> configs;
   for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
     ConfigDescription config;
     if (!ConfigDescription::Parse(config_str, &config)) {
@@ -141,6 +140,16 @@
   return decl;
 }
 
+static std::string MakePackageSafeName(const std::string &name) {
+  std::string result(name);
+  for (char &c : result) {
+    if (c == '-') {
+      c = '_';
+    }
+  }
+  return result;
+}
+
 std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
                                                         const SplitConstraints& constraints) {
   const ResourceId kVersionCode(0x0101021b);
@@ -172,7 +181,11 @@
   if (app_info.split_name) {
     split_name << app_info.split_name.value() << ".";
   }
-  split_name << "config." << util::Joiner(constraints.configs, "_");
+  std::vector<std::string> sanitized_config_names;
+  for (const auto &config : constraints.configs) {
+    sanitized_config_names.push_back(MakePackageSafeName(config.toString().string()));
+  }
+  split_name << "config." << util::Joiner(sanitized_config_names, "_");
 
   manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
 
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
new file mode 100644
index 0000000..9c33135
--- /dev/null
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "Util.h"
+
+#include "AppInfo.h"
+#include "split/TableSplitter.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+TEST(UtilTest, SplitNamesAreSanitized) {
+    AppInfo app_info{"com.pkg"};
+    SplitConstraints split_constraints{{test::ParseConfigOrDie("en-rUS-land")}};
+
+    const auto doc = GenerateSplitManifest(app_info, split_constraints);
+    const auto &root = doc->root;
+    EXPECT_EQ(root->name, "manifest");
+    // split names cannot contain hyphens
+    EXPECT_EQ(root->FindAttribute("", "split")->value, "config.en_rUS_land");
+    // but we should use resource qualifiers verbatim in 'targetConfig'.
+    EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "en-rUS-land");
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp
index a179260..8b6c524 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp
@@ -118,10 +118,11 @@
 
   size_t name_suffix_counter = 0;
   for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) {
-    auto new_doc = util::make_unique<xml::XmlResource>();
-    new_doc->file.config = doc->file.config;
-    new_doc->file.source = doc->file.source.WithLine(decl.el->line_number);
-    new_doc->file.name = doc->file.name;
+    // Create a new XmlResource with the same ResourceFile as the base XmlResource.
+    auto new_doc = util::make_unique<xml::XmlResource>(doc->file);
+
+    // Attach the line number.
+    new_doc->file.source.line = decl.el->line_number;
 
     // Modify the new entry name. We need to suffix the entry with a number to
     // avoid local collisions, then mangle it with the empty package, such that it won't show up
@@ -146,6 +147,10 @@
       } else {
         new_doc->root.reset(static_cast<xml::Element*>(child.release()));
         new_doc->root->parent = nullptr;
+        // Copy down the namespace declarations
+        new_doc->root->namespace_decls = doc->root->namespace_decls;
+        // Recurse for nested inlines
+        Consume(context, new_doc.get());
       }
     }
 
diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
index de7739a..2b4ab96 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
+++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp
@@ -54,6 +54,7 @@
       </View1>)");
 
   doc->file.name = test::ParseNameOrDie("layout/main");
+  doc->file.type = ResourceFile::Type::kProtoXml;
 
   InlineXmlFormatParser parser;
   ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
@@ -81,6 +82,9 @@
   // Make sure the generated reference is correct.
   EXPECT_THAT(extracted_doc->file.name, Eq(name_ref));
 
+  // Make sure the ResourceFile::Type is the same.
+  EXPECT_THAT(extracted_doc->file.type, Eq(ResourceFile::Type::kProtoXml));
+
   // Verify the structure of the extracted XML.
   el = extracted_doc->root.get();
   ASSERT_THAT(el, NotNull());
@@ -137,4 +141,47 @@
   EXPECT_THAT(extracted_doc_drawable->root->name, StrEq("vector"));
 }
 
+TEST(InlineXmlFormatParserTest, ExtractNestedXmlResources) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
+      <base_root xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:aapt="http://schemas.android.com/aapt">
+          <aapt:attr name="inline_xml">
+              <inline_root>
+                  <aapt:attr name="nested_inline_xml">
+                      <nested_inline_root/>
+                  </aapt:attr>
+                  <aapt:attr name="another_nested_inline_xml">
+                      <root/>
+                  </aapt:attr>
+              </inline_root>
+          </aapt:attr>
+          <aapt:attr name="turtles">
+              <root1>
+                  <aapt:attr name="all">
+                      <root2>
+                          <aapt:attr name="the">
+                              <root3>
+                                  <aapt:attr name="way">
+                                      <root4>
+                                          <aapt:attr name="down">
+                                              <root5/>
+                                          </aapt:attr>
+                                      </root4>
+                                  </aapt:attr>
+                              </root3>
+                          </aapt:attr>
+                      </root2>
+                  </aapt:attr>
+              </root1>
+          </aapt:attr>
+      </base_root>)");
+
+  doc->file.name = test::ParseNameOrDie("layout/main");
+
+  InlineXmlFormatParser parser;
+  ASSERT_TRUE(parser.Consume(context.get(), doc.get()));
+  // Confirm that all of the nested inline xmls are parsed out.
+  ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(8u));
+}
 }  // namespace aapt
diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp
index 6d6147d..33122dc 100644
--- a/tools/aapt2/compile/Png.cpp
+++ b/tools/aapt2/compile/Png.cpp
@@ -538,7 +538,7 @@
     if (kDebug) {
       diag->Note(DiagMessage() << "adding 9-patch info..");
     }
-    strcpy((char*)unknowns[pIndex].name, "npTc");
+    memcpy((char*)unknowns[pIndex].name, "npTc", 5);
     unknowns[pIndex].data = (png_byte*)info->serialize9Patch();
     unknowns[pIndex].size = info->info9Patch.serializedSize();
     // TODO: remove the check below when everything works
@@ -546,7 +546,7 @@
 
     // automatically generated 9 patch outline data
     int chunkSize = sizeof(png_uint_32) * 6;
-    strcpy((char*)unknowns[oIndex].name, "npOl");
+    memcpy((char*)unknowns[oIndex].name, "npOl", 5);
     unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1);
     png_byte outputData[chunkSize];
     memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
@@ -558,7 +558,7 @@
     // optional optical inset / layout bounds data
     if (info->haveLayoutBounds) {
       int chunkSize = sizeof(png_uint_32) * 4;
-      strcpy((char*)unknowns[bIndex].name, "npLb");
+      memcpy((char*)unknowns[bIndex].name, "npLb", 5);
       unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1);
       memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
       unknowns[bIndex].size = chunkSize;
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 5af91fd..bc2e699 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -21,8 +21,8 @@
 
 #include "io/Io.h"
 
-using android::StringPiece;
-using android::base::StringPrintf;
+using ::android::StringPiece;
+using ::android::base::StringPrintf;
 
 namespace aapt {
 
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 871ed4f..36c24bc 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -24,8 +24,8 @@
 #include "compile/Pseudolocalizer.h"
 #include "util/Util.h"
 
-using android::StringPiece;
-using android::StringPiece16;
+using ::android::StringPiece;
+using ::android::StringPiece16;
 
 namespace aapt {
 
@@ -215,7 +215,7 @@
 
 namespace {
 
-class Visitor : public RawValueVisitor {
+class Visitor : public ValueVisitor {
  public:
   // Either value or item will be populated upon visiting the value.
   std::unique_ptr<Value> value;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 9d6d328..b99240f 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -27,9 +27,10 @@
 
 #include "ConfigDescription.h"
 #include "Diagnostics.h"
+#include "ResourceUtils.h"
 #include "io/File.h"
 #include "io/FileSystem.h"
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
@@ -329,15 +330,32 @@
   // TODO: Validate all references in the configuration are valid. It should be safe to assume from
   // this point on that any references from one section to another will be present.
 
+  // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
+  // see: https://developer.android.com/google/play/publishing/multiple-apks.html
+  //
+  // For now, make sure the version codes are unique.
+  std::vector<Artifact>& artifacts = config.artifacts;
+  std::sort(artifacts.begin(), artifacts.end());
+  if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
+    diag_->Error(DiagMessage() << "Configuration has duplicate versions");
+    return {};
+  }
+
   return {config};
 }
 
 ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
     [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool {
+  // This will be incremented later so the first version will always be different to the base APK.
+  int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
+
   Artifact artifact{};
+  Maybe<int> version;
   for (const auto& attr : root_element->attributes) {
     if (attr.name == "name") {
       artifact.name = attr.value;
+    } else if (attr.name == "version") {
+      version = std::stoi(attr.value);
     } else if (attr.name == "abi-group") {
       artifact.abi_group = {attr.value};
     } else if (attr.name == "screen-density-group") {
@@ -355,6 +373,9 @@
                                << attr.value);
     }
   }
+
+  artifact.version = (version) ? version.value() : current_version + 1;
+
   config->artifacts.push_back(artifact);
   return true;
 };
@@ -499,11 +520,11 @@
       AndroidSdk entry;
       for (const auto& attr : child->attributes) {
         if (attr.name == "minSdkVersion") {
-          entry.min_sdk_version = {attr.value};
+          entry.min_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
         } else if (attr.name == "targetSdkVersion") {
-          entry.target_sdk_version = {attr.value};
+          entry.target_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
         } else if (attr.name == "maxSdkVersion") {
-          entry.max_sdk_version = {attr.value};
+          entry.max_sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
         } else {
           diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
         }
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 9bc9081..c5d3284 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -17,6 +17,7 @@
 #ifndef AAPT2_CONFIGURATION_H
 #define AAPT2_CONFIGURATION_H
 
+#include <set>
 #include <string>
 #include <unordered_map>
 #include <vector>
@@ -41,6 +42,12 @@
 struct Artifact {
   /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
   Maybe<std::string> name;
+  /**
+   * Value to add to the base Android manifest versionCode. If it is not present in the
+   * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
+   * a value, artifacts are a 1 based index.
+   */
+  int version;
   /** If present, uses the ABI group with this name. */
   Maybe<std::string> abi_group;
   /** If present, uses the screen density group with this name. */
@@ -60,6 +67,15 @@
 
   /** Convert an artifact name template into a name string based on configuration contents. */
   Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
+
+  bool operator<(const Artifact& rhs) const {
+    // TODO(safarmer): Order by play store multi-APK requirements.
+    return version < rhs.version;
+  }
+
+  bool operator==(const Artifact& rhs) const  {
+    return version == rhs.version;
+  }
 };
 
 /** Enumeration of currently supported ABIs. */
@@ -103,14 +119,14 @@
 };
 
 struct AndroidSdk {
-  Maybe<std::string> min_sdk_version;
-  Maybe<std::string> target_sdk_version;
-  Maybe<std::string> max_sdk_version;
+  Maybe<int> min_sdk_version;
+  Maybe<int> target_sdk_version;
+  Maybe<int> max_sdk_version;
   Maybe<AndroidManifest> manifest;
 
-  static AndroidSdk ForMinSdk(std::string min_sdk) {
+  static AndroidSdk ForMinSdk(int min_sdk) {
     AndroidSdk sdk;
-    sdk.min_sdk_version = {std::move(min_sdk)};
+    sdk.min_sdk_version = min_sdk;
     return sdk;
   }
 
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 7ffb3d5..3654901 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -24,6 +24,15 @@
 #include "xml/XmlDom.h"
 
 namespace aapt {
+
+namespace configuration {
+void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
+  *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+      << ", target=" << sdk.target_sdk_version.value_or_default(-1)
+      << ", max=" << sdk.max_sdk_version.value_or_default(-1);
+}
+}  // namespace configuration
+
 namespace {
 
 using ::android::ResTable_config;
@@ -76,9 +85,9 @@
     </locale-group>
     <android-sdk-group label="v19">
       <android-sdk
-          minSdkVersion="v19"
-          targetSdkVersion="v24"
-          maxSdkVersion="v25">
+          minSdkVersion="19"
+          targetSdkVersion="24"
+          maxSdkVersion="25">
         <manifest>
           <!--- manifest additions here XSLT? TODO -->
         </manifest>
@@ -156,7 +165,7 @@
 
   EXPECT_EQ(1ul, value.android_sdk_groups.size());
   EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version);
-  EXPECT_EQ("v19", value.android_sdk_groups["v19"].min_sdk_version.value());
+  EXPECT_EQ(19, value.android_sdk_groups["v19"].min_sdk_version.value());
 
   EXPECT_EQ(1ul, value.gl_texture_groups.size());
   EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size());
@@ -174,55 +183,117 @@
 }
 
 TEST_F(ConfigurationParserTest, ArtifactAction) {
-  static constexpr const char* xml = R"xml(
+  PostProcessingConfiguration config;
+  {
+    const auto doc = test::BuildXmlDom(R"xml(
+      <artifact
+          abi-group="arm"
+          screen-density-group="large"
+          locale-group="europe"
+          android-sdk-group="v19"
+          gl-texture-group="dxt1"
+          device-feature-group="low-latency"/>)xml");
+
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_));
+
+    EXPECT_EQ(1ul, config.artifacts.size());
+
+    auto& artifact = config.artifacts.back();
+    EXPECT_FALSE(artifact.name);  // TODO: make this fail.
+    EXPECT_EQ(1, artifact.version);
+    EXPECT_EQ("arm", artifact.abi_group.value());
+    EXPECT_EQ("large", artifact.screen_density_group.value());
+    EXPECT_EQ("europe", artifact.locale_group.value());
+    EXPECT_EQ("v19", artifact.android_sdk_group.value());
+    EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+    EXPECT_EQ("low-latency", artifact.device_feature_group.value());
+  }
+
+  {
+    // Perform a second action to ensure we get 2 artifacts.
+    const auto doc = test::BuildXmlDom(R"xml(
+      <artifact
+          abi-group="other"
+          screen-density-group="large"
+          locale-group="europe"
+          android-sdk-group="v19"
+          gl-texture-group="dxt1"
+          device-feature-group="low-latency"/>)xml");
+
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_EQ(2ul, config.artifacts.size());
+    EXPECT_EQ(2, config.artifacts.back().version);
+  }
+
+  {
+    // Perform a third action with a set version code.
+    const auto doc = test::BuildXmlDom(R"xml(
     <artifact
-        abi-group="arm"
+        version="5"
+        abi-group="other"
         screen-density-group="large"
         locale-group="europe"
         android-sdk-group="v19"
         gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml";
+        device-feature-group="low-latency"/>)xml");
 
-  auto doc = test::BuildXmlDom(xml);
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_EQ(3ul, config.artifacts.size());
+    EXPECT_EQ(5, config.artifacts.back().version);
+  }
 
-  PostProcessingConfiguration config;
-  bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_);
-  ASSERT_TRUE(ok);
-
-  EXPECT_EQ(1ul, config.artifacts.size());
-
-  auto& artifact = config.artifacts.front();
-  EXPECT_FALSE(artifact.name);  // TODO: make this fail.
-  EXPECT_EQ("arm", artifact.abi_group.value());
-  EXPECT_EQ("large", artifact.screen_density_group.value());
-  EXPECT_EQ("europe", artifact.locale_group.value());
-  EXPECT_EQ("v19", artifact.android_sdk_group.value());
-  EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
-  EXPECT_EQ("low-latency", artifact.device_feature_group.value());
-
-  // Perform a second action to ensure we get 2 artifacts.
-  static constexpr const char* second = R"xml(
+  {
+    // Perform a fourth action to ensure the version code still increments.
+    const auto doc = test::BuildXmlDom(R"xml(
     <artifact
         abi-group="other"
         screen-density-group="large"
         locale-group="europe"
         android-sdk-group="v19"
         gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml";
-  doc = test::BuildXmlDom(second);
+        device-feature-group="low-latency"/>)xml");
 
-  ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
-  ASSERT_TRUE(ok);
-  EXPECT_EQ(2ul, config.artifacts.size());
+    ASSERT_TRUE(artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
+    EXPECT_EQ(4ul, config.artifacts.size());
+    EXPECT_EQ(6, config.artifacts.back().version);
+  }
+}
+
+TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
+  static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
+      <pst-process xmlns="http://schemas.android.com/tools/aapt">>
+        <artifacts>
+          <artifact-format>
+            ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
+          </artifact-format>
+          <artifact
+              name="art1"
+              abi-group="arm"
+              screen-density-group="large"
+              locale-group="europe"
+              android-sdk-group="v19"
+              gl-texture-group="dxt1"
+              device-feature-group="low-latency"/>
+          <artifact
+              name="art2"
+              version = "1"
+              abi-group="other"
+              screen-density-group="alldpi"
+              locale-group="north-america"
+              android-sdk-group="v19"
+              gl-texture-group="dxt1"
+              device-feature-group="low-latency"/>
+        </artifacts>
+      </post-process>)xml";
+  auto result = ConfigurationParser::ForContents(configuration).Parse();
+  ASSERT_FALSE(result);
 }
 
 TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
-  static constexpr const char* xml = R"xml(
+  const auto doc = test::BuildXmlDom(R"xml(
     <artifact-format>
       ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
-    </artifact-format>)xml";
-
-  auto doc = test::BuildXmlDom(xml);
+    </artifact-format>)xml");
 
   PostProcessingConfiguration config;
   bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
@@ -321,9 +392,9 @@
   static constexpr const char* xml = R"xml(
     <android-sdk-group label="v19">
       <android-sdk
-          minSdkVersion="v19"
-          targetSdkVersion="v24"
-          maxSdkVersion="v25">
+          minSdkVersion="19"
+          targetSdkVersion="24"
+          maxSdkVersion="25">
         <manifest>
           <!--- manifest additions here XSLT? TODO -->
         </manifest>
@@ -342,14 +413,43 @@
   auto& out = config.android_sdk_groups["v19"];
 
   AndroidSdk sdk;
-  sdk.min_sdk_version = std::string("v19");
-  sdk.target_sdk_version = std::string("v24");
-  sdk.max_sdk_version = std::string("v25");
+  sdk.min_sdk_version = 19;
+  sdk.target_sdk_version = 24;
+  sdk.max_sdk_version = 25;
   sdk.manifest = AndroidManifest();
 
   ASSERT_EQ(sdk, out);
 }
 
+TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
+  static constexpr const char* xml = R"xml(
+    <android-sdk-group label="O">
+      <android-sdk
+          minSdkVersion="M"
+          targetSdkVersion="O"
+          maxSdkVersion="O">
+      </android-sdk>
+    </android-sdk-group>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  ASSERT_EQ(1ul, config.android_sdk_groups.size());
+  ASSERT_EQ(1u, config.android_sdk_groups.count("O"));
+
+  auto& out = config.android_sdk_groups["O"];
+
+  AndroidSdk sdk;
+  sdk.min_sdk_version = {};  // Only the latest development version is supported.
+  sdk.target_sdk_version = 26;
+  sdk.max_sdk_version = 26;
+
+  ASSERT_EQ(sdk, out);
+}
+
 TEST_F(ConfigurationParserTest, GlTextureGroupAction) {
   static constexpr const char* xml = R"xml(
     <gl-texture-group label="dxt1">
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 47bf99e..134153a 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -39,6 +39,7 @@
   <!-- Groups output artifacts together by dimension labels. -->
   <xsd:complexType name="artifact">
     <xsd:attribute name="name" type="xsd:string"/>
+    <xsd:attribute name="version" type="xsd:integer"/>
     <xsd:attribute name="abi-group" type="xsd:string"/>
     <xsd:attribute name="android-sdk-group" type="xsd:string"/>
     <xsd:attribute name="device-feature-group" type="xsd:string"/>
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
deleted file mode 100644
index 5f8bd06..0000000
--- a/tools/aapt2/flatten/Archive.cpp
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2015 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 "flatten/Archive.h"
-
-#include <cstdio>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "android-base/errors.h"
-#include "android-base/macros.h"
-#include "android-base/utf8.h"
-#include "androidfw/StringPiece.h"
-#include "ziparchive/zip_writer.h"
-
-#include "util/Files.h"
-
-using ::android::StringPiece;
-using ::android::base::SystemErrorCodeToString;
-
-namespace aapt {
-
-namespace {
-
-class DirectoryWriter : public IArchiveWriter {
- public:
-  DirectoryWriter() = default;
-
-  bool Open(const StringPiece& out_dir) {
-    dir_ = out_dir.to_string();
-    file::FileType type = file::GetFileType(dir_);
-    if (type == file::FileType::kNonexistant) {
-      error_ = "directory does not exist";
-      return false;
-    } else if (type != file::FileType::kDirectory) {
-      error_ = "not a directory";
-      return false;
-    }
-    return true;
-  }
-
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
-    if (file_) {
-      return false;
-    }
-
-    std::string full_path = dir_;
-    file::AppendPath(&full_path, path);
-    file::mkdirs(file::GetStem(full_path).to_string());
-
-    file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};
-    if (!file_) {
-      error_ = SystemErrorCodeToString(errno);
-      return false;
-    }
-    return true;
-  }
-
-  bool Write(const void* data, int len) override {
-    if (!file_) {
-      return false;
-    }
-
-    if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) {
-      error_ = SystemErrorCodeToString(errno);
-      file_.reset(nullptr);
-      return false;
-    }
-    return true;
-  }
-
-  bool FinishEntry() override {
-    if (!file_) {
-      return false;
-    }
-    file_.reset(nullptr);
-    return true;
-  }
-
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
-    if (!StartEntry(path, flags)) {
-      return false;
-    }
-
-    const void* data = nullptr;
-    size_t len = 0;
-    while (in->Next(&data, &len)) {
-      if (!Write(data, static_cast<int>(len))) {
-        return false;
-      }
-    }
-    return !in->HadError();
-  }
-
-  bool HadError() const override { return !error_.empty(); }
-
-  std::string GetError() const override { return error_; }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
-
-  std::string dir_;
-  std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
-  std::string error_;
-};
-
-class ZipFileWriter : public IArchiveWriter {
- public:
-  ZipFileWriter() = default;
-
-  bool Open(const StringPiece& path) {
-    file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};
-    if (!file_) {
-      error_ = SystemErrorCodeToString(errno);
-      return false;
-    }
-    writer_ = util::make_unique<ZipWriter>(file_.get());
-    return true;
-  }
-
-  bool StartEntry(const StringPiece& path, uint32_t flags) override {
-    if (!writer_) {
-      return false;
-    }
-
-    size_t zip_flags = 0;
-    if (flags & ArchiveEntry::kCompress) {
-      zip_flags |= ZipWriter::kCompress;
-    }
-
-    if (flags & ArchiveEntry::kAlign) {
-      zip_flags |= ZipWriter::kAlign32;
-    }
-
-    int32_t result = writer_->StartEntry(path.data(), zip_flags);
-    if (result != 0) {
-      error_ = ZipWriter::ErrorCodeString(result);
-      return false;
-    }
-    return true;
-  }
-
-  bool Write(const void* data, int len) override {
-    int32_t result = writer_->WriteBytes(data, len);
-    if (result != 0) {
-      error_ = ZipWriter::ErrorCodeString(result);
-      return false;
-    }
-    return true;
-  }
-
-  bool FinishEntry() override {
-    int32_t result = writer_->FinishEntry();
-    if (result != 0) {
-      error_ = ZipWriter::ErrorCodeString(result);
-      return false;
-    }
-    return true;
-  }
-
-  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
-    while (true) {
-      if (!StartEntry(path, flags)) {
-        return false;
-      }
-
-      const void* data = nullptr;
-      size_t len = 0;
-      while (in->Next(&data, &len)) {
-        if (!Write(data, static_cast<int>(len))) {
-          return false;
-        }
-      }
-
-      if (in->HadError()) {
-        return false;
-      }
-
-      if (!FinishEntry()) {
-        return false;
-      }
-
-      // Check to see if the file was compressed enough. This is preserving behavior of AAPT.
-      if ((flags & ArchiveEntry::kCompress) != 0 && in->CanRewind()) {
-        ZipWriter::FileEntry last_entry;
-        int32_t result = writer_->GetLastEntry(&last_entry);
-        CHECK(result == 0);
-        if (last_entry.compressed_size + (last_entry.compressed_size / 10) >
-            last_entry.uncompressed_size) {
-          // The file was not compressed enough, rewind and store it uncompressed.
-          if (!in->Rewind()) {
-            // Well we tried, may as well keep what we had.
-            return true;
-          }
-
-          int32_t result = writer_->DiscardLastEntry();
-          if (result != 0) {
-            error_ = ZipWriter::ErrorCodeString(result);
-            return false;
-          }
-          flags &= ~ArchiveEntry::kCompress;
-
-          continue;
-        }
-      }
-      return true;
-    }
-  }
-
-  bool HadError() const override { return !error_.empty(); }
-
-  std::string GetError() const override { return error_; }
-
-  virtual ~ZipFileWriter() {
-    if (writer_) {
-      writer_->Finish();
-    }
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ZipFileWriter);
-
-  std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
-  std::unique_ptr<ZipWriter> writer_;
-  std::string error_;
-};
-
-}  // namespace
-
-std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
-                                                             const StringPiece& path) {
-  std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
-  if (!writer->Open(path)) {
-    diag->Error(DiagMessage(path) << writer->GetError());
-    return {};
-  }
-  return std::move(writer);
-}
-
-std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
-                                                           const StringPiece& path) {
-  std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
-  if (!writer->Open(path)) {
-    diag->Error(DiagMessage(path) << writer->GetError());
-    return {};
-  }
-  return std::move(writer);
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
deleted file mode 100644
index 4ee4ce7..0000000
--- a/tools/aapt2/flatten/Archive.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_FLATTEN_ARCHIVE_H
-#define AAPT_FLATTEN_ARCHIVE_H
-
-#include <fstream>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "androidfw/StringPiece.h"
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
-#include "Diagnostics.h"
-#include "io/Io.h"
-#include "util/BigBuffer.h"
-#include "util/Files.h"
-
-namespace aapt {
-
-struct ArchiveEntry {
-  enum : uint32_t {
-    kCompress = 0x01,
-    kAlign = 0x02,
-  };
-
-  std::string path;
-  uint32_t flags;
-  size_t uncompressed_size;
-};
-
-class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream {
- public:
-  virtual ~IArchiveWriter() = default;
-
-  virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
-
-  // Starts a new entry and allows caller to write bytes to it sequentially.
-  // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
-  // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
-  virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
-
-  // Called to finish writing an entry previously started by StartEntry.
-  // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
-  virtual bool FinishEntry() = 0;
-
-  // CopyingOutputStream implementation that allows sequential writes to this archive. Only
-  // valid between calls to StartEntry and FinishEntry.
-  virtual bool Write(const void* buffer, int size) = 0;
-
-  // Returns true if there was an error writing to the archive.
-  // The resulting error message can be retrieved from GetError().
-  virtual bool HadError() const = 0;
-
-  // Returns the error message if HadError() returns true.
-  virtual std::string GetError() const = 0;
-};
-
-std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
-                                                             const android::StringPiece& path);
-
-std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
-                                                           const android::StringPiece& path);
-
-}  // namespace aapt
-
-#endif /* AAPT_FLATTEN_ARCHIVE_H */
diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h
deleted file mode 100644
index 968d3ee..0000000
--- a/tools/aapt2/flatten/ChunkWriter.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_FLATTEN_CHUNKWRITER_H
-#define AAPT_FLATTEN_CHUNKWRITER_H
-
-#include "android-base/macros.h"
-#include "androidfw/ResourceTypes.h"
-
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-namespace aapt {
-
-class ChunkWriter {
- public:
-  explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {}
-  ChunkWriter(ChunkWriter&&) = default;
-  ChunkWriter& operator=(ChunkWriter&&) = default;
-
-  template <typename T>
-  inline T* StartChunk(uint16_t type) {
-    start_size_ = buffer_->size();
-    T* chunk = buffer_->NextBlock<T>();
-    header_ = &chunk->header;
-    header_->type = util::HostToDevice16(type);
-    header_->headerSize = util::HostToDevice16(sizeof(T));
-    return chunk;
-  }
-
-  template <typename T>
-  inline T* NextBlock(size_t count = 1) {
-    return buffer_->NextBlock<T>(count);
-  }
-
-  inline BigBuffer* buffer() { return buffer_; }
-
-  inline android::ResChunk_header* chunk_header() { return header_; }
-
-  inline size_t size() { return buffer_->size() - start_size_; }
-
-  inline android::ResChunk_header* Finish() {
-    buffer_->Align4();
-    header_->size = util::HostToDevice32(buffer_->size() - start_size_);
-    return header_;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ChunkWriter);
-
-  BigBuffer* buffer_;
-  size_t start_size_ = 0;
-  android::ResChunk_header* header_ = nullptr;
-};
-
-template <>
-inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) {
-  start_size_ = buffer_->size();
-  header_ = buffer_->NextBlock<android::ResChunk_header>();
-  header_->type = util::HostToDevice16(type);
-  header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header));
-  return header_;
-}
-
-}  // namespace aapt
-
-#endif /* AAPT_FLATTEN_CHUNKWRITER_H */
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
deleted file mode 100644
index 6359b41..0000000
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_RESOURCE_TYPE_EXTENSIONS_H
-#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
-
-#include "androidfw/ResourceTypes.h"
-
-namespace aapt {
-
-/**
- * An alternative struct to use instead of ResTable_map_entry. This one is a
- * standard_layout
- * struct.
- */
-struct ResTable_entry_ext {
-  android::ResTable_entry entry;
-  android::ResTable_ref parent;
-  uint32_t count;
-};
-
-}  // namespace aapt
-
-#endif  // AAPT_RESOURCE_TYPE_EXTENSIONS_H
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
deleted file mode 100644
index 14b776b..0000000
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ /dev/null
@@ -1,603 +0,0 @@
-/*
- * Copyright (C) 2015 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 "flatten/TableFlattener.h"
-
-#include <algorithm>
-#include <numeric>
-#include <sstream>
-#include <type_traits>
-
-#include "android-base/logging.h"
-#include "android-base/macros.h"
-
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "SdkConstants.h"
-#include "ValueVisitor.h"
-#include "flatten/ChunkWriter.h"
-#include "flatten/ResourceTypeExtensions.h"
-#include "util/BigBuffer.h"
-
-using namespace android;
-
-namespace aapt {
-
-namespace {
-
-template <typename T>
-static bool cmp_ids(const T* a, const T* b) {
-  return a->id.value() < b->id.value();
-}
-
-static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
-  if (len == 0) {
-    return;
-  }
-
-  size_t i;
-  const char16_t* src_data = src.data();
-  for (i = 0; i < len - 1 && i < src.size(); i++) {
-    dst[i] = util::HostToDevice16((uint16_t)src_data[i]);
-  }
-  dst[i] = 0;
-}
-
-static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
-  if (a.key.id) {
-    if (b.key.id) {
-      return a.key.id.value() < b.key.id.value();
-    }
-    return true;
-  } else if (!b.key.id) {
-    return a.key.name.value() < b.key.name.value();
-  }
-  return false;
-}
-
-struct FlatEntry {
-  ResourceEntry* entry;
-  Value* value;
-
-  // The entry string pool index to the entry's name.
-  uint32_t entry_key;
-};
-
-class MapFlattenVisitor : public RawValueVisitor {
- public:
-  using RawValueVisitor::Visit;
-
-  MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
-      : out_entry_(out_entry), buffer_(buffer) {}
-
-  void Visit(Attribute* attr) override {
-    {
-      Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
-      FlattenEntry(&key, &val);
-    }
-
-    if (attr->min_int != std::numeric_limits<int32_t>::min()) {
-      Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC,
-                          static_cast<uint32_t>(attr->min_int));
-      FlattenEntry(&key, &val);
-    }
-
-    if (attr->max_int != std::numeric_limits<int32_t>::max()) {
-      Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC,
-                          static_cast<uint32_t>(attr->max_int));
-      FlattenEntry(&key, &val);
-    }
-
-    for (Attribute::Symbol& s : attr->symbols) {
-      BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
-      FlattenEntry(&s.symbol, &val);
-    }
-  }
-
-  void Visit(Style* style) override {
-    if (style->parent) {
-      const Reference& parent_ref = style->parent.value();
-      CHECK(bool(parent_ref.id)) << "parent has no ID";
-      out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id);
-    }
-
-    // Sort the style.
-    std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries);
-
-    for (Style::Entry& entry : style->entries) {
-      FlattenEntry(&entry.key, entry.value.get());
-    }
-  }
-
-  void Visit(Styleable* styleable) override {
-    for (auto& attr_ref : styleable->entries) {
-      BinaryPrimitive val(Res_value{});
-      FlattenEntry(&attr_ref, &val);
-    }
-  }
-
-  void Visit(Array* array) override {
-    for (auto& item : array->elements) {
-      ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
-      FlattenValue(item.get(), out_entry);
-      out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
-      entry_count_++;
-    }
-  }
-
-  void Visit(Plural* plural) override {
-    const size_t count = plural->values.size();
-    for (size_t i = 0; i < count; i++) {
-      if (!plural->values[i]) {
-        continue;
-      }
-
-      ResourceId q;
-      switch (i) {
-        case Plural::Zero:
-          q.id = android::ResTable_map::ATTR_ZERO;
-          break;
-
-        case Plural::One:
-          q.id = android::ResTable_map::ATTR_ONE;
-          break;
-
-        case Plural::Two:
-          q.id = android::ResTable_map::ATTR_TWO;
-          break;
-
-        case Plural::Few:
-          q.id = android::ResTable_map::ATTR_FEW;
-          break;
-
-        case Plural::Many:
-          q.id = android::ResTable_map::ATTR_MANY;
-          break;
-
-        case Plural::Other:
-          q.id = android::ResTable_map::ATTR_OTHER;
-          break;
-
-        default:
-          LOG(FATAL) << "unhandled plural type";
-          break;
-      }
-
-      Reference key(q);
-      FlattenEntry(&key, plural->values[i].get());
-    }
-  }
-
-  /**
-   * Call this after visiting a Value. This will finish any work that
-   * needs to be done to prepare the entry.
-   */
-  void Finish() { out_entry_->count = util::HostToDevice32(entry_count_); }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
-
-  void FlattenKey(Reference* key, ResTable_map* out_entry) {
-    CHECK(bool(key->id)) << "key has no ID";
-    out_entry->name.ident = util::HostToDevice32(key->id.value().id);
-  }
-
-  void FlattenValue(Item* value, ResTable_map* out_entry) {
-    CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
-  }
-
-  void FlattenEntry(Reference* key, Item* value) {
-    ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
-    FlattenKey(key, out_entry);
-    FlattenValue(value, out_entry);
-    out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
-    entry_count_++;
-  }
-
-  ResTable_entry_ext* out_entry_;
-  BigBuffer* buffer_;
-  size_t entry_count_ = 0;
-};
-
-class PackageFlattener {
- public:
-  PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
-                   const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries)
-      : context_(context),
-        diag_(context->GetDiagnostics()),
-        package_(package),
-        shared_libs_(shared_libs),
-        use_sparse_entries_(use_sparse_entries) {}
-
-  bool FlattenPackage(BigBuffer* buffer) {
-    ChunkWriter pkg_writer(buffer);
-    ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
-    pkg_header->id = util::HostToDevice32(package_->id.value());
-
-    // AAPT truncated the package name, so do the same.
-    // Shared libraries require full package names, so don't truncate theirs.
-    if (context_->GetPackageType() != PackageType::kApp &&
-        package_->name.size() >= arraysize(pkg_header->name)) {
-      diag_->Error(DiagMessage() << "package name '" << package_->name
-                                 << "' is too long. "
-                                    "Shared libraries cannot have truncated package names");
-      return false;
-    }
-
-    // Copy the package name in device endianness.
-    strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_->name));
-
-    // Serialize the types. We do this now so that our type and key strings
-    // are populated. We write those first.
-    BigBuffer type_buffer(1024);
-    FlattenTypes(&type_buffer);
-
-    pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size());
-    StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_);
-
-    pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size());
-    StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_);
-
-    // Append the types.
-    buffer->AppendBuffer(std::move(type_buffer));
-
-    // If there are libraries (or if the package ID is 0x00), encode a library chunk.
-    if (package_->id.value() == 0x00 || !shared_libs_->empty()) {
-      FlattenLibrarySpec(buffer);
-    }
-
-    pkg_writer.Finish();
-    return true;
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
-
-  template <typename T, bool IsItem>
-  T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) {
-    static_assert(std::is_same<ResTable_entry, T>::value ||
-                      std::is_same<ResTable_entry_ext, T>::value,
-                  "T must be ResTable_entry or ResTable_entry_ext");
-
-    T* result = buffer->NextBlock<T>();
-    ResTable_entry* out_entry = (ResTable_entry*)result;
-    if (entry->entry->symbol_status.state == SymbolState::kPublic) {
-      out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
-    }
-
-    if (entry->value->IsWeak()) {
-      out_entry->flags |= ResTable_entry::FLAG_WEAK;
-    }
-
-    if (!IsItem) {
-      out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
-    }
-
-    out_entry->flags = util::HostToDevice16(out_entry->flags);
-    out_entry->key.index = util::HostToDevice32(entry->entry_key);
-    out_entry->size = util::HostToDevice16(sizeof(T));
-    return result;
-  }
-
-  bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) {
-    if (Item* item = ValueCast<Item>(entry->value)) {
-      WriteEntry<ResTable_entry, true>(entry, buffer);
-      Res_value* outValue = buffer->NextBlock<Res_value>();
-      CHECK(item->Flatten(outValue)) << "flatten failed";
-      outValue->size = util::HostToDevice16(sizeof(*outValue));
-    } else {
-      ResTable_entry_ext* out_entry =
-          WriteEntry<ResTable_entry_ext, false>(entry, buffer);
-      MapFlattenVisitor visitor(out_entry, buffer);
-      entry->value->Accept(&visitor);
-      visitor.Finish();
-    }
-    return true;
-  }
-
-  bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config,
-                     const size_t num_total_entries, std::vector<FlatEntry>* entries,
-                     BigBuffer* buffer) {
-    CHECK(num_total_entries != 0);
-    CHECK(num_total_entries <= std::numeric_limits<uint16_t>::max());
-
-    ChunkWriter type_writer(buffer);
-    ResTable_type* type_header =
-        type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
-    type_header->id = type->id.value();
-    type_header->config = config;
-    type_header->config.swapHtoD();
-
-    std::vector<uint32_t> offsets;
-    offsets.resize(num_total_entries, 0xffffffffu);
-
-    BigBuffer values_buffer(512);
-    for (FlatEntry& flat_entry : *entries) {
-      CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
-      offsets[flat_entry.entry->id.value()] = values_buffer.size();
-      if (!FlattenValue(&flat_entry, &values_buffer)) {
-        diag_->Error(DiagMessage()
-                     << "failed to flatten resource '"
-                     << ResourceNameRef(package_->name, type->type, flat_entry.entry->name)
-                     << "' for configuration '" << config << "'");
-        return false;
-      }
-    }
-
-    bool sparse_encode = use_sparse_entries_;
-
-    // Only sparse encode if the entries will be read on platforms O+.
-    sparse_encode =
-        sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O);
-
-    // Only sparse encode if the offsets are representable in 2 bytes.
-    sparse_encode =
-        sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
-
-    // Only sparse encode if the ratio of populated entries to total entries is below some
-    // threshold.
-    sparse_encode =
-        sparse_encode && ((100 * entries->size()) / num_total_entries) < kSparseEncodingThreshold;
-
-    if (sparse_encode) {
-      type_header->entryCount = util::HostToDevice32(entries->size());
-      type_header->flags |= ResTable_type::FLAG_SPARSE;
-      ResTable_sparseTypeEntry* indices =
-          type_writer.NextBlock<ResTable_sparseTypeEntry>(entries->size());
-      for (size_t i = 0; i < num_total_entries; i++) {
-        if (offsets[i] != ResTable_type::NO_ENTRY) {
-          CHECK((offsets[i] & 0x03) == 0);
-          indices->idx = util::HostToDevice16(i);
-          indices->offset = util::HostToDevice16(offsets[i] / 4u);
-          indices++;
-        }
-      }
-    } else {
-      type_header->entryCount = util::HostToDevice32(num_total_entries);
-      uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
-      for (size_t i = 0; i < num_total_entries; i++) {
-        indices[i] = util::HostToDevice32(offsets[i]);
-      }
-    }
-
-    type_header->entriesStart = util::HostToDevice32(type_writer.size());
-    type_writer.buffer()->AppendBuffer(std::move(values_buffer));
-    type_writer.Finish();
-    return true;
-  }
-
-  std::vector<ResourceTableType*> CollectAndSortTypes() {
-    std::vector<ResourceTableType*> sorted_types;
-    for (auto& type : package_->types) {
-      if (type->type == ResourceType::kStyleable) {
-        // Styleables aren't real Resource Types, they are represented in the
-        // R.java file.
-        continue;
-      }
-
-      CHECK(bool(type->id)) << "type must have an ID set";
-
-      sorted_types.push_back(type.get());
-    }
-    std::sort(sorted_types.begin(), sorted_types.end(),
-              cmp_ids<ResourceTableType>);
-    return sorted_types;
-  }
-
-  std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
-    // Sort the entries by entry ID.
-    std::vector<ResourceEntry*> sorted_entries;
-    for (auto& entry : type->entries) {
-      CHECK(bool(entry->id)) << "entry must have an ID set";
-      sorted_entries.push_back(entry.get());
-    }
-    std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>);
-    return sorted_entries;
-  }
-
-  bool FlattenTypeSpec(ResourceTableType* type,
-                       std::vector<ResourceEntry*>* sorted_entries,
-                       BigBuffer* buffer) {
-    ChunkWriter type_spec_writer(buffer);
-    ResTable_typeSpec* spec_header =
-        type_spec_writer.StartChunk<ResTable_typeSpec>(
-            RES_TABLE_TYPE_SPEC_TYPE);
-    spec_header->id = type->id.value();
-
-    if (sorted_entries->empty()) {
-      type_spec_writer.Finish();
-      return true;
-    }
-
-    // We can't just take the size of the vector. There may be holes in the
-    // entry ID space.
-    // Since the entries are sorted by ID, the last one will be the biggest.
-    const size_t num_entries = sorted_entries->back()->id.value() + 1;
-
-    spec_header->entryCount = util::HostToDevice32(num_entries);
-
-    // Reserve space for the masks of each resource in this type. These
-    // show for which configuration axis the resource changes.
-    uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
-
-    const size_t actual_num_entries = sorted_entries->size();
-    for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
-      ResourceEntry* entry = sorted_entries->at(entryIndex);
-
-      // Populate the config masks for this entry.
-
-      if (entry->symbol_status.state == SymbolState::kPublic) {
-        config_masks[entry->id.value()] |=
-            util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
-      }
-
-      const size_t config_count = entry->values.size();
-      for (size_t i = 0; i < config_count; i++) {
-        const ConfigDescription& config = entry->values[i]->config;
-        for (size_t j = i + 1; j < config_count; j++) {
-          config_masks[entry->id.value()] |=
-              util::HostToDevice32(config.diff(entry->values[j]->config));
-        }
-      }
-    }
-    type_spec_writer.Finish();
-    return true;
-  }
-
-  bool FlattenTypes(BigBuffer* buffer) {
-    // Sort the types by their IDs. They will be inserted into the StringPool in
-    // this order.
-    std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes();
-
-    size_t expected_type_id = 1;
-    for (ResourceTableType* type : sorted_types) {
-      // If there is a gap in the type IDs, fill in the StringPool
-      // with empty values until we reach the ID we expect.
-      while (type->id.value() > expected_type_id) {
-        std::stringstream type_name;
-        type_name << "?" << expected_type_id;
-        type_pool_.MakeRef(type_name.str());
-        expected_type_id++;
-      }
-      expected_type_id++;
-      type_pool_.MakeRef(ToString(type->type));
-
-      std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
-      if (sorted_entries.empty()) {
-        continue;
-      }
-
-      if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
-        return false;
-      }
-
-      // Since the entries are sorted by ID, the last ID will be the largest.
-      const size_t num_entries = sorted_entries.back()->id.value() + 1;
-
-      // The binary resource table lists resource entries for each
-      // configuration.
-      // We store them inverted, where a resource entry lists the values for
-      // each
-      // configuration available. Here we reverse this to match the binary
-      // table.
-      std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
-      for (ResourceEntry* entry : sorted_entries) {
-        const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
-
-        // Group values by configuration.
-        for (auto& config_value : entry->values) {
-          config_to_entry_list_map[config_value->config].push_back(
-              FlatEntry{entry, config_value->value.get(), key_index});
-        }
-      }
-
-      // Flatten a configuration value.
-      for (auto& entry : config_to_entry_list_map) {
-        if (!FlattenConfig(type, entry.first, num_entries, &entry.second, buffer)) {
-          return false;
-        }
-      }
-    }
-    return true;
-  }
-
-  void FlattenLibrarySpec(BigBuffer* buffer) {
-    ChunkWriter lib_writer(buffer);
-    ResTable_lib_header* lib_header =
-        lib_writer.StartChunk<ResTable_lib_header>(RES_TABLE_LIBRARY_TYPE);
-
-    const size_t num_entries = (package_->id.value() == 0x00 ? 1 : 0) + shared_libs_->size();
-    CHECK(num_entries > 0);
-
-    lib_header->count = util::HostToDevice32(num_entries);
-
-    ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries);
-    if (package_->id.value() == 0x00) {
-      // Add this package
-      lib_entry->packageId = util::HostToDevice32(0x00);
-      strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
-                    util::Utf8ToUtf16(package_->name));
-      ++lib_entry;
-    }
-
-    for (auto& map_entry : *shared_libs_) {
-      lib_entry->packageId = util::HostToDevice32(map_entry.first);
-      strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
-                    util::Utf8ToUtf16(map_entry.second));
-      ++lib_entry;
-    }
-    lib_writer.Finish();
-  }
-
-  IAaptContext* context_;
-  IDiagnostics* diag_;
-  ResourceTablePackage* package_;
-  const std::map<size_t, std::string>* shared_libs_;
-  bool use_sparse_entries_;
-  StringPool type_pool_;
-  StringPool key_pool_;
-};
-
-}  // namespace
-
-bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
-  // We must do this before writing the resources, since the string pool IDs may change.
-  table->string_pool.Prune();
-  table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
-    int diff = util::compare(a.priority, b.priority);
-    if (diff == 0) {
-      diff = a.config.compare(b.config);
-    }
-    return diff;
-  });
-
-  // Write the ResTable header.
-  ChunkWriter table_writer(buffer_);
-  ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
-  table_header->packageCount = util::HostToDevice32(table->packages.size());
-
-  // Write a self mapping entry for this package if the ID is non-standard (0x7f).
-  if (context->GetPackageType() == PackageType::kApp) {
-    const uint8_t package_id = context->GetPackageId();
-    if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
-      table->included_packages_[package_id] = context->GetCompilationPackage();
-    }
-  }
-
-  // Flatten the values string pool.
-  StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
-
-  BigBuffer package_buffer(1024);
-
-  // Flatten each package.
-  for (auto& package : table->packages) {
-    PackageFlattener flattener(context, package.get(), &table->included_packages_,
-                               options_.use_sparse_entries);
-    if (!flattener.FlattenPackage(&package_buffer)) {
-      return false;
-    }
-  }
-
-  // Finally merge all the packages into the main buffer.
-  table_writer.buffer()->AppendBuffer(std::move(package_buffer));
-  table_writer.Finish();
-  return true;
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
deleted file mode 100644
index 223aef8..0000000
--- a/tools/aapt2/flatten/TableFlattener.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_FLATTEN_TABLEFLATTENER_H
-#define AAPT_FLATTEN_TABLEFLATTENER_H
-
-#include "android-base/macros.h"
-
-#include "ResourceTable.h"
-#include "process/IResourceTableConsumer.h"
-#include "util/BigBuffer.h"
-
-namespace aapt {
-
-// The percentage of used entries for a type for which using a sparse encoding is
-// preferred.
-constexpr const size_t kSparseEncodingThreshold = 60;
-
-struct TableFlattenerOptions {
-  // When true, types for configurations with a sparse set of entries are encoded
-  // as a sparse map of entry ID and offset to actual data.
-  // This is only available on platforms O+ and will only be respected when
-  // minSdk is O+.
-  bool use_sparse_entries = false;
-};
-
-class TableFlattener : public IResourceTableConsumer {
- public:
-  explicit TableFlattener(const TableFlattenerOptions& options, BigBuffer* buffer)
-      : options_(options), buffer_(buffer) {}
-
-  bool Consume(IAaptContext* context, ResourceTable* table) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-
-  TableFlattenerOptions options_;
-  BigBuffer* buffer_;
-};
-
-}  // namespace aapt
-
-#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
deleted file mode 100644
index 4fdb2ec..0000000
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright (C) 2015 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 "flatten/TableFlattener.h"
-
-#include "android-base/stringprintf.h"
-
-#include "ResourceUtils.h"
-#include "SdkConstants.h"
-#include "test/Test.h"
-#include "unflatten/BinaryResourceParser.h"
-#include "util/Util.h"
-
-using namespace android;
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-
-namespace aapt {
-
-class TableFlattenerTest : public ::testing::Test {
- public:
-  void SetUp() override {
-    context_ = test::ContextBuilder()
-                   .SetCompilationPackage("com.app.test")
-                   .SetPackageId(0x7f)
-                   .Build();
-  }
-
-  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
-                                     ResourceTable* table, std::string* out_content) {
-    BigBuffer buffer(1024);
-    TableFlattener flattener(options, &buffer);
-    if (!flattener.Consume(context, table)) {
-      return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
-    }
-    *out_content = buffer.to_string();
-    return ::testing::AssertionSuccess();
-  }
-
-  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
-                                     ResourceTable* table, ResTable* out_table) {
-    std::string content;
-    auto result = Flatten(context, options, table, &content);
-    if (!result) {
-      return result;
-    }
-
-    if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) {
-      return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
-    }
-    return ::testing::AssertionSuccess();
-  }
-
-  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
-                                     ResourceTable* table, ResourceTable* out_table) {
-    std::string content;
-    auto result = Flatten(context, options, table, &content);
-    if (!result) {
-      return result;
-    }
-
-    BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
-    if (!parser.Parse()) {
-      return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
-    }
-    return ::testing::AssertionSuccess();
-  }
-
-  ::testing::AssertionResult Exists(ResTable* table,
-                                    const StringPiece& expected_name,
-                                    const ResourceId& expected_id,
-                                    const ConfigDescription& expected_config,
-                                    const uint8_t expected_data_type,
-                                    const uint32_t expected_data,
-                                    const uint32_t expected_spec_flags) {
-    const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
-
-    table->setParameters(&expected_config);
-
-    ResTable_config config;
-    Res_value val;
-    uint32_t spec_flags;
-    if (table->getResource(expected_id.id, &val, false, 0, &spec_flags,
-                           &config) < 0) {
-      return ::testing::AssertionFailure() << "could not find resource with";
-    }
-
-    if (expected_data_type != val.dataType) {
-      return ::testing::AssertionFailure()
-             << "expected data type " << std::hex << (int)expected_data_type
-             << " but got data type " << (int)val.dataType << std::dec
-             << " instead";
-    }
-
-    if (expected_data != val.data) {
-      return ::testing::AssertionFailure()
-             << "expected data " << std::hex << expected_data
-             << " but got data " << val.data << std::dec << " instead";
-    }
-
-    if (expected_spec_flags != spec_flags) {
-      return ::testing::AssertionFailure()
-             << "expected specFlags " << std::hex << expected_spec_flags
-             << " but got specFlags " << spec_flags << std::dec << " instead";
-    }
-
-    ResTable::resource_name actual_name;
-    if (!table->getResourceName(expected_id.id, false, &actual_name)) {
-      return ::testing::AssertionFailure() << "failed to find resource name";
-    }
-
-    Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
-    if (!resName) {
-      return ::testing::AssertionFailure()
-             << "expected name '" << expected_res_name << "' but got '"
-             << StringPiece16(actual_name.package, actual_name.packageLen)
-             << ":" << StringPiece16(actual_name.type, actual_name.typeLen)
-             << "/" << StringPiece16(actual_name.name, actual_name.nameLen)
-             << "'";
-    }
-
-    if (expected_config != config) {
-      return ::testing::AssertionFailure() << "expected config '"
-                                           << expected_config << "' but got '"
-                                           << ConfigDescription(config) << "'";
-    }
-    return ::testing::AssertionSuccess();
-  }
-
- protected:
-  std::unique_ptr<IAaptContext> context_;
-};
-
-TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId("com.app.test", 0x7f)
-          .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
-          .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
-          .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
-                    test::BuildReference("com.app.test:id/one",
-                                         ResourceId(0x7f020000)))
-          .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
-                    util::make_unique<BinaryPrimitive>(
-                        uint8_t(Res_value::TYPE_INT_DEC), 1u))
-          .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
-                    ResourceId(0x7f030000),
-                    util::make_unique<BinaryPrimitive>(
-                        uint8_t(Res_value::TYPE_INT_DEC), 2u))
-          .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
-          .AddString("com.app.test:layout/bar", ResourceId(0x7f050000),
-                     "res/layout/bar.xml")
-          .Build();
-
-  ResTable res_table;
-  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
-
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000),
-                     {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
-
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001),
-                     {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
-
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three",
-                     ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE,
-                     0x7f020000u, 0u));
-
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one",
-                     ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
-                     ResTable_config::CONFIG_VERSION));
-
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one",
-                     ResourceId(0x7f030000), test::ParseConfigOrDie("v1"),
-                     Res_value::TYPE_INT_DEC, 2u,
-                     ResTable_config::CONFIG_VERSION));
-
-  std::u16string foo_str = u"foo";
-  ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(),
-                                                                foo_str.size());
-  ASSERT_GE(idx, 0);
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test",
-                     ResourceId(0x7f040000), {}, Res_value::TYPE_STRING,
-                     (uint32_t)idx, 0u));
-
-  std::u16string bar_path = u"res/layout/bar.xml";
-  idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(),
-                                                        bar_path.size());
-  ASSERT_GE(idx, 0);
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar",
-                     ResourceId(0x7f050000), {}, Res_value::TYPE_STRING,
-                     (uint32_t)idx, 0u));
-}
-
-TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId("com.app.test", 0x7f)
-          .AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
-          .AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
-          .Build();
-
-  ResTable res_table;
-  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
-
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001),
-                     {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
-  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three",
-                     ResourceId(0x7f020003), {}, Res_value::TYPE_INT_BOOLEAN,
-                     0u, 0u));
-}
-
-TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
-  Attribute attr(false);
-  attr.type_mask = android::ResTable_map::TYPE_INTEGER;
-  attr.min_int = 10;
-  attr.max_int = 23;
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId("android", 0x01)
-          .AddValue("android:attr/foo", ResourceId(0x01010000),
-                    util::make_unique<Attribute>(attr))
-          .Build();
-
-  ResourceTable result;
-  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
-
-  Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo");
-  ASSERT_THAT(actual_attr, NotNull());
-  EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak());
-  EXPECT_EQ(attr.type_mask, actual_attr->type_mask);
-  EXPECT_EQ(attr.min_int, actual_attr->min_int);
-  EXPECT_EQ(attr.max_int, actual_attr->max_int);
-}
-
-static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
-    IAaptContext* context, const ConfigDescription& sparse_config, float load) {
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId(context->GetCompilationPackage(), context->GetPackageId())
-          .Build();
-
-  // Add regular entries.
-  int stride = static_cast<int>(1.0f / load);
-  for (int i = 0; i < 100; i++) {
-    const ResourceName name = test::ParseNameOrDie(
-        base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
-    const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
-    const auto value =
-        util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
-    CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
-                             std::unique_ptr<Value>(value->Clone(nullptr)),
-                             context->GetDiagnostics()));
-
-    // Every few entries, write out a sparse_config value. This will give us the desired load.
-    if (i % stride == 0) {
-      CHECK(table->AddResource(name, resid, sparse_config, "",
-                               std::unique_ptr<Value>(value->Clone(nullptr)),
-                               context->GetDiagnostics()));
-    }
-  }
-  return table;
-}
-
-TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
-                                              .SetCompilationPackage("android")
-                                              .SetPackageId(0x01)
-                                              .SetMinSdkVersion(SDK_O)
-                                              .Build();
-
-  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
-  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
-
-  TableFlattenerOptions options;
-  options.use_sparse_entries = true;
-
-  std::string no_sparse_contents;
-  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
-
-  std::string sparse_contents;
-  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
-
-  EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
-
-  // Attempt to parse the sparse contents.
-
-  ResourceTable sparse_table;
-  BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
-                              sparse_contents.data(), sparse_contents.size());
-  ASSERT_TRUE(parser.Parse());
-
-  auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
-                                                        sparse_config);
-  ASSERT_THAT(value, NotNull());
-  EXPECT_EQ(0u, value->value.data);
-
-  ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", sparse_config), IsNull());
-
-  value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", sparse_config);
-  ASSERT_THAT(value, NotNull());
-  EXPECT_EQ(4u, value->value.data);
-}
-
-TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
-                                              .SetCompilationPackage("android")
-                                              .SetPackageId(0x01)
-                                              .SetMinSdkVersion(SDK_LOLLIPOP)
-                                              .Build();
-
-  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
-  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
-
-  TableFlattenerOptions options;
-  options.use_sparse_entries = true;
-
-  std::string no_sparse_contents;
-  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
-
-  std::string sparse_contents;
-  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
-
-  EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
-}
-
-TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
-                                              .SetCompilationPackage("android")
-                                              .SetPackageId(0x01)
-                                              .SetMinSdkVersion(SDK_O)
-                                              .Build();
-
-  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
-  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
-
-  TableFlattenerOptions options;
-  options.use_sparse_entries = true;
-
-  std::string no_sparse_contents;
-  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
-
-  std::string sparse_contents;
-  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
-
-  EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
-}
-
-TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
-  std::unique_ptr<IAaptContext> context =
-      test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId("lib", 0x00)
-          .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>())
-          .Build();
-  ResourceTable result;
-  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
-
-  Maybe<ResourceTable::SearchResult> search_result =
-      result.FindResource(test::ParseNameOrDie("lib:id/foo"));
-  ASSERT_TRUE(search_result);
-  EXPECT_EQ(0x00u, search_result.value().package->id.value());
-
-  auto iter = result.included_packages_.find(0x00);
-  ASSERT_NE(result.included_packages_.end(), iter);
-  EXPECT_EQ("lib", iter->second);
-}
-
-TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
-  std::unique_ptr<IAaptContext> context =
-      test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId("app", 0x7f)
-          .AddValue("app:id/foo", ResourceId(0x7f010000),
-                    test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
-          .AddValue("app:id/bar", ResourceId(0x7f010001),
-                    test::BuildReference("lib_two:id/bar", ResourceId(0x03010000)))
-          .Build();
-  table->included_packages_[0x02] = "lib_one";
-  table->included_packages_[0x03] = "lib_two";
-
-  ResTable result;
-  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
-
-  const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
-  ASSERT_THAT(dynamic_ref_table, NotNull());
-
-  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
-
-  ssize_t idx = entries.indexOfKey(android::String16("lib_one"));
-  ASSERT_GE(idx, 0);
-  EXPECT_EQ(0x02u, entries.valueAt(idx));
-
-  idx = entries.indexOfKey(android::String16("lib_two"));
-  ASSERT_GE(idx, 0);
-  EXPECT_EQ(0x03u, entries.valueAt(idx));
-}
-
-TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) {
-  std::unique_ptr<IAaptContext> context =
-      test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build();
-  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-                                             .SetPackageId("app", 0x80)
-                                             .AddSimple("app:id/foo", ResourceId(0x80010000))
-                                             .Build();
-
-  ResTable result;
-  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
-
-  const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
-  ASSERT_THAT(dynamic_ref_table, NotNull());
-
-  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
-  ssize_t idx = entries.indexOfKey(android::String16("app"));
-  ASSERT_GE(idx, 0);
-  EXPECT_EQ(0x80u, entries.valueAt(idx));
-}
-
-TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) {
-  std::string kPackageName(256, 'F');
-
-  std::unique_ptr<IAaptContext> context =
-      test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build();
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId(kPackageName, 0x7f)
-          .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
-          .Build();
-
-  ResTable result;
-  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
-
-  ASSERT_EQ(1u, result.getBasePackageCount());
-  EXPECT_EQ(127u, result.getBasePackageName(0).size());
-}
-
-TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
-  std::string kPackageName(256, 'F');
-
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
-                                              .SetCompilationPackage(kPackageName)
-                                              .SetPackageId(0x7f)
-                                              .SetPackageType(PackageType::kSharedLib)
-                                              .Build();
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId(kPackageName, 0x7f)
-          .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
-          .Build();
-
-  ResTable result;
-  ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
deleted file mode 100644
index b3b308a..0000000
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright (C) 2015 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 "flatten/XmlFlattener.h"
-
-#include <algorithm>
-#include <map>
-#include <vector>
-
-#include "android-base/logging.h"
-#include "android-base/macros.h"
-#include "androidfw/ResourceTypes.h"
-#include "utils/misc.h"
-
-#include "SdkConstants.h"
-#include "flatten/ChunkWriter.h"
-#include "flatten/ResourceTypeExtensions.h"
-#include "xml/XmlDom.h"
-
-using namespace android;
-
-namespace aapt {
-
-namespace {
-
-constexpr uint32_t kLowPriority = 0xffffffffu;
-
-static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribute* b) {
-  if (a->compiled_attribute && a->compiled_attribute.value().id) {
-    if (b->compiled_attribute && b->compiled_attribute.value().id) {
-      return a->compiled_attribute.value().id.value() < b->compiled_attribute.value().id.value();
-    }
-    return true;
-  } else if (!b->compiled_attribute) {
-    int diff = a->namespace_uri.compare(b->namespace_uri);
-    if (diff < 0) {
-      return true;
-    } else if (diff > 0) {
-      return false;
-    }
-    return a->name < b->name;
-  }
-  return false;
-}
-
-class XmlFlattenerVisitor : public xml::Visitor {
- public:
-  using xml::Visitor::Visit;
-
-  StringPool pool;
-  std::map<uint8_t, StringPool> package_pools;
-
-  struct StringFlattenDest {
-    StringPool::Ref ref;
-    ResStringPool_ref* dest;
-  };
-
-  std::vector<StringFlattenDest> string_refs;
-
-  XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
-      : buffer_(buffer), options_(options) {}
-
-  void Visit(xml::Text* node) override {
-    if (util::TrimWhitespace(node->text).empty()) {
-      // Skip whitespace only text nodes.
-      return;
-    }
-
-    ChunkWriter writer(buffer_);
-    ResXMLTree_node* flat_node = writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
-    flat_node->lineNumber = util::HostToDevice32(node->line_number);
-    flat_node->comment.index = util::HostToDevice32(-1);
-
-    ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
-
-    // Process plain strings to make sure they get properly escaped.
-    util::StringBuilder builder;
-    builder.Append(node->text);
-    AddString(builder.ToString(), kLowPriority, &flat_text->data);
-
-    writer.Finish();
-  }
-
-  void Visit(xml::Element* node) override {
-    for (const xml::NamespaceDecl& decl : node->namespace_decls) {
-      // Skip dedicated tools namespace.
-      if (decl.uri != xml::kSchemaTools) {
-        WriteNamespace(decl, android::RES_XML_START_NAMESPACE_TYPE);
-      }
-    }
-
-    {
-      ChunkWriter start_writer(buffer_);
-      ResXMLTree_node* flat_node =
-          start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
-      flat_node->lineNumber = util::HostToDevice32(node->line_number);
-      flat_node->comment.index = util::HostToDevice32(-1);
-
-      ResXMLTree_attrExt* flat_elem = start_writer.NextBlock<ResXMLTree_attrExt>();
-
-      // A missing namespace must be null, not an empty string. Otherwise the runtime complains.
-      AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,
-                true /* treat_empty_string_as_null */);
-      AddString(node->name, kLowPriority, &flat_elem->name, true /* treat_empty_string_as_null */);
-
-      flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem));
-      flat_elem->attributeSize = util::HostToDevice16(sizeof(ResXMLTree_attribute));
-
-      WriteAttributes(node, flat_elem, &start_writer);
-
-      start_writer.Finish();
-    }
-
-    xml::Visitor::Visit(node);
-
-    {
-      ChunkWriter end_writer(buffer_);
-      ResXMLTree_node* flat_end_node =
-          end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
-      flat_end_node->lineNumber = util::HostToDevice32(node->line_number);
-      flat_end_node->comment.index = util::HostToDevice32(-1);
-
-      ResXMLTree_endElementExt* flat_end_elem = end_writer.NextBlock<ResXMLTree_endElementExt>();
-      AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,
-                true /* treat_empty_string_as_null */);
-      AddString(node->name, kLowPriority, &flat_end_elem->name);
-
-      end_writer.Finish();
-    }
-
-    for (auto iter = node->namespace_decls.rbegin(); iter != node->namespace_decls.rend(); ++iter) {
-      // Skip dedicated tools namespace.
-      if (iter->uri != xml::kSchemaTools) {
-        WriteNamespace(*iter, android::RES_XML_END_NAMESPACE_TYPE);
-      }
-    }
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
-
-  void AddString(const StringPiece& str, uint32_t priority,
-                 android::ResStringPool_ref* dest,
-                 bool treat_empty_string_as_null = false) {
-    if (str.empty() && treat_empty_string_as_null) {
-      // Some parts of the runtime treat null differently than empty string.
-      dest->index = util::DeviceToHost32(-1);
-    } else {
-      string_refs.push_back(StringFlattenDest{
-          pool.MakeRef(str, StringPool::Context(priority)), dest});
-    }
-  }
-
-  void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
-    string_refs.push_back(StringFlattenDest{ref, dest});
-  }
-
-  void WriteNamespace(const xml::NamespaceDecl& decl, uint16_t type) {
-    ChunkWriter writer(buffer_);
-
-    ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type);
-    flatNode->lineNumber = util::HostToDevice32(decl.line_number);
-    flatNode->comment.index = util::HostToDevice32(-1);
-
-    ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>();
-    AddString(decl.prefix, kLowPriority, &flat_ns->prefix);
-    AddString(decl.uri, kLowPriority, &flat_ns->uri);
-
-    writer.Finish();
-  }
-
-  void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, ChunkWriter* writer) {
-    filtered_attrs_.clear();
-    filtered_attrs_.reserve(node->attributes.size());
-
-    // Filter the attributes.
-    for (xml::Attribute& attr : node->attributes) {
-      if (attr.namespace_uri != xml::kSchemaTools) {
-        filtered_attrs_.push_back(&attr);
-      }
-    }
-
-    if (filtered_attrs_.empty()) {
-      return;
-    }
-
-    const ResourceId kIdAttr(0x010100d0);
-
-    std::sort(filtered_attrs_.begin(), filtered_attrs_.end(), cmp_xml_attribute_by_id);
-
-    flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size());
-
-    ResXMLTree_attribute* flat_attr =
-        writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size());
-    uint16_t attribute_index = 1;
-    for (const xml::Attribute* xml_attr : filtered_attrs_) {
-      // Assign the indices for specific attributes.
-      if (xml_attr->compiled_attribute &&
-          xml_attr->compiled_attribute.value().id &&
-          xml_attr->compiled_attribute.value().id.value() == kIdAttr) {
-        flat_elem->idIndex = util::HostToDevice16(attribute_index);
-      } else if (xml_attr->namespace_uri.empty()) {
-        if (xml_attr->name == "class") {
-          flat_elem->classIndex = util::HostToDevice16(attribute_index);
-        } else if (xml_attr->name == "style") {
-          flat_elem->styleIndex = util::HostToDevice16(attribute_index);
-        }
-      }
-      attribute_index++;
-
-      // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
-      // is empty (doesn't exist).
-      AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns,
-                true /* treat_empty_string_as_null */);
-
-      flat_attr->rawValue.index = util::HostToDevice32(-1);
-
-      if (!xml_attr->compiled_attribute || !xml_attr->compiled_attribute.value().id) {
-        // The attribute has no associated ResourceID, so the string order doesn't matter.
-        AddString(xml_attr->name, kLowPriority, &flat_attr->name);
-      } else {
-        // Attribute names are stored without packages, but we use
-        // their StringPool index to lookup their resource IDs.
-        // This will cause collisions, so we can't dedupe
-        // attribute names from different packages. We use separate
-        // pools that we later combine.
-        //
-        // Lookup the StringPool for this package and make the reference there.
-        const xml::AaptAttribute& aapt_attr = xml_attr->compiled_attribute.value();
-
-        StringPool::Ref name_ref =
-            package_pools[aapt_attr.id.value().package_id()].MakeRef(
-                xml_attr->name, StringPool::Context(aapt_attr.id.value().id));
-
-        // Add it to the list of strings to flatten.
-        AddString(name_ref, &flat_attr->name);
-      }
-
-      // Process plain strings to make sure they get properly escaped.
-      StringPiece raw_value = xml_attr->value;
-
-      util::StringBuilder str_builder(true /*preserve_spaces*/);
-      str_builder.Append(xml_attr->value);
-
-      if (!options_.keep_raw_values) {
-        raw_value = str_builder.ToString();
-      }
-
-      if (options_.keep_raw_values || !xml_attr->compiled_value) {
-        // Keep raw values if the value is not compiled or
-        // if we're building a static library (need symbols).
-        AddString(raw_value, kLowPriority, &flat_attr->rawValue);
-      }
-
-      if (xml_attr->compiled_value) {
-        CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
-      } else {
-        // Flatten as a regular string type.
-        flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
-
-        AddString(str_builder.ToString(), kLowPriority,
-                  (ResStringPool_ref*) &flat_attr->typedValue.data);
-      }
-
-      flat_attr->typedValue.size = util::HostToDevice16(sizeof(flat_attr->typedValue));
-      flat_attr++;
-    }
-  }
-
-  BigBuffer* buffer_;
-  XmlFlattenerOptions options_;
-
-  // Scratch vector to filter attributes. We avoid allocations making this a member.
-  std::vector<xml::Attribute*> filtered_attrs_;
-};
-
-}  // namespace
-
-bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
-  BigBuffer node_buffer(1024);
-  XmlFlattenerVisitor visitor(&node_buffer, options_);
-  node->Accept(&visitor);
-
-  // Merge the package pools into the main pool.
-  for (auto& package_pool_entry : visitor.package_pools) {
-    visitor.pool.Merge(std::move(package_pool_entry.second));
-  }
-
-  // Sort the string pool so that attribute resource IDs show up first.
-  visitor.pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
-    return util::compare(a.priority, b.priority);
-  });
-
-  // Now we flatten the string pool references into the correct places.
-  for (const auto& ref_entry : visitor.string_refs) {
-    ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index());
-  }
-
-  // Write the XML header.
-  ChunkWriter xml_header_writer(buffer_);
-  xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE);
-
-  // Flatten the StringPool.
-  StringPool::FlattenUtf8(buffer_, visitor.pool);
-
-  {
-    // Write the array of resource IDs, indexed by StringPool order.
-    ChunkWriter res_id_map_writer(buffer_);
-    res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
-    for (const auto& str : visitor.pool.strings()) {
-      ResourceId id(str->context.priority);
-      if (str->context.priority == kLowPriority || !id.is_valid()) {
-        // When we see the first non-resource ID, we're done.
-        break;
-      }
-      *res_id_map_writer.NextBlock<uint32_t>() = util::HostToDevice32(id.id);
-    }
-    res_id_map_writer.Finish();
-  }
-
-  // Move the nodeBuffer and append it to the out buffer.
-  buffer_->AppendBuffer(std::move(node_buffer));
-
-  // Finish the xml header.
-  xml_header_writer.Finish();
-  return true;
-}
-
-bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
-  if (!resource->root) {
-    return false;
-  }
-  return Flatten(context, resource->root.get());
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
deleted file mode 100644
index 87557f2..0000000
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_FLATTEN_XMLFLATTENER_H
-#define AAPT_FLATTEN_XMLFLATTENER_H
-
-#include "android-base/macros.h"
-
-#include "process/IResourceTableConsumer.h"
-#include "util/BigBuffer.h"
-#include "xml/XmlDom.h"
-
-namespace aapt {
-
-struct XmlFlattenerOptions {
-  /**
-   * Keep attribute raw string values along with typed values.
-   */
-  bool keep_raw_values = false;
-};
-
-class XmlFlattener : public IXmlResourceConsumer {
- public:
-  XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
-      : buffer_(buffer), options_(options) {}
-
-  bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
-
-  bool Flatten(IAaptContext* context, xml::Node* node);
-
-  BigBuffer* buffer_;
-  XmlFlattenerOptions options_;
-};
-
-}  // namespace aapt
-
-#endif /* AAPT_FLATTEN_XMLFLATTENER_H */
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
deleted file mode 100644
index a57e317..0000000
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2015 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 "flatten/XmlFlattener.h"
-
-#include "androidfw/ResourceTypes.h"
-
-#include "link/Linkers.h"
-#include "test/Test.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-using ::aapt::test::StrEq;
-using ::android::StringPiece16;
-using ::testing::Eq;
-using ::testing::Ge;
-using ::testing::IsNull;
-using ::testing::Ne;
-using ::testing::NotNull;
-
-namespace aapt {
-
-class XmlFlattenerTest : public ::testing::Test {
- public:
-  void SetUp() override {
-    context_ = test::ContextBuilder()
-                   .SetCompilationPackage("com.app.test")
-                   .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
-                   .AddSymbolSource(
-                       test::StaticSymbolSourceBuilder()
-                           .AddPublicSymbol("android:attr/id", ResourceId(0x010100d0),
-                                            test::AttributeBuilder().Build())
-                           .AddSymbol("com.app.test:id/id", ResourceId(0x7f020000))
-                           .AddPublicSymbol("android:attr/paddingStart", ResourceId(0x010103b3),
-                                            test::AttributeBuilder().Build())
-                           .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
-                                            test::AttributeBuilder().Build())
-                           .AddSymbol("com.app.test.feature:id/foo", ResourceId(0x80020000))
-                           .AddSymbol("com.app.test.feature:attr/foo", ResourceId(0x80010000),
-                                      test::AttributeBuilder().Build())
-                           .Build())
-                   .Build();
-  }
-
-  ::testing::AssertionResult Flatten(xml::XmlResource* doc,
-                                     android::ResXMLTree* out_tree,
-                                     const XmlFlattenerOptions& options = {}) {
-    using namespace android;  // For NO_ERROR on windows because it is a macro.
-
-    BigBuffer buffer(1024);
-    XmlFlattener flattener(&buffer, options);
-    if (!flattener.Consume(context_.get(), doc)) {
-      return ::testing::AssertionFailure() << "failed to flatten XML Tree";
-    }
-
-    std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
-    if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
-      return ::testing::AssertionFailure() << "flattened XML is corrupt";
-    }
-    return ::testing::AssertionSuccess();
-  }
-
- protected:
-  std::unique_ptr<test::Context> context_;
-};
-
-TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
-      <View xmlns:test="http://com.test" attr="hey">
-          <Layout test:hello="hi" />
-          <Layout>Some text\\</Layout>
-      </View>)");
-
-  android::ResXMLTree tree;
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
-
-  size_t len;
-  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
-  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
-  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
-  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
-
-  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
-  EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull());
-  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr"));
-
-  const StringPiece16 kAttr(u"attr");
-  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
-  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
-  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
-
-  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
-  EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test"));
-  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello"));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
-
-  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
-  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
-  ASSERT_THAT(tree.getAttributeCount(), Eq(0u));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
-  EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\"));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
-  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
-  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
-  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
-  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE));
-  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
-  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT));
-}
-
-TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
-      <View xmlns:tools="http://schemas.android.com/tools"
-          xmlns:foo="http://schemas.android.com/foo"
-          foo:bar="Foo"
-          tools:ignore="MissingTranslation"/>)");
-
-  android::ResXMLTree tree;
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
-
-  size_t len;
-  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo"));
-  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo"));
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
-
-  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
-              Eq(android::NAME_NOT_FOUND));
-  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0));
-}
-
-TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
-      <View xmlns:android="http://schemas.android.com/apk/res/android"
-          android:id="@id/id"
-          class="str"
-          style="@id/id"/>)");
-
-  android::ResXMLTree tree;
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-
-  while (tree.next() != android::ResXMLTree::START_TAG) {
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
-  }
-
-  EXPECT_THAT(tree.indexOfClass(), Eq(0));
-  EXPECT_THAT(tree.indexOfStyle(), Eq(1));
-}
-
-// The device ResXMLParser in libandroidfw differentiates between empty namespace and null
-// namespace.
-TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)");
-
-  android::ResXMLTree tree;
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-
-  while (tree.next() != android::ResXMLTree::START_TAG) {
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
-  }
-
-  const StringPiece16 kPackage = u"package";
-  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0));
-}
-
-TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)");
-
-  android::ResXMLTree tree;
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-
-  while (tree.next() != android::ResXMLTree::START_TAG) {
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
-  }
-
-  const StringPiece16 kPackage = u"package";
-  ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
-  ASSERT_THAT(idx, Ge(0));
-
-  size_t len;
-  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull());
-}
-
-TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
-  context_->SetCompilationPackage("com.app.test.feature");
-  context_->SetPackageId(0x80);
-  context_->SetNameManglerPolicy({"com.app.test.feature"});
-
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
-      <View xmlns:android="http://schemas.android.com/apk/res/android"
-            xmlns:app="http://schemas.android.com/apk/res-auto"
-            android:id="@id/foo"
-            app:foo="@id/foo" />)");
-
-  XmlReferenceLinker linker;
-  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
-
-  // The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f).
-  android::DynamicRefTable dynamic_ref_table;
-  dynamic_ref_table.addMapping(0x80, 0x80);
-
-  android::ResXMLTree tree(&dynamic_ref_table);
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-
-  while (tree.next() != android::ResXMLTree::START_TAG) {
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
-  }
-
-  ssize_t idx;
-
-  idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id");
-  ASSERT_THAT(idx, Ge(0));
-  EXPECT_THAT(tree.indexOfID(), Eq(idx));
-  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u));
-
-  idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo");
-  ASSERT_THAT(idx, Ge(0));
-  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u));
-  EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE));
-  EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000)));
-}
-
-TEST_F(XmlFlattenerTest, ProcessEscapedStrings) {
-  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
-      R"(<element value="\?hello" pattern="\\d{5}" other="&quot;">\\d{5}</element>)");
-
-  android::ResXMLTree tree;
-  ASSERT_TRUE(Flatten(doc.get(), &tree));
-
-  while (tree.next() != android::ResXMLTree::START_TAG) {
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
-    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
-  }
-
-  const StringPiece16 kValue = u"value";
-  const StringPiece16 kPattern = u"pattern";
-  const StringPiece16 kOther = u"other";
-
-  size_t len;
-  ssize_t idx;
-
-  idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size());
-  ASSERT_THAT(idx, Ge(0));
-  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello"));
-
-  idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size());
-  ASSERT_THAT(idx, Ge(0));
-  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}"));
-
-  idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size());
-  ASSERT_THAT(idx, Ge(0));
-  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\""));
-
-  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
-  EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}"));
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
new file mode 100644
index 0000000..d152a9c
--- /dev/null
+++ b/tools/aapt2/format/Archive.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2015 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 "format/Archive.h"
+
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/errors.h"
+#include "android-base/macros.h"
+#include "android-base/utf8.h"
+#include "androidfw/StringPiece.h"
+#include "ziparchive/zip_writer.h"
+
+#include "util/Files.h"
+
+using ::android::StringPiece;
+using ::android::base::SystemErrorCodeToString;
+
+namespace aapt {
+
+namespace {
+
+class DirectoryWriter : public IArchiveWriter {
+ public:
+  DirectoryWriter() = default;
+
+  bool Open(const StringPiece& out_dir) {
+    dir_ = out_dir.to_string();
+    file::FileType type = file::GetFileType(dir_);
+    if (type == file::FileType::kNonexistant) {
+      error_ = "directory does not exist";
+      return false;
+    } else if (type != file::FileType::kDirectory) {
+      error_ = "not a directory";
+      return false;
+    }
+    return true;
+  }
+
+  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+    if (file_) {
+      return false;
+    }
+
+    std::string full_path = dir_;
+    file::AppendPath(&full_path, path);
+    file::mkdirs(file::GetStem(full_path).to_string());
+
+    file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};
+    if (!file_) {
+      error_ = SystemErrorCodeToString(errno);
+      return false;
+    }
+    return true;
+  }
+
+  bool Write(const void* data, int len) override {
+    if (!file_) {
+      return false;
+    }
+
+    if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) {
+      error_ = SystemErrorCodeToString(errno);
+      file_.reset(nullptr);
+      return false;
+    }
+    return true;
+  }
+
+  bool FinishEntry() override {
+    if (!file_) {
+      return false;
+    }
+    file_.reset(nullptr);
+    return true;
+  }
+
+  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+    if (!StartEntry(path, flags)) {
+      return false;
+    }
+
+    const void* data = nullptr;
+    size_t len = 0;
+    while (in->Next(&data, &len)) {
+      if (!Write(data, static_cast<int>(len))) {
+        return false;
+      }
+    }
+    return !in->HadError();
+  }
+
+  bool HadError() const override {
+    return !error_.empty();
+  }
+
+  std::string GetError() const override {
+    return error_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DirectoryWriter);
+
+  std::string dir_;
+  std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+  std::string error_;
+};
+
+class ZipFileWriter : public IArchiveWriter {
+ public:
+  ZipFileWriter() = default;
+
+  bool Open(const StringPiece& path) {
+    file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};
+    if (!file_) {
+      error_ = SystemErrorCodeToString(errno);
+      return false;
+    }
+    writer_ = util::make_unique<ZipWriter>(file_.get());
+    return true;
+  }
+
+  bool StartEntry(const StringPiece& path, uint32_t flags) override {
+    if (!writer_) {
+      return false;
+    }
+
+    size_t zip_flags = 0;
+    if (flags & ArchiveEntry::kCompress) {
+      zip_flags |= ZipWriter::kCompress;
+    }
+
+    if (flags & ArchiveEntry::kAlign) {
+      zip_flags |= ZipWriter::kAlign32;
+    }
+
+    int32_t result = writer_->StartEntry(path.data(), zip_flags);
+    if (result != 0) {
+      error_ = ZipWriter::ErrorCodeString(result);
+      return false;
+    }
+    return true;
+  }
+
+  bool Write(const void* data, int len) override {
+    int32_t result = writer_->WriteBytes(data, len);
+    if (result != 0) {
+      error_ = ZipWriter::ErrorCodeString(result);
+      return false;
+    }
+    return true;
+  }
+
+  bool FinishEntry() override {
+    int32_t result = writer_->FinishEntry();
+    if (result != 0) {
+      error_ = ZipWriter::ErrorCodeString(result);
+      return false;
+    }
+    return true;
+  }
+
+  bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+    while (true) {
+      if (!StartEntry(path, flags)) {
+        return false;
+      }
+
+      const void* data = nullptr;
+      size_t len = 0;
+      while (in->Next(&data, &len)) {
+        if (!Write(data, static_cast<int>(len))) {
+          return false;
+        }
+      }
+
+      if (in->HadError()) {
+        return false;
+      }
+
+      if (!FinishEntry()) {
+        return false;
+      }
+
+      // Check to see if the file was compressed enough. This is preserving behavior of AAPT.
+      if ((flags & ArchiveEntry::kCompress) != 0 && in->CanRewind()) {
+        ZipWriter::FileEntry last_entry;
+        int32_t result = writer_->GetLastEntry(&last_entry);
+        CHECK(result == 0);
+        if (last_entry.compressed_size + (last_entry.compressed_size / 10) >
+            last_entry.uncompressed_size) {
+          // The file was not compressed enough, rewind and store it uncompressed.
+          if (!in->Rewind()) {
+            // Well we tried, may as well keep what we had.
+            return true;
+          }
+
+          int32_t result = writer_->DiscardLastEntry();
+          if (result != 0) {
+            error_ = ZipWriter::ErrorCodeString(result);
+            return false;
+          }
+          flags &= ~ArchiveEntry::kCompress;
+
+          continue;
+        }
+      }
+      return true;
+    }
+  }
+
+  bool HadError() const override {
+    return !error_.empty();
+  }
+
+  std::string GetError() const override {
+    return error_;
+  }
+
+  virtual ~ZipFileWriter() {
+    if (writer_) {
+      writer_->Finish();
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ZipFileWriter);
+
+  std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose};
+  std::unique_ptr<ZipWriter> writer_;
+  std::string error_;
+};
+
+}  // namespace
+
+std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
+                                                             const StringPiece& path) {
+  std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+  if (!writer->Open(path)) {
+    diag->Error(DiagMessage(path) << writer->GetError());
+    return {};
+  }
+  return std::move(writer);
+}
+
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
+                                                           const StringPiece& path) {
+  std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+  if (!writer->Open(path)) {
+    diag->Error(DiagMessage(path) << writer->GetError());
+    return {};
+  }
+  return std::move(writer);
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
new file mode 100644
index 0000000..4e8a39d
--- /dev/null
+++ b/tools/aapt2/format/Archive.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_ARCHIVE_H
+#define AAPT_FORMAT_ARCHIVE_H
+
+#include <fstream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "androidfw/StringPiece.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+#include "Diagnostics.h"
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+#include "util/Files.h"
+
+namespace aapt {
+
+struct ArchiveEntry {
+  enum : uint32_t {
+    kCompress = 0x01,
+    kAlign = 0x02,
+  };
+
+  std::string path;
+  uint32_t flags;
+  size_t uncompressed_size;
+};
+
+class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream {
+ public:
+  virtual ~IArchiveWriter() = default;
+
+  virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+
+  // Starts a new entry and allows caller to write bytes to it sequentially.
+  // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
+  // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
+  virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
+
+  // Called to finish writing an entry previously started by StartEntry.
+  // Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
+  virtual bool FinishEntry() = 0;
+
+  // CopyingOutputStream implementation that allows sequential writes to this archive. Only
+  // valid between calls to StartEntry and FinishEntry.
+  virtual bool Write(const void* buffer, int size) = 0;
+
+  // Returns true if there was an error writing to the archive.
+  // The resulting error message can be retrieved from GetError().
+  virtual bool HadError() const = 0;
+
+  // Returns the error message if HadError() returns true.
+  virtual std::string GetError() const = 0;
+};
+
+std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag,
+                                                             const android::StringPiece& path);
+
+std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag,
+                                                           const android::StringPiece& path);
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_ARCHIVE_H */
diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp
new file mode 100644
index 0000000..739555c
--- /dev/null
+++ b/tools/aapt2/format/Container.cpp
@@ -0,0 +1,353 @@
+/*
+ * 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 "format/Container.h"
+
+#include "android-base/scopeguard.h"
+#include "android-base/stringprintf.h"
+
+using ::android::base::StringPrintf;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::CodedOutputStream;
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
+namespace aapt {
+
+constexpr const static uint32_t kContainerFormatMagic = 0x54504141u;
+constexpr const static uint32_t kContainerFormatVersion = 1u;
+
+ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count)
+    : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) {
+  CodedOutputStream coded_out(out_);
+
+  // Write the magic.
+  coded_out.WriteLittleEndian32(kContainerFormatMagic);
+
+  // Write the version.
+  coded_out.WriteLittleEndian32(kContainerFormatVersion);
+
+  // Write the total number of entries.
+  coded_out.WriteLittleEndian32(static_cast<uint32_t>(total_entry_count_));
+
+  if (coded_out.HadError()) {
+    error_ = "failed writing container format header";
+  }
+}
+
+inline static void WritePadding(int padding, CodedOutputStream* out) {
+  if (padding < 4) {
+    const uint32_t zero = 0u;
+    out->WriteRaw(&zero, padding);
+  }
+}
+
+bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) {
+  if (current_entry_count_ >= total_entry_count_) {
+    error_ = "too many entries being serialized";
+    return false;
+  }
+  current_entry_count_++;
+
+  CodedOutputStream coded_out(out_);
+
+  // Write the type.
+  coded_out.WriteLittleEndian32(kResTable);
+
+  // Write the aligned size.
+  const ::google::protobuf::uint64 size = table.ByteSize();
+  const int padding = 4 - (size % 4);
+  coded_out.WriteLittleEndian64(size);
+
+  // Write the table.
+  table.SerializeWithCachedSizes(&coded_out);
+
+  // Write the padding.
+  WritePadding(padding, &coded_out);
+
+  if (coded_out.HadError()) {
+    error_ = "failed writing to output";
+    return false;
+  }
+  return true;
+}
+
+bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file,
+                                      io::KnownSizeInputStream* in) {
+  if (current_entry_count_ >= total_entry_count_) {
+    error_ = "too many entries being serialized";
+    return false;
+  }
+  current_entry_count_++;
+
+  constexpr const static int kResFileEntryHeaderSize = 12;
+
+  CodedOutputStream coded_out(out_);
+
+  // Write the type.
+  coded_out.WriteLittleEndian32(kResFile);
+
+  // Write the aligned size.
+  const ::google::protobuf::uint32 header_size = file.ByteSize();
+  const int header_padding = 4 - (header_size % 4);
+  const ::google::protobuf::uint64 data_size = in->TotalSize();
+  const int data_padding = 4 - (data_size % 4);
+  coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size +
+                                data_padding);
+
+  // Write the res file header size.
+  coded_out.WriteLittleEndian32(header_size);
+
+  // Write the data payload size.
+  coded_out.WriteLittleEndian64(data_size);
+
+  // Write the header.
+  file.SerializeToCodedStream(&coded_out);
+
+  WritePadding(header_padding, &coded_out);
+
+  // Write the data payload. We need to call Trim() since we are going to write to the underlying
+  // ZeroCopyOutputStream.
+  coded_out.Trim();
+
+  // Check at this point if there were any errors.
+  if (coded_out.HadError()) {
+    error_ = "failed writing to output";
+    return false;
+  }
+
+  if (!io::Copy(out_, in)) {
+    if (in->HadError()) {
+      std::ostringstream error;
+      error << "failed reading from input: " << in->GetError();
+      error_ = error.str();
+    } else {
+      error_ = "failed writing to output";
+    }
+    return false;
+  }
+  WritePadding(data_padding, &coded_out);
+
+  if (coded_out.HadError()) {
+    error_ = "failed writing to output";
+    return false;
+  }
+  return true;
+}
+
+bool ContainerWriter::HadError() const {
+  return !error_.empty();
+}
+
+std::string ContainerWriter::GetError() const {
+  return error_;
+}
+
+static bool AlignRead(CodedInputStream* in) {
+  const int padding = 4 - (in->CurrentPosition() % 4);
+  if (padding < 4) {
+    return in->Skip(padding);
+  }
+  return true;
+}
+
+ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) {
+}
+
+ContainerEntryType ContainerReaderEntry::Type() const {
+  return type_;
+}
+
+bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) {
+  CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile";
+  if (length_ > std::numeric_limits<int>::max()) {
+    reader_->error_ = StringPrintf("entry length %zu is too large", length_);
+    return false;
+  }
+
+  CodedInputStream& coded_in = reader_->coded_in_;
+
+  const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(length_));
+  auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+  if (!out_table->ParseFromCodedStream(&coded_in)) {
+    reader_->error_ = "failed to parse ResourceTable";
+    return false;
+  }
+  return true;
+}
+
+bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file,
+                                             off64_t* out_offset, size_t* out_len) {
+  CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable";
+
+  CodedInputStream& coded_in = reader_->coded_in_;
+
+  // Read the ResFile header.
+  ::google::protobuf::uint32 header_length;
+  if (!coded_in.ReadLittleEndian32(&header_length)) {
+    std::ostringstream error;
+    error << "failed to read header length from input: " << reader_->in_->GetError();
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  ::google::protobuf::uint64 data_length;
+  if (!coded_in.ReadLittleEndian64(&data_length)) {
+    std::ostringstream error;
+    error << "failed to read data length from input: " << reader_->in_->GetError();
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  if (header_length > std::numeric_limits<int>::max()) {
+    std::ostringstream error;
+    error << "header length " << header_length << " is too large";
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  if (data_length > std::numeric_limits<size_t>::max()) {
+    std::ostringstream error;
+    error << "data length " << data_length << " is too large";
+    reader_->error_ = error.str();
+    return false;
+  }
+
+  {
+    const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast<int>(header_length));
+    auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); });
+
+    if (!out_file->ParseFromCodedStream(&coded_in)) {
+      reader_->error_ = "failed to parse CompiledFile header";
+      return false;
+    }
+  }
+
+  AlignRead(&coded_in);
+
+  *out_offset = coded_in.CurrentPosition();
+  *out_len = data_length;
+
+  coded_in.Skip(static_cast<int>(data_length));
+  AlignRead(&coded_in);
+  return true;
+}
+
+bool ContainerReaderEntry::HadError() const {
+  return reader_->HadError();
+}
+
+std::string ContainerReaderEntry::GetError() const {
+  return reader_->GetError();
+}
+
+ContainerReader::ContainerReader(io::InputStream* in)
+    : in_(in),
+      adaptor_(in),
+      coded_in_(&adaptor_),
+      total_entry_count_(0u),
+      current_entry_count_(0u),
+      entry_(this) {
+  ::google::protobuf::uint32 magic;
+  if (!coded_in_.ReadLittleEndian32(&magic)) {
+    std::ostringstream error;
+    error << "failed to read magic from input: " << in_->GetError();
+    error_ = error.str();
+    return;
+  }
+
+  if (magic != kContainerFormatMagic) {
+    error_ = "magic value doesn't match AAPT";
+    return;
+  }
+
+  ::google::protobuf::uint32 version;
+  if (!coded_in_.ReadLittleEndian32(&version)) {
+    std::ostringstream error;
+    error << "failed to read version from input: " << in_->GetError();
+    error_ = error.str();
+    return;
+  }
+
+  if (version != kContainerFormatVersion) {
+    error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version,
+                          kContainerFormatVersion);
+    return;
+  }
+
+  ::google::protobuf::uint32 total_entry_count;
+  if (!coded_in_.ReadLittleEndian32(&total_entry_count)) {
+    std::ostringstream error;
+    error << "failed to read entry count from input: " << in_->GetError();
+    error_ = error.str();
+    return;
+  }
+
+  total_entry_count_ = total_entry_count;
+}
+
+ContainerReaderEntry* ContainerReader::Next() {
+  if (current_entry_count_ >= total_entry_count_) {
+    return nullptr;
+  }
+  current_entry_count_++;
+
+  // Ensure the next read is aligned.
+  AlignRead(&coded_in_);
+
+  ::google::protobuf::uint32 entry_type;
+  if (!coded_in_.ReadLittleEndian32(&entry_type)) {
+    std::ostringstream error;
+    error << "failed reading entry type from input: " << in_->GetError();
+    error_ = error.str();
+    return nullptr;
+  }
+
+  ::google::protobuf::uint64 entry_length;
+  if (!coded_in_.ReadLittleEndian64(&entry_length)) {
+    std::ostringstream error;
+    error << "failed reading entry length from input: " << in_->GetError();
+    error_ = error.str();
+    return nullptr;
+  }
+
+  if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) {
+    entry_.type_ = static_cast<ContainerEntryType>(entry_type);
+  } else {
+    error_ = StringPrintf("entry type 0x%08x is invalid", entry_type);
+    return nullptr;
+  }
+
+  if (entry_length > std::numeric_limits<size_t>::max()) {
+    std::ostringstream error;
+    error << "entry length " << entry_length << " is too large";
+    error_ = error.str();
+    return nullptr;
+  }
+
+  entry_.length_ = entry_length;
+  return &entry_;
+}
+
+bool ContainerReader::HadError() const {
+  return !error_.empty();
+}
+
+std::string ContainerReader::GetError() const {
+  return error_;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h
new file mode 100644
index 0000000..aa5c82c
--- /dev/null
+++ b/tools/aapt2/format/Container.h
@@ -0,0 +1,106 @@
+/*
+ * 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 AAPT_FORMAT_CONTAINER_H
+#define AAPT_FORMAT_CONTAINER_H
+
+#include <inttypes.h>
+
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/io/zero_copy_stream.h"
+
+#include "Resources.pb.h"
+#include "ResourcesInternal.pb.h"
+#include "io/Io.h"
+#include "io/Util.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+enum ContainerEntryType : uint8_t {
+  kResTable = 0x00u,
+  kResFile = 0x01u,
+};
+
+class ContainerWriter {
+ public:
+  explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count);
+
+  bool AddResTableEntry(const pb::ResourceTable& table);
+  bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in);
+  bool HadError() const;
+  std::string GetError() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerWriter);
+
+  ::google::protobuf::io::ZeroCopyOutputStream* out_;
+  size_t total_entry_count_;
+  size_t current_entry_count_;
+  std::string error_;
+};
+
+class ContainerReader;
+
+class ContainerReaderEntry {
+ public:
+  ContainerEntryType Type() const;
+
+  bool GetResTable(pb::ResourceTable* out_table);
+  bool GetResFileOffsets(pb::internal::CompiledFile* out_file, off64_t* out_offset,
+                         size_t* out_len);
+
+  bool HadError() const;
+  std::string GetError() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerReaderEntry);
+
+  friend class ContainerReader;
+
+  explicit ContainerReaderEntry(ContainerReader* reader);
+
+  ContainerReader* reader_;
+  ContainerEntryType type_ = ContainerEntryType::kResTable;
+  size_t length_ = 0u;
+};
+
+class ContainerReader {
+ public:
+  explicit ContainerReader(io::InputStream* in);
+
+  ContainerReaderEntry* Next();
+
+  bool HadError() const;
+  std::string GetError() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerReader);
+
+  friend class ContainerReaderEntry;
+
+  io::InputStream* in_;
+  io::ZeroCopyInputAdaptor adaptor_;
+  ::google::protobuf::io::CodedInputStream coded_in_;
+  size_t total_entry_count_;
+  size_t current_entry_count_;
+  ContainerReaderEntry entry_;
+  std::string error_;
+};
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_CONTAINER_H */
diff --git a/tools/aapt2/format/Container_test.cpp b/tools/aapt2/format/Container_test.cpp
new file mode 100644
index 0000000..dc81a3a
--- /dev/null
+++ b/tools/aapt2/format/Container_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "format/Container.h"
+
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+#include "io/StringStream.h"
+#include "test/Test.h"
+
+using ::google::protobuf::io::StringOutputStream;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace aapt {
+
+TEST(ContainerTest, SerializeCompiledFile) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  const std::string expected_data = "123";
+
+  std::string output_str;
+  {
+    StringOutputStream out_stream(&output_str);
+    ContainerWriter writer(&out_stream, 2u);
+    ASSERT_FALSE(writer.HadError());
+
+    pb::internal::CompiledFile pb_compiled_file;
+    pb_compiled_file.set_resource_name("android:layout/main.xml");
+    pb_compiled_file.set_type(pb::FileReference::PROTO_XML);
+    pb_compiled_file.set_source_path("res/layout/main.xml");
+    io::StringInputStream data(expected_data);
+    ASSERT_TRUE(writer.AddResFileEntry(pb_compiled_file, &data));
+
+    pb::ResourceTable pb_table;
+    pb::Package* pb_pkg = pb_table.add_package();
+    pb_pkg->set_package_name("android");
+    pb_pkg->mutable_package_id()->set_id(0x01u);
+    ASSERT_TRUE(writer.AddResTableEntry(pb_table));
+
+    ASSERT_FALSE(writer.HadError());
+  }
+
+  io::StringInputStream input(output_str);
+  ContainerReader reader(&input);
+  ASSERT_FALSE(reader.HadError());
+
+  ContainerReaderEntry* entry = reader.Next();
+  ASSERT_THAT(entry, NotNull());
+  ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResFile));
+
+  pb::internal::CompiledFile pb_new_file;
+  off64_t offset;
+  size_t len;
+  ASSERT_TRUE(entry->GetResFileOffsets(&pb_new_file, &offset, &len)) << entry->GetError();
+  EXPECT_THAT(offset & 0x03, Eq(0u));
+  EXPECT_THAT(output_str.substr(static_cast<size_t>(offset), len), StrEq(expected_data));
+
+  entry = reader.Next();
+  ASSERT_THAT(entry, NotNull());
+  ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResTable));
+
+  pb::ResourceTable pb_new_table;
+  ASSERT_TRUE(entry->GetResTable(&pb_new_table));
+  ASSERT_THAT(pb_new_table.package_size(), Eq(1));
+  EXPECT_THAT(pb_new_table.package(0).package_name(), StrEq("android"));
+  EXPECT_THAT(pb_new_table.package(0).package_id().id(), Eq(0x01u));
+
+  EXPECT_THAT(reader.Next(), IsNull());
+  EXPECT_FALSE(reader.HadError());
+  EXPECT_THAT(reader.GetError(), IsEmpty());
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
new file mode 100644
index 0000000..95eec4a
--- /dev/null
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2015 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 "format/binary/BinaryResourceParser.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/TypeWrappers.h"
+
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "ValueVisitor.h"
+#include "format/binary/ResChunkPullParser.h"
+#include "util/Util.h"
+
+using namespace android;
+
+using ::android::base::StringPrintf;
+
+namespace aapt {
+
+namespace {
+
+// Visitor that converts a reference's resource ID to a resource name, given a mapping from
+// resource ID to resource name.
+class ReferenceIdToNameVisitor : public DescendingValueVisitor {
+ public:
+  using DescendingValueVisitor::Visit;
+
+  explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping)
+      : mapping_(mapping) {
+    CHECK(mapping_ != nullptr);
+  }
+
+  void Visit(Reference* reference) override {
+    if (!reference->id || !reference->id.value().is_valid()) {
+      return;
+    }
+
+    ResourceId id = reference->id.value();
+    auto cache_iter = mapping_->find(id);
+    if (cache_iter != mapping_->end()) {
+      reference->name = cache_iter->second;
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor);
+
+  const std::map<ResourceId, ResourceName>* mapping_;
+};
+
+}  // namespace
+
+BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+                                           const Source& source, const void* data, size_t len,
+                                           io::IFileCollection* files)
+    : context_(context),
+      table_(table),
+      source_(source),
+      data_(data),
+      data_len_(len),
+      files_(files) {
+}
+
+bool BinaryResourceParser::Parse() {
+  ResChunkPullParser parser(data_, data_len_);
+
+  if (!ResChunkPullParser::IsGoodEvent(parser.Next())) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "corrupt resources.arsc: " << parser.error());
+    return false;
+  }
+
+  if (parser.chunk()->type != android::RES_TABLE_TYPE) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << StringPrintf("unknown chunk of type 0x%02x",
+                                                      static_cast<int>(parser.chunk()->type)));
+    return false;
+  }
+
+  if (!ParseTable(parser.chunk())) {
+    return false;
+  }
+
+  if (parser.Next() != ResChunkPullParser::Event::kEndDocument) {
+    if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+      context_->GetDiagnostics()->Warn(
+          DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
+    } else {
+      context_->GetDiagnostics()->Warn(
+          DiagMessage(source_) << StringPrintf(
+              "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
+              static_cast<int>(parser.chunk()->type)));
+    }
+  }
+  return true;
+}
+
+// Parses the resource table, which contains all the packages, types, and entries.
+bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
+  const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
+  if (!table_header) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
+    return false;
+  }
+
+  ResChunkPullParser parser(GetChunkData(&table_header->header),
+                            GetChunkDataLen(&table_header->header));
+  while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+    switch (util::DeviceToHost16(parser.chunk()->type)) {
+      case android::RES_STRING_POOL_TYPE:
+        if (value_pool_.getError() == NO_INIT) {
+          status_t err =
+              value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+          if (err != NO_ERROR) {
+            context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                              << "corrupt string pool in ResTable: "
+                                              << value_pool_.getError());
+            return false;
+          }
+
+          // Reserve some space for the strings we are going to add.
+          table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount());
+        } else {
+          context_->GetDiagnostics()->Warn(DiagMessage(source_)
+                                           << "unexpected string pool in ResTable");
+        }
+        break;
+
+      case android::RES_TABLE_PACKAGE_TYPE:
+        if (!ParsePackage(parser.chunk())) {
+          return false;
+        }
+        break;
+
+      default:
+        context_->GetDiagnostics()->Warn(
+            DiagMessage(source_) << "unexpected chunk type "
+                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+        break;
+    }
+  }
+
+  if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "corrupt resource table: " << parser.error());
+    return false;
+  }
+  return true;
+}
+
+bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
+  constexpr size_t kMinPackageSize =
+      sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
+  const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
+  if (!package_header) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
+    return false;
+  }
+
+  uint32_t package_id = util::DeviceToHost32(package_header->id);
+  if (package_id > std::numeric_limits<uint8_t>::max()) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "package ID is too big (" << package_id << ")");
+    return false;
+  }
+
+  // Extract the package name.
+  size_t len = strnlen16((const char16_t*)package_header->name, arraysize(package_header->name));
+  std::u16string package_name;
+  package_name.resize(len);
+  for (size_t i = 0; i < len; i++) {
+    package_name[i] = util::DeviceToHost16(package_header->name[i]);
+  }
+
+  ResourceTablePackage* package =
+      table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
+  if (!package) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "incompatible package '" << package_name << "' with ID "
+                                      << package_id);
+    return false;
+  }
+
+  // There can be multiple packages in a table, so
+  // clear the type and key pool in case they were set from a previous package.
+  type_pool_.uninit();
+  key_pool_.uninit();
+
+  ResChunkPullParser parser(GetChunkData(&package_header->header),
+                            GetChunkDataLen(&package_header->header));
+  while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
+    switch (util::DeviceToHost16(parser.chunk()->type)) {
+      case android::RES_STRING_POOL_TYPE:
+        if (type_pool_.getError() == NO_INIT) {
+          status_t err =
+              type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+          if (err != NO_ERROR) {
+            context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                              << "corrupt type string pool in "
+                                              << "ResTable_package: " << type_pool_.getError());
+            return false;
+          }
+        } else if (key_pool_.getError() == NO_INIT) {
+          status_t err =
+              key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
+          if (err != NO_ERROR) {
+            context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                              << "corrupt key string pool in "
+                                              << "ResTable_package: " << key_pool_.getError());
+            return false;
+          }
+        } else {
+          context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
+        }
+        break;
+
+      case android::RES_TABLE_TYPE_SPEC_TYPE:
+        if (!ParseTypeSpec(parser.chunk())) {
+          return false;
+        }
+        break;
+
+      case android::RES_TABLE_TYPE_TYPE:
+        if (!ParseType(package, parser.chunk())) {
+          return false;
+        }
+        break;
+
+      case android::RES_TABLE_LIBRARY_TYPE:
+        if (!ParseLibrary(parser.chunk())) {
+          return false;
+        }
+        break;
+
+      default:
+        context_->GetDiagnostics()->Warn(
+            DiagMessage(source_) << "unexpected chunk type "
+                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+        break;
+    }
+  }
+
+  if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "corrupt ResTable_package: " << parser.error());
+    return false;
+  }
+
+  // Now go through the table and change local resource ID references to
+  // symbolic references.
+  ReferenceIdToNameVisitor visitor(&id_index_);
+  VisitAllValuesInTable(table_, &visitor);
+  return true;
+}
+
+bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+  if (type_pool_.getError() != NO_ERROR) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+    return false;
+  }
+
+  const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
+  if (!type_spec) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
+    return false;
+  }
+
+  if (type_spec->id == 0) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "ResTable_typeSpec has invalid id: " << type_spec->id);
+    return false;
+  }
+  return true;
+}
+
+bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
+                                     const ResChunk_header* chunk) {
+  if (type_pool_.getError() != NO_ERROR) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+    return false;
+  }
+
+  if (key_pool_.getError() != NO_ERROR) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing key string pool");
+    return false;
+  }
+
+  // Specify a manual size, because ResTable_type contains ResTable_config, which changes
+  // a lot and has its own code to handle variable size.
+  const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk);
+  if (!type) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
+    return false;
+  }
+
+  if (type->id == 0) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "ResTable_type has invalid id: " << (int)type->id);
+    return false;
+  }
+
+  ConfigDescription config;
+  config.copyFromDtoH(type->config);
+
+  const std::string type_str = util::GetString(type_pool_, type->id - 1);
+
+  const ResourceType* parsed_type = ParseResourceType(type_str);
+  if (!parsed_type) {
+    context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                      << "invalid type name '" << type_str << "' for type with ID "
+                                      << (int)type->id);
+    return false;
+  }
+
+  TypeVariant tv(type);
+  for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
+    const ResTable_entry* entry = *it;
+    if (!entry) {
+      continue;
+    }
+
+    const ResourceName name(package->name, *parsed_type,
+                            util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)));
+
+    const ResourceId res_id(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
+
+    std::unique_ptr<Value> resource_value;
+    if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
+      const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
+
+      // TODO(adamlesinski): Check that the entry count is valid.
+      resource_value = ParseMapEntry(name, config, mapEntry);
+    } else {
+      const Res_value* value =
+          (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size));
+      resource_value = ParseValue(name, config, *value);
+    }
+
+    if (!resource_value) {
+      context_->GetDiagnostics()->Error(DiagMessage(source_)
+                                        << "failed to parse value for resource " << name << " ("
+                                        << res_id << ") with configuration '" << config << "'");
+      return false;
+    }
+
+    if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
+                                         context_->GetDiagnostics())) {
+      return false;
+    }
+
+    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
+      Symbol symbol;
+      symbol.state = SymbolState::kPublic;
+      symbol.source = source_.WithLine(0);
+      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
+        return false;
+      }
+    }
+
+    // Add this resource name->id mapping to the index so
+    // that we can resolve all ID references to name references.
+    auto cache_iter = id_index_.find(res_id);
+    if (cache_iter == id_index_.end()) {
+      id_index_.insert({res_id, name});
+    }
+  }
+  return true;
+}
+
+bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) {
+  DynamicRefTable dynamic_ref_table;
+  if (dynamic_ref_table.load(reinterpret_cast<const ResTable_lib_header*>(chunk)) != NO_ERROR) {
+    return false;
+  }
+
+  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table.entries();
+  const size_t count = entries.size();
+  for (size_t i = 0; i < count; i++) {
+    table_->included_packages_[entries.valueAt(i)] =
+        util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string()));
+  }
+  return true;
+}
+
+std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
+                                                       const ConfigDescription& config,
+                                                       const android::Res_value& value) {
+  std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
+                                                                  value, &table_->string_pool);
+  if (files_ != nullptr && item != nullptr) {
+    FileReference* file_ref = ValueCast<FileReference>(item.get());
+    if (file_ref != nullptr) {
+      file_ref->file = files_->FindFile(*file_ref->path);
+      if (file_ref->file == nullptr) {
+        context_->GetDiagnostics()->Warn(DiagMessage()
+                                         << "resource " << name << " for config '" << config
+                                         << "' is a file reference to '" << *file_ref->path
+                                         << "' but no such path exists");
+      }
+    }
+  }
+  return item;
+}
+
+std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(const ResourceNameRef& name,
+                                                           const ConfigDescription& config,
+                                                           const ResTable_map_entry* map) {
+  switch (name.type) {
+    case ResourceType::kStyle:
+      return ParseStyle(name, config, map);
+    case ResourceType::kAttrPrivate:
+    // fallthrough
+    case ResourceType::kAttr:
+      return ParseAttr(name, config, map);
+    case ResourceType::kArray:
+      return ParseArray(name, config, map);
+    case ResourceType::kPlurals:
+      return ParsePlural(name, config, map);
+    case ResourceType::kId:
+      // Special case: An ID is not a bag, but some apps have defined the auto-generated
+      // IDs that come from declaring an enum value in an attribute as an empty map...
+      // We can ignore the value here.
+      return util::make_unique<Id>();
+    default:
+      context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
+                                                      << "' (" << (int)name.type << ")");
+      break;
+  }
+  return {};
+}
+
+std::unique_ptr<Style> BinaryResourceParser::ParseStyle(const ResourceNameRef& name,
+                                                        const ConfigDescription& config,
+                                                        const ResTable_map_entry* map) {
+  std::unique_ptr<Style> style = util::make_unique<Style>();
+  if (util::DeviceToHost32(map->parent.ident) != 0) {
+    // The parent is a regular reference to a resource.
+    style->parent = Reference(util::DeviceToHost32(map->parent.ident));
+  }
+
+  for (const ResTable_map& map_entry : map) {
+    if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
+      continue;
+    }
+
+    Style::Entry style_entry;
+    style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident));
+    style_entry.value = ParseValue(name, config, map_entry.value);
+    if (!style_entry.value) {
+      return {};
+    }
+    style->entries.push_back(std::move(style_entry));
+  }
+  return style;
+}
+
+std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef& name,
+                                                           const ConfigDescription& config,
+                                                           const ResTable_map_entry* map) {
+  const bool is_weak = (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
+  std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
+
+  // First we must discover what type of attribute this is. Find the type mask.
+  auto type_mask_iter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
+    return util::DeviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE;
+  });
+
+  if (type_mask_iter != end(map)) {
+    attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data);
+  }
+
+  for (const ResTable_map& map_entry : map) {
+    if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
+      switch (util::DeviceToHost32(map_entry.name.ident)) {
+        case ResTable_map::ATTR_MIN:
+          attr->min_int = static_cast<int32_t>(map_entry.value.data);
+          break;
+        case ResTable_map::ATTR_MAX:
+          attr->max_int = static_cast<int32_t>(map_entry.value.data);
+          break;
+      }
+      continue;
+    }
+
+    if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
+      Attribute::Symbol symbol;
+      symbol.value = util::DeviceToHost32(map_entry.value.data);
+      symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident));
+      attr->symbols.push_back(std::move(symbol));
+    }
+  }
+
+  // TODO(adamlesinski): Find i80n, attributes.
+  return attr;
+}
+
+std::unique_ptr<Array> BinaryResourceParser::ParseArray(const ResourceNameRef& name,
+                                                        const ConfigDescription& config,
+                                                        const ResTable_map_entry* map) {
+  std::unique_ptr<Array> array = util::make_unique<Array>();
+  for (const ResTable_map& map_entry : map) {
+    array->elements.push_back(ParseValue(name, config, map_entry.value));
+  }
+  return array;
+}
+
+std::unique_ptr<Plural> BinaryResourceParser::ParsePlural(const ResourceNameRef& name,
+                                                          const ConfigDescription& config,
+                                                          const ResTable_map_entry* map) {
+  std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+  for (const ResTable_map& map_entry : map) {
+    std::unique_ptr<Item> item = ParseValue(name, config, map_entry.value);
+    if (!item) {
+      return {};
+    }
+
+    switch (util::DeviceToHost32(map_entry.name.ident)) {
+      case ResTable_map::ATTR_ZERO:
+        plural->values[Plural::Zero] = std::move(item);
+        break;
+      case ResTable_map::ATTR_ONE:
+        plural->values[Plural::One] = std::move(item);
+        break;
+      case ResTable_map::ATTR_TWO:
+        plural->values[Plural::Two] = std::move(item);
+        break;
+      case ResTable_map::ATTR_FEW:
+        plural->values[Plural::Few] = std::move(item);
+        break;
+      case ResTable_map::ATTR_MANY:
+        plural->values[Plural::Many] = std::move(item);
+        break;
+      case ResTable_map::ATTR_OTHER:
+        plural->values[Plural::Other] = std::move(item);
+        break;
+    }
+  }
+  return plural;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
new file mode 100644
index 0000000..dc9a384
--- /dev/null
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_BINARY_RESOURCEPARSER_H
+#define AAPT_FORMAT_BINARY_RESOURCEPARSER_H
+
+#include <string>
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Source.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+struct SymbolTable_entry;
+
+// Parses a binary resource table (resources.arsc) and adds the entries to a ResourceTable.
+// This is different than the libandroidfw ResTable in that it scans the table from top to bottom
+// and doesn't require support for random access.
+class BinaryResourceParser {
+ public:
+  // Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to
+  // `table`. `source` is for logging purposes.
+  BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+                       const void* data, size_t data_len, io::IFileCollection* files = nullptr);
+
+  // Parses the binary resource table and returns true if successful.
+  bool Parse();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BinaryResourceParser);
+
+  bool ParseTable(const android::ResChunk_header* chunk);
+  bool ParsePackage(const android::ResChunk_header* chunk);
+  bool ParseTypeSpec(const android::ResChunk_header* chunk);
+  bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
+  bool ParseLibrary(const android::ResChunk_header* chunk);
+
+  std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const ConfigDescription& config,
+                                   const android::Res_value& value);
+
+  std::unique_ptr<Value> ParseMapEntry(const ResourceNameRef& name, const ConfigDescription& config,
+                                       const android::ResTable_map_entry* map);
+
+  std::unique_ptr<Style> ParseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+                                    const android::ResTable_map_entry* map);
+
+  std::unique_ptr<Attribute> ParseAttr(const ResourceNameRef& name, const ConfigDescription& config,
+                                       const android::ResTable_map_entry* map);
+
+  std::unique_ptr<Array> ParseArray(const ResourceNameRef& name, const ConfigDescription& config,
+                                    const android::ResTable_map_entry* map);
+
+  std::unique_ptr<Plural> ParsePlural(const ResourceNameRef& name, const ConfigDescription& config,
+                                      const android::ResTable_map_entry* map);
+
+  /**
+   * If the mapEntry is a special type that denotes meta data (source, comment),
+   * then it is
+   * read and added to the Value.
+   * Returns true if the mapEntry was meta data.
+   */
+  bool CollectMetaData(const android::ResTable_map& map_entry, Value* value);
+
+  IAaptContext* context_;
+  ResourceTable* table_;
+
+  const Source source_;
+
+  const void* data_;
+  const size_t data_len_;
+
+  // Optional file collection from which to create io::IFile objects.
+  io::IFileCollection* files_;
+
+  // The standard value string pool for resource values.
+  android::ResStringPool value_pool_;
+
+  // The string pool that holds the names of the types defined
+  // in this table.
+  android::ResStringPool type_pool_;
+
+  // The string pool that holds the names of the entries defined
+  // in this table.
+  android::ResStringPool key_pool_;
+
+  // A mapping of resource ID to resource name. When we finish parsing
+  // we use this to convert all resource IDs to symbolic references.
+  std::map<ResourceId, ResourceName> id_index_;
+};
+
+}  // namespace aapt
+
+namespace android {
+
+// Iterator functionality for ResTable_map_entry.
+
+inline const ResTable_map* begin(const ResTable_map_entry* map) {
+  return (const ResTable_map*)((const uint8_t*)map + ::aapt::util::DeviceToHost32(map->size));
+}
+
+inline const ResTable_map* end(const ResTable_map_entry* map) {
+  return begin(map) + aapt::util::DeviceToHost32(map->count);
+}
+
+}  // namespace android
+
+#endif  // AAPT_FORMAT_BINARY_RESOURCEPARSER_H
diff --git a/tools/aapt2/format/binary/ChunkWriter.h b/tools/aapt2/format/binary/ChunkWriter.h
new file mode 100644
index 0000000..1892a29
--- /dev/null
+++ b/tools/aapt2/format/binary/ChunkWriter.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_BINARY_CHUNKWRITER_H
+#define AAPT_FORMAT_BINARY_CHUNKWRITER_H
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+namespace aapt {
+
+class ChunkWriter {
+ public:
+  explicit inline ChunkWriter(BigBuffer* buffer) : buffer_(buffer) {
+  }
+  ChunkWriter(ChunkWriter&&) = default;
+  ChunkWriter& operator=(ChunkWriter&&) = default;
+
+  template <typename T>
+  inline T* StartChunk(uint16_t type) {
+    start_size_ = buffer_->size();
+    T* chunk = buffer_->NextBlock<T>();
+    header_ = &chunk->header;
+    header_->type = util::HostToDevice16(type);
+    header_->headerSize = util::HostToDevice16(sizeof(T));
+    return chunk;
+  }
+
+  template <typename T>
+  inline T* NextBlock(size_t count = 1) {
+    return buffer_->NextBlock<T>(count);
+  }
+
+  inline BigBuffer* buffer() {
+    return buffer_;
+  }
+
+  inline android::ResChunk_header* chunk_header() {
+    return header_;
+  }
+
+  inline size_t size() {
+    return buffer_->size() - start_size_;
+  }
+
+  inline android::ResChunk_header* Finish() {
+    buffer_->Align4();
+    header_->size = util::HostToDevice32(buffer_->size() - start_size_);
+    return header_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChunkWriter);
+
+  BigBuffer* buffer_;
+  size_t start_size_ = 0;
+  android::ResChunk_header* header_ = nullptr;
+};
+
+template <>
+inline android::ResChunk_header* ChunkWriter::StartChunk(uint16_t type) {
+  start_size_ = buffer_->size();
+  header_ = buffer_->NextBlock<android::ResChunk_header>();
+  header_->type = util::HostToDevice16(type);
+  header_->headerSize = util::HostToDevice16(sizeof(android::ResChunk_header));
+  return header_;
+}
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_BINARY_CHUNKWRITER_H */
diff --git a/tools/aapt2/format/binary/ResChunkPullParser.cpp b/tools/aapt2/format/binary/ResChunkPullParser.cpp
new file mode 100644
index 0000000..fd6919d
--- /dev/null
+++ b/tools/aapt2/format/binary/ResChunkPullParser.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 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 "format/binary/ResChunkPullParser.h"
+
+#include <inttypes.h>
+#include <cstddef>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/Util.h"
+
+namespace aapt {
+
+using android::ResChunk_header;
+using android::base::StringPrintf;
+
+static std::string ChunkHeaderDump(const ResChunk_header* header) {
+  return StringPrintf("(type=%02" PRIx16 " header_size=%" PRIu16 " size=%" PRIu32 ")",
+                      util::DeviceToHost16(header->type), util::DeviceToHost16(header->headerSize),
+                      util::DeviceToHost32(header->size));
+}
+
+ResChunkPullParser::Event ResChunkPullParser::Next() {
+  if (!IsGoodEvent(event_)) {
+    return event_;
+  }
+
+  if (event_ == Event::kStartDocument) {
+    current_chunk_ = data_;
+  } else {
+    current_chunk_ = (const ResChunk_header*)(((const char*)current_chunk_) +
+                                              util::DeviceToHost32(current_chunk_->size));
+  }
+
+  const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_;
+  CHECK(diff >= 0) << "diff is negative";
+  const size_t offset = static_cast<const size_t>(diff);
+
+  if (offset == len_) {
+    current_chunk_ = nullptr;
+    return (event_ = Event::kEndDocument);
+  } else if (offset + sizeof(ResChunk_header) > len_) {
+    error_ = "chunk is past the end of the document";
+    current_chunk_ = nullptr;
+    return (event_ = Event::kBadDocument);
+  }
+
+  if (util::DeviceToHost16(current_chunk_->headerSize) < sizeof(ResChunk_header)) {
+    error_ = "chunk has too small header";
+    current_chunk_ = nullptr;
+    return (event_ = Event::kBadDocument);
+  } else if (util::DeviceToHost32(current_chunk_->size) <
+             util::DeviceToHost16(current_chunk_->headerSize)) {
+    error_ = "chunk's total size is smaller than header " + ChunkHeaderDump(current_chunk_);
+    current_chunk_ = nullptr;
+    return (event_ = Event::kBadDocument);
+  } else if (offset + util::DeviceToHost32(current_chunk_->size) > len_) {
+    error_ = "chunk's data extends past the end of the document " + ChunkHeaderDump(current_chunk_);
+    current_chunk_ = nullptr;
+    return (event_ = Event::kBadDocument);
+  }
+  return (event_ = Event::kChunk);
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/ResChunkPullParser.h b/tools/aapt2/format/binary/ResChunkPullParser.h
new file mode 100644
index 0000000..5ff1359
--- /dev/null
+++ b/tools/aapt2/format/binary/ResChunkPullParser.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_BINARY_RESCHUNKPULLPARSER_H
+#define AAPT_FORMAT_BINARY_RESCHUNKPULLPARSER_H
+
+#include <string>
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "util/Util.h"
+
+namespace aapt {
+
+// A pull parser, modeled after XmlPullParser, that reads android::ResChunk_header structs from a
+// block of data.
+// An android::ResChunk_header specifies a type, headerSize, and size. The pull parser will verify
+// that the chunk's size doesn't extend beyond the available data, and will iterate over each chunk
+// in the given block of data.
+// Processing nested chunks is done by creating a new ResChunkPullParser pointing to the data
+// portion of a chunk.
+class ResChunkPullParser {
+ public:
+  enum class Event {
+    kStartDocument,
+    kEndDocument,
+    kBadDocument,
+
+    kChunk,
+  };
+
+  // Returns false if the event is EndDocument or BadDocument.
+  static bool IsGoodEvent(Event event);
+
+  // Create a ResChunkPullParser to read android::ResChunk_headers from the memory pointed to by
+  // data, of len bytes.
+  ResChunkPullParser(const void* data, size_t len);
+
+  Event event() const;
+  const std::string& error() const;
+  const android::ResChunk_header* chunk() const;
+
+  // Move to the next android::ResChunk_header.
+  Event Next();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ResChunkPullParser);
+
+  Event event_;
+  const android::ResChunk_header* data_;
+  size_t len_;
+  const android::ResChunk_header* current_chunk_;
+  std::string error_;
+};
+
+template <typename T, size_t MinSize = sizeof(T)>
+inline static const T* ConvertTo(const android::ResChunk_header* chunk) {
+  if (util::DeviceToHost16(chunk->headerSize) < MinSize) {
+    return nullptr;
+  }
+  return reinterpret_cast<const T*>(chunk);
+}
+
+inline static const uint8_t* GetChunkData(const android::ResChunk_header* chunk) {
+  return reinterpret_cast<const uint8_t*>(chunk) + util::DeviceToHost16(chunk->headerSize);
+}
+
+inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) {
+  return util::DeviceToHost32(chunk->size) - util::DeviceToHost16(chunk->headerSize);
+}
+
+//
+// Implementation
+//
+
+inline bool ResChunkPullParser::IsGoodEvent(ResChunkPullParser::Event event) {
+  return event != Event::kEndDocument && event != Event::kBadDocument;
+}
+
+inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len)
+    : event_(Event::kStartDocument),
+      data_(reinterpret_cast<const android::ResChunk_header*>(data)),
+      len_(len),
+      current_chunk_(nullptr) {
+}
+
+inline ResChunkPullParser::Event ResChunkPullParser::event() const {
+  return event_;
+}
+
+inline const std::string& ResChunkPullParser::error() const {
+  return error_;
+}
+
+inline const android::ResChunk_header* ResChunkPullParser::chunk() const {
+  return current_chunk_;
+}
+
+}  // namespace aapt
+
+#endif  // AAPT_FORMAT_BINARY_RESCHUNKPULLPARSER_H
diff --git a/tools/aapt2/format/binary/ResourceTypeExtensions.h b/tools/aapt2/format/binary/ResourceTypeExtensions.h
new file mode 100644
index 0000000..7f58df80
--- /dev/null
+++ b/tools/aapt2/format/binary/ResourceTypeExtensions.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_BINARY_RESOURCETYPEEXTENSIONS_H
+#define AAPT_FORMAT_BINARY_RESOURCETYPEEXTENSIONS_H
+
+#include "androidfw/ResourceTypes.h"
+
+namespace aapt {
+
+// An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout struct.
+struct ResTable_entry_ext {
+  android::ResTable_entry entry;
+  android::ResTable_ref parent;
+  uint32_t count;
+};
+
+}  // namespace aapt
+
+#endif  // AAPT_FORMAT_BINARY_RESOURCETYPEEXTENSIONS_H
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
new file mode 100644
index 0000000..57565a5
--- /dev/null
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2015 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 "format/binary/TableFlattener.h"
+
+#include <algorithm>
+#include <numeric>
+#include <sstream>
+#include <type_traits>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "ValueVisitor.h"
+#include "format/binary/ChunkWriter.h"
+#include "format/binary/ResourceTypeExtensions.h"
+#include "util/BigBuffer.h"
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+template <typename T>
+static bool cmp_ids(const T* a, const T* b) {
+  return a->id.value() < b->id.value();
+}
+
+static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
+  if (len == 0) {
+    return;
+  }
+
+  size_t i;
+  const char16_t* src_data = src.data();
+  for (i = 0; i < len - 1 && i < src.size(); i++) {
+    dst[i] = util::HostToDevice16((uint16_t)src_data[i]);
+  }
+  dst[i] = 0;
+}
+
+static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
+  if (a.key.id) {
+    if (b.key.id) {
+      return a.key.id.value() < b.key.id.value();
+    }
+    return true;
+  } else if (!b.key.id) {
+    return a.key.name.value() < b.key.name.value();
+  }
+  return false;
+}
+
+struct FlatEntry {
+  ResourceEntry* entry;
+  Value* value;
+
+  // The entry string pool index to the entry's name.
+  uint32_t entry_key;
+};
+
+class MapFlattenVisitor : public ValueVisitor {
+ public:
+  using ValueVisitor::Visit;
+
+  MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
+      : out_entry_(out_entry), buffer_(buffer) {
+  }
+
+  void Visit(Attribute* attr) override {
+    {
+      Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
+      FlattenEntry(&key, &val);
+    }
+
+    if (attr->min_int != std::numeric_limits<int32_t>::min()) {
+      Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int));
+      FlattenEntry(&key, &val);
+    }
+
+    if (attr->max_int != std::numeric_limits<int32_t>::max()) {
+      Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int));
+      FlattenEntry(&key, &val);
+    }
+
+    for (Attribute::Symbol& s : attr->symbols) {
+      BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
+      FlattenEntry(&s.symbol, &val);
+    }
+  }
+
+  void Visit(Style* style) override {
+    if (style->parent) {
+      const Reference& parent_ref = style->parent.value();
+      CHECK(bool(parent_ref.id)) << "parent has no ID";
+      out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id);
+    }
+
+    // Sort the style.
+    std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries);
+
+    for (Style::Entry& entry : style->entries) {
+      FlattenEntry(&entry.key, entry.value.get());
+    }
+  }
+
+  void Visit(Styleable* styleable) override {
+    for (auto& attr_ref : styleable->entries) {
+      BinaryPrimitive val(Res_value{});
+      FlattenEntry(&attr_ref, &val);
+    }
+  }
+
+  void Visit(Array* array) override {
+    for (auto& item : array->elements) {
+      ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
+      FlattenValue(item.get(), out_entry);
+      out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
+      entry_count_++;
+    }
+  }
+
+  void Visit(Plural* plural) override {
+    const size_t count = plural->values.size();
+    for (size_t i = 0; i < count; i++) {
+      if (!plural->values[i]) {
+        continue;
+      }
+
+      ResourceId q;
+      switch (i) {
+        case Plural::Zero:
+          q.id = android::ResTable_map::ATTR_ZERO;
+          break;
+
+        case Plural::One:
+          q.id = android::ResTable_map::ATTR_ONE;
+          break;
+
+        case Plural::Two:
+          q.id = android::ResTable_map::ATTR_TWO;
+          break;
+
+        case Plural::Few:
+          q.id = android::ResTable_map::ATTR_FEW;
+          break;
+
+        case Plural::Many:
+          q.id = android::ResTable_map::ATTR_MANY;
+          break;
+
+        case Plural::Other:
+          q.id = android::ResTable_map::ATTR_OTHER;
+          break;
+
+        default:
+          LOG(FATAL) << "unhandled plural type";
+          break;
+      }
+
+      Reference key(q);
+      FlattenEntry(&key, plural->values[i].get());
+    }
+  }
+
+  /**
+   * Call this after visiting a Value. This will finish any work that
+   * needs to be done to prepare the entry.
+   */
+  void Finish() {
+    out_entry_->count = util::HostToDevice32(entry_count_);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
+
+  void FlattenKey(Reference* key, ResTable_map* out_entry) {
+    CHECK(bool(key->id)) << "key has no ID";
+    out_entry->name.ident = util::HostToDevice32(key->id.value().id);
+  }
+
+  void FlattenValue(Item* value, ResTable_map* out_entry) {
+    CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
+  }
+
+  void FlattenEntry(Reference* key, Item* value) {
+    ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
+    FlattenKey(key, out_entry);
+    FlattenValue(value, out_entry);
+    out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
+    entry_count_++;
+  }
+
+  ResTable_entry_ext* out_entry_;
+  BigBuffer* buffer_;
+  size_t entry_count_ = 0;
+};
+
+class PackageFlattener {
+ public:
+  PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
+                   const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries)
+      : context_(context),
+        diag_(context->GetDiagnostics()),
+        package_(package),
+        shared_libs_(shared_libs),
+        use_sparse_entries_(use_sparse_entries) {
+  }
+
+  bool FlattenPackage(BigBuffer* buffer) {
+    ChunkWriter pkg_writer(buffer);
+    ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
+    pkg_header->id = util::HostToDevice32(package_->id.value());
+
+    // AAPT truncated the package name, so do the same.
+    // Shared libraries require full package names, so don't truncate theirs.
+    if (context_->GetPackageType() != PackageType::kApp &&
+        package_->name.size() >= arraysize(pkg_header->name)) {
+      diag_->Error(DiagMessage() << "package name '" << package_->name
+                                 << "' is too long. "
+                                    "Shared libraries cannot have truncated package names");
+      return false;
+    }
+
+    // Copy the package name in device endianness.
+    strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_->name));
+
+    // Serialize the types. We do this now so that our type and key strings
+    // are populated. We write those first.
+    BigBuffer type_buffer(1024);
+    FlattenTypes(&type_buffer);
+
+    pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size());
+    StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_);
+
+    pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size());
+    StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_);
+
+    // Append the types.
+    buffer->AppendBuffer(std::move(type_buffer));
+
+    // If there are libraries (or if the package ID is 0x00), encode a library chunk.
+    if (package_->id.value() == 0x00 || !shared_libs_->empty()) {
+      FlattenLibrarySpec(buffer);
+    }
+
+    pkg_writer.Finish();
+    return true;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
+
+  template <typename T, bool IsItem>
+  T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) {
+    static_assert(
+        std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value,
+        "T must be ResTable_entry or ResTable_entry_ext");
+
+    T* result = buffer->NextBlock<T>();
+    ResTable_entry* out_entry = (ResTable_entry*)result;
+    if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+      out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+    }
+
+    if (entry->value->IsWeak()) {
+      out_entry->flags |= ResTable_entry::FLAG_WEAK;
+    }
+
+    if (!IsItem) {
+      out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+    }
+
+    out_entry->flags = util::HostToDevice16(out_entry->flags);
+    out_entry->key.index = util::HostToDevice32(entry->entry_key);
+    out_entry->size = util::HostToDevice16(sizeof(T));
+    return result;
+  }
+
+  bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) {
+    if (Item* item = ValueCast<Item>(entry->value)) {
+      WriteEntry<ResTable_entry, true>(entry, buffer);
+      Res_value* outValue = buffer->NextBlock<Res_value>();
+      CHECK(item->Flatten(outValue)) << "flatten failed";
+      outValue->size = util::HostToDevice16(sizeof(*outValue));
+    } else {
+      ResTable_entry_ext* out_entry = WriteEntry<ResTable_entry_ext, false>(entry, buffer);
+      MapFlattenVisitor visitor(out_entry, buffer);
+      entry->value->Accept(&visitor);
+      visitor.Finish();
+    }
+    return true;
+  }
+
+  bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config,
+                     const size_t num_total_entries, std::vector<FlatEntry>* entries,
+                     BigBuffer* buffer) {
+    CHECK(num_total_entries != 0);
+    CHECK(num_total_entries <= std::numeric_limits<uint16_t>::max());
+
+    ChunkWriter type_writer(buffer);
+    ResTable_type* type_header = type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
+    type_header->id = type->id.value();
+    type_header->config = config;
+    type_header->config.swapHtoD();
+
+    std::vector<uint32_t> offsets;
+    offsets.resize(num_total_entries, 0xffffffffu);
+
+    BigBuffer values_buffer(512);
+    for (FlatEntry& flat_entry : *entries) {
+      CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
+      offsets[flat_entry.entry->id.value()] = values_buffer.size();
+      if (!FlattenValue(&flat_entry, &values_buffer)) {
+        diag_->Error(DiagMessage()
+                     << "failed to flatten resource '"
+                     << ResourceNameRef(package_->name, type->type, flat_entry.entry->name)
+                     << "' for configuration '" << config << "'");
+        return false;
+      }
+    }
+
+    bool sparse_encode = use_sparse_entries_;
+
+    // Only sparse encode if the entries will be read on platforms O+.
+    sparse_encode =
+        sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O);
+
+    // Only sparse encode if the offsets are representable in 2 bytes.
+    sparse_encode =
+        sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
+
+    // Only sparse encode if the ratio of populated entries to total entries is below some
+    // threshold.
+    sparse_encode =
+        sparse_encode && ((100 * entries->size()) / num_total_entries) < kSparseEncodingThreshold;
+
+    if (sparse_encode) {
+      type_header->entryCount = util::HostToDevice32(entries->size());
+      type_header->flags |= ResTable_type::FLAG_SPARSE;
+      ResTable_sparseTypeEntry* indices =
+          type_writer.NextBlock<ResTable_sparseTypeEntry>(entries->size());
+      for (size_t i = 0; i < num_total_entries; i++) {
+        if (offsets[i] != ResTable_type::NO_ENTRY) {
+          CHECK((offsets[i] & 0x03) == 0);
+          indices->idx = util::HostToDevice16(i);
+          indices->offset = util::HostToDevice16(offsets[i] / 4u);
+          indices++;
+        }
+      }
+    } else {
+      type_header->entryCount = util::HostToDevice32(num_total_entries);
+      uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
+      for (size_t i = 0; i < num_total_entries; i++) {
+        indices[i] = util::HostToDevice32(offsets[i]);
+      }
+    }
+
+    type_header->entriesStart = util::HostToDevice32(type_writer.size());
+    type_writer.buffer()->AppendBuffer(std::move(values_buffer));
+    type_writer.Finish();
+    return true;
+  }
+
+  std::vector<ResourceTableType*> CollectAndSortTypes() {
+    std::vector<ResourceTableType*> sorted_types;
+    for (auto& type : package_->types) {
+      if (type->type == ResourceType::kStyleable) {
+        // Styleables aren't real Resource Types, they are represented in the
+        // R.java file.
+        continue;
+      }
+
+      CHECK(bool(type->id)) << "type must have an ID set";
+
+      sorted_types.push_back(type.get());
+    }
+    std::sort(sorted_types.begin(), sorted_types.end(), cmp_ids<ResourceTableType>);
+    return sorted_types;
+  }
+
+  std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
+    // Sort the entries by entry ID.
+    std::vector<ResourceEntry*> sorted_entries;
+    for (auto& entry : type->entries) {
+      CHECK(bool(entry->id)) << "entry must have an ID set";
+      sorted_entries.push_back(entry.get());
+    }
+    std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>);
+    return sorted_entries;
+  }
+
+  bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries,
+                       BigBuffer* buffer) {
+    ChunkWriter type_spec_writer(buffer);
+    ResTable_typeSpec* spec_header =
+        type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
+    spec_header->id = type->id.value();
+
+    if (sorted_entries->empty()) {
+      type_spec_writer.Finish();
+      return true;
+    }
+
+    // We can't just take the size of the vector. There may be holes in the
+    // entry ID space.
+    // Since the entries are sorted by ID, the last one will be the biggest.
+    const size_t num_entries = sorted_entries->back()->id.value() + 1;
+
+    spec_header->entryCount = util::HostToDevice32(num_entries);
+
+    // Reserve space for the masks of each resource in this type. These
+    // show for which configuration axis the resource changes.
+    uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
+
+    const size_t actual_num_entries = sorted_entries->size();
+    for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
+      ResourceEntry* entry = sorted_entries->at(entryIndex);
+
+      // Populate the config masks for this entry.
+
+      if (entry->symbol_status.state == SymbolState::kPublic) {
+        config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
+      }
+
+      const size_t config_count = entry->values.size();
+      for (size_t i = 0; i < config_count; i++) {
+        const ConfigDescription& config = entry->values[i]->config;
+        for (size_t j = i + 1; j < config_count; j++) {
+          config_masks[entry->id.value()] |=
+              util::HostToDevice32(config.diff(entry->values[j]->config));
+        }
+      }
+    }
+    type_spec_writer.Finish();
+    return true;
+  }
+
+  bool FlattenTypes(BigBuffer* buffer) {
+    // Sort the types by their IDs. They will be inserted into the StringPool in
+    // this order.
+    std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes();
+
+    size_t expected_type_id = 1;
+    for (ResourceTableType* type : sorted_types) {
+      // If there is a gap in the type IDs, fill in the StringPool
+      // with empty values until we reach the ID we expect.
+      while (type->id.value() > expected_type_id) {
+        std::stringstream type_name;
+        type_name << "?" << expected_type_id;
+        type_pool_.MakeRef(type_name.str());
+        expected_type_id++;
+      }
+      expected_type_id++;
+      type_pool_.MakeRef(ToString(type->type));
+
+      std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
+      if (sorted_entries.empty()) {
+        continue;
+      }
+
+      if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
+        return false;
+      }
+
+      // Since the entries are sorted by ID, the last ID will be the largest.
+      const size_t num_entries = sorted_entries.back()->id.value() + 1;
+
+      // The binary resource table lists resource entries for each
+      // configuration.
+      // We store them inverted, where a resource entry lists the values for
+      // each
+      // configuration available. Here we reverse this to match the binary
+      // table.
+      std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
+      for (ResourceEntry* entry : sorted_entries) {
+        const uint32_t key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
+
+        // Group values by configuration.
+        for (auto& config_value : entry->values) {
+          config_to_entry_list_map[config_value->config].push_back(
+              FlatEntry{entry, config_value->value.get(), key_index});
+        }
+      }
+
+      // Flatten a configuration value.
+      for (auto& entry : config_to_entry_list_map) {
+        if (!FlattenConfig(type, entry.first, num_entries, &entry.second, buffer)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  void FlattenLibrarySpec(BigBuffer* buffer) {
+    ChunkWriter lib_writer(buffer);
+    ResTable_lib_header* lib_header =
+        lib_writer.StartChunk<ResTable_lib_header>(RES_TABLE_LIBRARY_TYPE);
+
+    const size_t num_entries = (package_->id.value() == 0x00 ? 1 : 0) + shared_libs_->size();
+    CHECK(num_entries > 0);
+
+    lib_header->count = util::HostToDevice32(num_entries);
+
+    ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries);
+    if (package_->id.value() == 0x00) {
+      // Add this package
+      lib_entry->packageId = util::HostToDevice32(0x00);
+      strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
+                    util::Utf8ToUtf16(package_->name));
+      ++lib_entry;
+    }
+
+    for (auto& map_entry : *shared_libs_) {
+      lib_entry->packageId = util::HostToDevice32(map_entry.first);
+      strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
+                    util::Utf8ToUtf16(map_entry.second));
+      ++lib_entry;
+    }
+    lib_writer.Finish();
+  }
+
+  IAaptContext* context_;
+  IDiagnostics* diag_;
+  ResourceTablePackage* package_;
+  const std::map<size_t, std::string>* shared_libs_;
+  bool use_sparse_entries_;
+  StringPool type_pool_;
+  StringPool key_pool_;
+};
+
+}  // namespace
+
+bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
+  // We must do this before writing the resources, since the string pool IDs may change.
+  table->string_pool.Prune();
+  table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
+    int diff = util::compare(a.priority, b.priority);
+    if (diff == 0) {
+      diff = a.config.compare(b.config);
+    }
+    return diff;
+  });
+
+  // Write the ResTable header.
+  ChunkWriter table_writer(buffer_);
+  ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
+  table_header->packageCount = util::HostToDevice32(table->packages.size());
+
+  // Write a self mapping entry for this package if the ID is non-standard (0x7f).
+  if (context->GetPackageType() == PackageType::kApp) {
+    const uint8_t package_id = context->GetPackageId();
+    if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
+      table->included_packages_[package_id] = context->GetCompilationPackage();
+    }
+  }
+
+  // Flatten the values string pool.
+  StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
+
+  BigBuffer package_buffer(1024);
+
+  // Flatten each package.
+  for (auto& package : table->packages) {
+    PackageFlattener flattener(context, package.get(), &table->included_packages_,
+                               options_.use_sparse_entries);
+    if (!flattener.FlattenPackage(&package_buffer)) {
+      return false;
+    }
+  }
+
+  // Finally merge all the packages into the main buffer.
+  table_writer.buffer()->AppendBuffer(std::move(package_buffer));
+  table_writer.Finish();
+  return true;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
new file mode 100644
index 0000000..88cbddf
--- /dev/null
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+
+#include "android-base/macros.h"
+
+#include "ResourceTable.h"
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+// The percentage of used entries for a type for which using a sparse encoding is
+// preferred.
+constexpr const size_t kSparseEncodingThreshold = 60;
+
+struct TableFlattenerOptions {
+  // When true, types for configurations with a sparse set of entries are encoded
+  // as a sparse map of entry ID and offset to actual data.
+  // This is only available on platforms O+ and will only be respected when
+  // minSdk is O+.
+  bool use_sparse_entries = false;
+};
+
+class TableFlattener : public IResourceTableConsumer {
+ public:
+  explicit TableFlattener(const TableFlattenerOptions& options, BigBuffer* buffer)
+      : options_(options), buffer_(buffer) {
+  }
+
+  bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TableFlattener);
+
+  TableFlattenerOptions options_;
+  BigBuffer* buffer_;
+};
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
new file mode 100644
index 0000000..6d75973
--- /dev/null
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2015 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 "format/binary/TableFlattener.h"
+
+#include "android-base/stringprintf.h"
+
+#include "ResourceUtils.h"
+#include "SdkConstants.h"
+#include "format/binary/BinaryResourceParser.h"
+#include "test/Test.h"
+#include "util/Util.h"
+
+using namespace android;
+
+using ::testing::IsNull;
+using ::testing::NotNull;
+
+namespace aapt {
+
+class TableFlattenerTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    context_ =
+        test::ContextBuilder().SetCompilationPackage("com.app.test").SetPackageId(0x7f).Build();
+  }
+
+  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
+                                     ResourceTable* table, std::string* out_content) {
+    BigBuffer buffer(1024);
+    TableFlattener flattener(options, &buffer);
+    if (!flattener.Consume(context, table)) {
+      return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
+    }
+    *out_content = buffer.to_string();
+    return ::testing::AssertionSuccess();
+  }
+
+  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
+                                     ResourceTable* table, ResTable* out_table) {
+    std::string content;
+    auto result = Flatten(context, options, table, &content);
+    if (!result) {
+      return result;
+    }
+
+    if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) {
+      return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+    }
+    return ::testing::AssertionSuccess();
+  }
+
+  ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
+                                     ResourceTable* table, ResourceTable* out_table) {
+    std::string content;
+    auto result = Flatten(context, options, table, &content);
+    if (!result) {
+      return result;
+    }
+
+    BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
+    if (!parser.Parse()) {
+      return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
+    }
+    return ::testing::AssertionSuccess();
+  }
+
+  ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name,
+                                    const ResourceId& expected_id,
+                                    const ConfigDescription& expected_config,
+                                    const uint8_t expected_data_type, const uint32_t expected_data,
+                                    const uint32_t expected_spec_flags) {
+    const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
+
+    table->setParameters(&expected_config);
+
+    ResTable_config config;
+    Res_value val;
+    uint32_t spec_flags;
+    if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, &config) < 0) {
+      return ::testing::AssertionFailure() << "could not find resource with";
+    }
+
+    if (expected_data_type != val.dataType) {
+      return ::testing::AssertionFailure()
+             << "expected data type " << std::hex << (int)expected_data_type
+             << " but got data type " << (int)val.dataType << std::dec << " instead";
+    }
+
+    if (expected_data != val.data) {
+      return ::testing::AssertionFailure()
+             << "expected data " << std::hex << expected_data << " but got data " << val.data
+             << std::dec << " instead";
+    }
+
+    if (expected_spec_flags != spec_flags) {
+      return ::testing::AssertionFailure()
+             << "expected specFlags " << std::hex << expected_spec_flags << " but got specFlags "
+             << spec_flags << std::dec << " instead";
+    }
+
+    ResTable::resource_name actual_name;
+    if (!table->getResourceName(expected_id.id, false, &actual_name)) {
+      return ::testing::AssertionFailure() << "failed to find resource name";
+    }
+
+    Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
+    if (!resName) {
+      return ::testing::AssertionFailure()
+             << "expected name '" << expected_res_name << "' but got '"
+             << StringPiece16(actual_name.package, actual_name.packageLen) << ":"
+             << StringPiece16(actual_name.type, actual_name.typeLen) << "/"
+             << StringPiece16(actual_name.name, actual_name.nameLen) << "'";
+    }
+
+    if (expected_config != config) {
+      return ::testing::AssertionFailure() << "expected config '" << expected_config
+                                           << "' but got '" << ConfigDescription(config) << "'";
+    }
+    return ::testing::AssertionSuccess();
+  }
+
+ protected:
+  std::unique_ptr<IAaptContext> context_;
+};
+
+TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
+          .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
+          .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
+                    test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
+          .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
+                    util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
+          .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
+                    ResourceId(0x7f030000),
+                    util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
+          .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
+          .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
+          .Build();
+
+  ResTable res_table;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {},
+                     Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), {},
+                     Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
+                     Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {},
+                     Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000),
+                     test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
+                     ResTable_config::CONFIG_VERSION));
+
+  std::u16string foo_str = u"foo";
+  ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
+  ASSERT_GE(idx, 0);
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
+                     Res_value::TYPE_STRING, (uint32_t)idx, 0u));
+
+  std::u16string bar_path = u"res/layout/bar.xml";
+  idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
+  ASSERT_GE(idx, 0);
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
+                     Res_value::TYPE_STRING, (uint32_t)idx, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
+          .AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
+          .Build();
+
+  ResTable res_table;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
+
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), {},
+                     Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+  EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020003), {},
+                     Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
+}
+
+TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
+  Attribute attr(false);
+  attr.type_mask = android::ResTable_map::TYPE_INTEGER;
+  attr.min_int = 10;
+  attr.max_int = 23;
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("android", 0x01)
+          .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr))
+          .Build();
+
+  ResourceTable result;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
+
+  Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo");
+  ASSERT_THAT(actual_attr, NotNull());
+  EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak());
+  EXPECT_EQ(attr.type_mask, actual_attr->type_mask);
+  EXPECT_EQ(attr.min_int, actual_attr->min_int);
+  EXPECT_EQ(attr.max_int, actual_attr->max_int);
+}
+
+static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
+    IAaptContext* context, const ConfigDescription& sparse_config, float load) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId(context->GetCompilationPackage(), context->GetPackageId())
+          .Build();
+
+  // Add regular entries.
+  int stride = static_cast<int>(1.0f / load);
+  for (int i = 0; i < 100; i++) {
+    const ResourceName name = test::ParseNameOrDie(
+        base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
+    const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
+    const auto value =
+        util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
+    CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
+                             std::unique_ptr<Value>(value->Clone(nullptr)),
+                             context->GetDiagnostics()));
+
+    // Every few entries, write out a sparse_config value. This will give us the desired load.
+    if (i % stride == 0) {
+      CHECK(table->AddResource(name, resid, sparse_config, "",
+                               std::unique_ptr<Value>(value->Clone(nullptr)),
+                               context->GetDiagnostics()));
+    }
+  }
+  return table;
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+                                              .SetCompilationPackage("android")
+                                              .SetPackageId(0x01)
+                                              .SetMinSdkVersion(SDK_O)
+                                              .Build();
+
+  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+  TableFlattenerOptions options;
+  options.use_sparse_entries = true;
+
+  std::string no_sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+  std::string sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+  EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+  // Attempt to parse the sparse contents.
+
+  ResourceTable sparse_table;
+  BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
+                              sparse_contents.data(), sparse_contents.size());
+  ASSERT_TRUE(parser.Parse());
+
+  auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
+                                                        sparse_config);
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(0u, value->value.data);
+
+  ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
+                                                       sparse_config),
+              IsNull());
+
+  value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
+                                                   sparse_config);
+  ASSERT_THAT(value, NotNull());
+  EXPECT_EQ(4u, value->value.data);
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+                                              .SetCompilationPackage("android")
+                                              .SetPackageId(0x01)
+                                              .SetMinSdkVersion(SDK_LOLLIPOP)
+                                              .Build();
+
+  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
+  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+  TableFlattenerOptions options;
+  options.use_sparse_entries = true;
+
+  std::string no_sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+  std::string sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+  EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+                                              .SetCompilationPackage("android")
+                                              .SetPackageId(0x01)
+                                              .SetMinSdkVersion(SDK_O)
+                                              .Build();
+
+  const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+  auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
+
+  TableFlattenerOptions options;
+  options.use_sparse_entries = true;
+
+  std::string no_sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+  std::string sparse_contents;
+  ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+  EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("lib", 0x00)
+          .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>())
+          .Build();
+  ResourceTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      result.FindResource(test::ParseNameOrDie("lib:id/foo"));
+  ASSERT_TRUE(search_result);
+  EXPECT_EQ(0x00u, search_result.value().package->id.value());
+
+  auto iter = result.included_packages_.find(0x00);
+  ASSERT_NE(result.included_packages_.end(), iter);
+  EXPECT_EQ("lib", iter->second);
+}
+
+TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("app", 0x7f)
+          .AddValue("app:id/foo", ResourceId(0x7f010000),
+                    test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
+          .AddValue("app:id/bar", ResourceId(0x7f010001),
+                    test::BuildReference("lib_two:id/bar", ResourceId(0x03010000)))
+          .Build();
+  table->included_packages_[0x02] = "lib_one";
+  table->included_packages_[0x03] = "lib_two";
+
+  ResTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
+  ASSERT_THAT(dynamic_ref_table, NotNull());
+
+  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
+
+  ssize_t idx = entries.indexOfKey(android::String16("lib_one"));
+  ASSERT_GE(idx, 0);
+  EXPECT_EQ(0x02u, entries.valueAt(idx));
+
+  idx = entries.indexOfKey(android::String16("lib_two"));
+  ASSERT_GE(idx, 0);
+  EXPECT_EQ(0x03u, entries.valueAt(idx));
+}
+
+TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) {
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build();
+  std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+                                             .SetPackageId("app", 0x80)
+                                             .AddSimple("app:id/foo", ResourceId(0x80010000))
+                                             .Build();
+
+  ResTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
+  ASSERT_THAT(dynamic_ref_table, NotNull());
+
+  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
+  ssize_t idx = entries.indexOfKey(android::String16("app"));
+  ASSERT_GE(idx, 0);
+  EXPECT_EQ(0x80u, entries.valueAt(idx));
+}
+
+TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) {
+  std::string kPackageName(256, 'F');
+
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId(kPackageName, 0x7f)
+          .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
+          .Build();
+
+  ResTable result;
+  ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+  ASSERT_EQ(1u, result.getBasePackageCount());
+  EXPECT_EQ(127u, result.getBasePackageName(0).size());
+}
+
+TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
+  std::string kPackageName(256, 'F');
+
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+                                              .SetCompilationPackage(kPackageName)
+                                              .SetPackageId(0x7f)
+                                              .SetPackageType(PackageType::kSharedLib)
+                                              .Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId(kPackageName, 0x7f)
+          .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
+          .Build();
+
+  ResTable result;
+  ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
new file mode 100644
index 0000000..345cc95
--- /dev/null
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2015 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 "format/binary/XmlFlattener.h"
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/misc.h"
+
+#include "SdkConstants.h"
+#include "format/binary/ChunkWriter.h"
+#include "format/binary/ResourceTypeExtensions.h"
+#include "xml/XmlDom.h"
+
+using namespace android;
+
+namespace aapt {
+
+namespace {
+
+constexpr uint32_t kLowPriority = 0xffffffffu;
+
+static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribute* b) {
+  if (a->compiled_attribute && a->compiled_attribute.value().id) {
+    if (b->compiled_attribute && b->compiled_attribute.value().id) {
+      return a->compiled_attribute.value().id.value() < b->compiled_attribute.value().id.value();
+    }
+    return true;
+  } else if (!b->compiled_attribute) {
+    int diff = a->namespace_uri.compare(b->namespace_uri);
+    if (diff < 0) {
+      return true;
+    } else if (diff > 0) {
+      return false;
+    }
+    return a->name < b->name;
+  }
+  return false;
+}
+
+class XmlFlattenerVisitor : public xml::ConstVisitor {
+ public:
+  using xml::ConstVisitor::Visit;
+
+  StringPool pool;
+  std::map<uint8_t, StringPool> package_pools;
+
+  struct StringFlattenDest {
+    StringPool::Ref ref;
+    ResStringPool_ref* dest;
+  };
+
+  std::vector<StringFlattenDest> string_refs;
+
+  XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
+      : buffer_(buffer), options_(options) {
+  }
+
+  void Visit(const xml::Text* node) override {
+    if (util::TrimWhitespace(node->text).empty()) {
+      // Skip whitespace only text nodes.
+      return;
+    }
+
+    ChunkWriter writer(buffer_);
+    ResXMLTree_node* flat_node = writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
+    flat_node->lineNumber = util::HostToDevice32(node->line_number);
+    flat_node->comment.index = util::HostToDevice32(-1);
+
+    ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
+
+    // Process plain strings to make sure they get properly escaped.
+    util::StringBuilder builder;
+    builder.Append(node->text);
+    AddString(builder.ToString(), kLowPriority, &flat_text->data);
+
+    writer.Finish();
+  }
+
+  void Visit(const xml::Element* node) override {
+    for (const xml::NamespaceDecl& decl : node->namespace_decls) {
+      // Skip dedicated tools namespace.
+      if (decl.uri != xml::kSchemaTools) {
+        WriteNamespace(decl, android::RES_XML_START_NAMESPACE_TYPE);
+      }
+    }
+
+    {
+      ChunkWriter start_writer(buffer_);
+      ResXMLTree_node* flat_node =
+          start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
+      flat_node->lineNumber = util::HostToDevice32(node->line_number);
+      flat_node->comment.index = util::HostToDevice32(-1);
+
+      ResXMLTree_attrExt* flat_elem = start_writer.NextBlock<ResXMLTree_attrExt>();
+
+      // A missing namespace must be null, not an empty string. Otherwise the runtime complains.
+      AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,
+                true /* treat_empty_string_as_null */);
+      AddString(node->name, kLowPriority, &flat_elem->name, true /* treat_empty_string_as_null */);
+
+      flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem));
+      flat_elem->attributeSize = util::HostToDevice16(sizeof(ResXMLTree_attribute));
+
+      WriteAttributes(node, flat_elem, &start_writer);
+
+      start_writer.Finish();
+    }
+
+    xml::ConstVisitor::Visit(node);
+
+    {
+      ChunkWriter end_writer(buffer_);
+      ResXMLTree_node* flat_end_node =
+          end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
+      flat_end_node->lineNumber = util::HostToDevice32(node->line_number);
+      flat_end_node->comment.index = util::HostToDevice32(-1);
+
+      ResXMLTree_endElementExt* flat_end_elem = end_writer.NextBlock<ResXMLTree_endElementExt>();
+      AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,
+                true /* treat_empty_string_as_null */);
+      AddString(node->name, kLowPriority, &flat_end_elem->name);
+
+      end_writer.Finish();
+    }
+
+    for (auto iter = node->namespace_decls.rbegin(); iter != node->namespace_decls.rend(); ++iter) {
+      // Skip dedicated tools namespace.
+      if (iter->uri != xml::kSchemaTools) {
+        WriteNamespace(*iter, android::RES_XML_END_NAMESPACE_TYPE);
+      }
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
+
+  void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
+                 bool treat_empty_string_as_null = false) {
+    if (str.empty() && treat_empty_string_as_null) {
+      // Some parts of the runtime treat null differently than empty string.
+      dest->index = util::DeviceToHost32(-1);
+    } else {
+      string_refs.push_back(
+          StringFlattenDest{pool.MakeRef(str, StringPool::Context(priority)), dest});
+    }
+  }
+
+  void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
+    string_refs.push_back(StringFlattenDest{ref, dest});
+  }
+
+  void WriteNamespace(const xml::NamespaceDecl& decl, uint16_t type) {
+    ChunkWriter writer(buffer_);
+
+    ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type);
+    flatNode->lineNumber = util::HostToDevice32(decl.line_number);
+    flatNode->comment.index = util::HostToDevice32(-1);
+
+    ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>();
+    AddString(decl.prefix, kLowPriority, &flat_ns->prefix);
+    AddString(decl.uri, kLowPriority, &flat_ns->uri);
+
+    writer.Finish();
+  }
+
+  void WriteAttributes(const xml::Element* node, ResXMLTree_attrExt* flat_elem,
+                       ChunkWriter* writer) {
+    filtered_attrs_.clear();
+    filtered_attrs_.reserve(node->attributes.size());
+
+    // Filter the attributes.
+    for (const xml::Attribute& attr : node->attributes) {
+      if (attr.namespace_uri != xml::kSchemaTools) {
+        filtered_attrs_.push_back(&attr);
+      }
+    }
+
+    if (filtered_attrs_.empty()) {
+      return;
+    }
+
+    const ResourceId kIdAttr(0x010100d0);
+
+    std::sort(filtered_attrs_.begin(), filtered_attrs_.end(), cmp_xml_attribute_by_id);
+
+    flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size());
+
+    ResXMLTree_attribute* flat_attr =
+        writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size());
+    uint16_t attribute_index = 1;
+    for (const xml::Attribute* xml_attr : filtered_attrs_) {
+      // Assign the indices for specific attributes.
+      if (xml_attr->compiled_attribute && xml_attr->compiled_attribute.value().id &&
+          xml_attr->compiled_attribute.value().id.value() == kIdAttr) {
+        flat_elem->idIndex = util::HostToDevice16(attribute_index);
+      } else if (xml_attr->namespace_uri.empty()) {
+        if (xml_attr->name == "class") {
+          flat_elem->classIndex = util::HostToDevice16(attribute_index);
+        } else if (xml_attr->name == "style") {
+          flat_elem->styleIndex = util::HostToDevice16(attribute_index);
+        }
+      }
+      attribute_index++;
+
+      // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
+      // is empty (doesn't exist).
+      AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns,
+                true /* treat_empty_string_as_null */);
+
+      flat_attr->rawValue.index = util::HostToDevice32(-1);
+
+      if (!xml_attr->compiled_attribute || !xml_attr->compiled_attribute.value().id) {
+        // The attribute has no associated ResourceID, so the string order doesn't matter.
+        AddString(xml_attr->name, kLowPriority, &flat_attr->name);
+      } else {
+        // Attribute names are stored without packages, but we use
+        // their StringPool index to lookup their resource IDs.
+        // This will cause collisions, so we can't dedupe
+        // attribute names from different packages. We use separate
+        // pools that we later combine.
+        //
+        // Lookup the StringPool for this package and make the reference there.
+        const xml::AaptAttribute& aapt_attr = xml_attr->compiled_attribute.value();
+
+        StringPool::Ref name_ref = package_pools[aapt_attr.id.value().package_id()].MakeRef(
+            xml_attr->name, StringPool::Context(aapt_attr.id.value().id));
+
+        // Add it to the list of strings to flatten.
+        AddString(name_ref, &flat_attr->name);
+      }
+
+      // Process plain strings to make sure they get properly escaped.
+      StringPiece raw_value = xml_attr->value;
+
+      util::StringBuilder str_builder(true /*preserve_spaces*/);
+      str_builder.Append(xml_attr->value);
+
+      if (!options_.keep_raw_values) {
+        raw_value = str_builder.ToString();
+      }
+
+      if (options_.keep_raw_values || !xml_attr->compiled_value) {
+        // Keep raw values if the value is not compiled or
+        // if we're building a static library (need symbols).
+        AddString(raw_value, kLowPriority, &flat_attr->rawValue);
+      }
+
+      if (xml_attr->compiled_value) {
+        CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
+      } else {
+        // Flatten as a regular string type.
+        flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
+
+        AddString(str_builder.ToString(), kLowPriority,
+                  (ResStringPool_ref*)&flat_attr->typedValue.data);
+      }
+
+      flat_attr->typedValue.size = util::HostToDevice16(sizeof(flat_attr->typedValue));
+      flat_attr++;
+    }
+  }
+
+  BigBuffer* buffer_;
+  XmlFlattenerOptions options_;
+
+  // Scratch vector to filter attributes. We avoid allocations making this a member.
+  std::vector<const xml::Attribute*> filtered_attrs_;
+};
+
+}  // namespace
+
+bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) {
+  BigBuffer node_buffer(1024);
+  XmlFlattenerVisitor visitor(&node_buffer, options_);
+  node->Accept(&visitor);
+
+  // Merge the package pools into the main pool.
+  for (auto& package_pool_entry : visitor.package_pools) {
+    visitor.pool.Merge(std::move(package_pool_entry.second));
+  }
+
+  // Sort the string pool so that attribute resource IDs show up first.
+  visitor.pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
+    return util::compare(a.priority, b.priority);
+  });
+
+  // Now we flatten the string pool references into the correct places.
+  for (const auto& ref_entry : visitor.string_refs) {
+    ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index());
+  }
+
+  // Write the XML header.
+  ChunkWriter xml_header_writer(buffer_);
+  xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE);
+
+  // Flatten the StringPool.
+  if (options_.use_utf16) {
+    StringPool::FlattenUtf16(buffer_, visitor.pool);
+  } else {
+    StringPool::FlattenUtf8(buffer_, visitor.pool);
+  }
+
+  {
+    // Write the array of resource IDs, indexed by StringPool order.
+    ChunkWriter res_id_map_writer(buffer_);
+    res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
+    for (const auto& str : visitor.pool.strings()) {
+      ResourceId id(str->context.priority);
+      if (str->context.priority == kLowPriority || !id.is_valid()) {
+        // When we see the first non-resource ID, we're done.
+        break;
+      }
+      *res_id_map_writer.NextBlock<uint32_t>() = util::HostToDevice32(id.id);
+    }
+    res_id_map_writer.Finish();
+  }
+
+  // Move the nodeBuffer and append it to the out buffer.
+  buffer_->AppendBuffer(std::move(node_buffer));
+
+  // Finish the xml header.
+  xml_header_writer.Finish();
+  return true;
+}
+
+bool XmlFlattener::Consume(IAaptContext* context, const xml::XmlResource* resource) {
+  if (!resource->root) {
+    return false;
+  }
+  return Flatten(context, resource->root.get());
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/binary/XmlFlattener.h b/tools/aapt2/format/binary/XmlFlattener.h
new file mode 100644
index 0000000..1f9e777
--- /dev/null
+++ b/tools/aapt2/format/binary/XmlFlattener.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 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 AAPT_FORMAT_BINARY_XMLFLATTENER_H
+#define AAPT_FORMAT_BINARY_XMLFLATTENER_H
+
+#include "android-base/macros.h"
+
+#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+struct XmlFlattenerOptions {
+  // Keep attribute raw string values along with typed values.
+  bool keep_raw_values = false;
+
+  // Encode the strings in UTF-16. Only needed for AndroidManifest.xml to avoid a bug in
+  // certain non-AOSP platforms: https://issuetracker.google.com/64434571
+  bool use_utf16 = false;
+};
+
+class XmlFlattener {
+ public:
+  XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options)
+      : buffer_(buffer), options_(options) {
+  }
+
+  bool Consume(IAaptContext* context, const xml::XmlResource* resource);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(XmlFlattener);
+
+  bool Flatten(IAaptContext* context, const xml::Node* node);
+
+  BigBuffer* buffer_;
+  XmlFlattenerOptions options_;
+};
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_BINARY_XMLFLATTENER_H */
diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp
new file mode 100644
index 0000000..0450f6c
--- /dev/null
+++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2015 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 "format/binary/XmlFlattener.h"
+
+#include "androidfw/ResourceTypes.h"
+
+#include "link/Linkers.h"
+#include "test/Test.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
+
+using ::aapt::test::StrEq;
+using ::android::StringPiece16;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsNull;
+using ::testing::Ne;
+using ::testing::NotNull;
+
+namespace aapt {
+
+class XmlFlattenerTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    context_ = test::ContextBuilder()
+                   .SetCompilationPackage("com.app.test")
+                   .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
+                   .AddSymbolSource(
+                       test::StaticSymbolSourceBuilder()
+                           .AddPublicSymbol("android:attr/id", ResourceId(0x010100d0),
+                                            test::AttributeBuilder().Build())
+                           .AddSymbol("com.app.test:id/id", ResourceId(0x7f020000))
+                           .AddPublicSymbol("android:attr/paddingStart", ResourceId(0x010103b3),
+                                            test::AttributeBuilder().Build())
+                           .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
+                                            test::AttributeBuilder().Build())
+                           .AddSymbol("com.app.test.feature:id/foo", ResourceId(0x80020000))
+                           .AddSymbol("com.app.test.feature:attr/foo", ResourceId(0x80010000),
+                                      test::AttributeBuilder().Build())
+                           .Build())
+                   .Build();
+  }
+
+  ::testing::AssertionResult Flatten(xml::XmlResource* doc, android::ResXMLTree* out_tree,
+                                     const XmlFlattenerOptions& options = {}) {
+    using namespace android;  // For NO_ERROR on windows because it is a macro.
+
+    BigBuffer buffer(1024);
+    XmlFlattener flattener(&buffer, options);
+    if (!flattener.Consume(context_.get(), doc)) {
+      return ::testing::AssertionFailure() << "failed to flatten XML Tree";
+    }
+
+    std::unique_ptr<uint8_t[]> data = util::Copy(buffer);
+    if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) {
+      return ::testing::AssertionFailure() << "flattened XML is corrupt";
+    }
+    return ::testing::AssertionSuccess();
+  }
+
+ protected:
+  std::unique_ptr<test::Context> context_;
+};
+
+TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
+      <View xmlns:test="http://com.test" attr="hey">
+          <Layout test:hello="hi" />
+          <Layout>Some text\\</Layout>
+      </View>)");
+
+  android::ResXMLTree tree;
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
+
+  size_t len;
+  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
+  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
+  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
+  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
+
+  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
+  EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull());
+  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr"));
+
+  const StringPiece16 kAttr(u"attr");
+  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
+  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
+  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
+
+  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
+  EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test"));
+  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello"));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
+
+  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
+  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
+  ASSERT_THAT(tree.getAttributeCount(), Eq(0u));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
+  EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\"));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
+  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
+  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
+  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
+  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE));
+  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
+  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT));
+}
+
+TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
+      <View xmlns:tools="http://schemas.android.com/tools"
+          xmlns:foo="http://schemas.android.com/foo"
+          foo:bar="Foo"
+          tools:ignore="MissingTranslation"/>)");
+
+  android::ResXMLTree tree;
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));
+
+  size_t len;
+  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo"));
+  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo"));
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
+
+  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
+              Eq(android::NAME_NOT_FOUND));
+  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0));
+}
+
+TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@id/id"
+          class="str"
+          style="@id/id"/>)");
+
+  android::ResXMLTree tree;
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+
+  while (tree.next() != android::ResXMLTree::START_TAG) {
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+  }
+
+  EXPECT_THAT(tree.indexOfClass(), Eq(0));
+  EXPECT_THAT(tree.indexOfStyle(), Eq(1));
+}
+
+// The device ResXMLParser in libandroidfw differentiates between empty namespace and null
+// namespace.
+TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)");
+
+  android::ResXMLTree tree;
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+
+  while (tree.next() != android::ResXMLTree::START_TAG) {
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+  }
+
+  const StringPiece16 kPackage = u"package";
+  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0));
+}
+
+TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)");
+
+  android::ResXMLTree tree;
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+
+  while (tree.next() != android::ResXMLTree::START_TAG) {
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+  }
+
+  const StringPiece16 kPackage = u"package";
+  ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
+  ASSERT_THAT(idx, Ge(0));
+
+  size_t len;
+  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull());
+}
+
+TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
+  context_->SetCompilationPackage("com.app.test.feature");
+  context_->SetPackageId(0x80);
+  context_->SetNameManglerPolicy({"com.app.test.feature"});
+
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            android:id="@id/foo"
+            app:foo="@id/foo" />)");
+
+  XmlReferenceLinker linker;
+  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
+
+  // The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f).
+  android::DynamicRefTable dynamic_ref_table;
+  dynamic_ref_table.addMapping(0x80, 0x80);
+
+  android::ResXMLTree tree(&dynamic_ref_table);
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+
+  while (tree.next() != android::ResXMLTree::START_TAG) {
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+  }
+
+  ssize_t idx;
+
+  idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id");
+  ASSERT_THAT(idx, Ge(0));
+  EXPECT_THAT(tree.indexOfID(), Eq(idx));
+  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u));
+
+  idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo");
+  ASSERT_THAT(idx, Ge(0));
+  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u));
+  EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE));
+  EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000)));
+}
+
+TEST_F(XmlFlattenerTest, ProcessEscapedStrings) {
+  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
+      R"(<element value="\?hello" pattern="\\d{5}" other="&quot;">\\d{5}</element>)");
+
+  android::ResXMLTree tree;
+  ASSERT_TRUE(Flatten(doc.get(), &tree));
+
+  while (tree.next() != android::ResXMLTree::START_TAG) {
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
+    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
+  }
+
+  const StringPiece16 kValue = u"value";
+  const StringPiece16 kPattern = u"pattern";
+  const StringPiece16 kOther = u"other";
+
+  size_t len;
+  ssize_t idx;
+
+  idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size());
+  ASSERT_THAT(idx, Ge(0));
+  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello"));
+
+  idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size());
+  ASSERT_THAT(idx, Ge(0));
+  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}"));
+
+  idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size());
+  ASSERT_THAT(idx, Ge(0));
+  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\""));
+
+  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
+  EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}"));
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
new file mode 100644
index 0000000..86bd865
--- /dev/null
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -0,0 +1,864 @@
+/*
+ * Copyright (C) 2016 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 "format/proto/ProtoDeserialize.h"
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "Locale.h"
+#include "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+
+using ::android::ResStringPool;
+
+namespace aapt {
+
+namespace {
+
+class ReferenceIdToNameVisitor : public DescendingValueVisitor {
+ public:
+  using DescendingValueVisitor::Visit;
+
+  explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping)
+      : mapping_(mapping) {
+    CHECK(mapping_ != nullptr);
+  }
+
+  void Visit(Reference* reference) override {
+    if (!reference->id || !reference->id.value().is_valid()) {
+      return;
+    }
+
+    ResourceId id = reference->id.value();
+    auto cache_iter = mapping_->find(id);
+    if (cache_iter != mapping_->end()) {
+      reference->name = cache_iter->second.ToResourceName();
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor);
+
+  const std::map<ResourceId, ResourceNameRef>* mapping_;
+};
+
+}  // namespace
+
+bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config,
+                             std::string* out_error) {
+  out_config->mcc = static_cast<uint16_t>(pb_config.mcc());
+  out_config->mnc = static_cast<uint16_t>(pb_config.mnc());
+
+  if (!pb_config.locale().empty()) {
+    LocaleValue lv;
+    if (!lv.InitFromBcp47Tag(pb_config.locale())) {
+      std::ostringstream error;
+      error << "configuration has invalid locale '" << pb_config.locale() << "'";
+      *out_error = error.str();
+      return false;
+    }
+    lv.WriteTo(out_config);
+  }
+
+  switch (pb_config.layout_direction()) {
+    case pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_LTR:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_LAYOUTDIR) |
+                                 ConfigDescription::LAYOUTDIR_LTR;
+      break;
+
+    case pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_RTL:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_LAYOUTDIR) |
+                                 ConfigDescription::LAYOUTDIR_RTL;
+      break;
+
+    default:
+      break;
+  }
+
+  out_config->smallestScreenWidthDp = static_cast<uint16_t>(pb_config.smallest_screen_width_dp());
+  out_config->screenWidthDp = static_cast<uint16_t>(pb_config.screen_width_dp());
+  out_config->screenHeightDp = static_cast<uint16_t>(pb_config.screen_height_dp());
+
+  switch (pb_config.screen_layout_size()) {
+    case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_SMALL:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) |
+                                 ConfigDescription::SCREENSIZE_SMALL;
+      break;
+
+    case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_NORMAL:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) |
+                                 ConfigDescription::SCREENSIZE_NORMAL;
+      break;
+
+    case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_LARGE:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) |
+                                 ConfigDescription::SCREENSIZE_LARGE;
+      break;
+
+    case pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_XLARGE:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENSIZE) |
+                                 ConfigDescription::SCREENSIZE_XLARGE;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.screen_layout_long()) {
+    case pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_LONG:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENLONG) |
+                                 ConfigDescription::SCREENLONG_YES;
+      break;
+
+    case pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_NOTLONG:
+      out_config->screenLayout = (out_config->screenLayout & ~ConfigDescription::MASK_SCREENLONG) |
+                                 ConfigDescription::SCREENLONG_NO;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.screen_round()) {
+    case pb::Configuration_ScreenRound_SCREEN_ROUND_ROUND:
+      out_config->screenLayout2 =
+          (out_config->screenLayout2 & ~ConfigDescription::MASK_SCREENROUND) |
+          ConfigDescription::SCREENROUND_YES;
+      break;
+
+    case pb::Configuration_ScreenRound_SCREEN_ROUND_NOTROUND:
+      out_config->screenLayout2 =
+          (out_config->screenLayout2 & ~ConfigDescription::MASK_SCREENROUND) |
+          ConfigDescription::SCREENROUND_NO;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.wide_color_gamut()) {
+    case pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_WIDECG:
+      out_config->colorMode = (out_config->colorMode & ~ConfigDescription::MASK_WIDE_COLOR_GAMUT) |
+                              ConfigDescription::WIDE_COLOR_GAMUT_YES;
+      break;
+
+    case pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_NOWIDECG:
+      out_config->colorMode = (out_config->colorMode & ~ConfigDescription::MASK_WIDE_COLOR_GAMUT) |
+                              ConfigDescription::WIDE_COLOR_GAMUT_NO;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.hdr()) {
+    case pb::Configuration_Hdr_HDR_HIGHDR:
+      out_config->colorMode =
+          (out_config->colorMode & ~ConfigDescription::MASK_HDR) | ConfigDescription::HDR_YES;
+      break;
+
+    case pb::Configuration_Hdr_HDR_LOWDR:
+      out_config->colorMode =
+          (out_config->colorMode & ~ConfigDescription::MASK_HDR) | ConfigDescription::HDR_NO;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.orientation()) {
+    case pb::Configuration_Orientation_ORIENTATION_PORT:
+      out_config->orientation = ConfigDescription::ORIENTATION_PORT;
+      break;
+
+    case pb::Configuration_Orientation_ORIENTATION_LAND:
+      out_config->orientation = ConfigDescription::ORIENTATION_LAND;
+      break;
+
+    case pb::Configuration_Orientation_ORIENTATION_SQUARE:
+      out_config->orientation = ConfigDescription::ORIENTATION_SQUARE;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.ui_mode_type()) {
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_NORMAL:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_NORMAL;
+      break;
+
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_DESK:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_DESK;
+      break;
+
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_CAR:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_CAR;
+      break;
+
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_TELEVISION:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_TELEVISION;
+      break;
+
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_APPLIANCE:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_APPLIANCE;
+      break;
+
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_WATCH:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_WATCH;
+      break;
+
+    case pb::Configuration_UiModeType_UI_MODE_TYPE_VRHEADSET:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_TYPE) |
+                           ConfigDescription::UI_MODE_TYPE_VR_HEADSET;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.ui_mode_night()) {
+    case pb::Configuration_UiModeNight_UI_MODE_NIGHT_NIGHT:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_NIGHT) |
+                           ConfigDescription::UI_MODE_NIGHT_YES;
+      break;
+
+    case pb::Configuration_UiModeNight_UI_MODE_NIGHT_NOTNIGHT:
+      out_config->uiMode = (out_config->uiMode & ~ConfigDescription::MASK_UI_MODE_NIGHT) |
+                           ConfigDescription::UI_MODE_NIGHT_NO;
+      break;
+
+    default:
+      break;
+  }
+
+  out_config->density = static_cast<uint16_t>(pb_config.density());
+
+  switch (pb_config.touchscreen()) {
+    case pb::Configuration_Touchscreen_TOUCHSCREEN_NOTOUCH:
+      out_config->touchscreen = ConfigDescription::TOUCHSCREEN_NOTOUCH;
+      break;
+
+    case pb::Configuration_Touchscreen_TOUCHSCREEN_STYLUS:
+      out_config->touchscreen = ConfigDescription::TOUCHSCREEN_STYLUS;
+      break;
+
+    case pb::Configuration_Touchscreen_TOUCHSCREEN_FINGER:
+      out_config->touchscreen = ConfigDescription::TOUCHSCREEN_FINGER;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.keys_hidden()) {
+    case pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSEXPOSED:
+      out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_KEYSHIDDEN) |
+                               ConfigDescription::KEYSHIDDEN_NO;
+      break;
+
+    case pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSHIDDEN:
+      out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_KEYSHIDDEN) |
+                               ConfigDescription::KEYSHIDDEN_YES;
+      break;
+
+    case pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSSOFT:
+      out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_KEYSHIDDEN) |
+                               ConfigDescription::KEYSHIDDEN_SOFT;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.keyboard()) {
+    case pb::Configuration_Keyboard_KEYBOARD_NOKEYS:
+      out_config->keyboard = ConfigDescription::KEYBOARD_NOKEYS;
+      break;
+
+    case pb::Configuration_Keyboard_KEYBOARD_QWERTY:
+      out_config->keyboard = ConfigDescription::KEYBOARD_QWERTY;
+      break;
+
+    case pb::Configuration_Keyboard_KEYBOARD_TWELVEKEY:
+      out_config->keyboard = ConfigDescription::KEYBOARD_12KEY;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.nav_hidden()) {
+    case pb::Configuration_NavHidden_NAV_HIDDEN_NAVEXPOSED:
+      out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_NAVHIDDEN) |
+                               ConfigDescription::NAVHIDDEN_NO;
+      break;
+
+    case pb::Configuration_NavHidden_NAV_HIDDEN_NAVHIDDEN:
+      out_config->inputFlags = (out_config->inputFlags & ~ConfigDescription::MASK_NAVHIDDEN) |
+                               ConfigDescription::NAVHIDDEN_YES;
+      break;
+
+    default:
+      break;
+  }
+
+  switch (pb_config.navigation()) {
+    case pb::Configuration_Navigation_NAVIGATION_NONAV:
+      out_config->navigation = ConfigDescription::NAVIGATION_NONAV;
+      break;
+
+    case pb::Configuration_Navigation_NAVIGATION_DPAD:
+      out_config->navigation = ConfigDescription::NAVIGATION_DPAD;
+      break;
+
+    case pb::Configuration_Navigation_NAVIGATION_TRACKBALL:
+      out_config->navigation = ConfigDescription::NAVIGATION_TRACKBALL;
+      break;
+
+    case pb::Configuration_Navigation_NAVIGATION_WHEEL:
+      out_config->navigation = ConfigDescription::NAVIGATION_WHEEL;
+      break;
+
+    default:
+      break;
+  }
+
+  out_config->screenWidth = static_cast<uint16_t>(pb_config.screen_width());
+  out_config->screenHeight = static_cast<uint16_t>(pb_config.screen_height());
+  out_config->sdkVersion = static_cast<uint16_t>(pb_config.sdk_version());
+  return true;
+}
+
+static void DeserializeSourceFromPb(const pb::Source& pb_source, const ResStringPool& src_pool,
+                                    Source* out_source) {
+  out_source->path = util::GetString(src_pool, pb_source.path_idx());
+  out_source->line = static_cast<size_t>(pb_source.position().line_number());
+}
+
+static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility& pb_visibility) {
+  switch (pb_visibility) {
+    case pb::SymbolStatus_Visibility_PRIVATE:
+      return SymbolState::kPrivate;
+    case pb::SymbolStatus_Visibility_PUBLIC:
+      return SymbolState::kPublic;
+    default:
+      break;
+  }
+  return SymbolState::kUndefined;
+}
+
+static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
+                                     ResourceTable* out_table, std::string* out_error) {
+  Maybe<uint8_t> id;
+  if (pb_package.has_package_id()) {
+    id = static_cast<uint8_t>(pb_package.package_id().id());
+  }
+
+  std::map<ResourceId, ResourceNameRef> id_index;
+
+  ResourceTablePackage* pkg = out_table->CreatePackage(pb_package.package_name(), id);
+  for (const pb::Type& pb_type : pb_package.type()) {
+    const ResourceType* res_type = ParseResourceType(pb_type.name());
+    if (res_type == nullptr) {
+      std::ostringstream error;
+      error << "unknown type '" << pb_type.name() << "'";
+      *out_error = error.str();
+      return false;
+    }
+
+    ResourceTableType* type = pkg->FindOrCreateType(*res_type);
+    if (pb_type.has_type_id()) {
+      type->id = static_cast<uint8_t>(pb_type.type_id().id());
+    }
+
+    for (const pb::Entry& pb_entry : pb_type.entry()) {
+      ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name());
+      if (pb_entry.has_entry_id()) {
+        entry->id = static_cast<uint16_t>(pb_entry.entry_id().id());
+      }
+
+      // Deserialize the symbol status (public/private with source and comments).
+      if (pb_entry.has_symbol_status()) {
+        const pb::SymbolStatus& pb_status = pb_entry.symbol_status();
+        if (pb_status.has_source()) {
+          DeserializeSourceFromPb(pb_status.source(), src_pool, &entry->symbol_status.source);
+        }
+
+        entry->symbol_status.comment = pb_status.comment();
+        entry->symbol_status.allow_new = pb_status.allow_new();
+
+        const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
+        entry->symbol_status.state = visibility;
+        if (visibility == SymbolState::kPublic) {
+          // Propagate the public visibility up to the Type.
+          type->symbol_status.state = SymbolState::kPublic;
+        } else if (visibility == SymbolState::kPrivate) {
+          // Only propagate if no previous state was assigned.
+          if (type->symbol_status.state == SymbolState::kUndefined) {
+            type->symbol_status.state = SymbolState::kPrivate;
+          }
+        }
+      }
+
+      ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
+                       pb_entry.entry_id().id());
+      if (resid.is_valid()) {
+        id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name);
+      }
+
+      for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) {
+        const pb::Configuration& pb_config = pb_config_value.config();
+
+        ConfigDescription config;
+        if (!DeserializeConfigFromPb(pb_config, &config, out_error)) {
+          return false;
+        }
+
+        ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product());
+        if (config_value->value != nullptr) {
+          *out_error = "duplicate configuration in resource table";
+          return false;
+        }
+
+        config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
+                                                     &out_table->string_pool, out_error);
+        if (config_value->value == nullptr) {
+          return false;
+        }
+      }
+    }
+  }
+
+  ReferenceIdToNameVisitor visitor(&id_index);
+  VisitAllValuesInPackage(pkg, &visitor);
+  return true;
+}
+
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
+                            std::string* out_error) {
+  // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
+  // causes errors when qualifying it with android::
+  using namespace android;
+
+  ResStringPool source_pool;
+  if (pb_table.has_source_pool()) {
+    status_t result = source_pool.setTo(pb_table.source_pool().data().data(),
+                                        pb_table.source_pool().data().size());
+    if (result != NO_ERROR) {
+      *out_error = "invalid source pool";
+      return false;
+    }
+  }
+
+  for (const pb::Package& pb_package : pb_table.package()) {
+    if (!DeserializePackageFromPb(pb_package, source_pool, out_table, out_error)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+static ResourceFile::Type DeserializeFileReferenceTypeFromPb(const pb::FileReference::Type& type) {
+  switch (type) {
+    case pb::FileReference::BINARY_XML:
+      return ResourceFile::Type::kBinaryXml;
+    case pb::FileReference::PROTO_XML:
+      return ResourceFile::Type::kProtoXml;
+    case pb::FileReference::PNG:
+      return ResourceFile::Type::kPng;
+    default:
+      return ResourceFile::Type::kUnknown;
+  }
+}
+
+bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
+                                   ResourceFile* out_file, std::string* out_error) {
+  ResourceNameRef name_ref;
+  if (!ResourceUtils::ParseResourceName(pb_file.resource_name(), &name_ref)) {
+    std::ostringstream error;
+    error << "invalid resource name in compiled file header: " << pb_file.resource_name();
+    *out_error = error.str();
+    return false;
+  }
+
+  out_file->name = name_ref.ToResourceName();
+  out_file->source.path = pb_file.source_path();
+  out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+
+  std::string config_error;
+  if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) {
+    std::ostringstream error;
+    error << "invalid resource configuration in compiled file header: " << config_error;
+    *out_error = error.str();
+    return false;
+  }
+
+  for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) {
+    if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) {
+      std::ostringstream error;
+      error << "invalid resource name for exported symbol in compiled file header: "
+            << pb_file.resource_name();
+      *out_error = error.str();
+      return false;
+    }
+
+    size_t line = 0u;
+    if (pb_symbol.has_source()) {
+      line = pb_symbol.source().line_number();
+    }
+    out_file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line});
+  }
+  return true;
+}
+
+static Reference::Type DeserializeReferenceTypeFromPb(const pb::Reference_Type& pb_type) {
+  switch (pb_type) {
+    case pb::Reference_Type_REFERENCE:
+      return Reference::Type::kResource;
+    case pb::Reference_Type_ATTRIBUTE:
+      return Reference::Type::kAttribute;
+    default:
+      break;
+  }
+  return Reference::Type::kResource;
+}
+
+static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* out_ref,
+                                       std::string* out_error) {
+  out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
+  out_ref->private_reference = pb_ref.private_();
+
+  if (pb_ref.id() != 0) {
+    out_ref->id = ResourceId(pb_ref.id());
+  }
+
+  if (!pb_ref.name().empty()) {
+    ResourceNameRef name_ref;
+    if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) {
+      std::ostringstream error;
+      error << "reference has invalid resource name '" << pb_ref.name() << "'";
+      *out_error = error.str();
+      return false;
+    }
+    out_ref->name = name_ref.ToResourceName();
+  }
+  return true;
+}
+
+template <typename T>
+static void DeserializeItemMetaDataFromPb(const T& pb_item, const android::ResStringPool& src_pool,
+                                          Value* out_value) {
+  if (pb_item.has_source()) {
+    Source source;
+    DeserializeSourceFromPb(pb_item.source(), src_pool, &source);
+    out_value->SetSource(std::move(source));
+  }
+  out_value->SetComment(pb_item.comment());
+}
+
+static size_t DeserializePluralEnumFromPb(const pb::Plural_Arity& arity) {
+  switch (arity) {
+    case pb::Plural_Arity_ZERO:
+      return Plural::Zero;
+    case pb::Plural_Arity_ONE:
+      return Plural::One;
+    case pb::Plural_Arity_TWO:
+      return Plural::Two;
+    case pb::Plural_Arity_FEW:
+      return Plural::Few;
+    case pb::Plural_Arity_MANY:
+      return Plural::Many;
+    default:
+      break;
+  }
+  return Plural::Other;
+}
+
+std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
+                                              const android::ResStringPool& src_pool,
+                                              const ConfigDescription& config,
+                                              StringPool* value_pool, std::string* out_error) {
+  std::unique_ptr<Value> value;
+  if (pb_value.has_item()) {
+    value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, out_error);
+    if (value == nullptr) {
+      return {};
+    }
+
+  } else if (pb_value.has_compound_value()) {
+    const pb::CompoundValue& pb_compound_value = pb_value.compound_value();
+    switch (pb_compound_value.value_case()) {
+      case pb::CompoundValue::kAttr: {
+        const pb::Attribute& pb_attr = pb_compound_value.attr();
+        std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();
+        attr->type_mask = pb_attr.format_flags();
+        attr->min_int = pb_attr.min_int();
+        attr->max_int = pb_attr.max_int();
+        for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) {
+          Attribute::Symbol symbol;
+          DeserializeItemMetaDataFromPb(pb_symbol, src_pool, &symbol.symbol);
+          if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol, out_error)) {
+            return {};
+          }
+          symbol.value = pb_symbol.value();
+          attr->symbols.push_back(std::move(symbol));
+        }
+        value = std::move(attr);
+      } break;
+
+      case pb::CompoundValue::kStyle: {
+        const pb::Style& pb_style = pb_compound_value.style();
+        std::unique_ptr<Style> style = util::make_unique<Style>();
+        if (pb_style.has_parent()) {
+          style->parent = Reference();
+          if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value(), out_error)) {
+            return {};
+          }
+
+          if (pb_style.has_parent_source()) {
+            Source parent_source;
+            DeserializeSourceFromPb(pb_style.parent_source(), src_pool, &parent_source);
+            style->parent.value().SetSource(std::move(parent_source));
+          }
+        }
+
+        for (const pb::Style_Entry& pb_entry : pb_style.entry()) {
+          Style::Entry entry;
+          if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key, out_error)) {
+            return {};
+          }
+          DeserializeItemMetaDataFromPb(pb_entry, src_pool, &entry.key);
+          entry.value =
+              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          if (entry.value == nullptr) {
+            return {};
+          }
+
+          // Copy the meta-data into the value as well.
+          DeserializeItemMetaDataFromPb(pb_entry, src_pool, entry.value.get());
+          style->entries.push_back(std::move(entry));
+        }
+        value = std::move(style);
+      } break;
+
+      case pb::CompoundValue::kStyleable: {
+        const pb::Styleable& pb_styleable = pb_compound_value.styleable();
+        std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+        for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) {
+          Reference attr_ref;
+          DeserializeItemMetaDataFromPb(pb_entry, src_pool, &attr_ref);
+          DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref, out_error);
+          styleable->entries.push_back(std::move(attr_ref));
+        }
+        value = std::move(styleable);
+      } break;
+
+      case pb::CompoundValue::kArray: {
+        const pb::Array& pb_array = pb_compound_value.array();
+        std::unique_ptr<Array> array = util::make_unique<Array>();
+        for (const pb::Array_Element& pb_entry : pb_array.element()) {
+          std::unique_ptr<Item> item =
+              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          if (item == nullptr) {
+            return {};
+          }
+
+          DeserializeItemMetaDataFromPb(pb_entry, src_pool, item.get());
+          array->elements.push_back(std::move(item));
+        }
+        value = std::move(array);
+      } break;
+
+      case pb::CompoundValue::kPlural: {
+        const pb::Plural& pb_plural = pb_compound_value.plural();
+        std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+        for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {
+          size_t plural_idx = DeserializePluralEnumFromPb(pb_entry.arity());
+          plural->values[plural_idx] =
+              DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+          if (!plural->values[plural_idx]) {
+            return {};
+          }
+
+          DeserializeItemMetaDataFromPb(pb_entry, src_pool, plural->values[plural_idx].get());
+        }
+        value = std::move(plural);
+      } break;
+
+      default:
+        LOG(FATAL) << "unknown compound value: " << (int)pb_compound_value.value_case();
+        break;
+    }
+  } else {
+    LOG(FATAL) << "unknown value: " << (int)pb_value.value_case();
+    return {};
+  }
+
+  CHECK(value) << "forgot to set value";
+
+  value->SetWeak(pb_value.weak());
+  DeserializeItemMetaDataFromPb(pb_value, src_pool, value.get());
+  return value;
+}
+
+std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
+                                            const android::ResStringPool& src_pool,
+                                            const ConfigDescription& config, StringPool* value_pool,
+                                            std::string* out_error) {
+  switch (pb_item.value_case()) {
+    case pb::Item::kRef: {
+      const pb::Reference& pb_ref = pb_item.ref();
+      std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+      if (!DeserializeReferenceFromPb(pb_ref, ref.get(), out_error)) {
+        return {};
+      }
+      return std::move(ref);
+    } break;
+
+    case pb::Item::kPrim: {
+      const pb::Primitive& pb_prim = pb_item.prim();
+      return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()),
+                                                pb_prim.data());
+    } break;
+
+    case pb::Item::kId: {
+      return util::make_unique<Id>();
+    } break;
+
+    case pb::Item::kStr: {
+      return util::make_unique<String>(
+          value_pool->MakeRef(pb_item.str().value(), StringPool::Context(config)));
+    } break;
+
+    case pb::Item::kRawStr: {
+      return util::make_unique<RawString>(
+          value_pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config)));
+    } break;
+
+    case pb::Item::kStyledStr: {
+      const pb::StyledString& pb_str = pb_item.styled_str();
+      StyleString style_str{pb_str.value()};
+      for (const pb::StyledString::Span& pb_span : pb_str.span()) {
+        style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()});
+      }
+      return util::make_unique<StyledString>(value_pool->MakeRef(
+          style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
+    } break;
+
+    case pb::Item::kFile: {
+      const pb::FileReference& pb_file = pb_item.file();
+      std::unique_ptr<FileReference> file_ref =
+          util::make_unique<FileReference>(value_pool->MakeRef(
+              pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
+      file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+      return std::move(file_ref);
+    } break;
+
+    default:
+      LOG(FATAL) << "unknown item: " << (int)pb_item.value_case();
+      break;
+  }
+  return {};
+}
+
+std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
+                                                               std::string* out_error) {
+  if (!pb_node.has_element()) {
+    return {};
+  }
+
+  std::unique_ptr<xml::XmlResource> resource = util::make_unique<xml::XmlResource>();
+  resource->root = util::make_unique<xml::Element>();
+  if (!DeserializeXmlFromPb(pb_node, resource->root.get(), &resource->string_pool, out_error)) {
+    return {};
+  }
+  return resource;
+}
+
+bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, StringPool* value_pool,
+                          std::string* out_error) {
+  const pb::XmlElement& pb_el = pb_node.element();
+  out_el->name = pb_el.name();
+  out_el->namespace_uri = pb_el.namespace_uri();
+  out_el->line_number = pb_node.source().line_number();
+  out_el->column_number = pb_node.source().column_number();
+
+  for (const pb::XmlNamespace& pb_ns : pb_el.namespace_declaration()) {
+    xml::NamespaceDecl decl;
+    decl.uri = pb_ns.uri();
+    decl.prefix = pb_ns.prefix();
+    decl.line_number = pb_ns.source().line_number();
+    decl.column_number = pb_ns.source().column_number();
+    out_el->namespace_decls.push_back(std::move(decl));
+  }
+
+  for (const pb::XmlAttribute& pb_attr : pb_el.attribute()) {
+    xml::Attribute attr;
+    attr.name = pb_attr.name();
+    attr.namespace_uri = pb_attr.namespace_uri();
+    attr.value = pb_attr.value();
+    if (pb_attr.resource_id() != 0u) {
+      attr.compiled_attribute = xml::AaptAttribute{Attribute(), ResourceId(pb_attr.resource_id())};
+    }
+    if (pb_attr.has_compiled_item()) {
+      attr.compiled_value =
+          DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, out_error);
+      if (attr.compiled_value == nullptr) {
+        return {};
+      }
+      attr.compiled_value->SetSource(Source().WithLine(pb_attr.source().line_number()));
+    }
+    out_el->attributes.push_back(std::move(attr));
+  }
+
+  // Deserialize the children.
+  for (const pb::XmlNode& pb_child : pb_el.child()) {
+    switch (pb_child.node_case()) {
+      case pb::XmlNode::NodeCase::kText: {
+        std::unique_ptr<xml::Text> text = util::make_unique<xml::Text>();
+        text->line_number = pb_child.source().line_number();
+        text->column_number = pb_child.source().column_number();
+        text->text = pb_child.text();
+        out_el->AppendChild(std::move(text));
+      } break;
+
+      case pb::XmlNode::NodeCase::kElement: {
+        std::unique_ptr<xml::Element> child_el = util::make_unique<xml::Element>();
+        if (!DeserializeXmlFromPb(pb_child, child_el.get(), value_pool, out_error)) {
+          return false;
+        }
+        out_el->AppendChild(std::move(child_el));
+      } break;
+
+      default:
+        LOG(FATAL) << "unknown XmlNode " << (int)pb_child.node_case();
+        break;
+    }
+  }
+  return true;
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
new file mode 100644
index 0000000..7dc54f2
--- /dev/null
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -0,0 +1,61 @@
+/*
+ * 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 AAPT_FORMAT_PROTO_PROTODESERIALIZE_H
+#define AAPT_FORMAT_PROTO_PROTODESERIALIZE_H
+
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "ConfigDescription.h"
+#include "Configuration.pb.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Resources.pb.h"
+#include "ResourcesInternal.pb.h"
+#include "StringPool.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
+                                              const android::ResStringPool& src_pool,
+                                              const ConfigDescription& config,
+                                              StringPool* value_pool, std::string* out_error);
+
+std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
+                                            const android::ResStringPool& src_pool,
+                                            const ConfigDescription& config, StringPool* value_pool,
+                                            std::string* out_error);
+
+std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
+                                                               std::string* out_error);
+
+bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, StringPool* value_pool,
+                          std::string* out_error);
+
+bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config,
+                             std::string* out_error);
+
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
+                            std::string* out_error);
+
+bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
+                                   ResourceFile* out_file, std::string* out_error);
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_PROTO_PROTODESERIALIZE_H */
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
new file mode 100644
index 0000000..1d184fe
--- /dev/null
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -0,0 +1,593 @@
+/*
+ * Copyright (C) 2016 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 "format/proto/ProtoSerialize.h"
+
+#include "ValueVisitor.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) {
+  BigBuffer buffer(1024);
+  StringPool::FlattenUtf8(&buffer, pool);
+
+  std::string* data = out_pb_pool->mutable_data();
+  data->reserve(buffer.size());
+
+  size_t offset = 0;
+  for (const BigBuffer::Block& block : buffer) {
+    data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
+    offset += block.size;
+  }
+}
+
+void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) {
+  StringPool::Ref ref = src_pool->MakeRef(source.path);
+  out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index()));
+  if (source.line) {
+    out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value()));
+  }
+}
+
+static pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
+  switch (state) {
+    case SymbolState::kPrivate:
+      return pb::SymbolStatus_Visibility_PRIVATE;
+    case SymbolState::kPublic:
+      return pb::SymbolStatus_Visibility_PUBLIC;
+    default:
+      break;
+  }
+  return pb::SymbolStatus_Visibility_UNKNOWN;
+}
+
+void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config) {
+  out_pb_config->set_mcc(config.mcc);
+  out_pb_config->set_mnc(config.mnc);
+  out_pb_config->set_locale(config.GetBcp47LanguageTag());
+
+  switch (config.screenLayout & ConfigDescription::MASK_LAYOUTDIR) {
+    case ConfigDescription::LAYOUTDIR_LTR:
+      out_pb_config->set_layout_direction(pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_LTR);
+      break;
+
+    case ConfigDescription::LAYOUTDIR_RTL:
+      out_pb_config->set_layout_direction(pb::Configuration_LayoutDirection_LAYOUT_DIRECTION_RTL);
+      break;
+  }
+
+  out_pb_config->set_screen_width(config.screenWidth);
+  out_pb_config->set_screen_height(config.screenHeight);
+  out_pb_config->set_screen_width_dp(config.screenWidthDp);
+  out_pb_config->set_screen_height_dp(config.screenHeightDp);
+  out_pb_config->set_smallest_screen_width_dp(config.smallestScreenWidthDp);
+
+  switch (config.screenLayout & ConfigDescription::MASK_SCREENSIZE) {
+    case ConfigDescription::SCREENSIZE_SMALL:
+      out_pb_config->set_screen_layout_size(
+          pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_SMALL);
+      break;
+
+    case ConfigDescription::SCREENSIZE_NORMAL:
+      out_pb_config->set_screen_layout_size(
+          pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_NORMAL);
+      break;
+
+    case ConfigDescription::SCREENSIZE_LARGE:
+      out_pb_config->set_screen_layout_size(
+          pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_LARGE);
+      break;
+
+    case ConfigDescription::SCREENSIZE_XLARGE:
+      out_pb_config->set_screen_layout_size(
+          pb::Configuration_ScreenLayoutSize_SCREEN_LAYOUT_SIZE_XLARGE);
+      break;
+  }
+
+  switch (config.screenLayout & ConfigDescription::MASK_SCREENLONG) {
+    case ConfigDescription::SCREENLONG_YES:
+      out_pb_config->set_screen_layout_long(
+          pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_LONG);
+      break;
+
+    case ConfigDescription::SCREENLONG_NO:
+      out_pb_config->set_screen_layout_long(
+          pb::Configuration_ScreenLayoutLong_SCREEN_LAYOUT_LONG_NOTLONG);
+      break;
+  }
+
+  switch (config.screenLayout2 & ConfigDescription::MASK_SCREENROUND) {
+    case ConfigDescription::SCREENROUND_YES:
+      out_pb_config->set_screen_round(pb::Configuration_ScreenRound_SCREEN_ROUND_ROUND);
+      break;
+
+    case ConfigDescription::SCREENROUND_NO:
+      out_pb_config->set_screen_round(pb::Configuration_ScreenRound_SCREEN_ROUND_NOTROUND);
+      break;
+  }
+
+  switch (config.colorMode & ConfigDescription::MASK_WIDE_COLOR_GAMUT) {
+    case ConfigDescription::WIDE_COLOR_GAMUT_YES:
+      out_pb_config->set_wide_color_gamut(pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_WIDECG);
+      break;
+
+    case ConfigDescription::WIDE_COLOR_GAMUT_NO:
+      out_pb_config->set_wide_color_gamut(
+          pb::Configuration_WideColorGamut_WIDE_COLOR_GAMUT_NOWIDECG);
+      break;
+  }
+
+  switch (config.colorMode & ConfigDescription::MASK_HDR) {
+    case ConfigDescription::HDR_YES:
+      out_pb_config->set_hdr(pb::Configuration_Hdr_HDR_HIGHDR);
+      break;
+
+    case ConfigDescription::HDR_NO:
+      out_pb_config->set_hdr(pb::Configuration_Hdr_HDR_LOWDR);
+      break;
+  }
+
+  switch (config.orientation) {
+    case ConfigDescription::ORIENTATION_PORT:
+      out_pb_config->set_orientation(pb::Configuration_Orientation_ORIENTATION_PORT);
+      break;
+
+    case ConfigDescription::ORIENTATION_LAND:
+      out_pb_config->set_orientation(pb::Configuration_Orientation_ORIENTATION_LAND);
+      break;
+
+    case ConfigDescription::ORIENTATION_SQUARE:
+      out_pb_config->set_orientation(pb::Configuration_Orientation_ORIENTATION_SQUARE);
+      break;
+  }
+
+  switch (config.uiMode & ConfigDescription::MASK_UI_MODE_TYPE) {
+    case ConfigDescription::UI_MODE_TYPE_NORMAL:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_NORMAL);
+      break;
+
+    case ConfigDescription::UI_MODE_TYPE_DESK:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_DESK);
+      break;
+
+    case ConfigDescription::UI_MODE_TYPE_CAR:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_CAR);
+      break;
+
+    case ConfigDescription::UI_MODE_TYPE_TELEVISION:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_TELEVISION);
+      break;
+
+    case ConfigDescription::UI_MODE_TYPE_APPLIANCE:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_APPLIANCE);
+      break;
+
+    case ConfigDescription::UI_MODE_TYPE_WATCH:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_WATCH);
+      break;
+
+    case ConfigDescription::UI_MODE_TYPE_VR_HEADSET:
+      out_pb_config->set_ui_mode_type(pb::Configuration_UiModeType_UI_MODE_TYPE_VRHEADSET);
+      break;
+  }
+
+  switch (config.uiMode & ConfigDescription::MASK_UI_MODE_NIGHT) {
+    case ConfigDescription::UI_MODE_NIGHT_YES:
+      out_pb_config->set_ui_mode_night(pb::Configuration_UiModeNight_UI_MODE_NIGHT_NIGHT);
+      break;
+
+    case ConfigDescription::UI_MODE_NIGHT_NO:
+      out_pb_config->set_ui_mode_night(pb::Configuration_UiModeNight_UI_MODE_NIGHT_NOTNIGHT);
+      break;
+  }
+
+  out_pb_config->set_density(config.density);
+
+  switch (config.touchscreen) {
+    case ConfigDescription::TOUCHSCREEN_NOTOUCH:
+      out_pb_config->set_touchscreen(pb::Configuration_Touchscreen_TOUCHSCREEN_NOTOUCH);
+      break;
+
+    case ConfigDescription::TOUCHSCREEN_STYLUS:
+      out_pb_config->set_touchscreen(pb::Configuration_Touchscreen_TOUCHSCREEN_STYLUS);
+      break;
+
+    case ConfigDescription::TOUCHSCREEN_FINGER:
+      out_pb_config->set_touchscreen(pb::Configuration_Touchscreen_TOUCHSCREEN_FINGER);
+      break;
+  }
+
+  switch (config.inputFlags & ConfigDescription::MASK_KEYSHIDDEN) {
+    case ConfigDescription::KEYSHIDDEN_NO:
+      out_pb_config->set_keys_hidden(pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSEXPOSED);
+      break;
+
+    case ConfigDescription::KEYSHIDDEN_YES:
+      out_pb_config->set_keys_hidden(pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSHIDDEN);
+      break;
+
+    case ConfigDescription::KEYSHIDDEN_SOFT:
+      out_pb_config->set_keys_hidden(pb::Configuration_KeysHidden_KEYS_HIDDEN_KEYSSOFT);
+      break;
+  }
+
+  switch (config.keyboard) {
+    case ConfigDescription::KEYBOARD_NOKEYS:
+      out_pb_config->set_keyboard(pb::Configuration_Keyboard_KEYBOARD_NOKEYS);
+      break;
+
+    case ConfigDescription::KEYBOARD_QWERTY:
+      out_pb_config->set_keyboard(pb::Configuration_Keyboard_KEYBOARD_QWERTY);
+      break;
+
+    case ConfigDescription::KEYBOARD_12KEY:
+      out_pb_config->set_keyboard(pb::Configuration_Keyboard_KEYBOARD_TWELVEKEY);
+      break;
+  }
+
+  switch (config.inputFlags & ConfigDescription::MASK_NAVHIDDEN) {
+    case ConfigDescription::NAVHIDDEN_NO:
+      out_pb_config->set_nav_hidden(pb::Configuration_NavHidden_NAV_HIDDEN_NAVEXPOSED);
+      break;
+
+    case ConfigDescription::NAVHIDDEN_YES:
+      out_pb_config->set_nav_hidden(pb::Configuration_NavHidden_NAV_HIDDEN_NAVHIDDEN);
+      break;
+  }
+
+  switch (config.navigation) {
+    case ConfigDescription::NAVIGATION_NONAV:
+      out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_NONAV);
+      break;
+
+    case ConfigDescription::NAVIGATION_DPAD:
+      out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_DPAD);
+      break;
+
+    case ConfigDescription::NAVIGATION_TRACKBALL:
+      out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_TRACKBALL);
+      break;
+
+    case ConfigDescription::NAVIGATION_WHEEL:
+      out_pb_config->set_navigation(pb::Configuration_Navigation_NAVIGATION_WHEEL);
+      break;
+  }
+
+  out_pb_config->set_sdk_version(config.sdkVersion);
+}
+
+void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table) {
+  StringPool source_pool;
+  for (const std::unique_ptr<ResourceTablePackage>& package : table.packages) {
+    pb::Package* pb_package = out_table->add_package();
+    if (package->id) {
+      pb_package->mutable_package_id()->set_id(package->id.value());
+    }
+    pb_package->set_package_name(package->name);
+
+    for (const std::unique_ptr<ResourceTableType>& type : package->types) {
+      pb::Type* pb_type = pb_package->add_type();
+      if (type->id) {
+        pb_type->mutable_type_id()->set_id(type->id.value());
+      }
+      pb_type->set_name(ToString(type->type).to_string());
+
+      for (const std::unique_ptr<ResourceEntry>& entry : type->entries) {
+        pb::Entry* pb_entry = pb_type->add_entry();
+        if (entry->id) {
+          pb_entry->mutable_entry_id()->set_id(entry->id.value());
+        }
+        pb_entry->set_name(entry->name);
+
+        // Write the SymbolStatus struct.
+        pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
+        pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state));
+        SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source());
+        pb_status->set_comment(entry->symbol_status.comment);
+        pb_status->set_allow_new(entry->symbol_status.allow_new);
+
+        for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
+          pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
+          SerializeConfig(config_value->config, pb_config_value->mutable_config());
+          pb_config_value->mutable_config()->set_product(config_value->product);
+          SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), &source_pool);
+        }
+      }
+    }
+  }
+  SerializeStringPoolToPb(source_pool, out_table->mutable_source_pool());
+}
+
+static pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) {
+  switch (type) {
+    case Reference::Type::kResource:
+      return pb::Reference_Type_REFERENCE;
+    case Reference::Type::kAttribute:
+      return pb::Reference_Type_ATTRIBUTE;
+    default:
+      break;
+  }
+  return pb::Reference_Type_REFERENCE;
+}
+
+static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) {
+  pb_ref->set_id(ref.id.value_or_default(ResourceId(0x0)).id);
+
+  if (ref.name) {
+    pb_ref->set_name(ref.name.value().ToString());
+  }
+
+  pb_ref->set_private_(ref.private_reference);
+  pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+}
+
+template <typename T>
+static void SerializeItemMetaDataToPb(const Item& item, T* pb_item, StringPool* src_pool) {
+  if (src_pool != nullptr) {
+    SerializeSourceToPb(item.GetSource(), src_pool, pb_item->mutable_source());
+  }
+  pb_item->set_comment(item.GetComment());
+}
+
+static pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) {
+  switch (plural_idx) {
+    case Plural::Zero:
+      return pb::Plural_Arity_ZERO;
+    case Plural::One:
+      return pb::Plural_Arity_ONE;
+    case Plural::Two:
+      return pb::Plural_Arity_TWO;
+    case Plural::Few:
+      return pb::Plural_Arity_FEW;
+    case Plural::Many:
+      return pb::Plural_Arity_MANY;
+    default:
+      break;
+  }
+  return pb::Plural_Arity_OTHER;
+}
+
+static pb::FileReference::Type SerializeFileReferenceTypeToPb(const ResourceFile::Type& type) {
+  switch (type) {
+    case ResourceFile::Type::kBinaryXml:
+      return pb::FileReference::BINARY_XML;
+    case ResourceFile::Type::kProtoXml:
+      return pb::FileReference::PROTO_XML;
+    case ResourceFile::Type::kPng:
+      return pb::FileReference::PNG;
+    default:
+      return pb::FileReference::UNKNOWN;
+  }
+}
+
+namespace {
+
+class ValueSerializer : public ConstValueVisitor {
+ public:
+  using ConstValueVisitor::Visit;
+
+  ValueSerializer(pb::Value* out_value, StringPool* src_pool)
+      : out_value_(out_value), src_pool_(src_pool) {
+  }
+
+  void Visit(const Reference* ref) override {
+    SerializeReferenceToPb(*ref, out_value_->mutable_item()->mutable_ref());
+  }
+
+  void Visit(const String* str) override {
+    out_value_->mutable_item()->mutable_str()->set_value(*str->value);
+  }
+
+  void Visit(const RawString* str) override {
+    out_value_->mutable_item()->mutable_raw_str()->set_value(*str->value);
+  }
+
+  void Visit(const StyledString* str) override {
+    pb::StyledString* pb_str = out_value_->mutable_item()->mutable_styled_str();
+    pb_str->set_value(str->value->value);
+    for (const StringPool::Span& span : str->value->spans) {
+      pb::StyledString::Span* pb_span = pb_str->add_span();
+      pb_span->set_tag(*span.name);
+      pb_span->set_first_char(span.first_char);
+      pb_span->set_last_char(span.last_char);
+    }
+  }
+
+  void Visit(const FileReference* file) override {
+    pb::FileReference* pb_file = out_value_->mutable_item()->mutable_file();
+    pb_file->set_path(*file->path);
+    pb_file->set_type(SerializeFileReferenceTypeToPb(file->type));
+  }
+
+  void Visit(const Id* /*id*/) override {
+    out_value_->mutable_item()->mutable_id();
+  }
+
+  void Visit(const BinaryPrimitive* prim) override {
+    android::Res_value val = {};
+    prim->Flatten(&val);
+
+    pb::Primitive* pb_prim = out_value_->mutable_item()->mutable_prim();
+    pb_prim->set_type(val.dataType);
+    pb_prim->set_data(val.data);
+  }
+
+  void Visit(const Attribute* attr) override {
+    pb::Attribute* pb_attr = out_value_->mutable_compound_value()->mutable_attr();
+    pb_attr->set_format_flags(attr->type_mask);
+    pb_attr->set_min_int(attr->min_int);
+    pb_attr->set_max_int(attr->max_int);
+
+    for (auto& symbol : attr->symbols) {
+      pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol();
+      SerializeItemMetaDataToPb(symbol.symbol, pb_symbol, src_pool_);
+      SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());
+      pb_symbol->set_value(symbol.value);
+    }
+  }
+
+  void Visit(const Style* style) override {
+    pb::Style* pb_style = out_value_->mutable_compound_value()->mutable_style();
+    if (style->parent) {
+      const Reference& parent = style->parent.value();
+      SerializeReferenceToPb(parent, pb_style->mutable_parent());
+      if (src_pool_ != nullptr) {
+        SerializeSourceToPb(parent.GetSource(), src_pool_, pb_style->mutable_parent_source());
+      }
+    }
+
+    for (const Style::Entry& entry : style->entries) {
+      pb::Style_Entry* pb_entry = pb_style->add_entry();
+      SerializeReferenceToPb(entry.key, pb_entry->mutable_key());
+      SerializeItemMetaDataToPb(entry.key, pb_entry, src_pool_);
+      SerializeItemToPb(*entry.value, pb_entry->mutable_item());
+    }
+  }
+
+  void Visit(const Styleable* styleable) override {
+    pb::Styleable* pb_styleable = out_value_->mutable_compound_value()->mutable_styleable();
+    for (const Reference& entry : styleable->entries) {
+      pb::Styleable_Entry* pb_entry = pb_styleable->add_entry();
+      SerializeItemMetaDataToPb(entry, pb_entry, src_pool_);
+      SerializeReferenceToPb(entry, pb_entry->mutable_attr());
+    }
+  }
+
+  void Visit(const Array* array) override {
+    pb::Array* pb_array = out_value_->mutable_compound_value()->mutable_array();
+    for (const std::unique_ptr<Item>& element : array->elements) {
+      pb::Array_Element* pb_element = pb_array->add_element();
+      SerializeItemMetaDataToPb(*element, pb_element, src_pool_);
+      SerializeItemToPb(*element, pb_element->mutable_item());
+    }
+  }
+
+  void Visit(const Plural* plural) override {
+    pb::Plural* pb_plural = out_value_->mutable_compound_value()->mutable_plural();
+    const size_t count = plural->values.size();
+    for (size_t i = 0; i < count; i++) {
+      if (!plural->values[i]) {
+        // No plural value set here.
+        continue;
+      }
+
+      pb::Plural_Entry* pb_entry = pb_plural->add_entry();
+      pb_entry->set_arity(SerializePluralEnumToPb(i));
+      SerializeItemMetaDataToPb(*plural->values[i], pb_entry, src_pool_);
+      SerializeItemToPb(*plural->values[i], pb_entry->mutable_item());
+    }
+  }
+
+  void VisitAny(const Value* unknown) override {
+    LOG(FATAL) << "unimplemented value: " << *unknown;
+  }
+
+ private:
+  pb::Value* out_value_;
+  StringPool* src_pool_;
+};
+
+}  // namespace
+
+void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool) {
+  ValueSerializer serializer(out_value, src_pool);
+  value.Accept(&serializer);
+
+  // Serialize the meta-data of the Value.
+  out_value->set_comment(value.GetComment());
+  out_value->set_weak(value.IsWeak());
+  if (src_pool != nullptr) {
+    SerializeSourceToPb(value.GetSource(), src_pool, out_value->mutable_source());
+  }
+}
+
+void SerializeItemToPb(const Item& item, pb::Item* out_item) {
+  pb::Value value;
+  ValueSerializer serializer(&value, nullptr);
+  item.Accept(&serializer);
+  out_item->MergeFrom(value.item());
+}
+
+void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
+  out_file->set_resource_name(file.name.ToString());
+  out_file->set_source_path(file.source.path);
+  out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
+  SerializeConfig(file.config, out_file->mutable_config());
+
+  for (const SourcedResourceName& exported : file.exported_symbols) {
+    pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
+    pb_symbol->set_resource_name(exported.name.ToString());
+    pb_symbol->mutable_source()->set_line_number(exported.line);
+  }
+}
+
+static void SerializeXmlCommon(const xml::Node& node, pb::XmlNode* out_node) {
+  pb::SourcePosition* pb_src = out_node->mutable_source();
+  pb_src->set_line_number(node.line_number);
+  pb_src->set_column_number(node.column_number);
+}
+
+void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node) {
+  SerializeXmlCommon(el, out_node);
+
+  pb::XmlElement* pb_element = out_node->mutable_element();
+  pb_element->set_name(el.name);
+  pb_element->set_namespace_uri(el.namespace_uri);
+
+  for (const xml::NamespaceDecl& ns : el.namespace_decls) {
+    pb::XmlNamespace* pb_ns = pb_element->add_namespace_declaration();
+    pb_ns->set_prefix(ns.prefix);
+    pb_ns->set_uri(ns.uri);
+    pb::SourcePosition* pb_src = pb_ns->mutable_source();
+    pb_src->set_line_number(ns.line_number);
+    pb_src->set_column_number(ns.column_number);
+  }
+
+  for (const xml::Attribute& attr : el.attributes) {
+    pb::XmlAttribute* pb_attr = pb_element->add_attribute();
+    pb_attr->set_name(attr.name);
+    pb_attr->set_namespace_uri(attr.namespace_uri);
+    pb_attr->set_value(attr.value);
+    if (attr.compiled_attribute) {
+      const ResourceId attr_id = attr.compiled_attribute.value().id.value_or_default({});
+      pb_attr->set_resource_id(attr_id.id);
+    }
+    if (attr.compiled_value != nullptr) {
+      SerializeItemToPb(*attr.compiled_value, pb_attr->mutable_compiled_item());
+      pb::SourcePosition* pb_src = pb_attr->mutable_source();
+      pb_src->set_line_number(attr.compiled_value->GetSource().line.value_or_default(0));
+    }
+  }
+
+  for (const std::unique_ptr<xml::Node>& child : el.children) {
+    if (const xml::Element* child_el = xml::NodeCast<xml::Element>(child.get())) {
+      SerializeXmlToPb(*child_el, pb_element->add_child());
+    } else if (const xml::Text* text_el = xml::NodeCast<xml::Text>(child.get())) {
+      pb::XmlNode* pb_child_node = pb_element->add_child();
+      SerializeXmlCommon(*text_el, pb_child_node);
+      pb_child_node->set_text(text_el->text);
+    } else {
+      LOG(FATAL) << "unhandled XmlNode type";
+    }
+  }
+}
+
+void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node) {
+  SerializeXmlToPb(*resource.root, out_node);
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h
new file mode 100644
index 0000000..95dd413
--- /dev/null
+++ b/tools/aapt2/format/proto/ProtoSerialize.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 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 AAPT_FORMAT_PROTO_PROTOSERIALIZE_H
+#define AAPT_FORMAT_PROTO_PROTOSERIALIZE_H
+
+#include "android-base/macros.h"
+
+#include "ConfigDescription.h"
+#include "Configuration.pb.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "Resources.pb.h"
+#include "ResourcesInternal.pb.h"
+#include "StringPool.h"
+#include "xml/XmlDom.h"
+
+namespace aapt {
+
+// Serializes a Value to its protobuf representation. An optional StringPool will hold the
+// source path string.
+void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool = nullptr);
+
+// Serialize an Item into its protobuf representation. pb::Item does not store the source path nor
+// comments of an Item.
+void SerializeItemToPb(const Item& item, pb::Item* out_item);
+
+// Serializes an XML element into its protobuf representation.
+void SerializeXmlToPb(const xml::Element& el, pb::XmlNode* out_node);
+
+// Serializes an XmlResource into its protobuf representation. The ResourceFile is NOT serialized.
+void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out_node);
+
+// Serializes a StringPool into its protobuf representation, which is really just the binary
+// ResStringPool representation stuffed into a bytes field.
+void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool);
+
+// Serializes a ConfigDescription into its protobuf representation.
+void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config);
+
+// Serializes a ResourceTable into its protobuf representation.
+void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table);
+
+// Serializes a ResourceFile into its protobuf representation.
+void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file);
+
+}  // namespace aapt
+
+#endif /* AAPT_FORMAT_PROTO_PROTOSERIALIZE_H */
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
new file mode 100644
index 0000000..8efac8a
--- /dev/null
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2016 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 "format/proto/ProtoSerialize.h"
+
+#include "ResourceUtils.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "test/Test.h"
+
+using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
+namespace aapt {
+
+TEST(ProtoSerializeTest, SerializeSinglePackage) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.a", 0x7f)
+          .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml")
+          .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
+          .AddString("com.app.a:string/text", {}, "hi")
+          .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
+          .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/)
+          .Build();
+
+  Symbol public_symbol;
+  public_symbol.state = SymbolState::kPublic;
+  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
+                                    ResourceId(0x7f020000), public_symbol,
+                                    context->GetDiagnostics()));
+
+  Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
+  ASSERT_THAT(id, NotNull());
+
+  // Make a plural.
+  std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+  plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one"));
+  ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), ConfigDescription{},
+                                 {}, std::move(plural), context->GetDiagnostics()));
+
+  // Make a styled string.
+  StyleString style_string;
+  style_string.str = "hello";
+  style_string.spans.push_back(Span{"b", 0u, 4u});
+  ASSERT_TRUE(
+      table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {},
+                         util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)),
+                         context->GetDiagnostics()));
+
+  // Make a resource with different products.
+  ASSERT_TRUE(table->AddResource(
+      test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), {},
+      test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics()));
+  ASSERT_TRUE(table->AddResource(
+      test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet",
+      test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), context->GetDiagnostics()));
+
+  // Make a reference with both resource name and resource ID.
+  // The reference should point to a resource outside of this table to test that both name and id
+  // get serialized.
+  Reference expected_ref;
+  expected_ref.name = test::ParseNameOrDie("android:layout/main");
+  expected_ref.id = ResourceId(0x01020000);
+  ASSERT_TRUE(table->AddResource(
+      test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {},
+      util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
+
+  pb::ResourceTable pb_table;
+  SerializeTableToPb(*table, &pb_table);
+
+  ResourceTable new_table;
+  std::string error;
+  ASSERT_TRUE(DeserializeTableFromPb(pb_table, &new_table, &error));
+  EXPECT_THAT(error, IsEmpty());
+
+  Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
+  ASSERT_THAT(new_id, NotNull());
+  EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak()));
+
+  Maybe<ResourceTable::SearchResult> result =
+      new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
+  ASSERT_TRUE(result);
+
+  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
+  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+
+  result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
+  ASSERT_TRUE(result);
+  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
+  EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
+
+  // Find the product-dependent values
+  BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+      &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");
+  ASSERT_THAT(prim, NotNull());
+  EXPECT_THAT(prim->value.data, Eq(123u));
+
+  prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
+      &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");
+  ASSERT_THAT(prim, NotNull());
+  EXPECT_THAT(prim->value.data, Eq(321u));
+
+  Reference* actual_ref = test::GetValue<Reference>(&new_table, "com.app.a:layout/abc");
+  ASSERT_THAT(actual_ref, NotNull());
+  ASSERT_TRUE(actual_ref->name);
+  ASSERT_TRUE(actual_ref->id);
+  EXPECT_THAT(*actual_ref, Eq(expected_ref));
+
+  StyledString* actual_styled_str =
+      test::GetValue<StyledString>(&new_table, "com.app.a:string/styled");
+  ASSERT_THAT(actual_styled_str, NotNull());
+  EXPECT_THAT(actual_styled_str->value->value, Eq("hello"));
+  ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u));
+  EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
+  EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
+  EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
+}
+
+TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
+  xml::Element element;
+  element.line_number = 22;
+  element.column_number = 23;
+  element.name = "element";
+  element.namespace_uri = "uri://";
+
+  xml::NamespaceDecl decl;
+  decl.prefix = "android";
+  decl.uri = xml::kSchemaAndroid;
+  decl.line_number = 21;
+  decl.column_number = 24;
+
+  element.namespace_decls.push_back(decl);
+
+  xml::Attribute attr;
+  attr.name = "name";
+  attr.namespace_uri = xml::kSchemaAndroid;
+  attr.value = "23dp";
+  attr.compiled_attribute = xml::AaptAttribute({}, ResourceId(0x01010000));
+  attr.compiled_value =
+      ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION);
+  attr.compiled_value->SetSource(Source().WithLine(25));
+  element.attributes.push_back(std::move(attr));
+
+  std::unique_ptr<xml::Text> text = util::make_unique<xml::Text>();
+  text->line_number = 25;
+  text->column_number = 3;
+  text->text = "hey there";
+  element.AppendChild(std::move(text));
+
+  std::unique_ptr<xml::Element> child = util::make_unique<xml::Element>();
+  child->name = "child";
+
+  text = util::make_unique<xml::Text>();
+  text->text = "woah there";
+  child->AppendChild(std::move(text));
+
+  element.AppendChild(std::move(child));
+
+  pb::XmlNode pb_xml;
+  SerializeXmlToPb(element, &pb_xml);
+
+  StringPool pool;
+  xml::Element actual_el;
+  std::string error;
+  ASSERT_TRUE(DeserializeXmlFromPb(pb_xml, &actual_el, &pool, &error));
+  ASSERT_THAT(error, IsEmpty());
+
+  EXPECT_THAT(actual_el.name, StrEq("element"));
+  EXPECT_THAT(actual_el.namespace_uri, StrEq("uri://"));
+  EXPECT_THAT(actual_el.line_number, Eq(22u));
+  EXPECT_THAT(actual_el.column_number, Eq(23u));
+
+  ASSERT_THAT(actual_el.namespace_decls, SizeIs(1u));
+  const xml::NamespaceDecl& actual_decl = actual_el.namespace_decls[0];
+  EXPECT_THAT(actual_decl.prefix, StrEq("android"));
+  EXPECT_THAT(actual_decl.uri, StrEq(xml::kSchemaAndroid));
+  EXPECT_THAT(actual_decl.line_number, Eq(21u));
+  EXPECT_THAT(actual_decl.column_number, Eq(24u));
+
+  ASSERT_THAT(actual_el.attributes, SizeIs(1u));
+  const xml::Attribute& actual_attr = actual_el.attributes[0];
+  EXPECT_THAT(actual_attr.name, StrEq("name"));
+  EXPECT_THAT(actual_attr.namespace_uri, StrEq(xml::kSchemaAndroid));
+  EXPECT_THAT(actual_attr.value, StrEq("23dp"));
+
+  ASSERT_THAT(actual_attr.compiled_value, NotNull());
+  const BinaryPrimitive* prim = ValueCast<BinaryPrimitive>(actual_attr.compiled_value.get());
+  ASSERT_THAT(prim, NotNull());
+  EXPECT_THAT(prim->value.dataType, Eq(android::Res_value::TYPE_DIMENSION));
+
+  ASSERT_TRUE(actual_attr.compiled_attribute);
+  ASSERT_TRUE(actual_attr.compiled_attribute.value().id);
+
+  ASSERT_THAT(actual_el.children, SizeIs(2u));
+  const xml::Text* child_text = xml::NodeCast<xml::Text>(actual_el.children[0].get());
+  ASSERT_THAT(child_text, NotNull());
+  const xml::Element* child_el = xml::NodeCast<xml::Element>(actual_el.children[1].get());
+  ASSERT_THAT(child_el, NotNull());
+
+  EXPECT_THAT(child_text->line_number, Eq(25u));
+  EXPECT_THAT(child_text->column_number, Eq(3u));
+  EXPECT_THAT(child_text->text, StrEq("hey there"));
+
+  EXPECT_THAT(child_el->name, StrEq("child"));
+  ASSERT_THAT(child_el->children, SizeIs(1u));
+
+  child_text = xml::NodeCast<xml::Text>(child_el->children[0].get());
+  ASSERT_THAT(child_text, NotNull());
+  EXPECT_THAT(child_text->text, StrEq("woah there"));
+}
+
+static void ExpectConfigSerializes(const StringPiece& config_str) {
+  const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
+  pb::Configuration pb_config;
+  SerializeConfig(expected_config, &pb_config);
+
+  ConfigDescription actual_config;
+  std::string error;
+  ASSERT_TRUE(DeserializeConfigFromPb(pb_config, &actual_config, &error));
+  ASSERT_THAT(error, IsEmpty());
+  EXPECT_EQ(expected_config, actual_config);
+}
+
+TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) {
+  ExpectConfigSerializes("");
+
+  ExpectConfigSerializes("mcc123");
+
+  ExpectConfigSerializes("mnc123");
+
+  ExpectConfigSerializes("en");
+  ExpectConfigSerializes("en-rGB");
+  ExpectConfigSerializes("b+en+GB");
+
+  ExpectConfigSerializes("ldltr");
+  ExpectConfigSerializes("ldrtl");
+
+  ExpectConfigSerializes("sw3600dp");
+
+  ExpectConfigSerializes("w300dp");
+
+  ExpectConfigSerializes("h400dp");
+
+  ExpectConfigSerializes("small");
+  ExpectConfigSerializes("normal");
+  ExpectConfigSerializes("large");
+  ExpectConfigSerializes("xlarge");
+
+  ExpectConfigSerializes("long");
+  ExpectConfigSerializes("notlong");
+
+  ExpectConfigSerializes("round");
+  ExpectConfigSerializes("notround");
+
+  ExpectConfigSerializes("widecg");
+  ExpectConfigSerializes("nowidecg");
+
+  ExpectConfigSerializes("highdr");
+  ExpectConfigSerializes("lowdr");
+
+  ExpectConfigSerializes("port");
+  ExpectConfigSerializes("land");
+  ExpectConfigSerializes("square");
+
+  ExpectConfigSerializes("desk");
+  ExpectConfigSerializes("car");
+  ExpectConfigSerializes("television");
+  ExpectConfigSerializes("appliance");
+  ExpectConfigSerializes("watch");
+  ExpectConfigSerializes("vrheadset");
+
+  ExpectConfigSerializes("night");
+  ExpectConfigSerializes("notnight");
+
+  ExpectConfigSerializes("300dpi");
+  ExpectConfigSerializes("hdpi");
+
+  ExpectConfigSerializes("notouch");
+  ExpectConfigSerializes("stylus");
+  ExpectConfigSerializes("finger");
+
+  ExpectConfigSerializes("keysexposed");
+  ExpectConfigSerializes("keyshidden");
+  ExpectConfigSerializes("keyssoft");
+
+  ExpectConfigSerializes("nokeys");
+  ExpectConfigSerializes("qwerty");
+  ExpectConfigSerializes("12key");
+
+  ExpectConfigSerializes("navhidden");
+  ExpectConfigSerializes("navexposed");
+
+  ExpectConfigSerializes("nonav");
+  ExpectConfigSerializes("dpad");
+  ExpectConfigSerializes("trackball");
+  ExpectConfigSerializes("wheel");
+
+  ExpectConfigSerializes("300x200");
+
+  ExpectConfigSerializes("v8");
+
+  ExpectConfigSerializes(
+      "mcc123-mnc456-b+en+GB-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-land-car-"
+      "night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23");
+}
+
+}  // namespace aapt
diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md
new file mode 100644
index 0000000..bb31a00
--- /dev/null
+++ b/tools/aapt2/formats.md
@@ -0,0 +1,44 @@
+# AAPT2 On-Disk Formats
+- AAPT2 Container Format (extension `.apc`)
+- AAPT2 Static Library Format (extension `.sapk`)
+
+## AAPT2 Container Format (extension `.apc`)
+The APC format (AAPT2 Container Format) is generated by AAPT2 during the compile phase and
+consumed by the AAPT2 link phase. It is a simple container format for storing compiled PNGs,
+binary and protobuf XML, and intermediate protobuf resource tables. It also stores all associated
+meta-data from the compile phase.
+
+### Format
+The file starts with a simple header. All multi-byte fields are little-endian.
+
+| Size (in bytes) | Field         | Description                                          |
+|:----------------|:--------------|:-----------------------------------------------------|
+| `4`             | `magic`       | The magic bytes must equal `'AAPT'` or `0x54504141`. |
+| `4`             | `version`     | The version of the container format.                 |
+| `4`             | `entry_count` | The number of entries in this container.             |
+
+This is followed by `entry_count` of the following data structure. It must be aligned on a 32-bit
+boundary, so if a previous entry ends unaligned, padding must be inserted.
+
+| Size (in bytes) | Field          | Description                                                                                               |
+|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
+| `4`             | `entry_type`   | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. |
+| `8`             | `entry_length` | The length of the data that follows.                                                                      |
+| `entry_length`  | `data`         | The payload. The contents of this varies based on the `entry_type`.                                       |
+
+If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized
+[aapt.pb.ResourceTable](Resources.proto).
+
+If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following:
+
+
+| Size (in bytes) | Field          | Description                                                                                               |
+|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------|
+| `4`             | `header_size`  | The size of the `header` field.                                                                 |
+| `8`             | `data_size`    | The size of the `data` field.                                                                   |
+| `header_size`   | `header`       | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto).       |
+| `x`             | `padding`      | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. |
+| `data_size`     | `data`         | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). |
+
+## AAPT2 Static Library Format (extension `.sapk`)
+
diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferInputStream.h
deleted file mode 100644
index 92612c7..0000000
--- a/tools/aapt2/io/BigBufferInputStream.h
+++ /dev/null
@@ -1,56 +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.
- */
-
-#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H
-#define AAPT_IO_BIGBUFFERINPUTSTREAM_H
-
-#include "io/Io.h"
-#include "util/BigBuffer.h"
-
-namespace aapt {
-namespace io {
-
-class BigBufferInputStream : public InputStream {
- public:
-  inline explicit BigBufferInputStream(const BigBuffer* buffer)
-      : buffer_(buffer), iter_(buffer->begin()) {}
-  virtual ~BigBufferInputStream() = default;
-
-  bool Next(const void** data, size_t* size) override;
-
-  void BackUp(size_t count) override;
-
-  bool CanRewind() const override;
-
-  bool Rewind() override;
-
-  size_t ByteCount() const override;
-
-  bool HadError() const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
-
-  const BigBuffer* buffer_;
-  BigBuffer::const_iterator iter_;
-  size_t offset_ = 0;
-  size_t bytes_read_ = 0;
-};
-
-}  // namespace io
-}  // namespace aapt
-
-#endif  // AAPT_IO_BIGBUFFERINPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h
deleted file mode 100644
index 95113bc..0000000
--- a/tools/aapt2/io/BigBufferOutputStream.h
+++ /dev/null
@@ -1,48 +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.
- */
-
-#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
-#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
-
-#include "io/Io.h"
-#include "util/BigBuffer.h"
-
-namespace aapt {
-namespace io {
-
-class BigBufferOutputStream : public OutputStream {
- public:
-  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {}
-  virtual ~BigBufferOutputStream() = default;
-
-  bool Next(void** data, size_t* size) override;
-
-  void BackUp(size_t count) override;
-
-  size_t ByteCount() const override;
-
-  bool HadError() const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
-
-  BigBuffer* buffer_;
-};
-
-}  // namespace io
-}  // namespace aapt
-
-#endif  // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStream.cpp b/tools/aapt2/io/BigBufferStream.cpp
new file mode 100644
index 0000000..9704caa
--- /dev/null
+++ b/tools/aapt2/io/BigBufferStream.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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 "io/BigBufferStream.h"
+
+namespace aapt {
+namespace io {
+
+//
+// BigBufferInputStream
+//
+
+bool BigBufferInputStream::Next(const void** data, size_t* size) {
+  if (iter_ == buffer_->end()) {
+    return false;
+  }
+
+  if (offset_ == iter_->size) {
+    ++iter_;
+    if (iter_ == buffer_->end()) {
+      return false;
+    }
+    offset_ = 0;
+  }
+
+  *data = iter_->buffer.get() + offset_;
+  *size = iter_->size - offset_;
+  bytes_read_ += iter_->size - offset_;
+  offset_ = iter_->size;
+  return true;
+}
+
+void BigBufferInputStream::BackUp(size_t count) {
+  if (count > offset_) {
+    bytes_read_ -= offset_;
+    offset_ = 0;
+  } else {
+    offset_ -= count;
+    bytes_read_ -= count;
+  }
+}
+
+bool BigBufferInputStream::CanRewind() const {
+  return true;
+}
+
+bool BigBufferInputStream::Rewind() {
+  iter_ = buffer_->begin();
+  offset_ = 0;
+  bytes_read_ = 0;
+  return true;
+}
+
+size_t BigBufferInputStream::ByteCount() const {
+  return bytes_read_;
+}
+
+bool BigBufferInputStream::HadError() const {
+  return false;
+}
+
+size_t BigBufferInputStream::TotalSize() const {
+  return buffer_->size();
+}
+
+//
+// BigBufferOutputStream
+//
+
+bool BigBufferOutputStream::Next(void** data, size_t* size) {
+  *data = buffer_->NextBlock(size);
+  return true;
+}
+
+void BigBufferOutputStream::BackUp(size_t count) {
+  buffer_->BackUp(count);
+}
+
+size_t BigBufferOutputStream::ByteCount() const {
+  return buffer_->size();
+}
+
+bool BigBufferOutputStream::HadError() const {
+  return false;
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/BigBufferStream.h b/tools/aapt2/io/BigBufferStream.h
new file mode 100644
index 0000000..8b5c8b8
--- /dev/null
+++ b/tools/aapt2/io/BigBufferStream.h
@@ -0,0 +1,79 @@
+/*
+ * 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 AAPT_IO_BIGBUFFERSTREAM_H
+#define AAPT_IO_BIGBUFFERSTREAM_H
+
+#include "io/Io.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+namespace io {
+
+class BigBufferInputStream : public KnownSizeInputStream {
+ public:
+  inline explicit BigBufferInputStream(const BigBuffer* buffer)
+      : buffer_(buffer), iter_(buffer->begin()) {
+  }
+  virtual ~BigBufferInputStream() = default;
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  bool CanRewind() const override;
+
+  bool Rewind() override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  size_t TotalSize() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream);
+
+  const BigBuffer* buffer_;
+  BigBuffer::const_iterator iter_;
+  size_t offset_ = 0;
+  size_t bytes_read_ = 0;
+};
+
+class BigBufferOutputStream : public OutputStream {
+ public:
+  inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {
+  }
+  virtual ~BigBufferOutputStream() = default;
+
+  bool Next(void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
+
+  BigBuffer* buffer_;
+};
+
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_BIGBUFFERSTREAM_H
diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStreams.cpp
deleted file mode 100644
index eb99033..0000000
--- a/tools/aapt2/io/BigBufferStreams.cpp
+++ /dev/null
@@ -1,86 +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.
- */
-
-#include "io/BigBufferInputStream.h"
-#include "io/BigBufferOutputStream.h"
-
-namespace aapt {
-namespace io {
-
-//
-// BigBufferInputStream
-//
-
-bool BigBufferInputStream::Next(const void** data, size_t* size) {
-  if (iter_ == buffer_->end()) {
-    return false;
-  }
-
-  if (offset_ == iter_->size) {
-    ++iter_;
-    if (iter_ == buffer_->end()) {
-      return false;
-    }
-    offset_ = 0;
-  }
-
-  *data = iter_->buffer.get() + offset_;
-  *size = iter_->size - offset_;
-  bytes_read_ += iter_->size - offset_;
-  offset_ = iter_->size;
-  return true;
-}
-
-void BigBufferInputStream::BackUp(size_t count) {
-  if (count > offset_) {
-    bytes_read_ -= offset_;
-    offset_ = 0;
-  } else {
-    offset_ -= count;
-    bytes_read_ -= count;
-  }
-}
-
-bool BigBufferInputStream::CanRewind() const { return true; }
-
-bool BigBufferInputStream::Rewind() {
-  iter_ = buffer_->begin();
-  offset_ = 0;
-  bytes_read_ = 0;
-  return true;
-}
-
-size_t BigBufferInputStream::ByteCount() const { return bytes_read_; }
-
-bool BigBufferInputStream::HadError() const { return false; }
-
-//
-// BigBufferOutputStream
-//
-
-bool BigBufferOutputStream::Next(void** data, size_t* size) {
-  *data = buffer_->NextBlock(size);
-  return true;
-}
-
-void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); }
-
-size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); }
-
-bool BigBufferOutputStream::HadError() const { return false; }
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 09dc7ea..db91a77 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -28,12 +28,16 @@
 namespace io {
 
 // Interface for a block of contiguous memory. An instance of this interface owns the data.
-class IData : public InputStream {
+class IData : public KnownSizeInputStream {
  public:
   virtual ~IData() = default;
 
   virtual const void* data() const = 0;
   virtual size_t size() const = 0;
+
+  virtual size_t TotalSize() const override {
+    return size();
+  }
 };
 
 class DataSegment : public IData {
diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp
index ee73728..b4f1ff3 100644
--- a/tools/aapt2/io/File.cpp
+++ b/tools/aapt2/io/File.cpp
@@ -39,5 +39,9 @@
   return {};
 }
 
+std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() {
+  return OpenAsData();
+}
+
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 7ef6d88..f06e28c 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -43,6 +43,8 @@
   // Returns nullptr on failure.
   virtual std::unique_ptr<IData> OpenAsData() = 0;
 
+  virtual std::unique_ptr<io::InputStream> OpenInputStream() = 0;
+
   // Returns the source of this file. This is for presentation to the user and
   // may not be a valid file system path (for example, it may contain a '@' sign to separate
   // the files within a ZIP archive from the path to the containing ZIP archive.
@@ -71,8 +73,11 @@
       : file_(file), offset_(offset), len_(len) {}
 
   std::unique_ptr<IData> OpenAsData() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
 
-  const Source& GetSource() const override { return file_->GetSource(); }
+  const Source& GetSource() const override {
+    return file_->GetSource();
+  }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(FileSegment);
diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp
deleted file mode 100644
index 07dbb5a..0000000
--- a/tools/aapt2/io/FileInputStream.cpp
+++ /dev/null
@@ -1,102 +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.
- */
-
-#include "io/FileInputStream.h"
-
-#include <errno.h>   // for errno
-#include <fcntl.h>   // for O_RDONLY
-#include <unistd.h>  // for read
-
-#include "android-base/errors.h"
-#include "android-base/file.h"  // for O_BINARY
-#include "android-base/macros.h"
-#include "android-base/utf8.h"
-
-using ::android::base::SystemErrorCodeToString;
-
-namespace aapt {
-namespace io {
-
-FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
-    : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
-                      buffer_capacity) {
-}
-
-FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
-    : fd_(fd),
-      buffer_capacity_(buffer_capacity),
-      buffer_offset_(0u),
-      buffer_size_(0u),
-      total_byte_count_(0u) {
-  if (fd_ == -1) {
-    error_ = SystemErrorCodeToString(errno);
-  } else {
-    buffer_.reset(new uint8_t[buffer_capacity_]);
-  }
-}
-
-bool FileInputStream::Next(const void** data, size_t* size) {
-  if (HadError()) {
-    return false;
-  }
-
-  // Deal with any remaining bytes after BackUp was called.
-  if (buffer_offset_ != buffer_size_) {
-    *data = buffer_.get() + buffer_offset_;
-    *size = buffer_size_ - buffer_offset_;
-    total_byte_count_ += buffer_size_ - buffer_offset_;
-    buffer_offset_ = buffer_size_;
-    return true;
-  }
-
-  ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
-  if (n < 0) {
-    error_ = SystemErrorCodeToString(errno);
-    fd_.reset();
-    return false;
-  }
-
-  buffer_size_ = static_cast<size_t>(n);
-  buffer_offset_ = buffer_size_;
-  total_byte_count_ += buffer_size_;
-
-  *data = buffer_.get();
-  *size = buffer_size_;
-  return buffer_size_ != 0u;
-}
-
-void FileInputStream::BackUp(size_t count) {
-  if (count > buffer_offset_) {
-    count = buffer_offset_;
-  }
-  buffer_offset_ -= count;
-  total_byte_count_ -= count;
-}
-
-size_t FileInputStream::ByteCount() const {
-  return total_byte_count_;
-}
-
-bool FileInputStream::HadError() const {
-  return !error_.empty();
-}
-
-std::string FileInputStream::GetError() const {
-  return error_;
-}
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileInputStream.h
deleted file mode 100644
index 6beb9a1..0000000
--- a/tools/aapt2/io/FileInputStream.h
+++ /dev/null
@@ -1,63 +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.
- */
-
-#ifndef AAPT_IO_FILEINPUTSTREAM_H
-#define AAPT_IO_FILEINPUTSTREAM_H
-
-#include "io/Io.h"
-
-#include <memory>
-#include <string>
-
-#include "android-base/macros.h"
-#include "android-base/unique_fd.h"
-
-namespace aapt {
-namespace io {
-
-class FileInputStream : public InputStream {
- public:
-  explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096);
-
-  // Takes ownership of `fd`.
-  explicit FileInputStream(int fd, size_t buffer_capacity = 4096);
-
-  bool Next(const void** data, size_t* size) override;
-
-  void BackUp(size_t count) override;
-
-  size_t ByteCount() const override;
-
-  bool HadError() const override;
-
-  std::string GetError() const override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(FileInputStream);
-
-  android::base::unique_fd fd_;
-  std::string error_;
-  std::unique_ptr<uint8_t[]> buffer_;
-  size_t buffer_capacity_;
-  size_t buffer_offset_;
-  size_t buffer_size_;
-  size_t total_byte_count_;
-};
-
-}  // namespace io
-}  // namespace aapt
-
-#endif  // AAPT_IO_FILEINPUTSTREAM_H
diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileInputStream_test.cpp
deleted file mode 100644
index 7314ab7..0000000
--- a/tools/aapt2/io/FileInputStream_test.cpp
+++ /dev/null
@@ -1,87 +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.
- */
-
-#include "io/FileInputStream.h"
-
-#include "android-base/macros.h"
-#include "android-base/test_utils.h"
-
-#include "test/Test.h"
-
-using ::android::StringPiece;
-using ::testing::Eq;
-using ::testing::NotNull;
-using ::testing::StrEq;
-
-namespace aapt {
-namespace io {
-
-TEST(FileInputStreamTest, NextAndBackup) {
-  std::string input = "this is a cool string";
-  TemporaryFile file;
-  ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21));
-  lseek64(file.fd, 0, SEEK_SET);
-
-  // Use a small buffer size so that we can call Next() a few times.
-  FileInputStream in(file.fd, 10u);
-  ASSERT_FALSE(in.HadError());
-  EXPECT_THAT(in.ByteCount(), Eq(0u));
-
-  const char* buffer;
-  size_t size;
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)) << in.GetError();
-  ASSERT_THAT(size, Eq(10u));
-  ASSERT_THAT(buffer, NotNull());
-  EXPECT_THAT(in.ByteCount(), Eq(10u));
-  EXPECT_THAT(StringPiece(buffer, size), Eq("this is a "));
-
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(10u));
-  ASSERT_THAT(buffer, NotNull());
-  EXPECT_THAT(in.ByteCount(), Eq(20u));
-  EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin"));
-
-  in.BackUp(5u);
-  EXPECT_THAT(in.ByteCount(), Eq(15u));
-
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(5u));
-  ASSERT_THAT(buffer, NotNull());
-  ASSERT_THAT(in.ByteCount(), Eq(20u));
-  EXPECT_THAT(StringPiece(buffer, size), Eq("strin"));
-
-  // Backup 1 more than possible. Should clamp.
-  in.BackUp(11u);
-  EXPECT_THAT(in.ByteCount(), Eq(10u));
-
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(10u));
-  ASSERT_THAT(buffer, NotNull());
-  ASSERT_THAT(in.ByteCount(), Eq(20u));
-  EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin"));
-
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(1u));
-  ASSERT_THAT(buffer, NotNull());
-  ASSERT_THAT(in.ByteCount(), Eq(21u));
-  EXPECT_THAT(StringPiece(buffer, size), Eq("g"));
-
-  EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  EXPECT_FALSE(in.HadError());
-}
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/FileStream.cpp b/tools/aapt2/io/FileStream.cpp
new file mode 100644
index 0000000..2f7a4b3
--- /dev/null
+++ b/tools/aapt2/io/FileStream.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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 "io/FileStream.h"
+
+#include <errno.h>   // for errno
+#include <fcntl.h>   // for O_RDONLY
+#include <unistd.h>  // for read
+
+#include "android-base/errors.h"
+#include "android-base/file.h"  // for O_BINARY
+#include "android-base/macros.h"
+#include "android-base/utf8.h"
+
+using ::android::base::SystemErrorCodeToString;
+
+namespace aapt {
+namespace io {
+
+FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity)
+    : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY),
+                      buffer_capacity) {
+}
+
+FileInputStream::FileInputStream(int fd, size_t buffer_capacity)
+    : fd_(fd),
+      buffer_capacity_(buffer_capacity),
+      buffer_offset_(0u),
+      buffer_size_(0u),
+      total_byte_count_(0u) {
+  if (fd_ == -1) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+bool FileInputStream::Next(const void** data, size_t* size) {
+  if (HadError()) {
+    return false;
+  }
+
+  // Deal with any remaining bytes after BackUp was called.
+  if (buffer_offset_ != buffer_size_) {
+    *data = buffer_.get() + buffer_offset_;
+    *size = buffer_size_ - buffer_offset_;
+    total_byte_count_ += buffer_size_ - buffer_offset_;
+    buffer_offset_ = buffer_size_;
+    return true;
+  }
+
+  ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_));
+  if (n < 0) {
+    error_ = SystemErrorCodeToString(errno);
+    fd_.reset();
+    buffer_.reset();
+    return false;
+  }
+
+  buffer_size_ = static_cast<size_t>(n);
+  buffer_offset_ = buffer_size_;
+  total_byte_count_ += buffer_size_;
+
+  *data = buffer_.get();
+  *size = buffer_size_;
+  return buffer_size_ != 0u;
+}
+
+void FileInputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    count = buffer_offset_;
+  }
+  buffer_offset_ -= count;
+  total_byte_count_ -= count;
+}
+
+size_t FileInputStream::ByteCount() const {
+  return total_byte_count_;
+}
+
+bool FileInputStream::HadError() const {
+  return fd_ == -1;
+}
+
+std::string FileInputStream::GetError() const {
+  return error_;
+}
+
+FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity)
+    : FileOutputStream(::android::base::utf8::open(path.c_str(), mode), buffer_capacity) {
+}
+
+FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity)
+    : fd_(fd), buffer_capacity_(buffer_capacity), buffer_offset_(0u), total_byte_count_(0u) {
+  if (fd_ == -1) {
+    error_ = SystemErrorCodeToString(errno);
+  } else {
+    buffer_.reset(new uint8_t[buffer_capacity_]);
+  }
+}
+
+FileOutputStream::~FileOutputStream() {
+  // Flush the buffer.
+  Flush();
+}
+
+bool FileOutputStream::Next(void** data, size_t* size) {
+  if (fd_ == -1 || HadError()) {
+    return false;
+  }
+
+  if (buffer_offset_ == buffer_capacity_) {
+    if (!FlushImpl()) {
+      return false;
+    }
+  }
+
+  const size_t buffer_size = buffer_capacity_ - buffer_offset_;
+  *data = buffer_.get() + buffer_offset_;
+  *size = buffer_size;
+  total_byte_count_ += buffer_size;
+  buffer_offset_ = buffer_capacity_;
+  return true;
+}
+
+void FileOutputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    count = buffer_offset_;
+  }
+  buffer_offset_ -= count;
+  total_byte_count_ -= count;
+}
+
+size_t FileOutputStream::ByteCount() const {
+  return total_byte_count_;
+}
+
+bool FileOutputStream::Flush() {
+  if (!HadError()) {
+    return FlushImpl();
+  }
+  return false;
+}
+
+bool FileOutputStream::FlushImpl() {
+  ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_));
+  if (n < 0) {
+    error_ = SystemErrorCodeToString(errno);
+    fd_.reset();
+    buffer_.reset();
+    return false;
+  }
+
+  buffer_offset_ = 0u;
+  return true;
+}
+
+bool FileOutputStream::HadError() const {
+  return fd_ == -1;
+}
+
+std::string FileOutputStream::GetError() const {
+  return error_;
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/FileStream.h b/tools/aapt2/io/FileStream.h
new file mode 100644
index 0000000..3b07667
--- /dev/null
+++ b/tools/aapt2/io/FileStream.h
@@ -0,0 +1,100 @@
+/*
+ * 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 AAPT_IO_FILESTREAM_H
+#define AAPT_IO_FILESTREAM_H
+
+#include "io/Io.h"
+
+#include <memory>
+#include <string>
+
+#include "android-base/file.h"  // for O_BINARY
+#include "android-base/macros.h"
+#include "android-base/unique_fd.h"
+
+namespace aapt {
+namespace io {
+
+class FileInputStream : public InputStream {
+ public:
+  explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096);
+
+  // Takes ownership of `fd`.
+  explicit FileInputStream(int fd, size_t buffer_capacity = 4096);
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  std::string GetError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileInputStream);
+
+  android::base::unique_fd fd_;
+  std::string error_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t buffer_capacity_;
+  size_t buffer_offset_;
+  size_t buffer_size_;
+  size_t total_byte_count_;
+};
+
+class FileOutputStream : public OutputStream {
+ public:
+  explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY,
+                            size_t buffer_capacity = 4096);
+
+  // Takes ownership of `fd`.
+  explicit FileOutputStream(int fd, size_t buffer_capacity = 4096);
+
+  ~FileOutputStream();
+
+  bool Next(void** data, size_t* size) override;
+
+  // Immediately flushes out the contents of the buffer to disk.
+  bool Flush();
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  bool HadError() const override;
+
+  std::string GetError() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FileOutputStream);
+
+  bool FlushImpl();
+
+  android::base::unique_fd fd_;
+  std::string error_;
+  std::unique_ptr<uint8_t[]> buffer_;
+  size_t buffer_capacity_;
+  size_t buffer_offset_;
+  size_t total_byte_count_;
+};
+
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_FILESTREAM_H
diff --git a/tools/aapt2/io/FileStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp
new file mode 100644
index 0000000..68c3cb1
--- /dev/null
+++ b/tools/aapt2/io/FileStream_test.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 "io/FileStream.h"
+
+#include "android-base/macros.h"
+#include "android-base/test_utils.h"
+
+#include "test/Test.h"
+
+using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace aapt {
+namespace io {
+
+TEST(FileInputStreamTest, NextAndBackup) {
+  std::string input = "this is a cool string";
+  TemporaryFile file;
+  ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21));
+  lseek64(file.fd, 0, SEEK_SET);
+
+  // Use a small buffer size so that we can call Next() a few times.
+  FileInputStream in(file.release(), 10u);
+  ASSERT_FALSE(in.HadError());
+  EXPECT_THAT(in.ByteCount(), Eq(0u));
+
+  const char* buffer;
+  size_t size;
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)) << in.GetError();
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(in.ByteCount(), Eq(10u));
+  EXPECT_THAT(StringPiece(buffer, size), Eq("this is a "));
+
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(in.ByteCount(), Eq(20u));
+  EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin"));
+
+  in.BackUp(5u);
+  EXPECT_THAT(in.ByteCount(), Eq(15u));
+
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(5u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(in.ByteCount(), Eq(20u));
+  EXPECT_THAT(StringPiece(buffer, size), Eq("strin"));
+
+  // Backup 1 more than possible. Should clamp.
+  in.BackUp(11u);
+  EXPECT_THAT(in.ByteCount(), Eq(10u));
+
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(in.ByteCount(), Eq(20u));
+  EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin"));
+
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(1u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(in.ByteCount(), Eq(21u));
+  EXPECT_THAT(StringPiece(buffer, size), Eq("g"));
+
+  EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  EXPECT_FALSE(in.HadError());
+}
+
+TEST(FileOutputStreamTest, NextAndBackup) {
+  const std::string input = "this is a cool string";
+
+  TemporaryFile file;
+  int fd = file.release();
+
+  // FileOutputStream takes ownership.
+  FileOutputStream out(fd, 10u);
+  ASSERT_FALSE(out.HadError());
+  EXPECT_THAT(out.ByteCount(), Eq(0u));
+
+  char* buffer;
+  size_t size;
+  ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(10u));
+  memcpy(buffer, input.c_str(), size);
+
+  ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(20u));
+  memcpy(buffer, input.c_str() + 10u, size);
+
+  ASSERT_TRUE(out.Next(reinterpret_cast<void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(10u));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(out.ByteCount(), Eq(30u));
+  buffer[0] = input[20u];
+  out.BackUp(size - 1);
+  EXPECT_THAT(out.ByteCount(), Eq(21u));
+
+  ASSERT_TRUE(out.Flush());
+
+  lseek64(fd, 0, SEEK_SET);
+
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFdToString(fd, &actual));
+  EXPECT_THAT(actual, StrEq(input));
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 027cbd0..1387d22 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -20,11 +20,12 @@
 #include "utils/FileMap.h"
 
 #include "Source.h"
+#include "io/FileStream.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 namespace io {
@@ -42,12 +43,20 @@
   return {};
 }
 
-const Source& RegularFile::GetSource() const { return source_; }
+std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() {
+  return util::make_unique<FileInputStream>(source_.path);
+}
+
+const Source& RegularFile::GetSource() const {
+  return source_;
+}
 
 FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
     : current_(collection->files_.begin()), end_(collection->files_.end()) {}
 
-bool FileCollectionIterator::HasNext() { return current_ != end_; }
+bool FileCollectionIterator::HasNext() {
+  return current_ != end_;
+}
 
 IFile* FileCollectionIterator::Next() {
   IFile* result = current_->second.get();
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index dfd3717..6be8807 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -24,17 +24,18 @@
 namespace aapt {
 namespace io {
 
-/**
- * A regular file from the file system. Uses mmap to open the data.
- */
+// A regular file from the file system. Uses mmap to open the data.
 class RegularFile : public IFile {
  public:
   explicit RegularFile(const Source& source);
 
   std::unique_ptr<IData> OpenAsData() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
   const Source& GetSource() const override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(RegularFile);
+
   Source source_;
 };
 
@@ -48,23 +49,26 @@
   io::IFile* Next() override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(FileCollectionIterator);
+
   std::map<std::string, std::unique_ptr<IFile>>::const_iterator current_, end_;
 };
 
-/**
- * An IFileCollection representing the file system.
- */
+// An IFileCollection representing the file system.
 class FileCollection : public IFileCollection {
  public:
-  /**
-   * Adds a file located at path. Returns the IFile representation of that file.
-   */
+  FileCollection() = default;
+
+  // Adds a file located at path. Returns the IFile representation of that file.
   IFile* InsertFile(const android::StringPiece& path);
   IFile* FindFile(const android::StringPiece& path) override;
   std::unique_ptr<IFileCollectionIterator> Iterator() override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(FileCollection);
+
   friend class FileCollectionIterator;
+
   std::map<std::string, std::unique_ptr<IFile>> files_;
 };
 
diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h
index a656740..e1df23a6 100644
--- a/tools/aapt2/io/Io.h
+++ b/tools/aapt2/io/Io.h
@@ -58,6 +58,12 @@
   virtual bool HadError() const = 0;
 };
 
+// A sub-InputStream interface that knows the total size of its stream.
+class KnownSizeInputStream : public InputStream {
+ public:
+  virtual size_t TotalSize() const = 0;
+};
+
 // OutputStream interface that mimics protobuf's ZeroCopyOutputStream,
 // with added error handling methods to better report issues.
 class OutputStream {
diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringInputStream.cpp
deleted file mode 100644
index 51a18a7..0000000
--- a/tools/aapt2/io/StringInputStream.cpp
+++ /dev/null
@@ -1,50 +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.
- */
-
-#include "io/StringInputStream.h"
-
-using ::android::StringPiece;
-
-namespace aapt {
-namespace io {
-
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
-}
-
-bool StringInputStream::Next(const void** data, size_t* size) {
-  if (offset_ == str_.size()) {
-    return false;
-  }
-
-  *data = str_.data() + offset_;
-  *size = str_.size() - offset_;
-  offset_ = str_.size();
-  return true;
-}
-
-void StringInputStream::BackUp(size_t count) {
-  if (count > offset_) {
-    count = offset_;
-  }
-  offset_ -= count;
-}
-
-size_t StringInputStream::ByteCount() const {
-  return offset_;
-}
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringInputStream.h
deleted file mode 100644
index ff5b112..0000000
--- a/tools/aapt2/io/StringInputStream.h
+++ /dev/null
@@ -1,56 +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.
- */
-
-#ifndef AAPT_IO_STRINGINPUTSTREAM_H
-#define AAPT_IO_STRINGINPUTSTREAM_H
-
-#include "io/Io.h"
-
-#include "android-base/macros.h"
-#include "androidfw/StringPiece.h"
-
-namespace aapt {
-namespace io {
-
-class StringInputStream : public InputStream {
- public:
-  explicit StringInputStream(const android::StringPiece& str);
-
-  bool Next(const void** data, size_t* size) override;
-
-  void BackUp(size_t count) override;
-
-  size_t ByteCount() const override;
-
-  inline bool HadError() const override {
-    return false;
-  }
-
-  inline std::string GetError() const override {
-    return {};
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(StringInputStream);
-
-  android::StringPiece str_;
-  size_t offset_;
-};
-
-}  // namespace io
-}  // namespace aapt
-
-#endif  // AAPT_IO_STRINGINPUTSTREAM_H
diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringInputStream_test.cpp
deleted file mode 100644
index cc57bc4..0000000
--- a/tools/aapt2/io/StringInputStream_test.cpp
+++ /dev/null
@@ -1,72 +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.
- */
-
-#include "io/StringInputStream.h"
-
-#include "test/Test.h"
-
-using ::android::StringPiece;
-using ::testing::Eq;
-using ::testing::NotNull;
-using ::testing::StrEq;
-
-namespace aapt {
-namespace io {
-
-TEST(StringInputStreamTest, OneCallToNextShouldReturnEntireBuffer) {
-  constexpr const size_t kCount = 1000;
-  std::string input;
-  input.resize(kCount, 0x7f);
-  input[0] = 0x00;
-  input[kCount - 1] = 0xff;
-  StringInputStream in(input);
-
-  const char* buffer;
-  size_t size;
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(kCount));
-  ASSERT_THAT(buffer, NotNull());
-
-  EXPECT_THAT(buffer[0], Eq(0x00));
-  EXPECT_THAT(buffer[kCount - 1], Eq('\xff'));
-
-  EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  EXPECT_FALSE(in.HadError());
-}
-
-TEST(StringInputStreamTest, BackUp) {
-  std::string input = "hello this is a string";
-  StringInputStream in(input);
-
-  const char* buffer;
-  size_t size;
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(input.size()));
-  ASSERT_THAT(buffer, NotNull());
-  EXPECT_THAT(in.ByteCount(), Eq(input.size()));
-
-  in.BackUp(6u);
-  EXPECT_THAT(in.ByteCount(), Eq(input.size() - 6u));
-
-  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
-  ASSERT_THAT(size, Eq(6u));
-  ASSERT_THAT(buffer, NotNull());
-  ASSERT_THAT(buffer, StrEq("string"));
-  EXPECT_THAT(in.ByteCount(), Eq(input.size()));
-}
-
-}  // namespace io
-}  // namespace aapt
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
new file mode 100644
index 0000000..4ca04a8
--- /dev/null
+++ b/tools/aapt2/io/StringStream.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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 "io/StringStream.h"
+
+using ::android::StringPiece;
+
+namespace aapt {
+namespace io {
+
+StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+}
+
+bool StringInputStream::Next(const void** data, size_t* size) {
+  if (offset_ == str_.size()) {
+    return false;
+  }
+
+  *data = str_.data() + offset_;
+  *size = str_.size() - offset_;
+  offset_ = str_.size();
+  return true;
+}
+
+void StringInputStream::BackUp(size_t count) {
+  if (count > offset_) {
+    offset_ = 0u;
+  } else {
+    offset_ -= count;
+  }
+}
+
+size_t StringInputStream::ByteCount() const {
+  return offset_;
+}
+
+size_t StringInputStream::TotalSize() const {
+  return str_.size();
+}
+
+StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity)
+    : str_(str),
+      buffer_capacity_(buffer_capacity),
+      buffer_offset_(0u),
+      buffer_(new char[buffer_capacity]) {
+}
+
+StringOutputStream::~StringOutputStream() {
+  Flush();
+}
+
+bool StringOutputStream::Next(void** data, size_t* size) {
+  if (buffer_offset_ == buffer_capacity_) {
+    FlushImpl();
+  }
+
+  *data = buffer_.get() + buffer_offset_;
+  *size = buffer_capacity_ - buffer_offset_;
+  buffer_offset_ = buffer_capacity_;
+  return true;
+}
+
+void StringOutputStream::BackUp(size_t count) {
+  if (count > buffer_offset_) {
+    buffer_offset_ = 0u;
+  } else {
+    buffer_offset_ -= count;
+  }
+}
+
+size_t StringOutputStream::ByteCount() const {
+  return str_->size() + buffer_offset_;
+}
+
+void StringOutputStream::Flush() {
+  if (buffer_offset_ != 0u) {
+    FlushImpl();
+  }
+}
+
+void StringOutputStream::FlushImpl() {
+  str_->append(buffer_.get(), buffer_offset_);
+  buffer_offset_ = 0u;
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
new file mode 100644
index 0000000..f29890a
--- /dev/null
+++ b/tools/aapt2/io/StringStream.h
@@ -0,0 +1,93 @@
+/*
+ * 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 AAPT_IO_STRINGSTREAM_H
+#define AAPT_IO_STRINGSTREAM_H
+
+#include "io/Io.h"
+
+#include <memory>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+namespace aapt {
+namespace io {
+
+class StringInputStream : public KnownSizeInputStream {
+ public:
+  explicit StringInputStream(const android::StringPiece& str);
+
+  bool Next(const void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  size_t ByteCount() const override;
+
+  inline bool HadError() const override {
+    return false;
+  }
+
+  inline std::string GetError() const override {
+    return {};
+  }
+
+  size_t TotalSize() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StringInputStream);
+
+  android::StringPiece str_;
+  size_t offset_;
+};
+
+class StringOutputStream : public OutputStream {
+ public:
+  explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u);
+
+  ~StringOutputStream();
+
+  bool Next(void** data, size_t* size) override;
+
+  void BackUp(size_t count) override;
+
+  void Flush();
+
+  size_t ByteCount() const override;
+
+  inline bool HadError() const override {
+    return false;
+  }
+
+  inline std::string GetError() const override {
+    return {};
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StringOutputStream);
+
+  void FlushImpl();
+
+  std::string* str_;
+  size_t buffer_capacity_;
+  size_t buffer_offset_;
+  std::unique_ptr<char[]> buffer_;
+};
+
+}  // namespace io
+}  // namespace aapt
+
+#endif  // AAPT_IO_STRINGSTREAM_H
diff --git a/tools/aapt2/io/StringStream_test.cpp b/tools/aapt2/io/StringStream_test.cpp
new file mode 100644
index 0000000..fb43fb7
--- /dev/null
+++ b/tools/aapt2/io/StringStream_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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 "io/StringStream.h"
+
+#include "io/Util.h"
+
+#include "test/Test.h"
+
+using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+namespace aapt {
+namespace io {
+
+TEST(StringInputStreamTest, OneCallToNextShouldReturnEntireBuffer) {
+  constexpr const size_t kCount = 1000;
+  std::string input;
+  input.resize(kCount, 0x7f);
+  input[0] = 0x00;
+  input[kCount - 1] = 0xff;
+  StringInputStream in(input);
+
+  const char* buffer;
+  size_t size;
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(kCount));
+  ASSERT_THAT(buffer, NotNull());
+
+  EXPECT_THAT(buffer[0], Eq(0x00));
+  EXPECT_THAT(buffer[kCount - 1], Eq('\xff'));
+
+  EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  EXPECT_FALSE(in.HadError());
+}
+
+TEST(StringInputStreamTest, BackUp) {
+  std::string input = "hello this is a string";
+  StringInputStream in(input);
+
+  const char* buffer;
+  size_t size;
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(input.size()));
+  ASSERT_THAT(buffer, NotNull());
+  EXPECT_THAT(in.ByteCount(), Eq(input.size()));
+
+  in.BackUp(6u);
+  EXPECT_THAT(in.ByteCount(), Eq(input.size() - 6u));
+
+  ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size));
+  ASSERT_THAT(size, Eq(6u));
+  ASSERT_THAT(buffer, NotNull());
+  ASSERT_THAT(buffer, StrEq("string"));
+  EXPECT_THAT(in.ByteCount(), Eq(input.size()));
+}
+
+TEST(StringOutputStreamTest, NextAndBackUp) {
+  std::string input = "hello this is a string";
+  std::string output;
+
+  StringInputStream in(input);
+  StringOutputStream out(&output, 10u);
+  ASSERT_TRUE(Copy(&out, &in));
+  out.Flush();
+  EXPECT_THAT(output, StrEq(input));
+}
+
+}  // namespace io
+}  // namespace aapt
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index 15114e8..d270340 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -18,6 +18,8 @@
 
 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
 
+using ::google::protobuf::io::ZeroCopyOutputStream;
+
 namespace aapt {
 namespace io {
 
@@ -91,5 +93,10 @@
   return !in->HadError();
 }
 
+bool Copy(ZeroCopyOutputStream* out, InputStream* in) {
+  OutputStreamAdaptor adaptor(out);
+  return Copy(&adaptor, in);
+}
+
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index ec1ddb8..1e48508 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -21,7 +21,7 @@
 
 #include "google/protobuf/message_lite.h"
 
-#include "flatten/Archive.h"
+#include "format/Archive.h"
 #include "io/File.h"
 #include "io/Io.h"
 #include "process/IResourceTableConsumer.h"
@@ -42,6 +42,81 @@
 // Copies the data from in to out. Returns false if there was an error.
 // If there was an error, check the individual streams' HadError/GetError methods.
 bool Copy(OutputStream* out, InputStream* in);
+bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
+
+class OutputStreamAdaptor : public io::OutputStream {
+ public:
+  explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) {
+  }
+
+  bool Next(void** data, size_t* size) override {
+    int out_size;
+    bool result = out_->Next(data, &out_size);
+    *size = static_cast<size_t>(out_size);
+    if (!result) {
+      error_ocurred_ = true;
+    }
+    return result;
+  }
+
+  void BackUp(size_t count) override {
+    out_->BackUp(static_cast<int>(count));
+  }
+
+  size_t ByteCount() const override {
+    return static_cast<size_t>(out_->ByteCount());
+  }
+
+  bool HadError() const override {
+    return error_ocurred_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OutputStreamAdaptor);
+
+  ::google::protobuf::io::ZeroCopyOutputStream* out_;
+  bool error_ocurred_ = false;
+};
+
+class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream {
+ public:
+  explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) {
+  }
+
+  bool Next(const void** data, int* size) override {
+    size_t out_size;
+    bool result = in_->Next(data, &out_size);
+    *size = static_cast<int>(out_size);
+    return result;
+  }
+
+  void BackUp(int count) override {
+    in_->BackUp(static_cast<size_t>(count));
+  }
+
+  bool Skip(int count) override {
+    const void* data;
+    int size;
+    while (Next(&data, &size)) {
+      if (size > count) {
+        BackUp(size - count);
+        return true;
+      } else {
+        count -= size;
+      }
+    }
+    return false;
+  }
+
+  ::google::protobuf::int64 ByteCount() const override {
+    return static_cast<::google::protobuf::int64>(in_->ByteCount());
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor);
+
+  io::InputStream* in_;
+};
 
 }  // namespace io
 }  // namespace aapt
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 6494d2d..269b6c5 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -22,7 +22,7 @@
 #include "Source.h"
 #include "util/Util.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 namespace io {
@@ -57,7 +57,13 @@
   }
 }
 
-const Source& ZipFile::GetSource() const { return source_; }
+std::unique_ptr<io::InputStream> ZipFile::OpenInputStream() {
+  return OpenAsData();
+}
+
+const Source& ZipFile::GetSource() const {
+  return source_;
+}
 
 bool ZipFile::WasCompressed() {
   return zip_entry_.method != kCompressStored;
@@ -67,7 +73,9 @@
     ZipFileCollection* collection)
     : current_(collection->files_.begin()), end_(collection->files_.end()) {}
 
-bool ZipFileCollectionIterator::HasNext() { return current_ != end_; }
+bool ZipFileCollectionIterator::HasNext() {
+  return current_ != end_;
+}
 
 IFile* ZipFileCollectionIterator::Next() {
   IFile* result = current_->get();
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 56c74e3..8381259 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -28,23 +28,20 @@
 namespace aapt {
 namespace io {
 
-/**
- * An IFile representing a file within a ZIP archive. If the file is compressed,
- * it is uncompressed
- * and copied into memory when opened. Otherwise it is mmapped from the ZIP
- * archive.
- */
+// An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+// and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
 class ZipFile : public IFile {
  public:
-  ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source);
+  ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const Source& source);
 
   std::unique_ptr<IData> OpenAsData() override;
+  std::unique_ptr<io::InputStream> OpenInputStream() override;
   const Source& GetSource() const override;
   bool WasCompressed() override;
 
  private:
-  ZipArchiveHandle zip_handle_;
-  ZipEntry zip_entry_;
+  ::ZipArchiveHandle zip_handle_;
+  ::ZipEntry zip_entry_;
   Source source_;
 };
 
@@ -61,9 +58,7 @@
   std::vector<std::unique_ptr<IFile>>::const_iterator current_, end_;
 };
 
-/**
- * An IFileCollection that represents a ZIP archive and the entries within it.
- */
+// An IFileCollection that represents a ZIP archive and the entries within it.
 class ZipFileCollection : public IFileCollection {
  public:
   static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 6ad0dd6..0c57e7e 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -41,18 +41,21 @@
 
 ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) {
   Result result = Result::kAdded;
-  auto iter = members_.find(member);
-  if (iter != members_.end()) {
-    members_.erase(iter);
+  auto iter = indexed_members_.find(member->GetName());
+  if (iter != indexed_members_.end()) {
+    // Overwrite the entry.
+    ordered_members_[iter->second].reset();
     result = Result::kOverridden;
   }
-  members_.insert(std::move(member));
+
+  indexed_members_[member->GetName()] = ordered_members_.size();
+  ordered_members_.push_back(std::move(member));
   return result;
 }
 
 bool ClassDefinition::empty() const {
-  for (const std::unique_ptr<ClassMember>& member : members_) {
-    if (!member->empty()) {
+  for (const std::unique_ptr<ClassMember>& member : ordered_members_) {
+    if (member != nullptr && !member->empty()) {
       return false;
     }
   }
@@ -61,7 +64,7 @@
 
 void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final,
                                     std::ostream* out) const {
-  if (members_.empty() && !create_if_empty_) {
+  if (empty() && !create_if_empty_) {
     return;
   }
 
@@ -76,9 +79,14 @@
   std::string new_prefix = prefix.to_string();
   new_prefix.append(kIndent);
 
-  for (const std::unique_ptr<ClassMember>& member : members_) {
-    member->WriteToStream(new_prefix, final, out);
-    *out << "\n";
+  for (const std::unique_ptr<ClassMember>& member : ordered_members_) {
+    // There can be nullptr members when a member is added to the ClassDefinition
+    // and takes precedence over a previous member with the same name. The overridden member is
+    // set to nullptr.
+    if (member != nullptr) {
+      member->WriteToStream(new_prefix, final, out);
+      *out << "\n";
+    }
   }
 
   *out << prefix << "}";
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 6c4bcad..28a3489 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -18,8 +18,9 @@
 #define AAPT_JAVA_CLASSDEFINITION_H
 
 #include <ostream>
-#include <set>
 #include <string>
+#include <unordered_map>
+#include <vector>
 
 #include "android-base/macros.h"
 #include "androidfw/StringPiece.h"
@@ -73,21 +74,18 @@
   void WriteToStream(const android::StringPiece& prefix, bool final,
                      std::ostream* out) const override {
     ClassMember::WriteToStream(prefix, final, out);
-
-    *out << prefix << "public static " << (final ? "final " : "") << "int "
-         << name_ << "=" << val_ << ";";
+    *out << prefix << "public static " << (final ? "final " : "") << "int " << name_ << "=" << val_
+         << ";";
   }
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+
   std::string name_;
   T val_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
 };
 
-/**
- * Specialization for strings so they get the right type and are quoted with "".
- */
+// Specialization for strings so they get the right type and are quoted with "".
 template <>
 class PrimitiveMember<std::string> : public ClassMember {
  public:
@@ -111,10 +109,10 @@
   }
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
+
   std::string name_;
   std::string val_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrimitiveMember);
 };
 
 using IntMember = PrimitiveMember<uint32_t>;
@@ -160,10 +158,10 @@
   }
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
+
   std::string name_;
   std::vector<T> elements_;
-
-  DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember);
 };
 
 using ResourceArrayMember = PrimitiveArrayMember<ResourceId>;
@@ -185,12 +183,16 @@
   }
 
   // Even if the method is empty, we always want to write the method signature.
-  bool empty() const override { return false; }
+  bool empty() const override {
+    return false;
+  }
 
   void WriteToStream(const android::StringPiece& prefix, bool final,
                      std::ostream* out) const override;
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(MethodDefinition);
+
   std::string signature_;
   std::vector<std::string> statements_;
 };
@@ -222,19 +224,13 @@
                      std::ostream* out) const override;
 
  private:
-  struct ClassMemberCompare {
-    using T = std::unique_ptr<ClassMember>;
-    bool operator()(const T& a, const T& b) const {
-      return a->GetName() < b->GetName();
-    }
-  };
+  DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
 
   std::string name_;
   ClassQualifier qualifier_;
   bool create_if_empty_;
-  std::set<std::unique_ptr<ClassMember>, ClassMemberCompare> members_;
-
-  DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
+  std::vector<std::unique_ptr<ClassMember>> ordered_members_;
+  std::unordered_map<android::StringPiece, size_t> indexed_members_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 8da9106..3ba4dd8 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -61,7 +61,7 @@
 
 // Java symbols can not contain . or -, but those are valid in a resource name.
 // Replace those with '_'.
-static std::string TransformToFieldName(const StringPiece& symbol) {
+std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
   std::string output = symbol.to_string();
   for (char& c : output) {
     if (c == '.' || c == '-') {
@@ -89,9 +89,9 @@
   // the package.
   if (!attr_name.package.empty() &&
       package_name_to_generate != attr_name.package) {
-    output += "_" + TransformToFieldName(attr_name.package);
+    output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package);
   }
-  output += "_" + TransformToFieldName(attr_name.entry);
+  output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry);
   return output;
 }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 18746ff..2541749 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -24,6 +24,7 @@
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
+#include "androidfw/StringPiece.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 
@@ -78,6 +79,8 @@
 
   const std::string& getError() const;
 
+  static std::string TransformToFieldName(const android::StringPiece& symbol);
+
  private:
   bool SkipSymbol(SymbolState state);
   bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 4f449f0..668e434 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -24,6 +24,8 @@
 
 using ::android::StringPiece;
 using ::testing::HasSubstr;
+using ::testing::Lt;
+using ::testing::Ne;
 using ::testing::Not;
 
 namespace aapt {
@@ -306,6 +308,53 @@
   EXPECT_THAT(output, HasSubstr(styleable.GetComment()));
 }
 
+TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("android", 0x01)
+          .AddValue("android:attr/layout_gravity", util::make_unique<Attribute>())
+          .AddValue("android:attr/background", util::make_unique<Attribute>())
+          .AddValue("android:styleable/ActionBar",
+                    test::StyleableBuilder()
+                        .AddItem("android:attr/background", ResourceId(0x01010000))
+                        .Build())
+          .AddValue("android:styleable/ActionBar.LayoutParams",
+                    test::StyleableBuilder()
+                        .AddItem("android:attr/layout_gravity", ResourceId(0x01010001))
+                        .Build())
+          .Build();
+
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder()
+          .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
+          .SetNameManglerPolicy(NameManglerPolicy{"android"})
+          .Build();
+
+  JavaClassGeneratorOptions options;
+  JavaClassGenerator generator(context.get(), table.get(), {});
+  std::stringstream out;
+  ASSERT_TRUE(generator.Generate("android", &out));
+  std::string output = out.str();
+
+  std::string::size_type actionbar_pos = output.find("int[] ActionBar");
+  ASSERT_THAT(actionbar_pos, Ne(std::string::npos));
+
+  std::string::size_type actionbar_background_pos = output.find("int ActionBar_background");
+  ASSERT_THAT(actionbar_background_pos, Ne(std::string::npos));
+
+  std::string::size_type actionbar_layout_params_pos = output.find("int[] ActionBar_LayoutParams");
+  ASSERT_THAT(actionbar_layout_params_pos, Ne(std::string::npos));
+
+  std::string::size_type actionbar_layout_params_layout_gravity_pos =
+      output.find("int ActionBar_LayoutParams_layout_gravity");
+  ASSERT_THAT(actionbar_layout_params_layout_gravity_pos, Ne(std::string::npos));
+
+  EXPECT_THAT(actionbar_pos, Lt(actionbar_background_pos));
+  EXPECT_THAT(actionbar_pos, Lt(actionbar_layout_params_pos));
+  EXPECT_THAT(actionbar_background_pos, Lt(actionbar_layout_params_pos));
+  EXPECT_THAT(actionbar_layout_params_pos, Lt(actionbar_layout_params_layout_gravity_pos));
+}
+
 TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
   Attribute attr(false);
   attr.SetComment(StringPiece("removed"));
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 10c4610..b9ae654 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -21,6 +21,10 @@
 
 #include "android-base/macros.h"
 
+#include "JavaClassGenerator.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
 #include "util/Util.h"
 #include "xml/XmlDom.h"
 
@@ -31,7 +35,7 @@
  public:
   using xml::Visitor::Visit;
 
-  BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) {
+  BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) {
   }
 
   void Visit(xml::Element* node) override {
@@ -52,27 +56,47 @@
     for (const auto& child : node->children) {
       child->Accept(this);
     }
+
+    for (const auto& attr : node->attributes) {
+      if (attr.compiled_value) {
+        auto ref = ValueCast<Reference>(attr.compiled_value.get());
+        if (ref) {
+          AddReference(node->line_number, ref);
+        }
+      }
+    }
   }
 
  protected:
-  void AddClass(size_t line_number, const std::string& class_name) {
-    keep_set_->AddClass(Source(source_.path, line_number), class_name);
+  ResourceFile file_;
+  KeepSet* keep_set_;
+
+  virtual void AddClass(size_t line_number, const std::string& class_name) {
+    keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name);
   }
 
   void AddMethod(size_t line_number, const std::string& method_name) {
-    keep_set_->AddMethod(Source(source_.path, line_number), method_name);
+    keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name);
+  }
+
+  void AddReference(size_t line_number, Reference* ref) {
+    if (ref && ref->name) {
+      ResourceName ref_name = ref->name.value();
+      if (ref_name.package.empty()) {
+        ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry);
+      }
+      keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name);
+    }
   }
 
  private:
   DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
 
-  Source source_;
-  KeepSet* keep_set_;
 };
 
 class LayoutVisitor : public BaseVisitor {
  public:
-  LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+  LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
   }
 
   void Visit(xml::Element* node) override {
@@ -110,7 +134,7 @@
 
 class MenuVisitor : public BaseVisitor {
  public:
-  MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+  MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
   }
 
   void Visit(xml::Element* node) override {
@@ -136,7 +160,7 @@
 
 class XmlResourceVisitor : public BaseVisitor {
  public:
-  XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+  XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
   }
 
   void Visit(xml::Element* node) override {
@@ -163,7 +187,7 @@
 
 class TransitionVisitor : public BaseVisitor {
  public:
-  TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
+  TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
   }
 
   void Visit(xml::Element* node) override {
@@ -185,8 +209,9 @@
 
 class ManifestVisitor : public BaseVisitor {
  public:
-  ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)
-      : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {}
+  ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only)
+      : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) {
+  }
 
   void Visit(xml::Element* node) override {
     if (node->namespace_uri.empty()) {
@@ -241,6 +266,10 @@
     BaseVisitor::Visit(node);
   }
 
+  virtual void AddClass(size_t line_number, const std::string& class_name) override {
+    keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name);
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
 
@@ -249,9 +278,8 @@
   std::string default_process_;
 };
 
-bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set,
-                                     bool main_dex_only) {
-  ManifestVisitor visitor(source, keep_set, main_dex_only);
+bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) {
+  ManifestVisitor visitor(res->file, keep_set, main_dex_only);
   if (res->root) {
     res->root->Accept(&visitor);
     return true;
@@ -259,58 +287,150 @@
   return false;
 }
 
-bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) {
+bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) {
   if (!res->root) {
     return false;
   }
 
   switch (res->file.name.type) {
     case ResourceType::kLayout: {
-      LayoutVisitor visitor(source, keep_set);
+      LayoutVisitor visitor(res->file, keep_set);
       res->root->Accept(&visitor);
       break;
     }
 
     case ResourceType::kXml: {
-      XmlResourceVisitor visitor(source, keep_set);
+      XmlResourceVisitor visitor(res->file, keep_set);
       res->root->Accept(&visitor);
       break;
     }
 
     case ResourceType::kTransition: {
-      TransitionVisitor visitor(source, keep_set);
+      TransitionVisitor visitor(res->file, keep_set);
       res->root->Accept(&visitor);
       break;
     }
 
     case ResourceType::kMenu: {
-      MenuVisitor visitor(source, keep_set);
+      MenuVisitor visitor(res->file, keep_set);
       res->root->Accept(&visitor);
       break;
     }
 
-    default:
+    default: {
+      BaseVisitor visitor(res->file, keep_set);
+      res->root->Accept(&visitor);
       break;
+    }
   }
   return true;
 }
 
 bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
-  for (const auto& entry : keep_set.keep_set_) {
-    for (const Source& source : entry.second) {
-      *out << "# Referenced at " << source << "\n";
+  for (const auto& entry : keep_set.manifest_class_set_) {
+    for (const UsageLocation& location : entry.second) {
+      *out << "# Referenced at " << location.source << "\n";
     }
     *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
   }
 
-  for (const auto& entry : keep_set.keep_method_set_) {
-    for (const Source& source : entry.second) {
-      *out << "# Referenced at " << source << "\n";
+  for (const auto& entry : keep_set.conditional_class_set_) {
+    std::set<UsageLocation> locations;
+    bool can_be_conditional = true;
+    for (const UsageLocation& location : entry.second) {
+      can_be_conditional &= CollectLocations(location, keep_set, &locations);
+    }
+
+    for (const UsageLocation& location : entry.second) {
+      *out << "# Referenced at " << location.source << "\n";
+    }
+    if (keep_set.conditional_keep_rules_ && can_be_conditional) {
+      *out << "-keep class " << entry.first << " {\n  ifused class **.R$layout {\n";
+      for (const UsageLocation& location : locations) {
+        auto transformed_name = JavaClassGenerator::TransformToFieldName(location.name.entry);
+        *out << "    int " << transformed_name << ";\n";
+      }
+      *out << "  };\n  <init>(...);\n}\n" << std::endl;
+    } else {
+      *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+    }
+  }
+
+  for (const auto& entry : keep_set.method_set_) {
+    for (const UsageLocation& location : entry.second) {
+      *out << "# Referenced at " << location.source << "\n";
     }
     *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
   }
   return true;
 }
 
+bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
+                      std::set<UsageLocation>* locations) {
+  locations->insert(location);
+
+  // TODO: allow for more reference types if we can determine its safe.
+  if (location.name.type != ResourceType::kLayout) {
+    return false;
+  }
+
+  for (const auto& entry : keep_set.reference_set_) {
+    if (entry.first == location.name) {
+      for (auto& refLocation : entry.second) {
+        // Don't get stuck in loops
+        if (locations->find(refLocation) != locations->end()) {
+          return false;
+        }
+        if (!CollectLocations(refLocation, keep_set, locations)) {
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+class ReferenceVisitor : public ValueVisitor {
+ public:
+  using ValueVisitor::Visit;
+
+  ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set)
+      : context_(context), from_(from), keep_set_(keep_set) {
+  }
+
+  void Visit(Reference* reference) override {
+    if (reference->name) {
+      ResourceName reference_name = reference->name.value();
+      if (reference_name.package.empty()) {
+        reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type,
+                                      reference_name.entry);
+      }
+      keep_set_->AddReference({from_, reference->GetSource()}, reference_name);
+    }
+  }
+
+ private:
+  aapt::IAaptContext* context_;
+  ResourceName from_;
+  KeepSet* keep_set_;
+};
+
+bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
+                               KeepSet* keep_set) {
+  for (auto& pkg : table->packages) {
+    for (auto& type : pkg->types) {
+      for (auto& entry : type->entries) {
+        for (auto& config_value : entry->values) {
+          ResourceName from(pkg->name, type->type, entry->name);
+          ReferenceVisitor visitor(context, from, keep_set);
+          config_value->value->Accept(&visitor);
+        }
+      }
+    }
+  }
+  return true;
+}
+
 }  // namespace proguard
 }  // namespace aapt
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index 3c349ba..8dbe3c2 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -23,37 +23,80 @@
 #include <string>
 
 #include "Resource.h"
+#include "ResourceTable.h"
 #include "Source.h"
+#include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
+#include "process/IResourceTableConsumer.h"
 #include "xml/XmlDom.h"
 
 namespace aapt {
 namespace proguard {
 
+struct UsageLocation {
+  ResourceName name;
+  Source source;
+};
+
 class KeepSet {
  public:
-  inline void AddClass(const Source& source, const std::string& class_name) {
-    keep_set_[class_name].insert(source);
+  KeepSet() = default;
+
+  KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) {
   }
 
-  inline void AddMethod(const Source& source, const std::string& method_name) {
-    keep_method_set_[method_name].insert(source);
+  inline void AddManifestClass(const UsageLocation& file, const std::string& class_name) {
+    manifest_class_set_[class_name].insert(file);
+  }
+
+  inline void AddConditionalClass(const UsageLocation& file, const std::string& class_name) {
+    conditional_class_set_[class_name].insert(file);
+  }
+
+  inline void AddMethod(const UsageLocation& file, const std::string& method_name) {
+    method_set_[method_name].insert(file);
+  }
+
+  inline void AddReference(const UsageLocation& file, const ResourceName& resource_name) {
+    reference_set_[resource_name].insert(file);
   }
 
  private:
   friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
 
-  std::map<std::string, std::set<Source>> keep_set_;
-  std::map<std::string, std::set<Source>> keep_method_set_;
+  friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
+                               std::set<UsageLocation>* locations);
+
+  bool conditional_keep_rules_ = false;
+  std::map<std::string, std::set<UsageLocation>> manifest_class_set_;
+  std::map<std::string, std::set<UsageLocation>> method_set_;
+  std::map<std::string, std::set<UsageLocation>> conditional_class_set_;
+  std::map<ResourceName, std::set<UsageLocation>> reference_set_;
 };
 
-bool CollectProguardRulesForManifest(const Source& source,
-                                     xml::XmlResource* res, KeepSet* keep_set,
+bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set,
                                      bool main_dex_only = false);
-bool CollectProguardRules(const Source& source, xml::XmlResource* res,
-                          KeepSet* keep_set);
+bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set);
+bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
+                               KeepSet* keep_set);
 
 bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);
 
+bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
+                      std::set<UsageLocation>* locations);
+
+//
+// UsageLocation implementation.
+//
+
+inline bool operator==(const UsageLocation& lhs, const UsageLocation& rhs) {
+  return lhs.name == rhs.name;
+}
+
+inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) {
+  return lhs.name.compare(rhs.name);
+}
+
 }  // namespace proguard
 }  // namespace aapt
 
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index 900b073..df3ac8b 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "java/ProguardRules.h"
+#include "link/Linkers.h"
 
 #include "test/Test.h"
 
@@ -31,7 +32,7 @@
   layout->file.name = test::ParseNameOrDie("layout/foo");
 
   proguard::KeepSet set;
-  ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
   std::stringstream out;
   ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -47,7 +48,7 @@
   layout->file.name = test::ParseNameOrDie("layout/foo");
 
   proguard::KeepSet set;
-  ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
   std::stringstream out;
   ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -65,7 +66,7 @@
   layout->file.name = test::ParseNameOrDie("layout/foo");
 
   proguard::KeepSet set;
-  ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
   std::stringstream out;
   ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -75,6 +76,107 @@
   EXPECT_THAT(actual, HasSubstr("com.foo.Baz"));
 }
 
+TEST(ProguardRulesTest, CustomViewRulesAreEmitted) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android">
+        <com.foo.Bar />
+      </View>)");
+  layout->file.name = test::ParseNameOrDie("layout/foo");
+
+  proguard::KeepSet set;
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+
+  std::stringstream out;
+  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+  std::string actual = out.str();
+  EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
+  std::unique_ptr<xml::XmlResource> bar_layout = test::BuildXmlDom(R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android">
+        <com.foo.Bar />
+      </View>)");
+  bar_layout->file.name = test::ParseNameOrDie("com.foo:layout/bar");
+
+  ResourceTable table;
+  StdErrDiagnostics errDiagnostics;
+  table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "",
+                    util::make_unique<FileReference>(), &errDiagnostics);
+
+  std::unique_ptr<IAaptContext> context =
+      test::ContextBuilder()
+          .SetCompilationPackage("com.foo")
+          .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(&table))
+          .Build();
+
+  std::unique_ptr<xml::XmlResource> foo_layout = test::BuildXmlDom(R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android">
+        <include layout="@layout/bar" />
+      </View>)");
+  foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo");
+
+  XmlReferenceLinker xml_linker;
+  ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get()));
+  ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get()));
+
+  proguard::KeepSet set = proguard::KeepSet(true);
+  ASSERT_TRUE(proguard::CollectProguardRules(bar_layout.get(), &set));
+  ASSERT_TRUE(proguard::CollectProguardRules(foo_layout.get(), &set));
+
+  std::stringstream out;
+  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+  std::string actual = out.str();
+  EXPECT_THAT(actual, HasSubstr("ifused class **.R$layout"));
+  EXPECT_THAT(actual, HasSubstr("int foo"));
+  EXPECT_THAT(actual, HasSubstr("int bar"));
+  EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android">
+        <com.foo.Bar />
+      </View>)");
+  layout->file.name = test::ParseNameOrDie("layout/foo");
+
+  proguard::KeepSet set = proguard::KeepSet(true);
+  set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name);
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+
+  std::stringstream out;
+  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+  std::string actual = out.str();
+  EXPECT_THAT(actual, HasSubstr("ifused class **.R$layout"));
+  EXPECT_THAT(actual, HasSubstr("int foo"));
+  EXPECT_THAT(actual, HasSubstr("int bar"));
+  EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
+}
+
+TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
+      <View xmlns:android="http://schemas.android.com/apk/res/android">
+        <com.foo.Bar />
+      </View>)");
+  layout->file.name = test::ParseNameOrDie("layout/foo");
+
+  proguard::KeepSet set = proguard::KeepSet(true);
+  set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name);
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
+
+  std::stringstream out;
+  ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
+
+  std::string actual = out.str();
+  EXPECT_THAT(actual, Not(HasSubstr("ifused")));
+}
+
 TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
@@ -83,7 +185,7 @@
   layout->file.name = test::ParseNameOrDie("layout/foo");
 
   proguard::KeepSet set;
-  ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set));
+  ASSERT_TRUE(proguard::CollectProguardRules(layout.get(), &set));
 
   std::stringstream out;
   ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
@@ -104,7 +206,7 @@
   menu->file.name = test::ParseNameOrDie("menu/foo");
 
   proguard::KeepSet set;
-  ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set));
+  ASSERT_TRUE(proguard::CollectProguardRules(menu.get(), &set));
 
   std::stringstream out;
   ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 71e828b..ad7d8b6 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -42,9 +42,9 @@
 // to the reference object.
 //
 // NOTE: All of the entries in the ResourceTable must be assigned IDs.
-class ReferenceLinkerVisitor : public ValueVisitor {
+class ReferenceLinkerVisitor : public DescendingValueVisitor {
  public:
-  using ValueVisitor::Visit;
+  using DescendingValueVisitor::Visit;
 
   ReferenceLinkerVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
                          StringPool* string_pool, xml::IPackageDeclStack* decl)
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 3b11bee..b0b4945 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -21,7 +21,6 @@
 
 #include "Resource.h"
 #include "ResourceValues.h"
-#include "ValueVisitor.h"
 #include "link/Linkers.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 93c904f..21c6b11 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,13 +37,12 @@
   CHECK(master_package_ != nullptr) << "package name or ID already taken";
 }
 
-bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) {
-  return MergeImpl(src, table, collection, false /*overlay*/, true /*allow_new*/);
-}
-
-bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table,
-                               io::IFileCollection* collection) {
-  return MergeImpl(src, table, collection, true /*overlay*/, options_.auto_add_overlay);
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
+                        io::IFileCollection* collection) {
+  // We allow adding new resources if this is not an overlay, or if the options allow overlays
+  // to add new resources.
+  return MergeImpl(src, table, collection, overlay,
+                   options_.auto_add_overlay || !overlay /*allow_new*/);
 }
 
 // This will merge packages with the same package name (or no package name).
@@ -322,17 +321,20 @@
         util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath));
     new_file_ref->SetComment(file_ref.GetComment());
     new_file_ref->SetSource(file_ref.GetSource());
+    new_file_ref->type = file_ref.type;
+    new_file_ref->file = file_ref.file;
     return new_file_ref;
   }
   return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool));
 }
 
-bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) {
+bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) {
   ResourceTable table;
   std::string path = ResourceUtils::BuildResourceFileName(file_desc);
   std::unique_ptr<FileReference> file_ref =
       util::make_unique<FileReference>(table.string_pool.MakeRef(path));
   file_ref->SetSource(file_desc.source);
+  file_ref->type = file_desc.type;
   file_ref->file = file;
 
   ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0);
@@ -341,17 +343,8 @@
       ->FindOrCreateValue(file_desc.config, {})
       ->value = std::move(file_ref);
 
-  return DoMerge(file->GetSource(), &table, pkg, false /* mangle */,
-                 overlay /* overlay */, true /* allow_new */, {});
-}
-
-bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) {
-  return MergeFileImpl(file_desc, file, false /* overlay */);
-}
-
-bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc,
-                                   io::IFile* file) {
-  return MergeFileImpl(file_desc, file, true /* overlay */);
+  return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
+                 true /* allow_new */, {});
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 81518ff..d024aa4 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -59,24 +59,18 @@
   }
 
   // Merges resources from the same or empty package. This is for local sources.
+  // If overlay is true, the resources are treated as overlays.
   // An io::IFileCollection is optional and used to find the referenced Files and process them.
-  bool Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection = nullptr);
-
-  // Merges resources from an overlay ResourceTable.
-  // An io::IFileCollection is optional and used to find the referenced Files and process them.
-  bool MergeOverlay(const Source& src, ResourceTable* table,
-                    io::IFileCollection* collection = nullptr);
+  bool Merge(const Source& src, ResourceTable* table, bool overlay,
+             io::IFileCollection* collection = nullptr);
 
   // Merges resources from the given package, mangling the name. This is for static libraries.
   // An io::IFileCollection is needed in order to find the referenced Files and process them.
   bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
                       io::IFileCollection* collection);
 
-  // Merges a compiled file that belongs to this same or empty package. This is for local sources.
-  bool MergeFile(const ResourceFile& fileDesc, io::IFile* file);
-
-  // Merges a compiled file from an overlay, overriding an existing definition.
-  bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file);
+  // Merges a compiled file that belongs to this same or empty package.
+  bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(TableMerger);
@@ -91,9 +85,6 @@
   ResourceTablePackage* master_package_;
   std::set<std::string> merged_packages_;
 
-  bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file,
-                     bool overlay);
-
   bool MergeImpl(const Source& src, ResourceTable* src_table,
                  io::IFileCollection* collection, bool overlay, bool allow_new);
 
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 45b01a4..3499809 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -69,7 +69,7 @@
   TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
   io::FileCollection collection;
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
   ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
 
   EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
@@ -98,7 +98,7 @@
   file_desc.source = Source("res/layout-hdpi/main.xml");
   test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat");
 
-  ASSERT_TRUE(merger.MergeFile(file_desc, &test_file));
+  ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &test_file));
 
   FileReference* file = test::GetValueForConfig<FileReference>(
       &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4"));
@@ -117,8 +117,8 @@
   test::TestFile file_a("path/to/fileA.xml.flat");
   test::TestFile file_b("path/to/fileB.xml.flat");
 
-  ASSERT_TRUE(merger.MergeFile(file_desc, &file_a));
-  ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b));
+  ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &file_a));
+  ASSERT_TRUE(merger.MergeFile(file_desc, true /*overlay*/, &file_b));
 }
 
 TEST_F(TableMergerTest, MergeFileReferences) {
@@ -138,7 +138,7 @@
   io::FileCollection collection;
   collection.InsertFile("res/xml/file.xml");
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
   ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
 
   FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
@@ -167,8 +167,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
 
   BinaryPrimitive* foo = test::GetValue<BinaryPrimitive>(&final_table, "com.app.a:bool/foo");
   ASSERT_THAT(foo,
@@ -194,8 +194,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) {
@@ -217,8 +217,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) {
@@ -240,8 +240,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, base.get()));
-  ASSERT_FALSE(merger.MergeOverlay({}, overlay.get()));
+  ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/));
+  ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
@@ -259,8 +259,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
 }
 
 TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) {
@@ -277,8 +277,8 @@
   options.auto_add_overlay = true;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
 }
 
 TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
@@ -295,8 +295,8 @@
   options.auto_add_overlay = false;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_FALSE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_FALSE(merger.Merge({}, table_b.get(), true /*overlay*/));
 }
 
 TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
@@ -337,8 +337,8 @@
   options.auto_add_overlay = true;
   TableMerger merger(context_.get(), &final_table, options);
 
-  ASSERT_TRUE(merger.Merge({}, table_a.get()));
-  ASSERT_TRUE(merger.MergeOverlay({}, table_b.get()));
+  ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
+  ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/));
 
   Styleable* styleable = test::GetValue<Styleable>(&final_table, "com.app.a:styleable/Foo");
   ASSERT_THAT(styleable, NotNull());
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 6ebb80f..8852c8e 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -21,6 +21,7 @@
 #include "Diagnostics.h"
 #include "ResourceUtils.h"
 #include "SdkConstants.h"
+#include "ValueVisitor.h"
 #include "link/ReferenceLinker.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
@@ -34,9 +35,9 @@
 // Visits all references (including parents of styles, references in styles, arrays, etc) and
 // links their symbolic name to their Resource ID, performing mangling and package aliasing
 // as needed.
-class ReferenceVisitor : public ValueVisitor {
+class ReferenceVisitor : public DescendingValueVisitor {
  public:
-  using ValueVisitor::Visit;
+  using DescendingValueVisitor::Visit;
 
   ReferenceVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
                    xml::IPackageDeclStack* decls)
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 5ff8908..6803088 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -22,22 +22,50 @@
 #include "androidfw/StringPiece.h"
 
 #include "LoadedApk.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
 #include "configuration/ConfigurationParser.h"
 #include "filter/AbiFilter.h"
 #include "filter/Filter.h"
-#include "flatten/Archive.h"
+#include "format/Archive.h"
+#include "format/binary/XmlFlattener.h"
 #include "optimize/VersionCollapser.h"
 #include "process/IResourceTableConsumer.h"
 #include "split/TableSplitter.h"
 #include "util/Files.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlUtil.h"
 
 namespace aapt {
 
 using ::aapt::configuration::AndroidSdk;
 using ::aapt::configuration::Artifact;
 using ::aapt::configuration::PostProcessingConfiguration;
+using ::aapt::xml::kSchemaAndroid;
+using ::aapt::xml::XmlResource;
 using ::android::StringPiece;
 
+namespace {
+
+Maybe<AndroidSdk> GetAndroidSdk(const Artifact& artifact, const PostProcessingConfiguration& config,
+                                IDiagnostics* diag) {
+  if (!artifact.android_sdk_group) {
+    return {};
+  }
+
+  const std::string& group_name = artifact.android_sdk_group.value();
+  auto group = config.android_sdk_groups.find(group_name);
+  // TODO: Remove validation when configuration parser ensures referential integrity.
+  if (group == config.android_sdk_groups.end()) {
+    diag->Error(DiagMessage() << "could not find referenced group '" << group_name << "'");
+    return {};
+  }
+
+  return group->second;
+}
+
+}  // namespace
+
 /**
  * Context wrapper that allows the min Android SDK value to be overridden.
  */
@@ -127,6 +155,13 @@
       return false;
     }
 
+    std::unique_ptr<XmlResource> manifest;
+    if (!UpdateManifest(artifact, config, &manifest, diag)) {
+      diag->Error(DiagMessage() << "could not update AndroidManifest.xml for "
+                                << artifact_name.value());
+      return false;
+    }
+
     std::string out = options.out_dir;
     if (!file::mkdirs(out)) {
       context_->GetDiagnostics()->Warn(DiagMessage() << "could not create out dir: " << out);
@@ -145,7 +180,7 @@
     }
 
     if (!apk_->WriteToArchive(context_, table.get(), options.table_flattener_options, &filters,
-                              writer.get())) {
+                              writer.get(), manifest.get())) {
       return false;
     }
   }
@@ -208,37 +243,15 @@
     splits.config_filter = &axis_filter;
   }
 
-  if (artifact.android_sdk_group) {
-    const std::string& group_name = artifact.android_sdk_group.value();
-    auto group = config.android_sdk_groups.find(group_name);
-    // TODO: Remove validation when configuration parser ensures referential integrity.
-    if (group == config.android_sdk_groups.end()) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '"
-                                                      << group_name << "'");
-      return {};
-    }
-
-    const AndroidSdk& sdk = group->second;
-    if (!sdk.min_sdk_version) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "skipping SDK version. No min SDK: " << group_name);
-      return {};
-    }
-
-    ConfigDescription c;
-    const std::string& version = sdk.min_sdk_version.value();
-    if (!ConfigDescription::Parse(version, &c)) {
-      context_->GetDiagnostics()->Error(DiagMessage() << "could not parse min SDK: " << version);
-      return {};
-    }
-
-    wrappedContext.SetMinSdkVersion(c.sdkVersion);
+  Maybe<AndroidSdk> sdk = GetAndroidSdk(artifact, config, context_->GetDiagnostics());
+  if (sdk && sdk.value().min_sdk_version) {
+    wrappedContext.SetMinSdkVersion(sdk.value().min_sdk_version.value());
   }
 
   std::unique_ptr<ResourceTable> table = old_table.Clone();
 
   VersionCollapser collapser;
-  if (!collapser.Consume(context_, table.get())) {
+  if (!collapser.Consume(&wrappedContext, table.get())) {
     context_->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
     return {};
   }
@@ -248,4 +261,95 @@
   return table;
 }
 
+bool MultiApkGenerator::UpdateManifest(const Artifact& artifact,
+                                       const PostProcessingConfiguration& config,
+                                       std::unique_ptr<XmlResource>* updated_manifest,
+                                       IDiagnostics* diag) {
+  *updated_manifest = apk_->InflateManifest(context_);
+  XmlResource* manifest = updated_manifest->get();
+  if (manifest == nullptr) {
+    return false;
+  }
+
+  // Make sure the first element is <manifest> with package attribute.
+  xml::Element* manifest_el = manifest->root.get();
+  if (manifest_el == nullptr) {
+    return false;
+  }
+
+  if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
+    diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
+    return false;
+  }
+
+  // Update the versionCode attribute.
+  xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
+  if (versionCode == nullptr) {
+    diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute");
+    return false;
+  }
+
+  auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get());
+  if (compiled_version == nullptr) {
+    diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid");
+    return false;
+  }
+
+  int new_version = compiled_version->value.data + artifact.version;
+  versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
+
+  // Check to see if the minSdkVersion needs to be updated.
+  Maybe<AndroidSdk> maybe_sdk = GetAndroidSdk(artifact, config, diag);
+  if (maybe_sdk) {
+    // TODO(safarmer): Handle the rest of the Android SDK.
+    const AndroidSdk& android_sdk = maybe_sdk.value();
+
+    if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+      if (xml::Attribute* min_sdk_attr =
+              uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
+        // Populate with a pre-compiles attribute to we don't need to relink etc.
+        const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+        min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
+      } else {
+        // There was no minSdkVersion. This is strange since at this point we should have been
+        // through the manifest fixer which sets the default minSdkVersion.
+        diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>");
+        return false;
+      }
+    } else {
+      // No uses-sdk present. This is strange since at this point we should have been
+      // through the manifest fixer which should have added it.
+      diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>");
+      return false;
+    }
+  }
+
+  if (artifact.screen_density_group) {
+    auto densities = config.screen_density_groups.find(artifact.screen_density_group.value());
+    CHECK(densities != config.screen_density_groups.end()) << "Missing density group";
+
+    xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
+    if (!screens_el) {
+      // create a new element.
+      std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>();
+      new_screens_el->name = "compatible-screens";
+      screens_el = new_screens_el.get();
+      manifest_el->InsertChild(0, std::move(new_screens_el));
+    } else {
+      // clear out the old element.
+      screens_el->GetChildElements().clear();
+    }
+
+    for (const auto& density : densities->second) {
+      std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>();
+      screen_el->name = "screen";
+      const char* density_str = density.toString().string();
+      screen_el->attributes.push_back(xml::Attribute{kSchemaAndroid, "screenDensity", density_str});
+      screens_el->AppendChild(std::move(screen_el));
+    }
+  }
+
+  return true;
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h
index b064400..e6546ee 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.h
+++ b/tools/aapt2/optimize/MultiApkGenerator.h
@@ -54,6 +54,10 @@
     return context_->GetDiagnostics();
   }
 
+  bool UpdateManifest(const configuration::Artifact& artifact,
+                      const configuration::PostProcessingConfiguration& config,
+                      std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag);
+
   LoadedApk* apk_;
   IAaptContext* context_;
 };
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index 23f573c..c8f3524 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -25,8 +25,8 @@
 #include "ResourceTable.h"
 #include "configuration/ConfigurationParser.h"
 #include "filter/Filter.h"
-#include "flatten/Archive.h"
-#include "flatten/TableFlattener.h"
+#include "format/Archive.h"
+#include "format/binary/TableFlattener.h"
 #include "process/IResourceTableConsumer.h"
 #include "test/Context.h"
 #include "test/Test.h"
@@ -50,14 +50,6 @@
 using ::testing::Test;
 using ::testing::_;
 
-/** Subclass the LoadedApk class so that we can mock the WriteToArchive method. */
-class MockApk : public LoadedApk {
- public:
-  MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){};
-  MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&,
-                                    FilterChain*, IArchiveWriter*));
-};
-
 /**
  * Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to
  * directly test table filter.
@@ -111,54 +103,10 @@
   ConfigDescription v21_ = ParseConfigOrDie("v21");
 };
 
-TEST_F(MultiApkGeneratorTest, FromBaseApk) {
-  std::unique_ptr<ResourceTable> table = BuildTable();
-
-  MockApk apk{std::move(table)};
-
-  EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0);
-
-  test::Context ctx;
-  PostProcessingConfiguration empty_config;
-  TableFlattenerOptions table_flattener_options;
-
-  MultiApkGenerator generator{&apk, &ctx};
-  EXPECT_TRUE(generator.FromBaseApk({"out", empty_config, table_flattener_options}));
-
-  Artifact x64 = test::ArtifactBuilder()
-                     .SetName("${basename}.x64.apk")
-                     .SetAbiGroup("x64")
-                     .SetLocaleGroup("en")
-                     .SetDensityGroup("xhdpi")
-                     .Build();
-
-  Artifact intel = test::ArtifactBuilder()
-                       .SetName("${basename}.intel.apk")
-                       .SetAbiGroup("intel")
-                       .SetLocaleGroup("europe")
-                       .SetDensityGroup("large")
-                       .Build();
-
-  auto config = test::PostProcessingConfigurationBuilder()
-                    .SetLocaleGroup("en", {"en"})
-                    .SetLocaleGroup("europe", {"en", "fr", "de", "es"})
-                    .SetAbiGroup("x64", {Abi::kX86_64})
-                    .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86})
-                    .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"})
-                    .AddArtifact(x64)
-                    .AddArtifact(intel)
-                    .Build();
-
-  // Called once for each artifact.
-  EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true));
-  EXPECT_TRUE(generator.FromBaseApk({"out", config, table_flattener_options}));
-}
-
 TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  MockApk apk{std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -174,7 +122,7 @@
                     .SetLocaleGroup("en", {"en"})
                     .SetAbiGroup("x64", {Abi::kX86_64})
                     .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetAndroidSdk("v23", AndroidSdk::ForMinSdk("v23"))
+                    .SetAndroidSdk("v23", AndroidSdk::ForMinSdk(23))
                     .AddArtifact(x64)
                     .Build();
 
@@ -199,7 +147,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  MockApk apk{std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
@@ -215,7 +163,7 @@
                     .SetLocaleGroup("en", {"en"})
                     .SetAbiGroup("x64", {Abi::kX86_64})
                     .SetDensityGroup("xhdpi", {"xhdpi"})
-                    .SetAndroidSdk("v4", AndroidSdk::ForMinSdk("v4"))
+                    .SetAndroidSdk("v4", AndroidSdk::ForMinSdk(4))
                     .AddArtifact(x64)
                     .Build();
 
@@ -238,7 +186,7 @@
 TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
   std::unique_ptr<ResourceTable> table = BuildTable();
 
-  MockApk apk{std::move(table)};
+  LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
   std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
   PostProcessingConfiguration empty_config;
   TableFlattenerOptions table_flattener_options;
diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
deleted file mode 100644
index aa99c98..0000000
--- a/tools/aapt2/proto/ProtoHelpers.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2016 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 "proto/ProtoHelpers.h"
-
-namespace aapt {
-
-void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) {
-  BigBuffer buffer(1024);
-  StringPool::FlattenUtf8(&buffer, pool);
-
-  std::string* data = out_pb_pool->mutable_data();
-  data->reserve(buffer.size());
-
-  size_t offset = 0;
-  for (const BigBuffer::Block& block : buffer) {
-    data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
-    offset += block.size;
-  }
-}
-
-void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source) {
-  StringPool::Ref ref = src_pool->MakeRef(source.path);
-  out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index()));
-  if (source.line) {
-    out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value()));
-  }
-}
-
-void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool,
-                             Source* out_source) {
-  if (pb_source.has_path_idx()) {
-    out_source->path = util::GetString(src_pool, pb_source.path_idx());
-  }
-
-  if (pb_source.has_position()) {
-    out_source->line = static_cast<size_t>(pb_source.position().line_number());
-  }
-}
-
-pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
-  switch (state) {
-    case SymbolState::kPrivate:
-      return pb::SymbolStatus_Visibility_PRIVATE;
-    case SymbolState::kPublic:
-      return pb::SymbolStatus_Visibility_PUBLIC;
-    default:
-      break;
-  }
-  return pb::SymbolStatus_Visibility_UNKNOWN;
-}
-
-SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility) {
-  switch (pb_visibility) {
-    case pb::SymbolStatus_Visibility_PRIVATE:
-      return SymbolState::kPrivate;
-    case pb::SymbolStatus_Visibility_PUBLIC:
-      return SymbolState::kPublic;
-    default:
-      break;
-  }
-  return SymbolState::kUndefined;
-}
-
-void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config) {
-  android::ResTable_config flat_config = config;
-  flat_config.size = sizeof(flat_config);
-  flat_config.swapHtoD();
-  out_pb_config->set_data(&flat_config, sizeof(flat_config));
-}
-
-bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,
-                                        ConfigDescription* out_config) {
-  if (!pb_config.has_data()) {
-    return false;
-  }
-
-  const android::ResTable_config* config;
-  if (pb_config.data().size() > sizeof(*config)) {
-    return false;
-  }
-
-  config = reinterpret_cast<const android::ResTable_config*>(pb_config.data().data());
-  out_config->copyFromDtoH(*config);
-  return true;
-}
-
-pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) {
-  switch (type) {
-    case Reference::Type::kResource:
-      return pb::Reference_Type_REFERENCE;
-    case Reference::Type::kAttribute:
-      return pb::Reference_Type_ATTRIBUTE;
-    default:
-      break;
-  }
-  return pb::Reference_Type_REFERENCE;
-}
-
-Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) {
-  switch (pb_type) {
-    case pb::Reference_Type_REFERENCE:
-      return Reference::Type::kResource;
-    case pb::Reference_Type_ATTRIBUTE:
-      return Reference::Type::kAttribute;
-    default:
-      break;
-  }
-  return Reference::Type::kResource;
-}
-
-pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) {
-  switch (plural_idx) {
-    case Plural::Zero:
-      return pb::Plural_Arity_ZERO;
-    case Plural::One:
-      return pb::Plural_Arity_ONE;
-    case Plural::Two:
-      return pb::Plural_Arity_TWO;
-    case Plural::Few:
-      return pb::Plural_Arity_FEW;
-    case Plural::Many:
-      return pb::Plural_Arity_MANY;
-    default:
-      break;
-  }
-  return pb::Plural_Arity_OTHER;
-}
-
-size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) {
-  switch (arity) {
-    case pb::Plural_Arity_ZERO:
-      return Plural::Zero;
-    case pb::Plural_Arity_ONE:
-      return Plural::One;
-    case pb::Plural_Arity_TWO:
-      return Plural::Two;
-    case pb::Plural_Arity_FEW:
-      return Plural::Few;
-    case pb::Plural_Arity_MANY:
-      return Plural::Many;
-    default:
-      break;
-  }
-  return Plural::Other;
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
deleted file mode 100644
index 2f268f4..0000000
--- a/tools/aapt2/proto/ProtoHelpers.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2016 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 AAPT_PROTO_PROTOHELPERS_H
-#define AAPT_PROTO_PROTOHELPERS_H
-
-#include "androidfw/ResourceTypes.h"
-
-#include "ConfigDescription.h"
-#include "ResourceTable.h"
-#include "Source.h"
-#include "StringPool.h"
-#include "Resources.pb.h"
-#include "ResourcesInternal.pb.h"
-
-namespace aapt {
-
-void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool);
-
-void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source);
-
-void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool,
-                             Source* out_source);
-
-pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state);
-
-SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility);
-
-void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config);
-
-bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config,
-                                        ConfigDescription* out_config);
-
-pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type);
-
-Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type);
-
-pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx);
-
-size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity);
-
-}  // namespace aapt
-
-#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
deleted file mode 100644
index 8c46642..0000000
--- a/tools/aapt2/proto/ProtoSerialize.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 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 AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
-#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
-
-#include "android-base/macros.h"
-#include "google/protobuf/io/coded_stream.h"
-#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
-
-#include "Diagnostics.h"
-#include "ResourceTable.h"
-#include "Source.h"
-#include "proto/ProtoHelpers.h"
-
-namespace aapt {
-
-class CompiledFileOutputStream {
- public:
-  explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out);
-
-  void WriteLittleEndian32(uint32_t value);
-  void WriteCompiledFile(const pb::internal::CompiledFile* compiledFile);
-  void WriteData(const BigBuffer* buffer);
-  void WriteData(const void* data, size_t len);
-  bool HadError();
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
-
-  void EnsureAlignedWrite();
-
-  google::protobuf::io::CodedOutputStream out_;
-};
-
-class CompiledFileInputStream {
- public:
-  explicit CompiledFileInputStream(const void* data, size_t size);
-
-  bool ReadLittleEndian32(uint32_t* outVal);
-  bool ReadCompiledFile(pb::internal::CompiledFile* outVal);
-  bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen);
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
-
-  void EnsureAlignedRead();
-
-  google::protobuf::io::CodedInputStream in_;
-};
-
-std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table);
-std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pbTable,
-                                                      const Source& source, IDiagnostics* diag);
-
-std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file);
-std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb(
-    const pb::internal::CompiledFile& pbFile, const Source& source, IDiagnostics* diag);
-
-}  // namespace aapt
-
-#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
deleted file mode 100644
index b9d5878..0000000
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Copyright (C) 2016 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 "proto/ProtoSerialize.h"
-
-#include "android-base/logging.h"
-#include "androidfw/ResourceTypes.h"
-
-#include "ResourceTable.h"
-#include "ResourceUtils.h"
-#include "ValueVisitor.h"
-#include "proto/ProtoHelpers.h"
-
-namespace aapt {
-
-namespace {
-
-class ReferenceIdToNameVisitor : public ValueVisitor {
- public:
-  using ValueVisitor::Visit;
-
-  explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping)
-      : mapping_(mapping) {
-    CHECK(mapping_ != nullptr);
-  }
-
-  void Visit(Reference* reference) override {
-    if (!reference->id || !reference->id.value().is_valid()) {
-      return;
-    }
-
-    ResourceId id = reference->id.value();
-    auto cache_iter = mapping_->find(id);
-    if (cache_iter != mapping_->end()) {
-      reference->name = cache_iter->second.ToResourceName();
-    }
-  }
-
- private:
-  const std::map<ResourceId, ResourceNameRef>* mapping_;
-};
-
-class PackagePbDeserializer {
- public:
-  PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source,
-                        IDiagnostics* diag)
-      : source_pool_(sourcePool), source_(source), diag_(diag) {
-  }
-
- public:
-  bool DeserializeFromPb(const pb::Package& pb_package, ResourceTable* table) {
-    Maybe<uint8_t> id;
-    if (pb_package.has_package_id()) {
-      id = static_cast<uint8_t>(pb_package.package_id());
-    }
-
-    std::map<ResourceId, ResourceNameRef> id_index;
-
-    ResourceTablePackage* pkg = table->CreatePackage(pb_package.package_name(), id);
-    for (const pb::Type& pb_type : pb_package.type()) {
-      const ResourceType* res_type = ParseResourceType(pb_type.name());
-      if (res_type == nullptr) {
-        diag_->Error(DiagMessage(source_) << "unknown type '" << pb_type.name() << "'");
-        return {};
-      }
-
-      ResourceTableType* type = pkg->FindOrCreateType(*res_type);
-
-      for (const pb::Entry& pb_entry : pb_type.entry()) {
-        ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name());
-
-        // Deserialize the symbol status (public/private with source and comments).
-        if (pb_entry.has_symbol_status()) {
-          const pb::SymbolStatus& pb_status = pb_entry.symbol_status();
-          if (pb_status.has_source()) {
-            DeserializeSourceFromPb(pb_status.source(), *source_pool_,
-                                    &entry->symbol_status.source);
-          }
-
-          if (pb_status.has_comment()) {
-            entry->symbol_status.comment = pb_status.comment();
-          }
-
-          entry->symbol_status.allow_new = pb_status.allow_new();
-
-          SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
-          entry->symbol_status.state = visibility;
-
-          if (visibility == SymbolState::kPublic) {
-            // This is a public symbol, we must encode the ID now if there is one.
-            if (pb_entry.has_id()) {
-              entry->id = static_cast<uint16_t>(pb_entry.id());
-            }
-
-            if (type->symbol_status.state != SymbolState::kPublic) {
-              // If the type has not been made public, do so now.
-              type->symbol_status.state = SymbolState::kPublic;
-              if (pb_type.has_id()) {
-                type->id = static_cast<uint8_t>(pb_type.id());
-              }
-            }
-          } else if (visibility == SymbolState::kPrivate) {
-            if (type->symbol_status.state == SymbolState::kUndefined) {
-              type->symbol_status.state = SymbolState::kPrivate;
-            }
-          }
-        }
-
-        ResourceId resid(pb_package.package_id(), pb_type.id(), pb_entry.id());
-        if (resid.is_valid()) {
-          id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name);
-        }
-
-        for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) {
-          const pb::ConfigDescription& pb_config = pb_config_value.config();
-
-          ConfigDescription config;
-          if (!DeserializeConfigDescriptionFromPb(pb_config, &config)) {
-            diag_->Error(DiagMessage(source_) << "invalid configuration");
-            return {};
-          }
-
-          ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product());
-          if (config_value->value) {
-            // Duplicate config.
-            diag_->Error(DiagMessage(source_) << "duplicate configuration");
-            return {};
-          }
-
-          config_value->value =
-              DeserializeValueFromPb(pb_config_value.value(), config, &table->string_pool);
-          if (!config_value->value) {
-            return {};
-          }
-        }
-      }
-    }
-
-    ReferenceIdToNameVisitor visitor(&id_index);
-    VisitAllValuesInPackage(pkg, &visitor);
-    return true;
-  }
-
- private:
-  std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
-                                              const ConfigDescription& config, StringPool* pool) {
-    if (pb_item.has_ref()) {
-      const pb::Reference& pb_ref = pb_item.ref();
-      std::unique_ptr<Reference> ref = util::make_unique<Reference>();
-      if (!DeserializeReferenceFromPb(pb_ref, ref.get())) {
-        return {};
-      }
-      return std::move(ref);
-
-    } else if (pb_item.has_prim()) {
-      const pb::Primitive& pb_prim = pb_item.prim();
-      return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()),
-                                                pb_prim.data());
-
-    } else if (pb_item.has_id()) {
-      return util::make_unique<Id>();
-
-    } else if (pb_item.has_str()) {
-      return util::make_unique<String>(
-          pool->MakeRef(pb_item.str().value(), StringPool::Context(config)));
-
-    } else if (pb_item.has_raw_str()) {
-      return util::make_unique<RawString>(
-          pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config)));
-
-    } else if (pb_item.has_styled_str()) {
-      const pb::StyledString& pb_str = pb_item.styled_str();
-      StyleString style_str{pb_str.value()};
-      for (const pb::StyledString::Span& pb_span : pb_str.span()) {
-        style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()});
-      }
-      return util::make_unique<StyledString>(pool->MakeRef(
-          style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
-
-    } else if (pb_item.has_file()) {
-      return util::make_unique<FileReference>(pool->MakeRef(
-          pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
-
-    } else {
-      diag_->Error(DiagMessage(source_) << "unknown item");
-    }
-    return {};
-  }
-
-  std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
-                                                const ConfigDescription& config,
-                                                StringPool* pool) {
-    std::unique_ptr<Value> value;
-    if (pb_value.has_item()) {
-      value = DeserializeItemFromPb(pb_value.item(), config, pool);
-      if (!value) {
-        return {};
-      }
-
-    } else if (pb_value.has_compound_value()) {
-      const pb::CompoundValue& pb_compound_value = pb_value.compound_value();
-      if (pb_compound_value.has_attr()) {
-        const pb::Attribute& pb_attr = pb_compound_value.attr();
-        std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();
-        attr->type_mask = pb_attr.format_flags();
-        attr->min_int = pb_attr.min_int();
-        attr->max_int = pb_attr.max_int();
-        for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) {
-          Attribute::Symbol symbol;
-          DeserializeItemCommon(pb_symbol, &symbol.symbol);
-          if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) {
-            return {};
-          }
-          symbol.value = pb_symbol.value();
-          attr->symbols.push_back(std::move(symbol));
-        }
-        value = std::move(attr);
-
-      } else if (pb_compound_value.has_style()) {
-        const pb::Style& pb_style = pb_compound_value.style();
-        std::unique_ptr<Style> style = util::make_unique<Style>();
-        if (pb_style.has_parent()) {
-          style->parent = Reference();
-          if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) {
-            return {};
-          }
-
-          if (pb_style.has_parent_source()) {
-            Source parent_source;
-            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source);
-            style->parent.value().SetSource(std::move(parent_source));
-          }
-        }
-
-        for (const pb::Style_Entry& pb_entry : pb_style.entry()) {
-          Style::Entry entry;
-          DeserializeItemCommon(pb_entry, &entry.key);
-          if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) {
-            return {};
-          }
-
-          entry.value = DeserializeItemFromPb(pb_entry.item(), config, pool);
-          if (!entry.value) {
-            return {};
-          }
-
-          DeserializeItemCommon(pb_entry, entry.value.get());
-          style->entries.push_back(std::move(entry));
-        }
-        value = std::move(style);
-
-      } else if (pb_compound_value.has_styleable()) {
-        const pb::Styleable& pb_styleable = pb_compound_value.styleable();
-        std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-        for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) {
-          Reference attr_ref;
-          DeserializeItemCommon(pb_entry, &attr_ref);
-          DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref);
-          styleable->entries.push_back(std::move(attr_ref));
-        }
-        value = std::move(styleable);
-
-      } else if (pb_compound_value.has_array()) {
-        const pb::Array& pb_array = pb_compound_value.array();
-        std::unique_ptr<Array> array = util::make_unique<Array>();
-        for (const pb::Array_Element& pb_entry : pb_array.element()) {
-          std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool);
-          if (!item) {
-            return {};
-          }
-
-          DeserializeItemCommon(pb_entry, item.get());
-          array->elements.push_back(std::move(item));
-        }
-        value = std::move(array);
-
-      } else if (pb_compound_value.has_plural()) {
-        const pb::Plural& pb_plural = pb_compound_value.plural();
-        std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-        for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {
-          size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity());
-          plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool);
-          if (!plural->values[pluralIdx]) {
-            return {};
-          }
-
-          DeserializeItemCommon(pb_entry, plural->values[pluralIdx].get());
-        }
-        value = std::move(plural);
-
-      } else {
-        diag_->Error(DiagMessage(source_) << "unknown compound value");
-        return {};
-      }
-    } else {
-      diag_->Error(DiagMessage(source_) << "unknown value");
-      return {};
-    }
-
-    CHECK(value) << "forgot to set value";
-
-    value->SetWeak(pb_value.weak());
-    DeserializeItemCommon(pb_value, value.get());
-    return value;
-  }
-
-  bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* out_ref) {
-    out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
-    out_ref->private_reference = pb_ref.private_();
-
-    if (pb_ref.has_id()) {
-      out_ref->id = ResourceId(pb_ref.id());
-    }
-
-    if (pb_ref.has_name()) {
-      ResourceNameRef name_ref;
-      if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) {
-        diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'");
-        return false;
-      }
-
-      out_ref->name = name_ref.ToResourceName();
-    }
-    return true;
-  }
-
-  template <typename T>
-  void DeserializeItemCommon(const T& pb_item, Value* out_value) {
-    if (pb_item.has_source()) {
-      Source source;
-      DeserializeSourceFromPb(pb_item.source(), *source_pool_, &source);
-      out_value->SetSource(std::move(source));
-    }
-
-    if (pb_item.has_comment()) {
-      out_value->SetComment(pb_item.comment());
-    }
-  }
-
- private:
-  const android::ResStringPool* source_pool_;
-  const Source source_;
-  IDiagnostics* diag_;
-};
-
-}  // namespace
-
-std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table,
-                                                      const Source& source, IDiagnostics* diag) {
-  // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
-  // causes errors when qualifying it with android::
-  using namespace android;
-
-  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-
-  ResStringPool source_pool;
-  if (pb_table.has_source_pool()) {
-    status_t result = source_pool.setTo(pb_table.source_pool().data().data(),
-                                        pb_table.source_pool().data().size());
-    if (result != NO_ERROR) {
-      diag->Error(DiagMessage(source) << "invalid source pool");
-      return {};
-    }
-  }
-
-  PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag);
-  for (const pb::Package& pb_package : pb_table.package()) {
-    if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) {
-      return {};
-    }
-  }
-  return table;
-}
-
-std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb(
-    const pb::internal::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) {
-  std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
-
-  ResourceNameRef name_ref;
-
-  // Need to create an lvalue here so that nameRef can point to something real.
-  if (!ResourceUtils::ParseResourceName(pb_file.resource_name(), &name_ref)) {
-    diag->Error(DiagMessage(source)
-                << "invalid resource name in compiled file header: "
-                << pb_file.resource_name());
-    return {};
-  }
-  file->name = name_ref.ToResourceName();
-  file->source.path = pb_file.source_path();
-  DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config);
-
-  for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) {
-    // Need to create an lvalue here so that nameRef can point to something real.
-    if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) {
-      diag->Error(DiagMessage(source)
-                  << "invalid resource name for exported symbol in "
-                     "compiled file header: "
-                  << pb_file.resource_name());
-      return {};
-    }
-    size_t line = 0u;
-    if (pb_symbol.has_source()) {
-      line = pb_symbol.source().line_number();
-    }
-    file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line});
-  }
-  return file;
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
deleted file mode 100644
index a08df71..0000000
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (C) 2016 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 "Resource.h"
-#include "ResourceTable.h"
-#include "StringPool.h"
-#include "ValueVisitor.h"
-#include "proto/ProtoHelpers.h"
-#include "proto/ProtoSerialize.h"
-#include "util/BigBuffer.h"
-
-#include "android-base/logging.h"
-
-using ::google::protobuf::io::CodedInputStream;
-using ::google::protobuf::io::CodedOutputStream;
-using ::google::protobuf::io::ZeroCopyOutputStream;
-
-namespace aapt {
-
-namespace {
-
-class PbSerializerVisitor : public RawValueVisitor {
- public:
-  using RawValueVisitor::Visit;
-
-  // Constructor to use when expecting to serialize any value.
-  PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value)
-      : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) {
-  }
-
-  // Constructor to use when expecting to serialize an Item.
-  PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem)
-      : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) {
-  }
-
-  void Visit(Reference* ref) override {
-    SerializeReferenceToPb(*ref, pb_item()->mutable_ref());
-  }
-
-  void Visit(String* str) override {
-    pb_item()->mutable_str()->set_value(*str->value);
-  }
-
-  void Visit(RawString* str) override {
-    pb_item()->mutable_raw_str()->set_value(*str->value);
-  }
-
-  void Visit(StyledString* str) override {
-    pb::StyledString* pb_str = pb_item()->mutable_styled_str();
-    pb_str->set_value(str->value->value);
-
-    for (const StringPool::Span& span : str->value->spans) {
-      pb::StyledString::Span* pb_span = pb_str->add_span();
-      pb_span->set_tag(*span.name);
-      pb_span->set_first_char(span.first_char);
-      pb_span->set_last_char(span.last_char);
-    }
-  }
-
-  void Visit(FileReference* file) override {
-    pb_item()->mutable_file()->set_path(*file->path);
-  }
-
-  void Visit(Id* /*id*/) override {
-    pb_item()->mutable_id();
-  }
-
-  void Visit(BinaryPrimitive* prim) override {
-    android::Res_value val = {};
-    prim->Flatten(&val);
-
-    pb::Primitive* pb_prim = pb_item()->mutable_prim();
-    pb_prim->set_type(val.dataType);
-    pb_prim->set_data(val.data);
-  }
-
-  void VisitItem(Item* item) override {
-    LOG(FATAL) << "unimplemented item";
-  }
-
-  void Visit(Attribute* attr) override {
-    pb::Attribute* pb_attr = pb_compound_value()->mutable_attr();
-    pb_attr->set_format_flags(attr->type_mask);
-    pb_attr->set_min_int(attr->min_int);
-    pb_attr->set_max_int(attr->max_int);
-
-    for (auto& symbol : attr->symbols) {
-      pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol();
-      SerializeItemCommonToPb(symbol.symbol, pb_symbol);
-      SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name());
-      pb_symbol->set_value(symbol.value);
-    }
-  }
-
-  void Visit(Style* style) override {
-    pb::Style* pb_style = pb_compound_value()->mutable_style();
-    if (style->parent) {
-      SerializeReferenceToPb(style->parent.value(), pb_style->mutable_parent());
-      SerializeSourceToPb(style->parent.value().GetSource(), source_pool_,
-                          pb_style->mutable_parent_source());
-    }
-
-    for (Style::Entry& entry : style->entries) {
-      pb::Style_Entry* pb_entry = pb_style->add_entry();
-      SerializeReferenceToPb(entry.key, pb_entry->mutable_key());
-
-      pb::Item* pb_item = pb_entry->mutable_item();
-      SerializeItemCommonToPb(entry.key, pb_entry);
-      PbSerializerVisitor sub_visitor(source_pool_, pb_item);
-      entry.value->Accept(&sub_visitor);
-    }
-  }
-
-  void Visit(Styleable* styleable) override {
-    pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable();
-    for (Reference& entry : styleable->entries) {
-      pb::Styleable_Entry* pb_entry = pb_styleable->add_entry();
-      SerializeItemCommonToPb(entry, pb_entry);
-      SerializeReferenceToPb(entry, pb_entry->mutable_attr());
-    }
-  }
-
-  void Visit(Array* array) override {
-    pb::Array* pb_array = pb_compound_value()->mutable_array();
-    for (auto& value : array->elements) {
-      pb::Array_Element* pb_element = pb_array->add_element();
-      SerializeItemCommonToPb(*value, pb_element);
-      PbSerializerVisitor sub_visitor(source_pool_, pb_element->mutable_item());
-      value->Accept(&sub_visitor);
-    }
-  }
-
-  void Visit(Plural* plural) override {
-    pb::Plural* pb_plural = pb_compound_value()->mutable_plural();
-    const size_t count = plural->values.size();
-    for (size_t i = 0; i < count; i++) {
-      if (!plural->values[i]) {
-        // No plural value set here.
-        continue;
-      }
-
-      pb::Plural_Entry* pb_entry = pb_plural->add_entry();
-      pb_entry->set_arity(SerializePluralEnumToPb(i));
-      pb::Item* pb_element = pb_entry->mutable_item();
-      SerializeItemCommonToPb(*plural->values[i], pb_entry);
-      PbSerializerVisitor sub_visitor(source_pool_, pb_element);
-      plural->values[i]->Accept(&sub_visitor);
-    }
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(PbSerializerVisitor);
-
-  pb::Item* pb_item() {
-    if (out_pb_value_) {
-      return out_pb_value_->mutable_item();
-    }
-    return out_pb_item_;
-  }
-
-  pb::CompoundValue* pb_compound_value() {
-    CHECK(out_pb_value_ != nullptr);
-    return out_pb_value_->mutable_compound_value();
-  }
-
-  template <typename T>
-  void SerializeItemCommonToPb(const Item& item, T* pb_item) {
-    SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source());
-    if (!item.GetComment().empty()) {
-      pb_item->set_comment(item.GetComment());
-    }
-  }
-
-  void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) {
-    if (ref.id) {
-      pb_ref->set_id(ref.id.value().id);
-    }
-
-    if (ref.name) {
-      pb_ref->set_name(ref.name.value().ToString());
-    }
-
-    pb_ref->set_private_(ref.private_reference);
-    pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
-  }
-
-  StringPool* source_pool_;
-  pb::Value* out_pb_value_;
-  pb::Item* out_pb_item_;
-};
-
-}  // namespace
-
-std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
-  // We must do this before writing the resources, since the string pool IDs may change.
-  table->string_pool.Prune();
-  table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
-    int diff = util::compare(a.priority, b.priority);
-    if (diff == 0) {
-      diff = a.config.compare(b.config);
-    }
-    return diff;
-  });
-
-  auto pb_table = util::make_unique<pb::ResourceTable>();
-  StringPool source_pool;
-
-  for (auto& package : table->packages) {
-    pb::Package* pb_package = pb_table->add_package();
-    if (package->id) {
-      pb_package->set_package_id(package->id.value());
-    }
-    pb_package->set_package_name(package->name);
-
-    for (auto& type : package->types) {
-      pb::Type* pb_type = pb_package->add_type();
-      if (type->id) {
-        pb_type->set_id(type->id.value());
-      }
-      pb_type->set_name(ToString(type->type).to_string());
-
-      for (auto& entry : type->entries) {
-        pb::Entry* pb_entry = pb_type->add_entry();
-        if (entry->id) {
-          pb_entry->set_id(entry->id.value());
-        }
-        pb_entry->set_name(entry->name);
-
-        // Write the SymbolStatus struct.
-        pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
-        pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state));
-        SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source());
-        pb_status->set_comment(entry->symbol_status.comment);
-        pb_status->set_allow_new(entry->symbol_status.allow_new);
-
-        for (auto& config_value : entry->values) {
-          pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
-          SerializeConfig(config_value->config, pb_config_value->mutable_config());
-          if (!config_value->product.empty()) {
-            pb_config_value->mutable_config()->set_product(config_value->product);
-          }
-
-          pb::Value* pb_value = pb_config_value->mutable_value();
-          SerializeSourceToPb(config_value->value->GetSource(), &source_pool,
-                              pb_value->mutable_source());
-          if (!config_value->value->GetComment().empty()) {
-            pb_value->set_comment(config_value->value->GetComment());
-          }
-
-          if (config_value->value->IsWeak()) {
-            pb_value->set_weak(true);
-          }
-
-          PbSerializerVisitor visitor(&source_pool, pb_value);
-          config_value->value->Accept(&visitor);
-        }
-      }
-    }
-  }
-
-  SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool());
-  return pb_table;
-}
-
-std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file) {
-  auto pb_file = util::make_unique<pb::internal::CompiledFile>();
-  pb_file->set_resource_name(file.name.ToString());
-  pb_file->set_source_path(file.source.path);
-  SerializeConfig(file.config, pb_file->mutable_config());
-
-  for (const SourcedResourceName& exported : file.exported_symbols) {
-    pb::internal::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbol();
-    pb_symbol->set_resource_name(exported.name.ToString());
-    pb_symbol->mutable_source()->set_line_number(exported.line);
-  }
-  return pb_file;
-}
-
-CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) {
-}
-
-void CompiledFileOutputStream::EnsureAlignedWrite() {
-  const int padding = out_.ByteCount() % 4;
-  if (padding > 0) {
-    uint32_t zero = 0u;
-    out_.WriteRaw(&zero, padding);
-  }
-}
-
-void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian32(val);
-}
-
-void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile* compiled_file) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize()));
-  compiled_file->SerializeWithCachedSizes(&out_);
-}
-
-void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian64(static_cast<uint64_t>(buffer->size()));
-  for (const BigBuffer::Block& block : *buffer) {
-    out_.WriteRaw(block.buffer.get(), block.size);
-  }
-}
-
-void CompiledFileOutputStream::WriteData(const void* data, size_t len) {
-  EnsureAlignedWrite();
-  out_.WriteLittleEndian64(static_cast<uint64_t>(len));
-  out_.WriteRaw(data, len);
-}
-
-bool CompiledFileOutputStream::HadError() {
-  return out_.HadError();
-}
-
-CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size)
-    : in_(static_cast<const uint8_t*>(data), size) {}
-
-void CompiledFileInputStream::EnsureAlignedRead() {
-  const int padding = in_.CurrentPosition() % 4;
-  if (padding > 0) {
-    // Reads are always 4 byte aligned.
-    in_.Skip(padding);
-  }
-}
-
-bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) {
-  EnsureAlignedRead();
-  return in_.ReadLittleEndian32(out_val);
-}
-
-bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) {
-  EnsureAlignedRead();
-
-  google::protobuf::uint64 pb_size = 0u;
-  if (!in_.ReadLittleEndian64(&pb_size)) {
-    return false;
-  }
-
-  CodedInputStream::Limit l = in_.PushLimit(static_cast<int>(pb_size));
-
-  // Check that we haven't tried to read past the end.
-  if (static_cast<uint64_t>(in_.BytesUntilLimit()) != pb_size) {
-    in_.PopLimit(l);
-    in_.PushLimit(0);
-    return false;
-  }
-
-  if (!out_val->ParsePartialFromCodedStream(&in_)) {
-    in_.PopLimit(l);
-    in_.PushLimit(0);
-    return false;
-  }
-
-  in_.PopLimit(l);
-  return true;
-}
-
-bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) {
-  EnsureAlignedRead();
-
-  google::protobuf::uint64 pb_size = 0u;
-  if (!in_.ReadLittleEndian64(&pb_size)) {
-    return false;
-  }
-
-  // Check that we aren't trying to read past the end.
-  if (pb_size > static_cast<uint64_t>(in_.BytesUntilLimit())) {
-    in_.PushLimit(0);
-    return false;
-  }
-
-  uint64_t offset = static_cast<uint64_t>(in_.CurrentPosition());
-  if (!in_.Skip(pb_size)) {
-    return false;
-  }
-
-  *out_offset = offset;
-  *out_len = pb_size;
-  return true;
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
deleted file mode 100644
index 80608b3..0000000
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2016 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 "proto/ProtoSerialize.h"
-
-#include "ResourceTable.h"
-#include "test/Test.h"
-
-using ::google::protobuf::io::StringOutputStream;
-using ::testing::Eq;
-using ::testing::NotNull;
-using ::testing::SizeIs;
-
-namespace aapt {
-
-TEST(TableProtoSerializer, SerializeSinglePackage) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-  std::unique_ptr<ResourceTable> table =
-      test::ResourceTableBuilder()
-          .SetPackageId("com.app.a", 0x7f)
-          .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml")
-          .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
-          .AddString("com.app.a:string/text", {}, "hi")
-          .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
-          .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/)
-          .Build();
-
-  Symbol public_symbol;
-  public_symbol.state = SymbolState::kPublic;
-  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
-                                    ResourceId(0x7f020000), public_symbol,
-                                    context->GetDiagnostics()));
-
-  Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
-  ASSERT_THAT(id, NotNull());
-
-  // Make a plural.
-  std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-  plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one"));
-  ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"),
-                                 ConfigDescription{}, {}, std::move(plural),
-                                 context->GetDiagnostics()));
-
-  // Make a styled string.
-  StyleString style_string;
-  style_string.str = "hello";
-  style_string.spans.push_back(Span{"b", 0u, 4u});
-  ASSERT_TRUE(
-      table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {},
-                         util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)),
-                         context->GetDiagnostics()));
-
-  // Make a resource with different products.
-  ASSERT_TRUE(table->AddResource(
-      test::ParseNameOrDie("com.app.a:integer/one"),
-      test::ParseConfigOrDie("land"), {},
-      test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
-      context->GetDiagnostics()));
-  ASSERT_TRUE(table->AddResource(
-      test::ParseNameOrDie("com.app.a:integer/one"),
-      test::ParseConfigOrDie("land"), "tablet",
-      test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
-      context->GetDiagnostics()));
-
-  // Make a reference with both resource name and resource ID.
-  // The reference should point to a resource outside of this table to test that both name and id
-  // get serialized.
-  Reference expected_ref;
-  expected_ref.name = test::ParseNameOrDie("android:layout/main");
-  expected_ref.id = ResourceId(0x01020000);
-  ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:layout/abc"),
-                                 ConfigDescription::DefaultConfig(), {},
-                                 util::make_unique<Reference>(expected_ref),
-                                 context->GetDiagnostics()));
-
-  std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get());
-  ASSERT_THAT(pb_table, NotNull());
-
-  std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb(
-      *pb_table, Source{"test"}, context->GetDiagnostics());
-  ASSERT_THAT(new_table, NotNull());
-
-  Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo");
-  ASSERT_THAT(new_id, NotNull());
-  EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak()));
-
-  Maybe<ResourceTable::SearchResult> result =
-      new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
-  ASSERT_TRUE(result);
-
-  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
-
-  result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
-  ASSERT_TRUE(result);
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
-  EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
-
-  // Find the product-dependent values
-  BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
-      new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");
-  ASSERT_THAT(prim, NotNull());
-  EXPECT_THAT(prim->value.data, Eq(123u));
-
-  prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
-      new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");
-  ASSERT_THAT(prim, NotNull());
-  EXPECT_THAT(prim->value.data, Eq(321u));
-
-  Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc");
-  ASSERT_THAT(actual_ref, NotNull());
-  ASSERT_TRUE(actual_ref->name);
-  ASSERT_TRUE(actual_ref->id);
-  EXPECT_THAT(*actual_ref, Eq(expected_ref));
-
-  StyledString* actual_styled_str =
-      test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled");
-  ASSERT_THAT(actual_styled_str, NotNull());
-  EXPECT_THAT(actual_styled_str->value->value, Eq("hello"));
-  ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u));
-  EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
-  EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
-  EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
-}
-
-TEST(TableProtoSerializer, SerializeFileHeader) {
-  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
-  ResourceFile f;
-  f.config = test::ParseConfigOrDie("hdpi-v9");
-  f.name = test::ParseNameOrDie("com.app.a:layout/main");
-  f.source.path = "res/layout-hdpi-v9/main.xml";
-  f.exported_symbols.push_back(
-      SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u});
-
-  const std::string expected_data1 = "123";
-  const std::string expected_data2 = "1234";
-
-  std::string output_str;
-  {
-    std::unique_ptr<pb::internal::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f);
-
-    f.name.entry = "__" + f.name.entry + "$0";
-    std::unique_ptr<pb::internal::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f);
-
-    StringOutputStream out_stream(&output_str);
-    CompiledFileOutputStream out_file_stream(&out_stream);
-    out_file_stream.WriteLittleEndian32(2);
-    out_file_stream.WriteCompiledFile(pb_file1.get());
-    out_file_stream.WriteData(expected_data1.data(), expected_data1.size());
-    out_file_stream.WriteCompiledFile(pb_file2.get());
-    out_file_stream.WriteData(expected_data2.data(), expected_data2.size());
-    ASSERT_FALSE(out_file_stream.HadError());
-  }
-
-  CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
-  uint32_t num_files = 0;
-  ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
-  ASSERT_EQ(2u, num_files);
-
-  // Read the first compiled file.
-
-  pb::internal::CompiledFile new_pb_file;
-  ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));
-
-  std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb(
-      new_pb_file, Source("test"), context->GetDiagnostics());
-  ASSERT_THAT(file, NotNull());
-
-  uint64_t offset, len;
-  ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
-
-  std::string actual_data(output_str.data() + offset, len);
-  EXPECT_EQ(expected_data1, actual_data);
-
-  // Expect the data to be aligned.
-  EXPECT_EQ(0u, offset & 0x03);
-
-  ASSERT_EQ(1u, file->exported_symbols.size());
-  EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), file->exported_symbols[0].name);
-
-  // Read the second compiled file.
-
-  ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file));
-
-  file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), context->GetDiagnostics());
-  ASSERT_THAT(file, NotNull());
-
-  ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len));
-
-  actual_data = std::string(output_str.data() + offset, len);
-  EXPECT_EQ(expected_data2, actual_data);
-
-  // Expect the data to be aligned.
-  EXPECT_EQ(0u, offset & 0x03);
-}
-
-TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {
-  ResourceFile f;
-  std::unique_ptr<pb::internal::CompiledFile> pb_file = SerializeCompiledFileToPb(f);
-
-  const std::string expected_data = "1234";
-
-  std::string output_str;
-  {
-    StringOutputStream out_stream(&output_str);
-    CompiledFileOutputStream out_file_stream(&out_stream);
-    out_file_stream.WriteLittleEndian32(1);
-    out_file_stream.WriteCompiledFile(pb_file.get());
-    out_file_stream.WriteData(expected_data.data(), expected_data.size());
-    ASSERT_FALSE(out_file_stream.HadError());
-  }
-
-  output_str[4] = 0xff;
-
-  CompiledFileInputStream in_file_stream(output_str.data(), output_str.size());
-
-  uint32_t num_files = 0;
-  EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files));
-  EXPECT_EQ(1u, num_files);
-
-  pb::internal::CompiledFile new_pb_file;
-  EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file));
-
-  uint64_t offset, len;
-  EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len));
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index e658664..5a62e97 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -19,7 +19,7 @@
 #include "android-base/logging.h"
 #include "androidfw/StringPiece.h"
 
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Common.h"
 #include "util/Util.h"
 
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 61d0563..4e318a9 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -90,6 +90,10 @@
     return {};
   }
 
+  std::unique_ptr<io::InputStream> OpenInputStream() override {
+    return OpenAsData();
+  }
+
   const Source& GetSource() const override {
     return source_;
   }
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
deleted file mode 100644
index 892aee6..0000000
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * Copyright (C) 2015 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 "unflatten/BinaryResourceParser.h"
-
-#include <algorithm>
-#include <map>
-#include <string>
-
-#include "android-base/logging.h"
-#include "android-base/macros.h"
-#include "android-base/stringprintf.h"
-#include "androidfw/ResourceTypes.h"
-#include "androidfw/TypeWrappers.h"
-
-#include "ResourceTable.h"
-#include "ResourceUtils.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "ValueVisitor.h"
-#include "unflatten/ResChunkPullParser.h"
-#include "util/Util.h"
-
-namespace aapt {
-
-using namespace android;
-
-using ::android::base::StringPrintf;
-
-namespace {
-
-// Visitor that converts a reference's resource ID to a resource name, given a mapping from
-// resource ID to resource name.
-class ReferenceIdToNameVisitor : public ValueVisitor {
- public:
-  using ValueVisitor::Visit;
-
-  explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping)
-      : mapping_(mapping) {
-    CHECK(mapping_ != nullptr);
-  }
-
-  void Visit(Reference* reference) override {
-    if (!reference->id || !reference->id.value().is_valid()) {
-      return;
-    }
-
-    ResourceId id = reference->id.value();
-    auto cache_iter = mapping_->find(id);
-    if (cache_iter != mapping_->end()) {
-      reference->name = cache_iter->second;
-    }
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor);
-
-  const std::map<ResourceId, ResourceName>* mapping_;
-};
-
-}  // namespace
-
-BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
-                                           const Source& source, const void* data, size_t len,
-                                           io::IFileCollection* files)
-    : context_(context),
-      table_(table),
-      source_(source),
-      data_(data),
-      data_len_(len),
-      files_(files) {
-}
-
-bool BinaryResourceParser::Parse() {
-  ResChunkPullParser parser(data_, data_len_);
-
-  if (!ResChunkPullParser::IsGoodEvent(parser.Next())) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt resources.arsc: " << parser.error());
-    return false;
-  }
-
-  if (parser.chunk()->type != android::RES_TABLE_TYPE) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << StringPrintf("unknown chunk of type 0x%02x",
-                                                      static_cast<int>(parser.chunk()->type)));
-    return false;
-  }
-
-  if (!ParseTable(parser.chunk())) {
-    return false;
-  }
-
-  if (parser.Next() != ResChunkPullParser::Event::kEndDocument) {
-    if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-      context_->GetDiagnostics()->Warn(
-          DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
-    } else {
-      context_->GetDiagnostics()->Warn(
-          DiagMessage(source_) << StringPrintf(
-              "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
-              static_cast<int>(parser.chunk()->type)));
-    }
-  }
-  return true;
-}
-
-/**
- * Parses the resource table, which contains all the packages, types, and
- * entries.
- */
-bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
-  const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
-  if (!table_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt ResTable_header chunk");
-    return false;
-  }
-
-  ResChunkPullParser parser(GetChunkData(&table_header->header),
-                            GetChunkDataLen(&table_header->header));
-  while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
-    switch (util::DeviceToHost16(parser.chunk()->type)) {
-      case android::RES_STRING_POOL_TYPE:
-        if (value_pool_.getError() == NO_INIT) {
-          status_t err = value_pool_.setTo(
-              parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
-          if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(
-                DiagMessage(source_) << "corrupt string pool in ResTable: "
-                                     << value_pool_.getError());
-            return false;
-          }
-
-          // Reserve some space for the strings we are going to add.
-          table_->string_pool.HintWillAdd(value_pool_.size(),
-                                          value_pool_.styleCount());
-        } else {
-          context_->GetDiagnostics()->Warn(
-              DiagMessage(source_) << "unexpected string pool in ResTable");
-        }
-        break;
-
-      case android::RES_TABLE_PACKAGE_TYPE:
-        if (!ParsePackage(parser.chunk())) {
-          return false;
-        }
-        break;
-
-      default:
-        context_->GetDiagnostics()->Warn(
-            DiagMessage(source_) << "unexpected chunk type "
-                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
-        break;
-    }
-  }
-
-  if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-    context_->GetDiagnostics()->Error(
-        DiagMessage(source_) << "corrupt resource table: " << parser.error());
-    return false;
-  }
-  return true;
-}
-
-bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
-  constexpr size_t kMinPackageSize =
-      sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
-  const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
-  if (!package_header) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
-    return false;
-  }
-
-  uint32_t package_id = util::DeviceToHost32(package_header->id);
-  if (package_id > std::numeric_limits<uint8_t>::max()) {
-    context_->GetDiagnostics()->Error(
-        DiagMessage(source_) << "package ID is too big (" << package_id << ")");
-    return false;
-  }
-
-  // Extract the package name.
-  size_t len = strnlen16((const char16_t*)package_header->name,
-                         arraysize(package_header->name));
-  std::u16string package_name;
-  package_name.resize(len);
-  for (size_t i = 0; i < len; i++) {
-    package_name[i] = util::DeviceToHost16(package_header->name[i]);
-  }
-
-  ResourceTablePackage* package = table_->CreatePackage(
-      util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
-  if (!package) {
-    context_->GetDiagnostics()->Error(
-        DiagMessage(source_) << "incompatible package '" << package_name
-                             << "' with ID " << package_id);
-    return false;
-  }
-
-  // There can be multiple packages in a table, so
-  // clear the type and key pool in case they were set from a previous package.
-  type_pool_.uninit();
-  key_pool_.uninit();
-
-  ResChunkPullParser parser(GetChunkData(&package_header->header),
-                            GetChunkDataLen(&package_header->header));
-  while (ResChunkPullParser::IsGoodEvent(parser.Next())) {
-    switch (util::DeviceToHost16(parser.chunk()->type)) {
-      case android::RES_STRING_POOL_TYPE:
-        if (type_pool_.getError() == NO_INIT) {
-          status_t err = type_pool_.setTo(
-              parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
-          if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt type string pool in "
-                                              << "ResTable_package: "
-                                              << type_pool_.getError());
-            return false;
-          }
-        } else if (key_pool_.getError() == NO_INIT) {
-          status_t err = key_pool_.setTo(
-              parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
-          if (err != NO_ERROR) {
-            context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                              << "corrupt key string pool in "
-                                              << "ResTable_package: "
-                                              << key_pool_.getError());
-            return false;
-          }
-        } else {
-          context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
-        }
-        break;
-
-      case android::RES_TABLE_TYPE_SPEC_TYPE:
-        if (!ParseTypeSpec(parser.chunk())) {
-          return false;
-        }
-        break;
-
-      case android::RES_TABLE_TYPE_TYPE:
-        if (!ParseType(package, parser.chunk())) {
-          return false;
-        }
-        break;
-
-      case android::RES_TABLE_LIBRARY_TYPE:
-        if (!ParseLibrary(parser.chunk())) {
-          return false;
-        }
-        break;
-
-      default:
-        context_->GetDiagnostics()->Warn(
-            DiagMessage(source_) << "unexpected chunk type "
-                                 << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
-        break;
-    }
-  }
-
-  if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
-    context_->GetDiagnostics()->Error(
-        DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
-    return false;
-  }
-
-  // Now go through the table and change local resource ID references to
-  // symbolic references.
-  ReferenceIdToNameVisitor visitor(&id_index_);
-  VisitAllValuesInTable(table_, &visitor);
-  return true;
-}
-
-bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
-  if (type_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "missing type string pool");
-    return false;
-  }
-
-  const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
-  if (!type_spec) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt ResTable_typeSpec chunk");
-    return false;
-  }
-
-  if (type_spec->id == 0) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "ResTable_typeSpec has invalid id: "
-                                      << type_spec->id);
-    return false;
-  }
-  return true;
-}
-
-bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
-                                     const ResChunk_header* chunk) {
-  if (type_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "missing type string pool");
-    return false;
-  }
-
-  if (key_pool_.getError() != NO_ERROR) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "missing key string pool");
-    return false;
-  }
-
-  // Specify a manual size, because ResTable_type contains ResTable_config, which changes
-  // a lot and has its own code to handle variable size.
-  const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk);
-  if (!type) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "corrupt ResTable_type chunk");
-    return false;
-  }
-
-  if (type->id == 0) {
-    context_->GetDiagnostics()->Error(DiagMessage(source_)
-                                      << "ResTable_type has invalid id: "
-                                      << (int)type->id);
-    return false;
-  }
-
-  ConfigDescription config;
-  config.copyFromDtoH(type->config);
-
-  const std::string type_str = util::GetString(type_pool_, type->id - 1);
-
-  const ResourceType* parsed_type = ParseResourceType(type_str);
-  if (!parsed_type) {
-    context_->GetDiagnostics()->Error(
-        DiagMessage(source_) << "invalid type name '" << type_str
-                             << "' for type with ID " << (int)type->id);
-    return false;
-  }
-
-  TypeVariant tv(type);
-  for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
-    const ResTable_entry* entry = *it;
-    if (!entry) {
-      continue;
-    }
-
-    const ResourceName name(
-        package->name, *parsed_type,
-        util::GetString(key_pool_, util::DeviceToHost32(entry->key.index)));
-
-    const ResourceId res_id(package->id.value(), type->id,
-                            static_cast<uint16_t>(it.index()));
-
-    std::unique_ptr<Value> resource_value;
-    if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
-      const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
-      // TODO(adamlesinski): Check that the entry count is valid.
-      resource_value = ParseMapEntry(name, config, mapEntry);
-    } else {
-      const Res_value* value =
-          (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size));
-      resource_value = ParseValue(name, config, *value);
-    }
-
-    if (!resource_value) {
-      context_->GetDiagnostics()->Error(
-          DiagMessage(source_) << "failed to parse value for resource " << name
-                               << " (" << res_id << ") with configuration '"
-                               << config << "'");
-      return false;
-    }
-
-    if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
-                                         context_->GetDiagnostics())) {
-      return false;
-    }
-
-    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-      Symbol symbol;
-      symbol.state = SymbolState::kPublic;
-      symbol.source = source_.WithLine(0);
-      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
-        return false;
-      }
-    }
-
-    // Add this resource name->id mapping to the index so
-    // that we can resolve all ID references to name references.
-    auto cache_iter = id_index_.find(res_id);
-    if (cache_iter == id_index_.end()) {
-      id_index_.insert({res_id, name});
-    }
-  }
-  return true;
-}
-
-bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) {
-  DynamicRefTable dynamic_ref_table;
-  if (dynamic_ref_table.load(reinterpret_cast<const ResTable_lib_header*>(chunk)) != NO_ERROR) {
-    return false;
-  }
-
-  const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table.entries();
-  const size_t count = entries.size();
-  for (size_t i = 0; i < count; i++) {
-    table_->included_packages_[entries.valueAt(i)] =
-        util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string()));
-  }
-  return true;
-}
-
-std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
-                                                       const ConfigDescription& config,
-                                                       const android::Res_value& value) {
-  std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
-                                                                  value, &table_->string_pool);
-  if (files_ != nullptr && item != nullptr) {
-    FileReference* file_ref = ValueCast<FileReference>(item.get());
-    if (file_ref != nullptr) {
-      file_ref->file = files_->FindFile(*file_ref->path);
-      if (file_ref->file == nullptr) {
-        context_->GetDiagnostics()->Warn(DiagMessage() << "resource " << name << " for config '"
-                                                        << config << "' is a file reference to '"
-                                                        << *file_ref->path
-                                                        << "' but no such path exists");
-      }
-    }
-  }
-  return item;
-}
-
-std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const ResTable_map_entry* map) {
-  switch (name.type) {
-    case ResourceType::kStyle:
-      return ParseStyle(name, config, map);
-    case ResourceType::kAttrPrivate:
-    // fallthrough
-    case ResourceType::kAttr:
-      return ParseAttr(name, config, map);
-    case ResourceType::kArray:
-      return ParseArray(name, config, map);
-    case ResourceType::kPlurals:
-      return ParsePlural(name, config, map);
-    case ResourceType::kId:
-      // Special case: An ID is not a bag, but some apps have defined the auto-generated
-      // IDs that come from declaring an enum value in an attribute as an empty map...
-      // We can ignore the value here.
-      return util::make_unique<Id>();
-    default:
-      context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
-                                                      << "' (" << (int)name.type << ")");
-      break;
-  }
-  return {};
-}
-
-std::unique_ptr<Style> BinaryResourceParser::ParseStyle(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const ResTable_map_entry* map) {
-  std::unique_ptr<Style> style = util::make_unique<Style>();
-  if (util::DeviceToHost32(map->parent.ident) != 0) {
-    // The parent is a regular reference to a resource.
-    style->parent = Reference(util::DeviceToHost32(map->parent.ident));
-  }
-
-  for (const ResTable_map& map_entry : map) {
-    if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
-      continue;
-    }
-
-    Style::Entry style_entry;
-    style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident));
-    style_entry.value = ParseValue(name, config, map_entry.value);
-    if (!style_entry.value) {
-      return {};
-    }
-    style->entries.push_back(std::move(style_entry));
-  }
-  return style;
-}
-
-std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const ResTable_map_entry* map) {
-  const bool is_weak =
-      (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
-  std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
-
-  // First we must discover what type of attribute this is. Find the type mask.
-  auto type_mask_iter =
-      std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
-        return util::DeviceToHost32(entry.name.ident) ==
-               ResTable_map::ATTR_TYPE;
-      });
-
-  if (type_mask_iter != end(map)) {
-    attr->type_mask = util::DeviceToHost32(type_mask_iter->value.data);
-  }
-
-  for (const ResTable_map& map_entry : map) {
-    if (Res_INTERNALID(util::DeviceToHost32(map_entry.name.ident))) {
-      switch (util::DeviceToHost32(map_entry.name.ident)) {
-        case ResTable_map::ATTR_MIN:
-          attr->min_int = static_cast<int32_t>(map_entry.value.data);
-          break;
-        case ResTable_map::ATTR_MAX:
-          attr->max_int = static_cast<int32_t>(map_entry.value.data);
-          break;
-      }
-      continue;
-    }
-
-    if (attr->type_mask &
-        (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
-      Attribute::Symbol symbol;
-      symbol.value = util::DeviceToHost32(map_entry.value.data);
-      symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident));
-      attr->symbols.push_back(std::move(symbol));
-    }
-  }
-
-  // TODO(adamlesinski): Find i80n, attributes.
-  return attr;
-}
-
-std::unique_ptr<Array> BinaryResourceParser::ParseArray(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const ResTable_map_entry* map) {
-  std::unique_ptr<Array> array = util::make_unique<Array>();
-  for (const ResTable_map& map_entry : map) {
-    array->elements.push_back(ParseValue(name, config, map_entry.value));
-  }
-  return array;
-}
-
-std::unique_ptr<Plural> BinaryResourceParser::ParsePlural(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const ResTable_map_entry* map) {
-  std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-  for (const ResTable_map& map_entry : map) {
-    std::unique_ptr<Item> item = ParseValue(name, config, map_entry.value);
-    if (!item) {
-      return {};
-    }
-
-    switch (util::DeviceToHost32(map_entry.name.ident)) {
-      case ResTable_map::ATTR_ZERO:
-        plural->values[Plural::Zero] = std::move(item);
-        break;
-      case ResTable_map::ATTR_ONE:
-        plural->values[Plural::One] = std::move(item);
-        break;
-      case ResTable_map::ATTR_TWO:
-        plural->values[Plural::Two] = std::move(item);
-        break;
-      case ResTable_map::ATTR_FEW:
-        plural->values[Plural::Few] = std::move(item);
-        break;
-      case ResTable_map::ATTR_MANY:
-        plural->values[Plural::Many] = std::move(item);
-        break;
-      case ResTable_map::ATTR_OTHER:
-        plural->values[Plural::Other] = std::move(item);
-        break;
-    }
-  }
-  return plural;
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
deleted file mode 100644
index c41ada0..0000000
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_BINARY_RESOURCE_PARSER_H
-#define AAPT_BINARY_RESOURCE_PARSER_H
-
-#include <string>
-
-#include "android-base/macros.h"
-#include "androidfw/ResourceTypes.h"
-
-#include "ResourceTable.h"
-#include "ResourceValues.h"
-#include "Source.h"
-#include "process/IResourceTableConsumer.h"
-#include "util/Util.h"
-
-namespace aapt {
-
-struct SymbolTable_entry;
-
-/*
- * Parses a binary resource table (resources.arsc) and adds the entries
- * to a ResourceTable. This is different than the libandroidfw ResTable
- * in that it scans the table from top to bottom and doesn't require
- * support for random access. It is also able to parse non-runtime
- * chunks and types.
- */
-class BinaryResourceParser {
- public:
-  /*
-   * Creates a parser, which will read `len` bytes from `data`, and
-   * add any resources parsed to `table`. `source` is for logging purposes.
-   */
-  BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
-                       const void* data, size_t data_len, io::IFileCollection* files = nullptr);
-
-  /*
-   * Parses the binary resource table and returns true if successful.
-   */
-  bool Parse();
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(BinaryResourceParser);
-
-  bool ParseTable(const android::ResChunk_header* chunk);
-  bool ParsePackage(const android::ResChunk_header* chunk);
-  bool ParseTypeSpec(const android::ResChunk_header* chunk);
-  bool ParseType(const ResourceTablePackage* package,
-                 const android::ResChunk_header* chunk);
-  bool ParseLibrary(const android::ResChunk_header* chunk);
-
-  std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const ConfigDescription& config,
-                                   const android::Res_value& value);
-
-  std::unique_ptr<Value> ParseMapEntry(const ResourceNameRef& name,
-                                       const ConfigDescription& config,
-                                       const android::ResTable_map_entry* map);
-
-  std::unique_ptr<Style> ParseStyle(const ResourceNameRef& name,
-                                    const ConfigDescription& config,
-                                    const android::ResTable_map_entry* map);
-
-  std::unique_ptr<Attribute> ParseAttr(const ResourceNameRef& name,
-                                       const ConfigDescription& config,
-                                       const android::ResTable_map_entry* map);
-
-  std::unique_ptr<Array> ParseArray(const ResourceNameRef& name,
-                                    const ConfigDescription& config,
-                                    const android::ResTable_map_entry* map);
-
-  std::unique_ptr<Plural> ParsePlural(const ResourceNameRef& name,
-                                      const ConfigDescription& config,
-                                      const android::ResTable_map_entry* map);
-
-  /**
-   * If the mapEntry is a special type that denotes meta data (source, comment),
-   * then it is
-   * read and added to the Value.
-   * Returns true if the mapEntry was meta data.
-   */
-  bool CollectMetaData(const android::ResTable_map& map_entry, Value* value);
-
-  IAaptContext* context_;
-  ResourceTable* table_;
-
-  const Source source_;
-
-  const void* data_;
-  const size_t data_len_;
-
-  // Optional file collection from which to create io::IFile objects.
-  io::IFileCollection* files_;
-
-  // The standard value string pool for resource values.
-  android::ResStringPool value_pool_;
-
-  // The string pool that holds the names of the types defined
-  // in this table.
-  android::ResStringPool type_pool_;
-
-  // The string pool that holds the names of the entries defined
-  // in this table.
-  android::ResStringPool key_pool_;
-
-  // A mapping of resource ID to resource name. When we finish parsing
-  // we use this to convert all resource IDs to symbolic references.
-  std::map<ResourceId, ResourceName> id_index_;
-};
-
-}  // namespace aapt
-
-namespace android {
-
-/**
- * Iterator functionality for ResTable_map_entry.
- */
-
-inline const ResTable_map* begin(const ResTable_map_entry* map) {
-  return (const ResTable_map*)((const uint8_t*)map +
-                               aapt::util::DeviceToHost32(map->size));
-}
-
-inline const ResTable_map* end(const ResTable_map_entry* map) {
-  return begin(map) + aapt::util::DeviceToHost32(map->count);
-}
-
-}  // namespace android
-
-#endif  // AAPT_BINARY_RESOURCE_PARSER_H
diff --git a/tools/aapt2/unflatten/ResChunkPullParser.cpp b/tools/aapt2/unflatten/ResChunkPullParser.cpp
deleted file mode 100644
index 8d92bd9..0000000
--- a/tools/aapt2/unflatten/ResChunkPullParser.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 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 "unflatten/ResChunkPullParser.h"
-
-#include <inttypes.h>
-#include <cstddef>
-
-#include "android-base/logging.h"
-#include "android-base/stringprintf.h"
-#include "androidfw/ResourceTypes.h"
-
-#include "util/Util.h"
-
-namespace aapt {
-
-using android::ResChunk_header;
-using android::base::StringPrintf;
-
-static std::string ChunkHeaderDump(const ResChunk_header* header) {
-  return StringPrintf("(type=%02" PRIx16 " header_size=%" PRIu16 " size=%" PRIu32 ")",
-                      util::DeviceToHost16(header->type), util::DeviceToHost16(header->headerSize),
-                      util::DeviceToHost32(header->size));
-}
-
-ResChunkPullParser::Event ResChunkPullParser::Next() {
-  if (!IsGoodEvent(event_)) {
-    return event_;
-  }
-
-  if (event_ == Event::kStartDocument) {
-    current_chunk_ = data_;
-  } else {
-    current_chunk_ =
-        (const ResChunk_header*)(((const char*)current_chunk_) +
-                                 util::DeviceToHost32(current_chunk_->size));
-  }
-
-  const std::ptrdiff_t diff = (const char*)current_chunk_ - (const char*)data_;
-  CHECK(diff >= 0) << "diff is negative";
-  const size_t offset = static_cast<const size_t>(diff);
-
-  if (offset == len_) {
-    current_chunk_ = nullptr;
-    return (event_ = Event::kEndDocument);
-  } else if (offset + sizeof(ResChunk_header) > len_) {
-    error_ = "chunk is past the end of the document";
-    current_chunk_ = nullptr;
-    return (event_ = Event::kBadDocument);
-  }
-
-  if (util::DeviceToHost16(current_chunk_->headerSize) < sizeof(ResChunk_header)) {
-    error_ = "chunk has too small header";
-    current_chunk_ = nullptr;
-    return (event_ = Event::kBadDocument);
-  } else if (util::DeviceToHost32(current_chunk_->size) <
-             util::DeviceToHost16(current_chunk_->headerSize)) {
-    error_ = "chunk's total size is smaller than header " + ChunkHeaderDump(current_chunk_);
-    current_chunk_ = nullptr;
-    return (event_ = Event::kBadDocument);
-  } else if (offset + util::DeviceToHost32(current_chunk_->size) > len_) {
-    error_ = "chunk's data extends past the end of the document " + ChunkHeaderDump(current_chunk_);
-    current_chunk_ = nullptr;
-    return (event_ = Event::kBadDocument);
-  }
-  return (event_ = Event::kChunk);
-}
-
-}  // namespace aapt
diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h
deleted file mode 100644
index 5827753..0000000
--- a/tools/aapt2/unflatten/ResChunkPullParser.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2015 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 AAPT_RES_CHUNK_PULL_PARSER_H
-#define AAPT_RES_CHUNK_PULL_PARSER_H
-
-#include <string>
-
-#include "android-base/macros.h"
-#include "androidfw/ResourceTypes.h"
-
-#include "util/Util.h"
-
-namespace aapt {
-
-/**
- * A pull parser, modeled after XmlPullParser, that reads
- * android::ResChunk_header structs from a block of data.
- *
- * An android::ResChunk_header specifies a type, headerSize,
- * and size. The pull parser will verify that the chunk's size
- * doesn't extend beyond the available data, and will iterate
- * over each chunk in the given block of data.
- *
- * Processing nested chunks is done by creating a new ResChunkPullParser
- * pointing to the data portion of a chunk.
- */
-class ResChunkPullParser {
- public:
-  enum class Event {
-    kStartDocument,
-    kEndDocument,
-    kBadDocument,
-
-    kChunk,
-  };
-
-  /**
-   * Returns false if the event is EndDocument or BadDocument.
-   */
-  static bool IsGoodEvent(Event event);
-
-  /**
-   * Create a ResChunkPullParser to read android::ResChunk_headers
-   * from the memory pointed to by data, of len bytes.
-   */
-  ResChunkPullParser(const void* data, size_t len);
-
-  Event event() const;
-  const std::string& error() const;
-  const android::ResChunk_header* chunk() const;
-
-  /**
-   * Move to the next android::ResChunk_header.
-   */
-  Event Next();
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ResChunkPullParser);
-
-  Event event_;
-  const android::ResChunk_header* data_;
-  size_t len_;
-  const android::ResChunk_header* current_chunk_;
-  std::string error_;
-};
-
-template <typename T, size_t MinSize = sizeof(T)>
-inline static const T* ConvertTo(const android::ResChunk_header* chunk) {
-  if (util::DeviceToHost16(chunk->headerSize) < MinSize) {
-    return nullptr;
-  }
-  return reinterpret_cast<const T*>(chunk);
-}
-
-inline static const uint8_t* GetChunkData(
-    const android::ResChunk_header* chunk) {
-  return reinterpret_cast<const uint8_t*>(chunk) +
-         util::DeviceToHost16(chunk->headerSize);
-}
-
-inline static uint32_t GetChunkDataLen(const android::ResChunk_header* chunk) {
-  return util::DeviceToHost32(chunk->size) -
-         util::DeviceToHost16(chunk->headerSize);
-}
-
-//
-// Implementation
-//
-
-inline bool ResChunkPullParser::IsGoodEvent(ResChunkPullParser::Event event) {
-  return event != Event::kEndDocument && event != Event::kBadDocument;
-}
-
-inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len)
-    : event_(Event::kStartDocument),
-      data_(reinterpret_cast<const android::ResChunk_header*>(data)),
-      len_(len),
-      current_chunk_(nullptr) {}
-
-inline ResChunkPullParser::Event ResChunkPullParser::event() const {
-  return event_;
-}
-
-inline const std::string& ResChunkPullParser::error() const { return error_; }
-
-inline const android::ResChunk_header* ResChunkPullParser::chunk() const {
-  return current_chunk_;
-}
-
-}  // namespace aapt
-
-#endif  // AAPT_RES_CHUNK_PULL_PARSER_H
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 32ec7bc..3522506 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -215,8 +215,8 @@
       return {};
     }
   }
-  return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{},
-                                        std::move(stack.root));
+  return util::make_unique<XmlResource>(ResourceFile{{}, {}, ResourceFile::Type::kUnknown, source},
+                                        StringPool{}, std::move(stack.root));
 }
 
 static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) {
@@ -461,6 +461,12 @@
   visitor->AfterVisitElement(this);
 }
 
+void Element::Accept(ConstVisitor* visitor) const {
+  visitor->BeforeVisitElement(this);
+  visitor->Visit(this);
+  visitor->AfterVisitElement(this);
+}
+
 std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) const {
   auto t = util::make_unique<Text>();
   t->comment = comment;
@@ -474,6 +480,10 @@
   visitor->Visit(this);
 }
 
+void Text::Accept(ConstVisitor* visitor) const {
+  visitor->Visit(this);
+}
+
 void PackageAwareVisitor::BeforeVisitElement(Element* el) {
   std::vector<PackageDecl> decls;
   for (const NamespaceDecl& decl : el->namespace_decls) {
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 9a9151d..063d7b9 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -35,6 +35,7 @@
 
 class Element;
 class Visitor;
+class ConstVisitor;
 
 // Base class for all XML nodes.
 class Node {
@@ -47,6 +48,7 @@
   std::string comment;
 
   virtual void Accept(Visitor* visitor) = 0;
+  virtual void Accept(ConstVisitor* visitor) const = 0;
 
   using ElementCloneFunc = std::function<void(const Element&, Element*)>;
 
@@ -112,6 +114,7 @@
   std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override;
 
   void Accept(Visitor* visitor) override;
+  void Accept(ConstVisitor* visitor) const override;
 };
 
 // A Text (CDATA) XML node. Can not have any children.
@@ -122,6 +125,7 @@
   std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override;
 
   void Accept(Visitor* visitor) override;
+  void Accept(ConstVisitor* visitor) const override;
 };
 
 // An XML resource with a source, name, and XML tree.
@@ -180,6 +184,38 @@
   friend class Element;
 };
 
+class ConstVisitor {
+ public:
+  virtual ~ConstVisitor() = default;
+
+  virtual void Visit(const Element* el) {
+    VisitChildren(el);
+  }
+
+  virtual void Visit(const Text* text) {
+  }
+
+ protected:
+  ConstVisitor() = default;
+
+  void VisitChildren(const Element* el) {
+    for (const auto& child : el->children) {
+      child->Accept(this);
+    }
+  }
+
+  virtual void BeforeVisitElement(const Element* el) {
+  }
+
+  virtual void AfterVisitElement(const Element* el) {
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ConstVisitor);
+
+  friend class Element;
+};
+
 // An XML DOM visitor that will record the package name for a namespace prefix.
 class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
  public:
@@ -207,19 +243,19 @@
 namespace internal {
 
 // Base class that overrides the default behaviour and does not descend into child nodes.
-class NodeCastBase : public Visitor {
+class NodeCastBase : public ConstVisitor {
  public:
-  void Visit(Element* el) override {
+  void Visit(const Element* el) override {
   }
-  void Visit(Text* el) override {
+  void Visit(const Text* el) override {
   }
 
  protected:
   NodeCastBase() = default;
 
-  void BeforeVisitElement(Element* el) override {
+  void BeforeVisitElement(const Element* el) override {
   }
-  void AfterVisitElement(Element* el) override {
+  void AfterVisitElement(const Element* el) override {
   }
 
  private:
@@ -233,9 +269,9 @@
 
   NodeCastImpl() = default;
 
-  T* value = nullptr;
+  const T* value = nullptr;
 
-  void Visit(T* v) override {
+  void Visit(const T* v) override {
     value = v;
   }
 
@@ -246,12 +282,17 @@
 }  // namespace internal
 
 template <typename T>
-T* NodeCast(Node* node) {
+const T* NodeCast(const Node* node) {
   internal::NodeCastImpl<T> visitor;
   node->Accept(&visitor);
   return visitor.value;
 }
 
+template <typename T>
+T* NodeCast(Node* node) {
+  return const_cast<T*>(NodeCast<T>(static_cast<const T*>(node)));
+}
+
 }  // namespace xml
 }  // namespace aapt
 
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 10a4587..34e6d3f 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -18,7 +18,8 @@
 
 #include <string>
 
-#include "io/StringInputStream.h"
+#include "format/binary/XmlFlattener.h"
+#include "io/StringStream.h"
 #include "test/Test.h"
 
 using ::aapt::io::StringInputStream;
@@ -51,6 +52,36 @@
   EXPECT_THAT(el->namespace_decls[0].prefix, StrEq("android"));
 }
 
+TEST(XmlDomTest, BinaryInflate) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+  std::unique_ptr<XmlResource> doc = util::make_unique<XmlResource>();
+  doc->root = util::make_unique<Element>();
+  doc->root->name = "Layout";
+  doc->root->line_number = 2u;
+
+  NamespaceDecl decl;
+  decl.uri = kSchemaAndroid;
+  decl.prefix = "android";
+  decl.line_number = 2u;
+  doc->root->namespace_decls.push_back(decl);
+
+  BigBuffer buffer(4096);
+  XmlFlattener flattener(&buffer, {});
+  ASSERT_TRUE(flattener.Consume(context.get(), doc.get()));
+
+  auto block = util::Copy(buffer);
+  std::unique_ptr<XmlResource> new_doc =
+      Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml"));
+  ASSERT_THAT(new_doc, NotNull());
+
+  EXPECT_THAT(new_doc->root->name, StrEq("Layout"));
+  EXPECT_THAT(new_doc->root->line_number, Eq(2u));
+  ASSERT_THAT(new_doc->root->namespace_decls, SizeIs(1u));
+  EXPECT_THAT(new_doc->root->namespace_decls[0].uri, StrEq(kSchemaAndroid));
+  EXPECT_THAT(new_doc->root->namespace_decls[0].prefix, StrEq("android"));
+  EXPECT_THAT(new_doc->root->namespace_decls[0].line_number, Eq(2u));
+}
+
 // Escaping is handled after parsing of the values for resource-specific values.
 TEST(XmlDomTest, ForwardEscapes) {
   std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(
diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 681d9d4..5304bde 100644
--- a/tools/aapt2/xml/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -18,43 +18,49 @@
 
 #include "androidfw/StringPiece.h"
 
-#include "io/StringInputStream.h"
+#include "io/StringStream.h"
 #include "test/Test.h"
 
 using ::aapt::io::StringInputStream;
 using ::android::StringPiece;
+using ::testing::Eq;
+using ::testing::StrEq;
+
+using Event = ::aapt::xml::XmlPullParser::Event;
 
 namespace aapt {
+namespace xml {
 
 TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
   std::string str =
       R"(<?xml version="1.0" encoding="utf-8"?>
          <a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)";
   StringInputStream input(str);
-  xml::XmlPullParser parser(&input);
+  XmlPullParser parser(&input);
 
   const size_t depth_outer = parser.depth();
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_outer));
 
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name()));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("a"));
 
   const size_t depth_a = parser.depth();
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a));
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name()));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_a));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("b"));
 
   const size_t depth_b = parser.depth();
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name()));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("c"));
 
-  ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b));
-  EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event());
-  EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name()));
+  ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement));
+  EXPECT_THAT(parser.element_name(), StrEq("e"));
 
-  ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer));
-  EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event());
+  ASSERT_FALSE(XmlPullParser::NextChildNode(&parser, depth_outer));
+  EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kEndDocument));
 }
 
+}  // namespace xml
 }  // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index c1186e8..0a622b2 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -16,20 +16,20 @@
 
 #include "xml/XmlUtil.h"
 
+#include <algorithm>
 #include <string>
 
 #include "util/Maybe.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 namespace xml {
 
-std::string BuildPackageNamespace(const StringPiece& package,
-                                  bool private_reference) {
-  std::string result =
-      private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
+std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+  std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
   result.append(package.data(), package.size());
   return result;
 }
@@ -39,8 +39,7 @@
   if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) {
     StringPiece schema_prefix = kSchemaPublicPrefix;
     StringPiece package = namespace_uri;
-    package = package.substr(schema_prefix.size(),
-                             package.size() - schema_prefix.size());
+    package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size());
     if (package.empty()) {
       return {};
     }
@@ -49,8 +48,7 @@
   } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
     StringPiece schema_prefix = kSchemaPrivatePrefix;
     StringPiece package = namespace_uri;
-    package = package.substr(schema_prefix.size(),
-                             package.size() - schema_prefix.size());
+    package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size());
     if (package.empty()) {
       return {};
     }
@@ -76,5 +74,33 @@
   }
 }
 
+namespace {
+
+class ToolsNamespaceRemover : public Visitor {
+ public:
+  using Visitor::Visit;
+
+  void Visit(Element* el) override {
+    auto new_end =
+        std::remove_if(el->namespace_decls.begin(), el->namespace_decls.end(),
+                       [](const NamespaceDecl& decl) -> bool { return decl.uri == kSchemaTools; });
+    el->namespace_decls.erase(new_end, el->namespace_decls.end());
+
+    auto new_attr_end = std::remove_if(
+        el->attributes.begin(), el->attributes.end(),
+        [](const Attribute& attr) -> bool { return attr.namespace_uri == kSchemaTools; });
+    el->attributes.erase(new_attr_end, el->attributes.end());
+
+    Visitor::Visit(el);
+  }
+};
+
+}  // namespace
+
+void StripAndroidStudioAttributes(Element* el) {
+  ToolsNamespaceRemover remover;
+  el->Accept(&remover);
+}
+
 }  // namespace xml
 }  // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 4eb359a..592a604 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -78,6 +78,12 @@
 // package declaration was private.
 void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref);
 
+class Element;
+
+// Strips out any attributes in the http://schemas.android.com/tools namespace, which is owned by
+// Android Studio and should not make it to the final APK.
+void StripAndroidStudioAttributes(Element* el);
+
 }  // namespace xml
 }  // namespace aapt
 
diff --git a/tools/bit/Android.bp b/tools/bit/Android.bp
index 258e9b5..a806271 100644
--- a/tools/bit/Android.bp
+++ b/tools/bit/Android.bp
@@ -30,6 +30,11 @@
         "util.cpp",
     ],
 
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
     static_libs: [
         "libexpat",
         "libinstrumentation",
diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp
index c8faf5c..fa7d3d4 100644
--- a/tools/bit/adb.cpp
+++ b/tools/bit/adb.cpp
@@ -302,7 +302,9 @@
     print_command(cmd);
 
     int fds[2];
-    pipe(fds);
+    if (0 != pipe(fds)) {
+        return errno;
+    }
 
     pid_t pid = fork();
 
diff --git a/tools/bit/command.cpp b/tools/bit/command.cpp
index 9a8449b..f95ea11 100644
--- a/tools/bit/command.cpp
+++ b/tools/bit/command.cpp
@@ -105,7 +105,9 @@
     }
 
     int fds[2];
-    pipe(fds);
+    if (0 != pipe(fds)) {
+        return string();
+    }
 
     pid_t pid = fork();
 
@@ -187,7 +189,7 @@
 int
 exec_with_path_search(const char* prog, char const* const* argv, char const* const* envp)
 {
-    if (prog[0] == '/') {
+    if (strchr(prog, '/') != NULL) {
         return execve(prog, (char*const*)argv, (char*const*)envp);
     } else {
         char* pathEnv = strdup(getenv("PATH"));
diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp
index 91ca514..a71cea1 100644
--- a/tools/bit/main.cpp
+++ b/tools/bit/main.cpp
@@ -596,6 +596,15 @@
     }
 }
 
+static void
+chdir_or_exit(const char *path) {
+    // TODO: print_command("cd", path);
+    if (0 != chdir(path)) {
+        print_error("Error: Could not chdir: %s", path);
+        exit(1);
+    }
+}
+
 /**
  * Run the build, install, and test actions.
  */
@@ -614,12 +623,12 @@
     const string buildProduct = get_required_env("TARGET_PRODUCT", false);
     const string buildVariant = get_required_env("TARGET_BUILD_VARIANT", false);
     const string buildType = get_required_env("TARGET_BUILD_TYPE", false);
-    const string buildDevice = get_build_var(buildTop, "TARGET_DEVICE", false);
-    const string buildId = get_build_var(buildTop, "BUILD_ID", false);
-    const string buildOut = get_out_dir();
 
-    // TODO: print_command("cd", buildTop.c_str());
-    chdir(buildTop.c_str());
+    chdir_or_exit(buildTop.c_str());
+
+    const string buildDevice = get_build_var("TARGET_DEVICE", false);
+    const string buildId = get_build_var("BUILD_ID", false);
+    const string buildOut = get_out_dir();
 
     // Get the modules for the targets
     map<string,Module> modules;
@@ -999,7 +1008,7 @@
     const string buildProduct = get_required_env("TARGET_PRODUCT", false);
     const string buildOut = get_out_dir();
 
-    chdir(buildTop.c_str());
+    chdir_or_exit(buildTop.c_str());
 
     string buildDevice = sniff_device_name(buildOut, buildProduct);
 
diff --git a/tools/bit/make.cpp b/tools/bit/make.cpp
index a800241..5a9ab22 100644
--- a/tools/bit/make.cpp
+++ b/tools/bit/make.cpp
@@ -36,31 +36,16 @@
 
 map<string,string> g_buildVars;
 
-static unsigned int
-get_thread_count()
-{
-    unsigned int threads = std::thread::hardware_concurrency();
-    // Guess if the value cannot be computed
-    return threads == 0 ? 4 : static_cast<unsigned int>(threads * 1.3f);
-}
-
 string
-get_build_var(const string& buildTop, const string& name, bool quiet)
+get_build_var(const string& name, bool quiet)
 {
     int err;
 
     map<string,string>::iterator it = g_buildVars.find(name);
     if (it == g_buildVars.end()) {
-        Command cmd("make");
-        cmd.AddArg("--no-print-directory");
-        cmd.AddArg(string("-j") + std::to_string(get_thread_count()));
-        cmd.AddArg("-C");
-        cmd.AddArg(buildTop);
-        cmd.AddArg("-f");
-        cmd.AddArg("build/core/config.mk");
-        cmd.AddArg(string("dumpvar-") + name);
-        cmd.AddEnv("CALLED_FROM_SETUP", "true");
-        cmd.AddEnv("BUILD_SYSTEM", "build/core");
+        Command cmd("build/soong/soong_ui.bash");
+        cmd.AddArg("--dumpvar-mode");
+        cmd.AddArg(name);
 
         string output = trim(get_command_output(cmd, &err, quiet));
         if (err == 0) {
@@ -182,7 +167,7 @@
         for (ssize_t i = module.classes.size() - 1; i >= 0; i--) {
             string cl = module.classes[i];
             if (!(cl == "JAVA_LIBRARIES" || cl == "EXECUTABLES" || cl == "SHARED_LIBRARIES"
-                    || cl == "APPS")) {
+                    || cl == "APPS" || cl == "NATIVE_TESTS")) {
                 module.classes.erase(module.classes.begin() + i);
             }
         }
@@ -208,10 +193,8 @@
 int
 build_goals(const vector<string>& goals)
 {
-    Command cmd("make");
-    cmd.AddArg(string("-j") + std::to_string(get_thread_count()));
-    cmd.AddArg("-f");
-    cmd.AddArg("build/core/main.mk");
+    Command cmd("build/soong/soong_ui.bash");
+    cmd.AddArg("--make-mode");
     for (size_t i=0; i<goals.size(); i++) {
         cmd.AddArg(goals[i]);
     }
diff --git a/tools/bit/make.h b/tools/bit/make.h
index bb83c6e..1c9504d 100644
--- a/tools/bit/make.h
+++ b/tools/bit/make.h
@@ -31,7 +31,7 @@
     vector<string> installed;
 };
 
-string get_build_var(const string& buildTop, const string& name, bool quiet);
+string get_build_var(const string& name, bool quiet);
 
 /**
  * Poke around in the out directory and try to find a device name that matches
diff --git a/tools/bit/util.cpp b/tools/bit/util.cpp
index fc93bcb..9223931 100644
--- a/tools/bit/util.cpp
+++ b/tools/bit/util.cpp
@@ -101,7 +101,6 @@
 void
 get_directory_contents(const string& name, map<string,FileInfo>* results)
 {
-    int err;
     DIR* dir = opendir(name.c_str());
     if (dir == NULL) {
         return;
@@ -241,7 +240,9 @@
     fseek(file, 0, SEEK_SET);
 
     char* buf = (char*)malloc(size);
-    fread(buf, 1, size, file);
+    if ((size_t) size != fread(buf, 1, size, file)) {
+        return string();
+    }
 
     string result(buf, size);
 
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index c6ad4c2..dcb90e4 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -13,6 +13,7 @@
 
 LANG_TO_SCRIPT = {
     'as': 'Beng',
+    'be': 'Cyrl',
     'bg': 'Cyrl',
     'bn': 'Beng',
     'cu': 'Cyrl',
@@ -33,6 +34,7 @@
     'ja': 'Jpan',
     'kn': 'Knda',
     'ko': 'Kore',
+    'la': 'Latn',
     'ml': 'Mlym',
     'mn': 'Cyrl',
     'mr': 'Deva',
diff --git a/tools/incident_report/Android.bp b/tools/incident_report/Android.bp
index ab55dbd..f2d0d0f 100644
--- a/tools/incident_report/Android.bp
+++ b/tools/incident_report/Android.bp
@@ -31,5 +31,5 @@
         "libprotobuf-cpp-full",
     ],
 
-    cflags: ["-Wno-unused-parameter"],
+    cflags: ["-Wall", "-Werror"],
 }
diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h
index df3f7b2..7c4ad34 100644
--- a/tools/incident_report/generic_message.h
+++ b/tools/incident_report/generic_message.h
@@ -25,7 +25,7 @@
 /**
  * Class to represent a protobuf Message, where we don't actually
  * know what any of the fields are, just their type codes. In other
- * words, this loslessly stores a parsed protobuf object without
+ * words, this losslessly stores a parsed protobuf object without
  * having the .proto file that generated it.
  */
 class GenericMessage
diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp
index 250d118..bd1b973 100644
--- a/tools/incident_report/main.cpp
+++ b/tools/incident_report/main.cpp
@@ -45,8 +45,9 @@
 read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor,
         GenericMessage* message)
 {
-    uint32 size;
+    uint32_t size;
     if (!in->ReadVarint32(&size)) {
+        fprintf(stderr, "Fail to read size of %s\n", descriptor->name().c_str());
         return false;
     }
 
@@ -68,6 +69,9 @@
                 message->addString(fieldId, str);
                 return true;
             } else {
+                fprintf(stderr, "Fail to read string of field %s, expect size %d, read %lu\n",
+                        field->full_name().c_str(), size, str.size());
+                fprintf(stderr, "String read \"%s\"\n", str.c_str());
                 return false;
             }
         } else if (type == FieldDescriptor::TYPE_BYTES) {
@@ -97,8 +101,8 @@
                     message->addInt64(fieldId, value64);
                     break;
                 } else {
-                    fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d\n", tag, tag,
-                                                    in->CurrentPosition());
+                    fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d of field %s\n",
+                            tag, tag, in->CurrentPosition(), descriptor->name().c_str());
                     return false;
                 }
             case WireFormatLite::WIRETYPE_FIXED64:
@@ -106,14 +110,14 @@
                     message->addInt64(fieldId, value64);
                     break;
                 } else {
-                    fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d\n", tag, tag,
-                            in->CurrentPosition());
+                    fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d of field %s\n",
+                            tag, tag, in->CurrentPosition(), descriptor->name().c_str());
                     return false;
                 }
             case WireFormatLite::WIRETYPE_LENGTH_DELIMITED:
                 if (!read_length_delimited(in, fieldId, descriptor, message)) {
-                    fprintf(stderr, "bad LENGTH_DELIMITED: 0x%x (%d) at index %d\n",
-                            tag, tag, in->CurrentPosition());
+                    fprintf(stderr, "bad LENGTH_DELIMITED: 0x%x (%d) at index %d of field %s\n",
+                            tag, tag, in->CurrentPosition(), descriptor->name().c_str());
                     return false;
                 }
                 break;
@@ -122,13 +126,13 @@
                     message->addInt32(fieldId, value32);
                     break;
                 } else {
-                    fprintf(stderr, "bad FIXED32: 0x%x (%d) at index %d\n", tag, tag,
-                            in->CurrentPosition());
+                    fprintf(stderr, "bad FIXED32: 0x%x (%d) at index %d of field %s\n",
+                            tag, tag, in->CurrentPosition(), descriptor->name().c_str());
                     return false;
                 }
             default:
-                fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag,
-                        in->CurrentPosition());
+                fprintf(stderr, "bad tag: 0x%x (%d) at index %d of field %s\n", tag, tag,
+                        in->CurrentPosition(), descriptor->name().c_str());
                 return false;
         }
     }
@@ -138,7 +142,6 @@
 static void
 print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node)
 {
-    uint32_t val32;
     FieldDescriptor::Type type = field->type();
 
     switch (node.type) {
@@ -154,29 +157,32 @@
                     out->printf("%f", *(float*)&node.value32);
                     break;
                 default:
-                    out->printf("(unexpected value32 %d (0x%x)", node.value32, node.value32);
+                    out->printf("(unexpected type %d: value32 %d (0x%x)",
+                                type, node.value32, node.value32);
                     break;
             }
             break;
         case GenericMessage::TYPE_VALUE64:
             switch (type) {
-                case FieldDescriptor::TYPE_FIXED64:
-                case FieldDescriptor::TYPE_SFIXED64:
                 case FieldDescriptor::TYPE_DOUBLE:
                     out->printf("%f", *(double*)&node.value64);
                     break;
+                // Int32s here were added with addInt64 from a WIRETYPE_VARINT,
+                // even if the definition is for a 32 bit int.
                 case FieldDescriptor::TYPE_SINT32:
                 case FieldDescriptor::TYPE_INT32:
-                    val32 = (uint32_t)node.value32;
-                    out->printf("%d", val32);
+                    out->printf("%d", node.value64);
                     break;
                 case FieldDescriptor::TYPE_INT64:
-                case FieldDescriptor::TYPE_UINT32:
-                    val32 = (uint32_t)node.value32;
-                    out->printf("%u", val32);
-                    break;
-                case FieldDescriptor::TYPE_UINT64:
                 case FieldDescriptor::TYPE_SINT64:
+                case FieldDescriptor::TYPE_SFIXED64:
+                    out->printf("%lld", node.value64);
+                    break;
+                case FieldDescriptor::TYPE_UINT32:
+                case FieldDescriptor::TYPE_UINT64:
+                case FieldDescriptor::TYPE_FIXED64:
+                    out->printf("%u", node.value64);
+                    break;
                 case FieldDescriptor::TYPE_BOOL:
                     if (node.value64) {
                         out->printf("true");
@@ -185,11 +191,16 @@
                     }
                     break;
                 case FieldDescriptor::TYPE_ENUM:
-                    out->printf("%s", field->enum_type()->FindValueByNumber((int)node.value64)
+                    if (field->enum_type()->FindValueByNumber((int)node.value64) == NULL) {
+                        out->printf("%lld", (int) node.value64);
+                    } else {
+                        out->printf("%s", field->enum_type()->FindValueByNumber((int)node.value64)
                             ->name().c_str());
+                    }
                     break;
                 default:
-                    out->printf("(unexpected value64 %ld (0x%x))", node.value64, node.value64);
+                    out->printf("(unexpected type %d: value64 %lld (0x%x))",
+                                type, node.value64, node.value64);
                     break;
             }
             break;
@@ -224,22 +235,13 @@
         out->printf("%s=", field->name().c_str());
         if (repeated) {
             if (it.first != it.second) {
-                out->printf("[");
-                if (type == FieldDescriptor::TYPE_MESSAGE
-                        || type == FieldDescriptor::TYPE_STRING
-                        || type == FieldDescriptor::TYPE_BYTES) {
-                    out->printf("\n");
-                }
+                out->printf("[\n");
                 out->indent();
 
                 for (GenericMessage::const_iterator_pair it = message->find(fieldId);
                         it.first != it.second; it.first++) {
                     print_value(out, field, it.first->second);
-                    if (type == FieldDescriptor::TYPE_MESSAGE
-                            || type == FieldDescriptor::TYPE_STRING
-                            || type == FieldDescriptor::TYPE_BYTES) {
-                        out->printf("\n");
-                    }
+                    out->printf("\n");
                 }
 
                 out->dedent();
@@ -335,7 +337,7 @@
             }
             id = field->number();
         }
-        
+
         int pfd[2];
         if (pipe(pfd) != 0) {
             fprintf(stderr, "pipe failed: %s\n", strerror(errno));
diff --git a/tools/incident_report/printer.cpp b/tools/incident_report/printer.cpp
index bd660dd2..bff1025 100644
--- a/tools/incident_report/printer.cpp
+++ b/tools/incident_report/printer.cpp
@@ -70,7 +70,6 @@
 
     len = vsnprintf(mBuf, mBufSize, format, args);
     va_end(args);
-    bool truncated = (len >= mBufSize) && (reallocate(len) < len);
 
     va_start(args, format);
     len = vsnprintf(mBuf, mBufSize, format, args);
diff --git a/tools/incident_section_gen/Android.bp b/tools/incident_section_gen/Android.bp
index 1756e06..f07445a 100644
--- a/tools/incident_section_gen/Android.bp
+++ b/tools/incident_section_gen/Android.bp
@@ -22,6 +22,8 @@
     cflags: [
         "-g",
         "-O0",
+        "-Wall",
+        "-Werror",
     ],
     srcs: ["main.cpp"],
     shared_libs: [
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
index 900690c..135df40 100644
--- a/tools/incident_section_gen/main.cpp
+++ b/tools/incident_section_gen/main.cpp
@@ -113,7 +113,7 @@
     return field->options().GetExtension(privacy).dest() == PrivacyFlags::default_instance().dest();
 }
 
-// Returns true if the descriptor doesn't have any non default privacy flags set, including its submessages
+// Returns false if the descriptor doesn't have any non default privacy flags set, including its submessages
 static bool generatePrivacyFlags(const Descriptor* descriptor, const char* alias, map<string, bool> &msgNames) {
     bool hasDefaultFlags[descriptor->field_count()];
     // iterate though its field and generate sub flags first
@@ -129,13 +129,18 @@
         };
 
         PrivacyFlags p = field->options().GetExtension(privacy);
-
         switch (field->type()) {
             case FieldDescriptor::TYPE_MESSAGE:
-                if (generatePrivacyFlags(field->message_type(), field_name, msgNames) &&
-                    isDefaultDest(field)) break;
-
-                printf("Privacy %s { %d, %d, %s_LIST, %d, NULL };\n", field_name, field->number(), field->type(), field_name, p.dest());
+                if (generatePrivacyFlags(field->message_type(), field_name, msgNames)) {
+                    printf("Privacy %s { %d, %d, %s_LIST, %d, NULL };\n", field_name, field->number(),
+                            field->type(), field_name, p.dest());
+                } else if (isDefaultDest(field)) {
+                    // don't create a new privacy if the value is default.
+                    break;
+                } else{
+                    printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(),
+                            field->type(), p.dest());
+                }
                 hasDefaultFlags[i] = false;
                 break;
             case FieldDescriptor::TYPE_STRING:
@@ -147,12 +152,14 @@
                     printf("    \"%s\",\n", replaceAll(p.patterns(i), '\\', "\\\\").c_str());
                 }
                 printf("    NULL };\n");
-                printf("Privacy %s { %d, %d, NULL, %d, %s_patterns };\n", field_name, field->number(), field->type(), p.dest(), field_name);
+                printf("Privacy %s { %d, %d, NULL, %d, %s_patterns };\n", field_name, field->number(),
+                        field->type(), p.dest(), field_name);
                 hasDefaultFlags[i] = false;
                 break;
             default:
                 if (isDefaultDest(field)) break;
-                printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(), field->type(), p.dest());
+                printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(),
+                        field->type(), p.dest());
                 hasDefaultFlags[i] = false;
         }
         // add the field name to message map, true means it has default flags
@@ -163,14 +170,14 @@
     for (int i=0; i<descriptor->field_count(); i++) {
         allDefaults &= hasDefaultFlags[i];
     }
-    if (allDefaults) return true;
+    if (allDefaults) return false;
 
     emptyline();
 
     bool needConst = strcmp(alias, "PRIVACY_POLICY") == 0;
     int policyCount = 0;
 
-    printf("%s Privacy* %s_LIST[] = {\n", needConst ? "const" : "", alias);
+    printf("%sPrivacy* %s_LIST[] = {\n", needConst ? "const " : "", alias);
     for (int i=0; i<descriptor->field_count(); i++) {
         const FieldDescriptor* field = descriptor->field(i);
         if (hasDefaultFlags[i]) continue;
@@ -184,7 +191,7 @@
         printf("    NULL };\n");
     }
     emptyline();
-    return false;
+    return true;
 }
 
 static bool generateSectionListCpp(Descriptor const* descriptor) {
@@ -222,7 +229,7 @@
 
     // generates PRIVACY_POLICY
     map<string, bool> messageNames;
-    if (generatePrivacyFlags(descriptor, "PRIVACY_POLICY", messageNames)) {
+    if (!generatePrivacyFlags(descriptor, "PRIVACY_POLICY", messageNames)) {
         // if no privacy options set at all, define an empty list
         printf("const Privacy* PRIVACY_POLICY_LIST[] = {};\n");
         printf("const int PRIVACY_POLICY_COUNT = 0;\n");
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
new file mode 100644
index 0000000..6dd6059
--- /dev/null
+++ b/tools/locked_region_code_injection/Android.bp
@@ -0,0 +1,12 @@
+java_library_host {
+    name: "lockedregioncodeinjection",
+    manifest: "manifest.txt",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "asm-6.0",
+        "asm-commons-6.0",
+        "asm-tree-6.0",
+        "asm-analysis-6.0",
+        "guava-21.0",
+    ],
+}
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
deleted file mode 100644
index 77d5163..0000000
--- a/tools/locked_region_code_injection/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_JAR_MANIFEST := manifest.txt
-LOCAL_MODULE := lockedregioncodeinjection
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    asm-5.2 \
-    asm-commons-5.2 \
-    asm-tree-5.2 \
-    asm-analysis-5.2 \
-    guava-21.0 \
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
index 99ef8a7..ee0e36c 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java
@@ -76,7 +76,7 @@
         private MethodVisitor chain;
 
         public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) {
-            super(Opcodes.ASM5, mn);
+            super(Utils.ASM_VERSION, mn);
             assert owner != null;
             this.owner = owner;
             this.chain = chain;
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
index d2a2e7b..219c2b3 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -19,7 +19,7 @@
 
 public class Utils {
 
-    public static final int ASM_VERSION = Opcodes.ASM5;
+    public static final int ASM_VERSION = Opcodes.ASM6;
 
     /**
      * Reads a comma separated configuration similar to the Jack definition.
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
index b86954d..c408b9e 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -23,11 +23,14 @@
  * <code>
  * set -x
  *
+ * croot frameworks/base/tools/locked_region_code_injection
+ *
  * # Clean
+ * mkdir -p out
  * rm -fr out/*
  *
  * # Make booster
- * javac -cp lib/asm-all-5.2.jar src&#47;*&#47;*.java -d out/
+ * javac -cp lib/asm-6.0_BETA.jar:lib/asm-commons-6.0_BETA.jar:lib/asm-tree-6.0_BETA.jar:lib/asm-analysis-6.0_BETA.jar:lib/guava-21.0.jar src&#47;*&#47;*.java -d out/
  * pushd out
  * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main *&#47;*.class
  * popd
@@ -40,7 +43,7 @@
  * popd
  *
  * # Run tool on unit tests.
- * java -ea -cp lib/asm-all-5.2.jar:out/lockedregioncodeinjection.jar \
+ * java -ea -cp lib/asm-6.0_BETA.jar:lib/asm-commons-6.0_BETA.jar:lib/asm-tree-6.0_BETA.jar:lib/asm-analysis-6.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \
  *     lockedregioncodeinjection.Main \
  *     -i out/test_input.jar -o out/test_output.jar \
  *     --targets 'Llockedregioncodeinjection/TestTarget;' \
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
new file mode 100644
index 0000000..a910c62
--- /dev/null
+++ b/tools/stats_log_api_gen/Android.bp
@@ -0,0 +1,98 @@
+//
+// 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.
+//
+
+// ==========================================================
+// Build the host executable: stats-log-api-gen
+// ==========================================================
+cc_binary_host {
+    name: "stats-log-api-gen",
+    srcs: [
+        "Collation.cpp",
+        "main.cpp",
+    ],
+
+    shared_libs: [
+        "libstats_proto_host",
+        "libprotobuf-cpp-full",
+    ],
+
+    proto: {
+        type: "full",
+    },
+}
+
+// ==========================================================
+// Build the host test executable: stats-log-api-gen
+// ==========================================================
+cc_test_host {
+    name: "stats-log-api-gen-test",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-g",
+        "-DUNIT_TEST",
+    ],
+    srcs: [
+        "Collation.cpp",
+        "test_collation.cpp",
+        "test.proto",
+    ],
+
+    static_libs: [
+        "libgmock_host",
+    ],
+
+    shared_libs: [
+        "libstats_proto_host",
+    ],
+
+    proto: {
+        type: "full",
+    },
+}
+
+// ==========================================================
+// Native library
+// ==========================================================
+genrule {
+    name: "statslog.h",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog.h",
+    out: [
+        "statslog.h",
+    ],
+}
+
+genrule {
+    name: "statslog.cpp",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog.cpp",
+    out: [
+        "statslog.cpp",
+    ],
+}
+
+cc_library_shared {
+    name: "libstatslog",
+    generated_sources: ["statslog.cpp"],
+    generated_headers: ["statslog.h"],
+    export_generated_headers: ["statslog.h"],
+    shared_libs: [
+        "liblog",
+    ],
+}
+
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
new file mode 100644
index 0000000..5d29268
--- /dev/null
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -0,0 +1,265 @@
+/*
+ * 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 "Collation.h"
+
+#include <stdio.h>
+#include <map>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using google::protobuf::FieldDescriptor;
+using google::protobuf::FileDescriptor;
+using google::protobuf::SourceLocation;
+using std::map;
+
+
+//
+// AtomDecl class
+//
+
+AtomDecl::AtomDecl()
+    :code(0),
+     name()
+{
+}
+
+AtomDecl::AtomDecl(const AtomDecl& that)
+    :code(that.code),
+     name(that.name),
+     message(that.message),
+     fields(that.fields)
+{
+}
+
+AtomDecl::AtomDecl(int c, const string& n, const string& m)
+    :code(c),
+     name(n),
+     message(m)
+{
+}
+
+AtomDecl::~AtomDecl()
+{
+}
+
+
+/**
+ * Print an error message for a FieldDescriptor, including the file name and line number.
+ */
+static void
+print_error(const FieldDescriptor* field, const char* format, ...)
+{
+    const Descriptor* message = field->containing_type();
+    const FileDescriptor* file = message->file();
+
+    SourceLocation loc;
+    if (field->GetSourceLocation(&loc)) {
+        // TODO: this will work if we can figure out how to pass --include_source_info to protoc
+        fprintf(stderr, "%s:%d: ", file->name().c_str(), loc.start_line);
+    } else {
+        fprintf(stderr, "%s: ", file->name().c_str());
+    }
+    va_list args;
+    va_start(args, format);
+    vfprintf(stderr, format, args);
+    va_end (args);
+}
+
+/**
+ * Convert a protobuf type into a java type.
+ */
+static java_type_t
+java_type(const FieldDescriptor* field)
+{
+    int protoType = field->type();
+    switch (protoType) {
+        case FieldDescriptor::TYPE_DOUBLE:
+            return JAVA_TYPE_DOUBLE;
+        case FieldDescriptor::TYPE_FLOAT:
+            return JAVA_TYPE_FLOAT;
+        case FieldDescriptor::TYPE_INT64:
+            return JAVA_TYPE_LONG;
+        case FieldDescriptor::TYPE_UINT64:
+            return JAVA_TYPE_LONG;
+        case FieldDescriptor::TYPE_INT32:
+            return JAVA_TYPE_INT;
+        case FieldDescriptor::TYPE_FIXED64:
+            return JAVA_TYPE_LONG;
+        case FieldDescriptor::TYPE_FIXED32:
+            return JAVA_TYPE_INT;
+        case FieldDescriptor::TYPE_BOOL:
+            return JAVA_TYPE_BOOLEAN;
+        case FieldDescriptor::TYPE_STRING:
+            return JAVA_TYPE_STRING;
+        case FieldDescriptor::TYPE_GROUP:
+            return JAVA_TYPE_UNKNOWN;
+        case FieldDescriptor::TYPE_MESSAGE:
+            // TODO: not the final package name
+            if (field->message_type()->full_name() == "android.os.statsd.WorkSource") {
+                return JAVA_TYPE_WORK_SOURCE;
+            } else {
+                return JAVA_TYPE_OBJECT;
+            }
+        case FieldDescriptor::TYPE_BYTES:
+            return JAVA_TYPE_BYTE_ARRAY;
+        case FieldDescriptor::TYPE_UINT32:
+            return JAVA_TYPE_INT;
+        case FieldDescriptor::TYPE_ENUM:
+            return JAVA_TYPE_INT;
+        case FieldDescriptor::TYPE_SFIXED32:
+            return JAVA_TYPE_INT;
+        case FieldDescriptor::TYPE_SFIXED64:
+            return JAVA_TYPE_LONG;
+        case FieldDescriptor::TYPE_SINT32:
+            return JAVA_TYPE_INT;
+        case FieldDescriptor::TYPE_SINT64:
+            return JAVA_TYPE_LONG;
+        default:
+            return JAVA_TYPE_UNKNOWN;
+    }
+}
+
+/**
+ * Gather the info about the atoms.
+ */
+int
+collate_atoms(const Descriptor* descriptor, Atoms* atoms)
+{
+    int errorCount = 0;
+    const bool dbg = false;
+
+    for (int i=0; i<descriptor->field_count(); i++) {
+        const FieldDescriptor* atomField = descriptor->field(i);
+
+        if (dbg) {
+            printf("   %s (%d)\n", atomField->name().c_str(), atomField->number());
+        }
+
+        // StatsEvent only has one oneof, which contains only messages. Don't allow other types.
+        if (atomField->type() != FieldDescriptor::TYPE_MESSAGE) {
+            print_error(atomField,
+                    "Bad type for atom. StatsEvent can only have message type fields: %s\n",
+                    atomField->name().c_str());
+            errorCount++;
+            continue;
+        }
+
+        const Descriptor* atom = atomField->message_type();
+
+        // Build a sorted list of the fields. Descriptor has them in source file order.
+        map<int,const FieldDescriptor*> fields;
+        for (int j=0; j<atom->field_count(); j++) {
+            const FieldDescriptor* field = atom->field(j);
+            fields[field->number()] = field;
+        }
+
+        // Check that the parameters start at 1 and go up sequentially.
+        int expectedNumber = 1;
+        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
+                it != fields.end(); it++) {
+            const int number = it->first;
+            const FieldDescriptor* field = it->second;
+            if (number != expectedNumber) {
+                print_error(field, "Fields must be numbered consecutively starting at 1:"
+                        " '%s' is %d but should be %d\n",
+                        field->name().c_str(), number, expectedNumber);
+                errorCount++;
+                expectedNumber = number;
+                continue;
+            }
+            expectedNumber++;
+        }
+
+        // Check that only allowed types are present. Remove any invalid ones.
+        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
+                it != fields.end(); it++) {
+            const FieldDescriptor* field = it->second;
+
+            java_type_t javaType = java_type(field);
+
+            if (javaType == JAVA_TYPE_UNKNOWN) {
+                print_error(field, "Unkown type for field: %s\n", field->name().c_str());
+                errorCount++;
+                continue;
+            } else if (javaType == JAVA_TYPE_OBJECT) {
+                // Allow WorkSources, but only at position 1.
+                print_error(field, "Message type not allowed for field: %s\n",
+                        field->name().c_str());
+                errorCount++;
+                continue;
+            } else if (javaType == JAVA_TYPE_BYTE_ARRAY) {
+                print_error(field, "Raw bytes type not allowed for field: %s\n",
+                        field->name().c_str());
+                errorCount++;
+                continue;
+            }
+
+        }
+
+        // Check that if there's a WorkSource, it's at position 1.
+        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
+                it != fields.end(); it++) {
+            int number = it->first;
+            if (number != 1) {
+                const FieldDescriptor* field = it->second;
+                java_type_t javaType = java_type(field);
+                if (javaType == JAVA_TYPE_WORK_SOURCE) {
+                    print_error(field, "WorkSource fields must have field id 1, in message: '%s'\n",
+                           atom->name().c_str());
+                    errorCount++;
+                }
+            }
+        }
+
+        AtomDecl atomDecl(atomField->number(), atomField->name(), atom->name());
+
+        // Build the type signature
+        vector<java_type_t> signature;
+        for (map<int,const FieldDescriptor*>::const_iterator it = fields.begin();
+                it != fields.end(); it++) {
+            const FieldDescriptor* field = it->second;
+            java_type_t javaType = java_type(field);
+
+            atomDecl.fields.push_back(AtomField(field->name(), javaType));
+            signature.push_back(javaType);
+        }
+
+        atoms->signatures.insert(signature);
+        atoms->decls.insert(atomDecl);
+    }
+
+    if (dbg) {
+        printf("signatures = [\n");
+        for (set<vector<java_type_t>>::const_iterator it = atoms->signatures.begin();
+                it != atoms->signatures.end(); it++) {
+            printf("   ");
+            for (vector<java_type_t>::const_iterator jt = it->begin(); jt != it->end(); jt++) {
+                printf(" %d", (int)*jt);
+            }
+            printf("\n");
+        }
+        printf("]\n");
+    }
+
+    return errorCount;
+}
+
+}  // namespace stats_log_api_gen
+}  // namespace android
+
+
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
new file mode 100644
index 0000000..50af7ea
--- /dev/null
+++ b/tools/stats_log_api_gen/Collation.h
@@ -0,0 +1,100 @@
+/*
+ * 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 ANDROID_STATS_LOG_API_GEN_COLLATION_H
+#define ANDROID_STATS_LOG_API_GEN_COLLATION_H
+
+
+#include <google/protobuf/descriptor.h>
+
+#include <set>
+#include <vector>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using std::set;
+using std::string;
+using std::vector;
+using google::protobuf::Descriptor;
+
+/**
+ * The types for atom parameters.
+ */
+typedef enum {
+    JAVA_TYPE_UNKNOWN = 0,
+
+    JAVA_TYPE_WORK_SOURCE = 1,
+    JAVA_TYPE_BOOLEAN = 2,
+    JAVA_TYPE_INT = 3,
+    JAVA_TYPE_LONG = 4,
+    JAVA_TYPE_FLOAT = 5,
+    JAVA_TYPE_DOUBLE = 6,
+    JAVA_TYPE_STRING = 7,
+
+    JAVA_TYPE_OBJECT = -1,
+    JAVA_TYPE_BYTE_ARRAY = -2,
+} java_type_t;
+
+
+/**
+ * The name and type for an atom field.
+ */
+struct AtomField {
+    string name;
+    java_type_t javaType;
+
+    inline AtomField() :name(), javaType(JAVA_TYPE_UNKNOWN) {}
+    inline AtomField(const AtomField& that) :name(that.name), javaType(that.javaType) {}
+    inline AtomField(string n, java_type_t jt) :name(n), javaType(jt) {}
+    inline ~AtomField() {}
+};
+
+/**
+ * The name and code for an atom.
+ */
+struct AtomDecl {
+    int code;
+    string name;
+
+    string message;
+    vector<AtomField> fields;
+
+    AtomDecl();
+    AtomDecl(const AtomDecl& that);
+    AtomDecl(int code, const string& name, const string& message);
+    ~AtomDecl();
+
+    inline bool operator<(const AtomDecl& that) const {
+        return (code == that.code) ? (name < that.name) : (code < that.code);
+    }
+};
+
+struct Atoms {
+    set<vector<java_type_t>> signatures;
+    set<AtomDecl> decls;
+};
+
+/**
+ * Gather the information about the atoms.  Returns the number of errors.
+ */
+int collate_atoms(const Descriptor* descriptor, Atoms* atoms);
+
+}  // namespace stats_log_api_gen
+}  // namespace android
+
+
+#endif // ANDROID_STATS_LOG_API_GEN_COLLATION_H
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
new file mode 100644
index 0000000..aaea4f6
--- /dev/null
+++ b/tools/stats_log_api_gen/main.cpp
@@ -0,0 +1,623 @@
+
+
+#include "Collation.h"
+
+#include "frameworks/base/cmds/statsd/src/stats_events.pb.h"
+
+#include <set>
+#include <vector>
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace google::protobuf;
+using namespace std;
+
+namespace android {
+namespace stats_log_api_gen {
+
+using android::os::statsd::StatsEvent;
+
+// TODO: Support WorkSources
+
+/**
+ * Turn lower and camel case into upper case with underscores.
+ */
+static string
+make_constant_name(const string& str)
+{
+    string result;
+    const int N = str.size();
+    bool underscore_next = false;
+    for (int i=0; i<N; i++) {
+        char c = str[i];
+        if (c >= 'A' && c <= 'Z') {
+            if (underscore_next) {
+                result += '_';
+                underscore_next = false;
+            }
+        } else if (c >= 'a' && c <= 'z') {
+            c = 'A' + c - 'a';
+            underscore_next = true;
+        } else if (c == '_') {
+            underscore_next = false;
+        }
+        result += c;
+    }
+    return result;
+}
+
+static const char*
+cpp_type_name(java_type_t type)
+{
+    switch (type) {
+        case JAVA_TYPE_BOOLEAN:
+            return "bool";
+        case JAVA_TYPE_INT:
+            return "int32_t";
+        case JAVA_TYPE_LONG:
+            return "int64_t";
+        case JAVA_TYPE_FLOAT:
+            return "float";
+        case JAVA_TYPE_DOUBLE:
+            return "double";
+        case JAVA_TYPE_STRING:
+            return "char const*";
+        default:
+            return "UNKNOWN";
+    }
+}
+
+static const char*
+java_type_name(java_type_t type)
+{
+    switch (type) {
+        case JAVA_TYPE_BOOLEAN:
+            return "boolean";
+        case JAVA_TYPE_INT:
+            return "int";
+        case JAVA_TYPE_LONG:
+            return "long";
+        case JAVA_TYPE_FLOAT:
+            return "float";
+        case JAVA_TYPE_DOUBLE:
+            return "double";
+        case JAVA_TYPE_STRING:
+            return "java.lang.String";
+        default:
+            return "UNKNOWN";
+    }
+}
+
+static int
+write_stats_log_cpp(FILE* out, const Atoms& atoms)
+{
+    int errorCount;
+
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "#include <log/log_event_list.h>\n");
+    fprintf(out, "#include <log/log.h>\n");
+    fprintf(out, "#include <statslog.h>\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "namespace android {\n");
+    fprintf(out, "namespace util {\n");
+
+    // Print write methods
+    fprintf(out, "\n");
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
+            signature != atoms.signatures.end(); signature++) {
+        int argIndex;
+
+        fprintf(out, "void\n");
+        fprintf(out, "stats_write(int code");
+        argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            argIndex++;
+        }
+        fprintf(out, ")\n");
+
+        fprintf(out, "{\n");
+        argIndex = 1;
+        fprintf(out, "    android_log_event_list event(code);\n");
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_STRING) {
+                fprintf(out, "    if (arg%d == NULL) {\n", argIndex);
+                fprintf(out, "        arg%d = \"\";\n", argIndex);
+                fprintf(out, "    }\n");
+            }
+            fprintf(out, "    event << arg%d;\n", argIndex);
+            argIndex++;
+        }
+
+        fprintf(out, "    event.write(LOG_ID_STATS);\n");
+        fprintf(out, "}\n");
+        fprintf(out, "\n");
+    }
+
+    // Print footer
+    fprintf(out, "\n");
+    fprintf(out, "} // namespace util\n");
+    fprintf(out, "} // namespace android\n");
+
+    return 0;
+}
+
+
+static int
+write_stats_log_header(FILE* out, const Atoms& atoms)
+{
+    int errorCount;
+
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+    fprintf(out, "#pragma once\n");
+    fprintf(out, "\n");
+    fprintf(out, "#include <stdint.h>\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "namespace android {\n");
+    fprintf(out, "namespace util {\n");
+    fprintf(out, "\n");
+    fprintf(out, "/*\n");
+    fprintf(out, " * API For logging statistics events.\n");
+    fprintf(out, " */\n");
+    fprintf(out, "\n");
+    fprintf(out, "/**\n");
+    fprintf(out, " * Constants for event codes.\n");
+    fprintf(out, " */\n");
+    fprintf(out, "enum {\n");
+
+    size_t i = 0;
+    // Print constants
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+            atom != atoms.decls.end(); atom++) {
+        string constant = make_constant_name(atom->name);
+        fprintf(out, "\n");
+        fprintf(out, "    /**\n");
+        fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
+        fprintf(out, "     * Usage: stats_write(StatsLog.%s", constant.c_str());
+        for (vector<AtomField>::const_iterator field = atom->fields.begin();
+                field != atom->fields.end(); field++) {
+            fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+        }
+        fprintf(out, ");\n");
+        fprintf(out, "     */\n");
+        char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
+        fprintf(out, "    %s = %d%s\n", constant.c_str(), atom->code, comma);
+        i++;
+    }
+    fprintf(out, "\n");
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    // Print write methods
+    fprintf(out, "//\n");
+    fprintf(out, "// Write methods\n");
+    fprintf(out, "//\n");
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
+            signature != atoms.signatures.end(); signature++) {
+
+        fprintf(out, "void stats_write(int code");
+        int argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+    }
+
+    fprintf(out, "\n");
+    fprintf(out, "} // namespace util\n");
+    fprintf(out, "} // namespace android\n");
+
+    return 0;
+}
+
+static int
+write_stats_log_java(FILE* out, const Atoms& atoms)
+{
+    int errorCount;
+
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+    fprintf(out, "package android.util;\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "/**\n");
+    fprintf(out, " * API For logging statistics events.\n");
+    fprintf(out, " * @hide\n");
+    fprintf(out, " */\n");
+    fprintf(out, "public final class StatsLog {\n");
+    fprintf(out, "    // Constants for event codes.\n");
+
+    // Print constants
+    for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+            atom != atoms.decls.end(); atom++) {
+        string constant = make_constant_name(atom->name);
+        fprintf(out, "\n");
+        fprintf(out, "    /**\n");
+        fprintf(out, "     * %s %s\n", atom->message.c_str(), atom->name.c_str());
+        fprintf(out, "     * Usage: StatsLog.write(StatsLog.%s", constant.c_str());
+        for (vector<AtomField>::const_iterator field = atom->fields.begin();
+                field != atom->fields.end(); field++) {
+            fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
+        }
+        fprintf(out, ");\n");
+        fprintf(out, "     */\n");
+        fprintf(out, "    public static final int %s = %d;\n", constant.c_str(), atom->code);
+    }
+    fprintf(out, "\n");
+
+    // Print write methods
+    fprintf(out, "    // Write methods\n");
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
+            signature != atoms.signatures.end(); signature++) {
+        fprintf(out, "    public static native void write(int code");
+        int argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+    }
+
+    fprintf(out, "}\n");
+
+    return 0;
+}
+
+static const char*
+jni_type_name(java_type_t type)
+{
+    switch (type) {
+        case JAVA_TYPE_BOOLEAN:
+            return "jboolean";
+        case JAVA_TYPE_INT:
+            return "jint";
+        case JAVA_TYPE_LONG:
+            return "jlong";
+        case JAVA_TYPE_FLOAT:
+            return "jfloat";
+        case JAVA_TYPE_DOUBLE:
+            return "jdouble";
+        case JAVA_TYPE_STRING:
+            return "jstring";
+        default:
+            return "UNKNOWN";
+    }
+}
+
+static string
+jni_function_name(const vector<java_type_t>& signature)
+{
+    string result("StatsLog_write");
+    for (vector<java_type_t>::const_iterator arg = signature.begin();
+            arg != signature.end(); arg++) {
+        switch (*arg) {
+            case JAVA_TYPE_BOOLEAN:
+                result += "_boolean";
+                break;
+            case JAVA_TYPE_INT:
+                result += "_int";
+                break;
+            case JAVA_TYPE_LONG:
+                result += "_long";
+                break;
+            case JAVA_TYPE_FLOAT:
+                result += "_float";
+                break;
+            case JAVA_TYPE_DOUBLE:
+                result += "_double";
+                break;
+            case JAVA_TYPE_STRING:
+                result += "_String";
+                break;
+            default:
+                result += "_UNKNOWN";
+                break;
+        }
+    }
+    return result;
+}
+
+static const char*
+java_type_signature(java_type_t type)
+{
+    switch (type) {
+        case JAVA_TYPE_BOOLEAN:
+            return "Z";
+        case JAVA_TYPE_INT:
+            return "I";
+        case JAVA_TYPE_LONG:
+            return "J";
+        case JAVA_TYPE_FLOAT:
+            return "F";
+        case JAVA_TYPE_DOUBLE:
+            return "D";
+        case JAVA_TYPE_STRING:
+            return "Ljava/lang/String;";
+        default:
+            return "UNKNOWN";
+    }
+}
+
+static string
+jni_function_signature(const vector<java_type_t>& signature)
+{
+    string result("(I");
+    for (vector<java_type_t>::const_iterator arg = signature.begin();
+            arg != signature.end(); arg++) {
+        result += java_type_signature(*arg);
+    }
+    result += ")V";
+    return result;
+}
+
+static int
+write_stats_log_jni(FILE* out, const Atoms& atoms)
+{
+    int errorCount;
+
+    // Print prelude
+    fprintf(out, "// This file is autogenerated\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "#include <statslog.h>\n");
+    fprintf(out, "\n");
+    fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
+    fprintf(out, "#include \"core_jni_helpers.h\"\n");
+    fprintf(out, "#include \"jni.h\"\n");
+    fprintf(out, "\n");
+    fprintf(out, "#define UNUSED  __attribute__((__unused__))\n");
+    fprintf(out, "\n");
+
+    fprintf(out, "namespace android {\n");
+    fprintf(out, "\n");
+
+    // Print write methods
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
+            signature != atoms.signatures.end(); signature++) {
+        int argIndex;
+
+        fprintf(out, "static void\n");
+        fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code",
+                jni_function_name(*signature).c_str());
+        argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            fprintf(out, ", %s arg%d", jni_type_name(*arg), argIndex);
+            argIndex++;
+        }
+        fprintf(out, ")\n");
+
+        fprintf(out, "{\n");
+
+        // Prepare strings
+        argIndex = 1;
+        bool hadString = false;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_STRING) {
+                fprintf(out, "    const char* str%d;\n", argIndex);
+                fprintf(out, "    if (arg%d != NULL) {\n", argIndex);
+                fprintf(out, "        str%d = env->GetStringUTFChars(arg%d, NULL);\n",
+                        argIndex, argIndex);
+                fprintf(out, "    } else {\n");
+                fprintf(out, "        str%d = NULL;\n", argIndex);
+                fprintf(out, "    }\n");
+                hadString = true;
+            }
+            argIndex++;
+        }
+
+        // Emit this to quiet the unused parameter warning if there were no strings.
+        if (!hadString) {
+            fprintf(out, "    (void)env;\n");
+        }
+
+        // stats_write call
+        argIndex = 1;
+        fprintf(out, "    android::util::stats_write(code");
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            const char* argName = (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
+            fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
+            argIndex++;
+        }
+        fprintf(out, ");\n");
+
+        // Clean up strings
+        argIndex = 1;
+        for (vector<java_type_t>::const_iterator arg = signature->begin();
+                arg != signature->end(); arg++) {
+            if (*arg == JAVA_TYPE_STRING) {
+                fprintf(out, "    if (str%d != NULL) {\n", argIndex);
+                fprintf(out, "        env->ReleaseStringUTFChars(arg%d, str%d);\n",
+                        argIndex, argIndex);
+                fprintf(out, "    }\n");
+            }
+            argIndex++;
+        }
+
+        fprintf(out, "}\n");
+        fprintf(out, "\n");
+    }
+
+    // Print registration function table
+    fprintf(out, "/*\n");
+    fprintf(out, " * JNI registration.\n");
+    fprintf(out, " */\n");
+    fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n");
+    for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
+            signature != atoms.signatures.end(); signature++) {
+        fprintf(out, "    { \"write\", \"%s\", (void*)%s },\n",
+                jni_function_signature(*signature).c_str(),
+                jni_function_name(*signature).c_str());
+    }
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
+    // Print registration function
+    fprintf(out, "int register_android_util_StatsLog(JNIEnv* env) {\n");
+    fprintf(out, "    return RegisterMethodsOrDie(\n");
+    fprintf(out, "            env,\n");
+    fprintf(out, "            \"android/util/StatsLog\",\n");
+    fprintf(out, "            gRegisterMethods, NELEM(gRegisterMethods));\n");
+    fprintf(out, "}\n");
+
+    fprintf(out, "\n");
+    fprintf(out, "} // namespace android\n");
+
+    return 0;
+}
+
+
+static void
+print_usage()
+{
+    fprintf(stderr, "usage: stats-log-api-gen OPTIONS\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "OPTIONS\n");
+    fprintf(stderr, "  --cpp FILENAME       the header file to output\n");
+    fprintf(stderr, "  --header FILENAME    the cpp file to output\n");
+    fprintf(stderr, "  --help               this message\n");
+    fprintf(stderr, "  --java FILENAME      the java file to output\n");
+    fprintf(stderr, "  --jni FILENAME       the jni file to output\n");
+}
+
+/**
+ * Do the argument parsing and execute the tasks.
+ */
+static int
+run(int argc, char const*const* argv)
+{
+    string cppFilename;
+    string headerFilename;
+    string javaFilename;
+    string jniFilename;
+
+    int index = 1;
+    while (index < argc) {
+        if (0 == strcmp("--help", argv[index])) {
+            print_usage();
+            return 0;
+        } else if (0 == strcmp("--cpp", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            cppFilename = argv[index];
+        } else if (0 == strcmp("--header", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            headerFilename = argv[index];
+        } else if (0 == strcmp("--java", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            javaFilename = argv[index];
+        } else if (0 == strcmp("--jni", argv[index])) {
+            index++;
+            if (index >= argc) {
+                print_usage();
+                return 1;
+            }
+            jniFilename = argv[index];
+        }
+        index++;
+    }
+
+    if (cppFilename.size() == 0
+            && headerFilename.size() == 0
+            && javaFilename.size() == 0
+            && jniFilename.size() == 0) {
+        print_usage();
+        return 1;
+    }
+
+    // Collate the parameters
+    Atoms atoms;
+    int errorCount = collate_atoms(StatsEvent::descriptor(), &atoms);
+    if (errorCount != 0) {
+        return 1;
+    }
+
+    // Write the .cpp file
+    if (cppFilename.size() != 0) {
+        FILE* out = fopen(cppFilename.c_str(), "w");
+        if (out == NULL) {
+            fprintf(stderr, "Unable to open file for write: %s\n", cppFilename.c_str());
+            return 1;
+        }
+        errorCount = android::stats_log_api_gen::write_stats_log_cpp(out, atoms);
+        fclose(out);
+    }
+
+    // Write the .h file
+    if (headerFilename.size() != 0) {
+        FILE* out = fopen(headerFilename.c_str(), "w");
+        if (out == NULL) {
+            fprintf(stderr, "Unable to open file for write: %s\n", headerFilename.c_str());
+            return 1;
+        }
+        errorCount = android::stats_log_api_gen::write_stats_log_header(out, atoms);
+        fclose(out);
+    }
+
+    // Write the .java file
+    if (javaFilename.size() != 0) {
+        FILE* out = fopen(javaFilename.c_str(), "w");
+        if (out == NULL) {
+            fprintf(stderr, "Unable to open file for write: %s\n", javaFilename.c_str());
+            return 1;
+        }
+        errorCount = android::stats_log_api_gen::write_stats_log_java(out, atoms);
+        fclose(out);
+    }
+
+    // Write the jni file
+    if (jniFilename.size() != 0) {
+        FILE* out = fopen(jniFilename.c_str(), "w");
+        if (out == NULL) {
+            fprintf(stderr, "Unable to open file for write: %s\n", jniFilename.c_str());
+            return 1;
+        }
+        errorCount = android::stats_log_api_gen::write_stats_log_jni(out, atoms);
+        fclose(out);
+    }
+
+    return 0;
+}
+
+}
+}
+
+/**
+ * Main.
+ */
+int
+main(int argc, char const*const* argv)
+{
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+    return android::stats_log_api_gen::run(argc, argv);
+}
diff --git a/tools/stats_log_api_gen/test.proto b/tools/stats_log_api_gen/test.proto
new file mode 100644
index 0000000..2311a11
--- /dev/null
+++ b/tools/stats_log_api_gen/test.proto
@@ -0,0 +1,112 @@
+/*
+ * 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";
+
+import "frameworks/base/cmds/statsd/src/stats_events.proto";
+
+package android.stats_log_api_gen;
+
+message IntAtom {
+    optional int32 field1 = 1;
+}
+
+message AnotherIntAtom {
+    optional int32 field1 = 1;
+}
+
+message OutOfOrderAtom {
+    optional int32 field2 = 2;
+    optional int32 field1 = 1;
+}
+
+enum AnEnum {
+    VALUE0 = 0;
+    VALUE1 = 1;
+}
+
+message AllTypesAtom {
+    optional android.os.statsd.WorkSource attribution = 1;
+    optional double double_field = 2;
+    optional float float_field = 3;
+    optional int64 int64_field = 4;
+    optional uint64 uint64_field = 5;
+    optional int32 int32_field = 6;
+    optional fixed64 fixed64_field = 7;
+    optional fixed32 fixed32_field = 8;
+    optional bool bool_field = 9;
+    optional string string_field = 10;
+    optional uint32 uint32_field = 11;
+    optional AnEnum enum_field = 12;
+    optional sfixed32 sfixed32_field = 13;
+    optional sfixed64 sfixed64_field = 14;
+    optional sint32 sint32_field = 15;
+    optional sint64 sint64_field = 16;
+}
+
+message Event {
+    oneof event {
+        OutOfOrderAtom out_of_order_atom = 2;
+        IntAtom int_atom = 1;
+        AnotherIntAtom another_int_atom = 3;
+        AllTypesAtom all_types_atom = 4;
+    }
+}
+
+message BadTypesAtom {
+    optional IntAtom bad_int_atom = 1;
+    optional bytes bad_bytes = 2;
+}
+
+message BadTypesEvent {
+    oneof event {
+        BadTypesAtom bad_types_atom = 1;
+    }
+}
+
+message BadSkippedFieldSingleAtom {
+    optional int32 field2 = 2;
+}
+
+message BadSkippedFieldSingle {
+    oneof event {
+        BadSkippedFieldSingleAtom bad = 1;
+    }
+}
+
+message BadSkippedFieldMultipleAtom {
+    optional int32 field1 = 1;
+    optional int32 field3 = 3;
+    optional int32 field5 = 5;
+}
+
+message BadSkippedFieldMultiple {
+    oneof event {
+        BadSkippedFieldMultipleAtom bad = 1;
+    }
+}
+
+message BadWorkSourcePositionAtom {
+    optional int32 field1 = 1;
+    optional android.os.statsd.WorkSource attribution = 2;
+}
+
+message BadWorkSourcePosition {
+    oneof event {
+        BadWorkSourcePositionAtom bad = 1;
+    }
+}
+
diff --git a/tools/stats_log_api_gen/test_collation.cpp b/tools/stats_log_api_gen/test_collation.cpp
new file mode 100644
index 0000000..1bd2e3d
--- /dev/null
+++ b/tools/stats_log_api_gen/test_collation.cpp
@@ -0,0 +1,171 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "frameworks/base/tools/stats_log_api_gen/test.pb.h"
+#include "Collation.h"
+
+#include <stdio.h>
+
+namespace android {
+namespace stats_log_api_gen {
+
+using std::set;
+using std::vector;
+
+/**
+ * Return whether the set contains a vector of the elements provided.
+ */
+static bool
+set_contains_vector(const set<vector<java_type_t>>& s, int count, ...)
+{
+    va_list args;
+    vector<java_type_t> v;
+
+    va_start(args, count);
+    for (int i=0; i<count; i++) {
+        v.push_back((java_type_t)va_arg(args, int));
+    }
+    va_end(args);
+
+    return s.find(v) != s.end();
+}
+
+/**
+ * Expect that the provided set contains the elements provided.
+ */
+#define EXPECT_SET_CONTAINS_SIGNATURE(s, ...) \
+    do { \
+        int count = sizeof((int[]){__VA_ARGS__})/sizeof(int); \
+        EXPECT_TRUE(set_contains_vector(s, count, __VA_ARGS__)); \
+    } while(0)
+
+/**
+ * Test a correct collation, with all the types.
+ */
+TEST(CollationTest, CollateStats) {
+    Atoms atoms;
+    int errorCount = collate_atoms(Event::descriptor(), &atoms);
+
+    EXPECT_EQ(0, errorCount);
+    EXPECT_EQ(3ul, atoms.signatures.size());
+
+    // IntAtom, AnotherIntAtom
+    EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT);
+
+    // OutOfOrderAtom
+    EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures, JAVA_TYPE_INT, JAVA_TYPE_INT);
+
+    // AllTypesAtom
+    EXPECT_SET_CONTAINS_SIGNATURE(atoms.signatures,
+                JAVA_TYPE_WORK_SOURCE, // WorkSource
+                JAVA_TYPE_DOUBLE, // double
+                JAVA_TYPE_FLOAT, // float
+                JAVA_TYPE_LONG, // int64
+                JAVA_TYPE_LONG, // uint64
+                JAVA_TYPE_INT, // int32
+                JAVA_TYPE_LONG, // fixed64
+                JAVA_TYPE_INT, // fixed32
+                JAVA_TYPE_BOOLEAN, // bool
+                JAVA_TYPE_STRING, // string
+                JAVA_TYPE_INT, // uint32
+                JAVA_TYPE_INT, // AnEnum
+                JAVA_TYPE_INT, // sfixed32
+                JAVA_TYPE_LONG, // sfixed64
+                JAVA_TYPE_INT, // sint32
+                JAVA_TYPE_LONG // sint64
+            );
+
+    set<AtomDecl>::const_iterator atom = atoms.decls.begin();
+    EXPECT_EQ(1, atom->code);
+    EXPECT_EQ("int_atom", atom->name);
+    EXPECT_EQ("IntAtom", atom->message);
+    atom++;
+
+    EXPECT_EQ(2, atom->code);
+    EXPECT_EQ("out_of_order_atom", atom->name);
+    EXPECT_EQ("OutOfOrderAtom", atom->message);
+    atom++;
+
+    EXPECT_EQ(3, atom->code);
+    EXPECT_EQ("another_int_atom", atom->name);
+    EXPECT_EQ("AnotherIntAtom", atom->message);
+    atom++;
+
+    EXPECT_EQ(4, atom->code);
+    EXPECT_EQ("all_types_atom", atom->name);
+    EXPECT_EQ("AllTypesAtom", atom->message);
+    atom++;
+
+    EXPECT_TRUE(atom == atoms.decls.end());
+}
+
+/**
+ * Test that event class that contains stuff other than the atoms is rejected.
+ */
+TEST(CollationTest, NonMessageTypeFails) {
+    Atoms atoms;
+    int errorCount = collate_atoms(IntAtom::descriptor(), &atoms);
+
+    EXPECT_EQ(1, errorCount);
+}
+
+/**
+ * Test that atoms that have non-primitve types are rejected.
+ */
+TEST(CollationTest, FailOnBadTypes) {
+    Atoms atoms;
+    int errorCount = collate_atoms(BadTypesEvent::descriptor(), &atoms);
+
+    EXPECT_EQ(2, errorCount);
+}
+
+/**
+ * Test that atoms that skip field numbers (in the first position) are rejected.
+ */
+TEST(CollationTest, FailOnSkippedFieldsSingle) {
+    Atoms atoms;
+    int errorCount = collate_atoms(BadSkippedFieldSingle::descriptor(), &atoms);
+
+    EXPECT_EQ(1, errorCount);
+}
+
+/**
+ * Test that atoms that skip field numbers (not in the first position, and multiple
+ * times) are rejected.
+ */
+TEST(CollationTest, FailOnSkippedFieldsMultiple) {
+    Atoms atoms;
+    int errorCount = collate_atoms(BadSkippedFieldMultiple::descriptor(), &atoms);
+
+    EXPECT_EQ(2, errorCount);
+}
+
+/**
+ * Test that atoms that have a WorkSource not in the first position are rejected.
+ */
+TEST(CollationTest, FailBadWorkSourcePosition) {
+    Atoms atoms;
+    int errorCount = collate_atoms(BadWorkSourcePosition::descriptor(), &atoms);
+
+    EXPECT_EQ(1, errorCount);
+}
+
+
+}  // namespace stats_log_api_gen
+}  // namespace android
+
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 24068e9..756549c 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,13 +17,31 @@
 // ==========================================================
 // Build the host executable: protoc-gen-javastream
 // ==========================================================
-cc_binary_host {
-    name: "protoc-gen-javastream",
+cc_defaults {
+    name: "protoc-gen-stream-defaults",
     srcs: [
         "Errors.cpp",
         "string_utils.cpp",
-        "main.cpp",
+    ],
+}
+
+
+cc_binary_host {
+    name: "protoc-gen-javastream",
+    srcs: [
+        "java/main.cpp",
     ],
 
+    defaults: ["protoc-gen-stream-defaults"],
+    shared_libs: ["libprotoc"],
+}
+
+cc_binary_host {
+    name: "protoc-gen-cppstream",
+    srcs: [
+        "cpp/main.cpp",
+    ],
+
+    defaults: ["protoc-gen-stream-defaults"],
     shared_libs: ["libprotoc"],
 }
diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp
index 91c6b92..0cd9037 100644
--- a/tools/streaming_proto/Errors.cpp
+++ b/tools/streaming_proto/Errors.cpp
@@ -3,7 +3,7 @@
 #include <stdlib.h>
 
 namespace android {
-namespace javastream_proto {
+namespace stream_proto {
 
 Errors ERRORS;
 
@@ -82,6 +82,6 @@
     return m_errors.size() > 0;
 }
 
-} // namespace javastream_proto
+} // namespace stream_proto
 } // namespace android
 
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
index 109195a..f14bbfd 100644
--- a/tools/streaming_proto/Errors.h
+++ b/tools/streaming_proto/Errors.h
@@ -4,7 +4,7 @@
 #include <vector>
 
 namespace android {
-namespace javastream_proto {
+namespace stream_proto {
 
 using namespace std;
 
@@ -44,5 +44,5 @@
 extern const int UNKNOWN_LINE;
 
 
-} // namespace javastream_proto
+} // namespace stream_proto
 } // namespace android
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
new file mode 100644
index 0000000..d4e1b7a
--- /dev/null
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -0,0 +1,273 @@
+#include "Errors.h"
+#include "string_utils.h"
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+using namespace android::stream_proto;
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * Position of the field type in a (long long) fieldId.
+ */
+const uint64_t FIELD_TYPE_SHIFT = 32;
+
+//
+// FieldId flags for whether the field is single, repeated or packed.
+// TODO: packed is not supported yet.
+//
+const uint64_t FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_MASK = 0x0fULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_UNKNOWN = 0;
+const uint64_t FIELD_COUNT_SINGLE = 1ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2ULL << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 4ULL << FIELD_COUNT_SHIFT;
+
+// Indent
+const string INDENT = "    ";
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+static bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+    const int N = request.file_to_generate_size();
+    for (int i=0; i<N; i++) {
+        if (request.file_to_generate(i) == file) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static string
+make_filename(const FileDescriptorProto& file_descriptor)
+{
+    return file_descriptor.name() + ".h";
+}
+
+static string
+get_proto_type(const FieldDescriptorProto& field)
+{
+    switch (field.type()) {
+        case FieldDescriptorProto::TYPE_DOUBLE:
+            return "double";
+        case FieldDescriptorProto::TYPE_FLOAT:
+            return "float";
+        case FieldDescriptorProto::TYPE_INT64:
+            return "int64";
+        case FieldDescriptorProto::TYPE_UINT64:
+            return "uint64";
+        case FieldDescriptorProto::TYPE_INT32:
+            return "int32";
+        case FieldDescriptorProto::TYPE_FIXED64:
+            return "fixed64";
+        case FieldDescriptorProto::TYPE_FIXED32:
+            return "fixed32";
+        case FieldDescriptorProto::TYPE_BOOL:
+            return "bool";
+        case FieldDescriptorProto::TYPE_STRING:
+            return "string";
+        case FieldDescriptorProto::TYPE_GROUP:
+            return "group<unsupported!>";
+        case FieldDescriptorProto::TYPE_MESSAGE:
+            return field.type_name();
+        case FieldDescriptorProto::TYPE_BYTES:
+            return "bytes";
+        case FieldDescriptorProto::TYPE_UINT32:
+            return "uint32";
+        case FieldDescriptorProto::TYPE_ENUM:
+            return field.type_name();
+        case FieldDescriptorProto::TYPE_SFIXED32:
+            return "sfixed32";
+        case FieldDescriptorProto::TYPE_SFIXED64:
+            return "sfixed64";
+        case FieldDescriptorProto::TYPE_SINT32:
+            return "sint32";
+        case FieldDescriptorProto::TYPE_SINT64:
+            return "sint64";
+        default:
+            // won't happen
+            return "void";
+    }
+}
+
+static void
+write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
+{
+    const int N = enu.value_size();
+    text << indent << "// enum " << enu.name() << endl;
+    for (int i=0; i<N; i++) {
+        const EnumValueDescriptorProto& value = enu.value(i);
+        text << indent << "const uint32_t "
+                << make_constant_name(value.name())
+                << " = " << value.number() << ";" << endl;
+    }
+    text << endl;
+}
+
+static uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+    // Number
+    uint64_t result = (uint64_t)field.number();
+
+    // Type
+    result |= (uint64_t)field.type() << FIELD_TYPE_SHIFT;
+
+    // Count
+    if (field.options().packed()) {
+        result |= FIELD_COUNT_PACKED;
+    } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+        result |= FIELD_COUNT_REPEATED;
+    } else {
+        result |= FIELD_COUNT_SINGLE;
+    }
+
+    return result;
+}
+
+static void
+write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
+{
+    string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
+            ? "optional " : "";
+    string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
+            ? "repeated " : "";
+    string proto_type = get_proto_type(field);
+    string packed_comment = field.options().packed()
+            ? " [packed=true]" : "";
+    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+            << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+    text << indent << "const uint64_t " << make_constant_name(field.name()) << " = 0x";
+
+    ios::fmtflags fmt(text.flags());
+    text << setfill('0') << setw(16) << hex << get_field_id(field);
+    text.flags(fmt);
+
+    text << "LL;" << endl;
+
+    text << endl;
+}
+
+static void
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
+{
+    int N;
+    const string indented = indent + INDENT;
+
+    text << indent << "// message " << message.name() << endl;
+    text << indent << "class " << message.name() << " {" << endl;
+    text << indent << "public:" << endl;
+
+    // Enums
+    N = message.enum_type_size();
+    for (int i=0; i<N; i++) {
+        write_enum(text, message.enum_type(i), indented);
+    }
+
+    // Nested classes
+    N = message.nested_type_size();
+    for (int i=0; i<N; i++) {
+        write_message(text, message.nested_type(i), indented);
+    }
+
+    // Fields
+    N = message.field_size();
+    for (int i=0; i<N; i++) {
+        write_field(text, message.field(i), indented);
+    }
+
+    text << indent << "};" << endl;
+    text << endl;
+}
+
+static void
+write_cpp_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+    stringstream text;
+
+    text << "// Generated by protoc-gen-cppstream. DO NOT MODIFY." << endl;
+    text << "// source: " << file_descriptor.name() << endl << endl;
+
+    string header = "ANDROID_" + replace_string(file_descriptor.name(), '/', '_');
+    header = replace_string(header, '.', '_') + "_stream_h";
+    header = make_constant_name(header);
+
+    text << "#ifndef " << header << endl;
+    text << "#define " << header << endl;
+    text << endl;
+
+    vector<string> namespaces = split(file_descriptor.package(), '.');
+    for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
+        text << "namespace " << *it << " {" << endl;
+    }
+    text << endl;
+
+    size_t N;
+    N = file_descriptor.enum_type_size();
+    for (size_t i=0; i<N; i++) {
+        write_enum(text, file_descriptor.enum_type(i), "");
+    }
+
+    N = file_descriptor.message_type_size();
+    for (size_t i=0; i<N; i++) {
+        write_message(text, file_descriptor.message_type(i), "");
+    }
+
+    for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
+        text << "} // " << *it << endl;
+    }
+
+    text << endl;
+    text << "#endif // " << header << endl;
+
+    CodeGeneratorResponse::File* file_response = response->add_file();
+    file_response->set_name(make_filename(file_descriptor));
+    file_response->set_content(text.str());
+}
+
+int main(int argc, char const *argv[])
+{
+    (void)argc;
+    (void)argv;
+
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+    CodeGeneratorRequest request;
+    CodeGeneratorResponse response;
+
+    // Read the request
+    request.ParseFromIstream(&cin);
+
+    // Build the files we need.
+    const int N = request.proto_file_size();
+    for (int i=0; i<N; i++) {
+        const FileDescriptorProto& file_descriptor = request.proto_file(i);
+        if (should_generate_for_file(request, file_descriptor.name())) {
+            write_cpp_file(&response, file_descriptor);
+        }
+    }
+
+    // If we had errors, don't write the response. Print the errors and exit.
+    if (ERRORS.HasErrors()) {
+        ERRORS.Print();
+        return 1;
+    }
+
+    // If we didn't have errors, write the response and exit happily.
+    response.SerializeToOstream(&cout);
+
+    /* code */
+    return 0;
+}
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
new file mode 100644
index 0000000..b7d594b
--- /dev/null
+++ b/tools/streaming_proto/java/main.cpp
@@ -0,0 +1,474 @@
+#include "Errors.h"
+
+#include "string_utils.h"
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
+
+#include <stdio.h>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <map>
+
+using namespace android::stream_proto;
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace google::protobuf::io;
+using namespace std;
+
+const int FIELD_TYPE_SHIFT = 32;
+const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+
+const int FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+static bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+    const int N = request.file_to_generate_size();
+    for (int i=0; i<N; i++) {
+        if (request.file_to_generate(i) == file) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string
+make_outer_class_name(const FileDescriptorProto& file_descriptor)
+{
+    string name = file_descriptor.options().java_outer_classname();
+    if (name.size() == 0) {
+        name = to_camel_case(file_base_name(file_descriptor.name()));
+        if (name.size() == 0) {
+            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+                    "Unable to make an outer class name for file: %s",
+                    file_descriptor.name().c_str());
+            name = "Unknown";
+        }
+    }
+    return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string
+make_java_package(const FileDescriptorProto& file_descriptor) {
+    if (file_descriptor.options().has_java_package()) {
+        return file_descriptor.options().java_package();
+    } else {
+        return file_descriptor.package();
+    }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string
+make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
+{
+    string const package = make_java_package(file_descriptor);
+    string result;
+    if (package.size() > 0) {
+        result = replace_string(package, '.', '/');
+        result += '/';
+    }
+
+    result += class_name;
+    result += ".java";
+
+    return result;
+}
+
+static string
+indent_more(const string& indent)
+{
+    return indent + "    ";
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void
+write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
+{
+    const int N = enu.value_size();
+    text << indent << "// enum " << enu.name() << endl;
+    for (int i=0; i<N; i++) {
+        const EnumValueDescriptorProto& value = enu.value(i);
+        text << indent << "public static final int "
+                << make_constant_name(value.name())
+                << " = " << value.number() << ";" << endl;
+    }
+    text << endl;
+}
+
+/**
+ * Get the string name for a field.
+ */
+static string
+get_proto_type(const FieldDescriptorProto& field)
+{
+    switch (field.type()) {
+        case FieldDescriptorProto::TYPE_DOUBLE:
+            return "double";
+        case FieldDescriptorProto::TYPE_FLOAT:
+            return "float";
+        case FieldDescriptorProto::TYPE_INT64:
+            return "int64";
+        case FieldDescriptorProto::TYPE_UINT64:
+            return "uint64";
+        case FieldDescriptorProto::TYPE_INT32:
+            return "int32";
+        case FieldDescriptorProto::TYPE_FIXED64:
+            return "fixed64";
+        case FieldDescriptorProto::TYPE_FIXED32:
+            return "fixed32";
+        case FieldDescriptorProto::TYPE_BOOL:
+            return "bool";
+        case FieldDescriptorProto::TYPE_STRING:
+            return "string";
+        case FieldDescriptorProto::TYPE_GROUP:
+            return "group<unsupported!>";
+        case FieldDescriptorProto::TYPE_MESSAGE:
+            return field.type_name();
+        case FieldDescriptorProto::TYPE_BYTES:
+            return "bytes";
+        case FieldDescriptorProto::TYPE_UINT32:
+            return "uint32";
+        case FieldDescriptorProto::TYPE_ENUM:
+            return field.type_name();
+        case FieldDescriptorProto::TYPE_SFIXED32:
+            return "sfixed32";
+        case FieldDescriptorProto::TYPE_SFIXED64:
+            return "sfixed64";
+        case FieldDescriptorProto::TYPE_SINT32:
+            return "sint32";
+        case FieldDescriptorProto::TYPE_SINT64:
+            return "sint64";
+        default:
+            // won't happen
+            return "void";
+    }
+}
+
+static uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+    // Number
+    uint64_t result = (uint32_t)field.number();
+
+    // Type
+    switch (field.type()) {
+        case FieldDescriptorProto::TYPE_DOUBLE:
+            result |= FIELD_TYPE_DOUBLE;
+            break;
+        case FieldDescriptorProto::TYPE_FLOAT:
+            result |= FIELD_TYPE_FLOAT;
+            break;
+        case FieldDescriptorProto::TYPE_INT64:
+            result |= FIELD_TYPE_INT64;
+            break;
+        case FieldDescriptorProto::TYPE_UINT64:
+            result |= FIELD_TYPE_UINT64;
+            break;
+        case FieldDescriptorProto::TYPE_INT32:
+            result |= FIELD_TYPE_INT32;
+            break;
+        case FieldDescriptorProto::TYPE_FIXED64:
+            result |= FIELD_TYPE_FIXED64;
+            break;
+        case FieldDescriptorProto::TYPE_FIXED32:
+            result |= FIELD_TYPE_FIXED32;
+            break;
+        case FieldDescriptorProto::TYPE_BOOL:
+            result |= FIELD_TYPE_BOOL;
+            break;
+        case FieldDescriptorProto::TYPE_STRING:
+            result |= FIELD_TYPE_STRING;
+            break;
+        case FieldDescriptorProto::TYPE_MESSAGE:
+            result |= FIELD_TYPE_OBJECT;
+            break;
+        case FieldDescriptorProto::TYPE_BYTES:
+            result |= FIELD_TYPE_BYTES;
+            break;
+        case FieldDescriptorProto::TYPE_UINT32:
+            result |= FIELD_TYPE_UINT32;
+            break;
+        case FieldDescriptorProto::TYPE_ENUM:
+            result |= FIELD_TYPE_ENUM;
+            break;
+        case FieldDescriptorProto::TYPE_SFIXED32:
+            result |= FIELD_TYPE_SFIXED32;
+            break;
+        case FieldDescriptorProto::TYPE_SFIXED64:
+            result |= FIELD_TYPE_SFIXED64;
+            break;
+        case FieldDescriptorProto::TYPE_SINT32:
+            result |= FIELD_TYPE_SINT32;
+            break;
+        case FieldDescriptorProto::TYPE_SINT64:
+            result |= FIELD_TYPE_SINT64;
+            break;
+        default:
+            ;
+    }
+
+    // Count
+    if (field.options().packed()) {
+        result |= FIELD_COUNT_PACKED;
+    } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+        result |= FIELD_COUNT_REPEATED;
+    } else {
+        result |= FIELD_COUNT_SINGLE;
+    }
+
+    return result;
+}
+
+/**
+ * Write a field.
+ */
+static void
+write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
+{
+    string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
+            ? "optional " : "";
+    string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
+            ? "repeated " : "";
+    string proto_type = get_proto_type(field);
+    string packed_comment = field.options().packed()
+            ? " [packed=true]" : "";
+    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+            << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+    text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+    ios::fmtflags fmt(text.flags());
+    text << setfill('0') << setw(16) << hex << get_field_id(field);
+    text.flags(fmt);
+
+    text << "L;" << endl;
+
+    text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
+{
+    int N;
+    const string indented = indent_more(indent);
+
+    text << indent << "// message " << message.name() << endl;
+    text << indent << "public final class " << message.name() << " {" << endl;
+    text << endl;
+
+    // Enums
+    N = message.enum_type_size();
+    for (int i=0; i<N; i++) {
+        write_enum(text, message.enum_type(i), indented);
+    }
+
+    // Nested classes
+    N = message.nested_type_size();
+    for (int i=0; i<N; i++) {
+        write_message(text, message.nested_type(i), indented);
+    }
+
+    // Fields
+    N = message.field_size();
+    for (int i=0; i<N; i++) {
+        write_field(text, message.field(i), indented);
+    }
+
+    text << indent << "}" << endl;
+    text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void
+write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+        const string& filename, bool generate_outer,
+        const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
+{
+    stringstream text;
+
+    string const package_name = make_java_package(file_descriptor);
+    string const outer_class_name = make_outer_class_name(file_descriptor);
+
+    text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+    text << "// source: " << file_descriptor.name() << endl << endl;
+
+    if (package_name.size() > 0) {
+        if (package_name.size() > 0) {
+            text << "package " << package_name << ";" << endl;
+            text << endl;
+        }
+    }
+
+    // This bit of policy is android api rules specific: Raw proto classes
+    // must never be in the API
+    text << "/** @hide */" << endl;
+//    text << "@android.annotation.TestApi" << endl;
+
+    if (generate_outer) {
+        text << "public final class " << outer_class_name << " {" << endl;
+        text << endl;
+    }
+
+    size_t N;
+    const string indented = generate_outer ? indent_more("") : string();
+    
+    N = enums.size();
+    for (size_t i=0; i<N; i++) {
+        write_enum(text, enums[i], indented);
+    }
+
+    N = messages.size();
+    for (size_t i=0; i<N; i++) {
+        write_message(text, messages[i], indented);
+    }
+
+    if (generate_outer) {
+        text << "}" << endl;
+    }
+
+    CodeGeneratorResponse::File* file_response = response->add_file();
+    file_response->set_name(filename);
+    file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class.  Put all of the enums into the "outer" class.
+ */
+static void
+write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+    // If there is anything to put in the outer class file, create one
+    if (file_descriptor.enum_type_size() > 0) {
+        vector<EnumDescriptorProto> enums;
+        int N = file_descriptor.enum_type_size();
+        for (int i=0; i<N; i++) {
+            enums.push_back(file_descriptor.enum_type(i));
+        }
+
+        vector<DescriptorProto> messages;
+
+        write_file(response, file_descriptor,
+                make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+                true, enums, messages);
+    }
+
+    // For each of the message types, make a file
+    int N = file_descriptor.message_type_size();
+    for (int i=0; i<N; i++) {
+        vector<EnumDescriptorProto> enums;
+
+        vector<DescriptorProto> messages;
+        messages.push_back(file_descriptor.message_type(i));
+
+        write_file(response, file_descriptor,
+                make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+                false, enums, messages);
+    }
+}
+
+static void
+write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+    int N;
+
+    vector<EnumDescriptorProto> enums;
+    N = file_descriptor.enum_type_size();
+    for (int i=0; i<N; i++) {
+        enums.push_back(file_descriptor.enum_type(i));
+    }
+
+    vector<DescriptorProto> messages;
+    N = file_descriptor.message_type_size();
+    for (int i=0; i<N; i++) {
+        messages.push_back(file_descriptor.message_type(i));
+    }
+
+    write_file(response, file_descriptor,
+            make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+            true, enums, messages);
+}
+
+/**
+ * Main.
+ */
+int
+main(int argc, char const*const* argv)
+{
+    (void)argc;
+    (void)argv;
+
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+    CodeGeneratorRequest request;
+    CodeGeneratorResponse response;
+
+    // Read the request
+    request.ParseFromIstream(&cin);
+
+    // Build the files we need.
+    const int N = request.proto_file_size();
+    for (int i=0; i<N; i++) {
+        const FileDescriptorProto& file_descriptor = request.proto_file(i);
+        if (should_generate_for_file(request, file_descriptor.name())) {
+            if (file_descriptor.options().java_multiple_files()) {
+                write_multiple_files(&response, file_descriptor);
+            } else {
+                write_single_file(&response, file_descriptor);
+            }
+        }
+    }
+
+    // If we had errors, don't write the response. Print the errors and exit.
+    if (ERRORS.HasErrors()) {
+        ERRORS.Print();
+        return 1;
+    }
+
+    // If we didn't have errors, write the response and exit happily.
+    response.SerializeToOstream(&cout);
+    return 0;
+}
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
deleted file mode 100644
index 5b4ba04..0000000
--- a/tools/streaming_proto/main.cpp
+++ /dev/null
@@ -1,474 +0,0 @@
-#include "Errors.h"
-
-#include "string_utils.h"
-
-#include "google/protobuf/compiler/plugin.pb.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
-
-#include <stdio.h>
-#include <iomanip>
-#include <iostream>
-#include <sstream>
-#include <map>
-
-using namespace android::javastream_proto;
-using namespace google::protobuf;
-using namespace google::protobuf::compiler;
-using namespace google::protobuf::io;
-using namespace std;
-
-const int FIELD_TYPE_SHIFT = 32;
-const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
-const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
-
-const int FIELD_COUNT_SHIFT = 40;
-const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
-const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
-
-
-/**
- * See if this is the file for this request, and not one of the imported ones.
- */
-static bool
-should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
-{
-    const int N = request.file_to_generate_size();
-    for (int i=0; i<N; i++) {
-        if (request.file_to_generate(i) == file) {
-            return true;
-        }
-    }
-    return false;
-}
-
-/**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
-    string name = file_descriptor.options().java_outer_classname();
-    if (name.size() == 0) {
-        name = to_camel_case(file_base_name(file_descriptor.name()));
-        if (name.size() == 0) {
-            ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
-                    "Unable to make an outer class name for file: %s",
-                    file_descriptor.name().c_str());
-            name = "Unknown";
-        }
-    }
-    return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
-    if (file_descriptor.options().has_java_package()) {
-        return file_descriptor.options().java_package();
-    } else {
-        return file_descriptor.package();
-    }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
-    string const package = make_java_package(file_descriptor);
-    string result;
-    if (package.size() > 0) {
-        result = replace_string(package, '.', '/');
-        result += '/';
-    }
-
-    result += class_name;
-    result += ".java";
-
-    return result;
-}
-
-static string
-indent_more(const string& indent)
-{
-    return indent + "    ";
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
-    const int N = enu.value_size();
-    text << indent << "// enum " << enu.name() << endl;
-    for (int i=0; i<N; i++) {
-        const EnumValueDescriptorProto& value = enu.value(i);
-        text << indent << "public static final int "
-                << make_constant_name(value.name())
-                << " = " << value.number() << ";" << endl;
-    }
-    text << endl;
-}
-
-/**
- * Get the string name for a field.
- */
-static string
-get_proto_type(const FieldDescriptorProto& field)
-{
-    switch (field.type()) {
-        case FieldDescriptorProto::TYPE_DOUBLE:
-            return "double";
-        case FieldDescriptorProto::TYPE_FLOAT:
-            return "float";
-        case FieldDescriptorProto::TYPE_INT64:
-            return "int64";
-        case FieldDescriptorProto::TYPE_UINT64:
-            return "uint64";
-        case FieldDescriptorProto::TYPE_INT32:
-            return "int32";
-        case FieldDescriptorProto::TYPE_FIXED64:
-            return "fixed64";
-        case FieldDescriptorProto::TYPE_FIXED32:
-            return "fixed32";
-        case FieldDescriptorProto::TYPE_BOOL:
-            return "bool";
-        case FieldDescriptorProto::TYPE_STRING:
-            return "string";
-        case FieldDescriptorProto::TYPE_GROUP:
-            return "group<unsupported!>";
-        case FieldDescriptorProto::TYPE_MESSAGE:
-            return field.type_name();
-        case FieldDescriptorProto::TYPE_BYTES:
-            return "bytes";
-        case FieldDescriptorProto::TYPE_UINT32:
-            return "uint32";
-        case FieldDescriptorProto::TYPE_ENUM:
-            return field.type_name();
-        case FieldDescriptorProto::TYPE_SFIXED32:
-            return "sfixed32";
-        case FieldDescriptorProto::TYPE_SFIXED64:
-            return "sfixed64";
-        case FieldDescriptorProto::TYPE_SINT32:
-            return "sint32";
-        case FieldDescriptorProto::TYPE_SINT64:
-            return "sint64";
-        default:
-            // won't happen
-            return "void";
-    }
-}
-
-static uint64_t
-get_field_id(const FieldDescriptorProto& field)
-{
-    // Number
-    uint64_t result = (uint32_t)field.number();
-
-    // Type
-    switch (field.type()) {
-        case FieldDescriptorProto::TYPE_DOUBLE:
-            result |= FIELD_TYPE_DOUBLE;
-            break;
-        case FieldDescriptorProto::TYPE_FLOAT:
-            result |= FIELD_TYPE_FLOAT;
-            break;
-        case FieldDescriptorProto::TYPE_INT64:
-            result |= FIELD_TYPE_INT64;
-            break;
-        case FieldDescriptorProto::TYPE_UINT64:
-            result |= FIELD_TYPE_UINT64;
-            break;
-        case FieldDescriptorProto::TYPE_INT32:
-            result |= FIELD_TYPE_INT32;
-            break;
-        case FieldDescriptorProto::TYPE_FIXED64:
-            result |= FIELD_TYPE_FIXED64;
-            break;
-        case FieldDescriptorProto::TYPE_FIXED32:
-            result |= FIELD_TYPE_FIXED32;
-            break;
-        case FieldDescriptorProto::TYPE_BOOL:
-            result |= FIELD_TYPE_BOOL;
-            break;
-        case FieldDescriptorProto::TYPE_STRING:
-            result |= FIELD_TYPE_STRING;
-            break;
-        case FieldDescriptorProto::TYPE_MESSAGE:
-            result |= FIELD_TYPE_OBJECT;
-            break;
-        case FieldDescriptorProto::TYPE_BYTES:
-            result |= FIELD_TYPE_BYTES;
-            break;
-        case FieldDescriptorProto::TYPE_UINT32:
-            result |= FIELD_TYPE_UINT32;
-            break;
-        case FieldDescriptorProto::TYPE_ENUM:
-            result |= FIELD_TYPE_ENUM;
-            break;
-        case FieldDescriptorProto::TYPE_SFIXED32:
-            result |= FIELD_TYPE_SFIXED32;
-            break;
-        case FieldDescriptorProto::TYPE_SFIXED64:
-            result |= FIELD_TYPE_SFIXED64;
-            break;
-        case FieldDescriptorProto::TYPE_SINT32:
-            result |= FIELD_TYPE_SINT32;
-            break;
-        case FieldDescriptorProto::TYPE_SINT64:
-            result |= FIELD_TYPE_SINT64;
-            break;
-        default:
-            ;
-    }
-
-    // Count
-    if (field.options().packed()) {
-        result |= FIELD_COUNT_PACKED;
-    } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
-        result |= FIELD_COUNT_REPEATED;
-    } else {
-        result |= FIELD_COUNT_SINGLE;
-    }
-
-    return result;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
-    string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
-            ? "optional " : "";
-    string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
-            ? "repeated " : "";
-    string proto_type = get_proto_type(field);
-    string packed_comment = field.options().packed()
-            ? " [packed=true]" : "";
-    text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
-            << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
-    text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
-    ios::fmtflags fmt(text.flags());
-    text << setfill('0') << setw(16) << hex << get_field_id(field);
-    text.flags(fmt);
-
-    text << "L;" << endl;
-
-    text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
-    int N;
-    const string indented = indent_more(indent);
-
-    text << indent << "// message " << message.name() << endl;
-    text << indent << "public final class " << message.name() << " {" << endl;
-    text << endl;
-
-    // Enums
-    N = message.enum_type_size();
-    for (int i=0; i<N; i++) {
-        write_enum(text, message.enum_type(i), indented);
-    }
-
-    // Nested classes
-    N = message.nested_type_size();
-    for (int i=0; i<N; i++) {
-        write_message(text, message.nested_type(i), indented);
-    }
-
-    // Fields
-    N = message.field_size();
-    for (int i=0; i<N; i++) {
-        write_field(text, message.field(i), indented);
-    }
-
-    text << indent << "}" << endl;
-    text << endl;
-}
-
-/**
- * Write the contents of a file.
- *
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
-        const string& filename, bool generate_outer,
-        const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
-    stringstream text;
-
-    string const package_name = make_java_package(file_descriptor);
-    string const outer_class_name = make_outer_class_name(file_descriptor);
-
-    text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
-    text << "// source: " << file_descriptor.name() << endl << endl;
-
-    if (package_name.size() > 0) {
-        if (package_name.size() > 0) {
-            text << "package " << package_name << ";" << endl;
-            text << endl;
-        }
-    }
-
-    // This bit of policy is android api rules specific: Raw proto classes
-    // must never be in the API
-    text << "/** @hide */" << endl;
-//    text << "@android.annotation.TestApi" << endl;
-
-    if (generate_outer) {
-        text << "public final class " << outer_class_name << " {" << endl;
-        text << endl;
-    }
-
-    size_t N;
-    const string indented = generate_outer ? indent_more("") : string();
-    
-    N = enums.size();
-    for (size_t i=0; i<N; i++) {
-        write_enum(text, enums[i], indented);
-    }
-
-    N = messages.size();
-    for (size_t i=0; i<N; i++) {
-        write_message(text, messages[i], indented);
-    }
-
-    if (generate_outer) {
-        text << "}" << endl;
-    }
-
-    CodeGeneratorResponse::File* file_response = response->add_file();
-    file_response->set_name(filename);
-    file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class.  Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
-    // If there is anything to put in the outer class file, create one
-    if (file_descriptor.enum_type_size() > 0) {
-        vector<EnumDescriptorProto> enums;
-        int N = file_descriptor.enum_type_size();
-        for (int i=0; i<N; i++) {
-            enums.push_back(file_descriptor.enum_type(i));
-        }
-
-        vector<DescriptorProto> messages;
-
-        write_file(response, file_descriptor,
-                make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
-                true, enums, messages);
-    }
-
-    // For each of the message types, make a file
-    int N = file_descriptor.message_type_size();
-    for (int i=0; i<N; i++) {
-        vector<EnumDescriptorProto> enums;
-
-        vector<DescriptorProto> messages;
-        messages.push_back(file_descriptor.message_type(i));
-
-        write_file(response, file_descriptor,
-                make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
-                false, enums, messages);
-    }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
-    int N;
-
-    vector<EnumDescriptorProto> enums;
-    N = file_descriptor.enum_type_size();
-    for (int i=0; i<N; i++) {
-        enums.push_back(file_descriptor.enum_type(i));
-    }
-
-    vector<DescriptorProto> messages;
-    N = file_descriptor.message_type_size();
-    for (int i=0; i<N; i++) {
-        messages.push_back(file_descriptor.message_type(i));
-    }
-
-    write_file(response, file_descriptor,
-            make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
-            true, enums, messages);
-}
-
-/**
- * Main.
- */
-int
-main(int argc, char const*const* argv)
-{
-    (void)argc;
-    (void)argv;
-
-    GOOGLE_PROTOBUF_VERIFY_VERSION;
-
-    CodeGeneratorRequest request;
-    CodeGeneratorResponse response;
-
-    // Read the request
-    request.ParseFromIstream(&cin);
-
-    // Build the files we need.
-    const int N = request.proto_file_size();
-    for (int i=0; i<N; i++) {
-        const FileDescriptorProto& file_descriptor = request.proto_file(i);
-        if (should_generate_for_file(request, file_descriptor.name())) {
-            if (file_descriptor.options().java_multiple_files()) {
-                write_multiple_files(&response, file_descriptor);
-            } else {
-                write_single_file(&response, file_descriptor);
-            }
-        }
-    }
-
-    // If we had errors, don't write the response. Print the errors and exit.
-    if (ERRORS.HasErrors()) {
-        ERRORS.Print();
-        return 1;
-    }
-
-    // If we didn't have errors, write the response and exit happily.
-    response.SerializeToOstream(&cout);
-    return 0;
-}
diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp
index cc738c4..bd34ab7 100644
--- a/tools/streaming_proto/string_utils.cpp
+++ b/tools/streaming_proto/string_utils.cpp
@@ -3,7 +3,7 @@
 #include <iostream>
 
 namespace android {
-namespace javastream_proto {
+namespace stream_proto {
 
 using namespace std;
 
@@ -89,7 +89,26 @@
     return result;
 }
 
-} // namespace javastream_proto
+vector<string>
+split(const string& str, const char delimiter)
+{
+    vector<string> result;
+    size_t base = 0, found = 0;
+    while (true) {
+        found = str.find_first_of(delimiter, base);
+        if (found != base) {
+            string part = str.substr(base, found - base);
+            if (!part.empty()) {
+                result.push_back(part);
+            }
+        }
+        if (found == str.npos) break;
+        base = found + 1;
+    }
+    return result;
+}
+
+} // namespace stream_proto
 } // namespace android
 
 
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
index ffe83ca..03284d1 100644
--- a/tools/streaming_proto/string_utils.h
+++ b/tools/streaming_proto/string_utils.h
@@ -1,7 +1,8 @@
 #include <string>
+#include <vector>
 
 namespace android {
-namespace javastream_proto {
+namespace stream_proto {
 
 using namespace std;
 
@@ -26,7 +27,11 @@
  */
 string replace_string(const string& str, const char replace, const char with);
 
+/**
+ * Split a string to parts by delimiter.
+ */
+vector<string> split(const string& str, const char delimiter);
 
-} // namespace javastream_proto
+} // namespace stream_proto
 } // namespace android
 
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index a3a1054..551e4df 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -62,6 +62,8 @@
 
     WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
 
+    List<WifiConfiguration> getAllMatchingWifiConfigs(in ScanResult scanResult);
+
     List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
 
     int addOrUpdateNetwork(in WifiConfiguration config);
@@ -126,8 +128,6 @@
 
     void releaseMulticastLock();
 
-    void setWifiApEnabled(in WifiConfiguration wifiConfig, boolean enable);
-
     void updateInterfaceIpState(String ifaceName, int mode);
 
     boolean startSoftAp(in WifiConfiguration wifiConfig);
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a145327..6438631 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -790,6 +790,28 @@
 
     /**
      * @hide
+     * Returns true if this WiFi config is for an open network.
+     */
+    public boolean isOpenNetwork() {
+        final int cardinality = allowedKeyManagement.cardinality();
+        final boolean hasNoKeyMgmt = cardinality == 0
+                || (cardinality == 1 && allowedKeyManagement.get(KeyMgmt.NONE));
+
+        boolean hasNoWepKeys = true;
+        if (wepKeys != null) {
+            for (int i = 0; i < wepKeys.length; i++) {
+                if (wepKeys[i] != null) {
+                    hasNoWepKeys = false;
+                    break;
+                }
+            }
+        }
+
+        return hasNoKeyMgmt && hasNoWepKeys;
+    }
+
+    /**
+     * @hide
      * Setting this value will force scan results associated with this configuration to
      * be included in the bucket of networks that are externally scored.
      * If not set, associated scan results will be treated as legacy saved networks and
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 9c8ea88..c2959d5 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -68,6 +68,7 @@
  * leaks within the calling process.
  * <p>
  * It deals with several categories of items:
+ * </p>
  * <ul>
  * <li>The list of configured networks. The list can be viewed and updated, and
  * attributes of individual entries can be modified.</li>
@@ -79,9 +80,11 @@
  * <li>It defines the names of various Intent actions that are broadcast upon
  * any sort of change in Wi-Fi state.
  * </ul>
+ * <p>
  * This is the API to use when performing Wi-Fi specific operations. To perform
  * operations that pertain to network connectivity at an abstract level, use
  * {@link android.net.ConnectivityManager}.
+ * </p>
  */
 @SystemService(Context.WIFI_SERVICE)
 public class WifiManager {
@@ -967,8 +970,7 @@
      * <li>allowedGroupCiphers</li>
      * </ul>
      * @return a list of network configurations in the form of a list
-     * of {@link WifiConfiguration} objects. Upon failure to fetch or
-     * when Wi-Fi is turned off, it can be null.
+     * of {@link WifiConfiguration} objects.
      */
     public List<WifiConfiguration> getConfiguredNetworks() {
         try {
@@ -1030,6 +1032,26 @@
     }
 
     /**
+     * Return all matching WifiConfigurations for this ScanResult.
+     *
+     * An empty list will be returned when no configurations are installed or if no configurations
+     * match the ScanResult.
+     *
+     * @param scanResult scanResult that represents the BSSID
+     * @return A list of {@link WifiConfiguration}
+     * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
+     * @hide
+     */
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+        try {
+            return mService.getAllMatchingWifiConfigs(scanResult);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
      * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
      *
      * An empty list will be returned if no match is found.
@@ -1556,7 +1578,21 @@
      * Request a scan for access points. Returns immediately. The availability
      * of the results is made known later by means of an asynchronous event sent
      * on completion of the scan.
-     * @return {@code true} if the operation succeeded, i.e., the scan was initiated
+     * <p>
+     * To initiate a Wi-Fi scan, declare the
+     * {@link android.Manifest.permission#CHANGE_WIFI_STATE}
+     * permission in the manifest, and perform these steps:
+     * </p>
+     * <ol style="1">
+     * <li>Invoke the following method:
+     * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).startScan()}</li>
+     * <li>
+     * Register a BroadcastReceiver to listen to
+     * {@code SCAN_RESULTS_AVAILABLE_ACTION}.</li>
+     * <li>When a broadcast is received, call:
+     * {@code ((WifiManager) getSystemService(WIFI_SERVICE)).getScanResults()}</li>
+     * </ol>
+     * @return {@code true} if the operation succeeded, i.e., the scan was initiated.
      */
     public boolean startScan() {
         return startScan(null);
@@ -1858,32 +1894,6 @@
     }
 
     /**
-     * This call is deprecated and removed.  It is no longer used to
-     * start WiFi Tethering.  Please use {@link ConnectivityManager#startTethering(int, boolean,
-     * ConnectivityManager#OnStartTetheringCallback)} if
-     * the caller has proper permissions.  Callers can also use the LocalOnlyHotspot feature for a
-     * hotspot capable of communicating with co-located devices {@link
-     * WifiManager#startLocalOnlyHotspot(LocalOnlyHotspotCallback)}.
-     *
-     * @param wifiConfig SSID, security and channel details as
-     *        part of WifiConfiguration
-     * @return {@code false}
-     *
-     * @hide
-     * @deprecated This API is nolonger supported.
-     * @removed
-     */
-    @SystemApi
-    @Deprecated
-    @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
-    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
-        String packageName = mContext.getOpPackageName();
-
-        Log.w(TAG, packageName + " attempted call to setWifiApEnabled: enabled = " + enabled);
-        return false;
-    }
-
-    /**
      * Call allowing ConnectivityService to update WifiService with interface mode changes.
      *
      * The possible modes include: {@link IFACE_IP_MODE_TETHERED},
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index f47d5ca..e3752ac 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -1105,8 +1105,6 @@
     private static final int BASE = Protocol.BASE_WIFI_SCANNER;
 
     /** @hide */
-    public static final int CMD_SCAN                        = BASE + 0;
-    /** @hide */
     public static final int CMD_START_BACKGROUND_SCAN       = BASE + 2;
     /** @hide */
     public static final int CMD_STOP_BACKGROUND_SCAN        = BASE + 3;
@@ -1115,20 +1113,10 @@
     /** @hide */
     public static final int CMD_SCAN_RESULT                 = BASE + 5;
     /** @hide */
-    public static final int CMD_AP_FOUND                    = BASE + 9;
-    /** @hide */
-    public static final int CMD_AP_LOST                     = BASE + 10;
-    /** @hide */
-    public static final int CMD_WIFI_CHANGE_DETECTED        = BASE + 15;
-    /** @hide */
-    public static final int CMD_WIFI_CHANGES_STABILIZED     = BASE + 16;
-    /** @hide */
     public static final int CMD_OP_SUCCEEDED                = BASE + 17;
     /** @hide */
     public static final int CMD_OP_FAILED                   = BASE + 18;
     /** @hide */
-    public static final int CMD_PERIOD_CHANGED              = BASE + 19;
-    /** @hide */
     public static final int CMD_FULL_SCAN_RESULT            = BASE + 20;
     /** @hide */
     public static final int CMD_START_SINGLE_SCAN           = BASE + 21;
@@ -1359,25 +1347,6 @@
                     ScanResult result = (ScanResult) msg.obj;
                     ((ScanListener) listener).onFullResult(result);
                     return;
-                case CMD_PERIOD_CHANGED:
-                    ((ScanListener) listener).onPeriodChanged(msg.arg1);
-                    return;
-                case CMD_AP_FOUND:
-                    ((BssidListener) listener).onFound(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
-                case CMD_AP_LOST:
-                    ((BssidListener) listener).onLost(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
-                case CMD_WIFI_CHANGE_DETECTED:
-                    ((WifiChangeListener) listener).onChanging(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                   return;
-                case CMD_WIFI_CHANGES_STABILIZED:
-                    ((WifiChangeListener) listener).onQuiescence(
-                            ((ParcelableScanResults) msg.obj).getResults());
-                    return;
                 case CMD_SINGLE_SCAN_COMPLETED:
                     if (DBG) Log.d(TAG, "removing listener for single scan");
                     removeListener(msg.arg2);
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 357f76e..9f73622 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.NetworkSpecifier;
-import android.net.wifi.RttManager;
 import android.util.Log;
 
 import dalvik.system.CloseGuard;
@@ -224,37 +223,6 @@
     }
 
     /**
-     * Start a ranging operation with the specified peers. The peer IDs are obtained from an
-     * {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
-     * byte[], java.util.List)} or
-     * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
-     * byte[])} operation - can
-     * only range devices which are part of an ongoing discovery session.
-     *
-     * @param params   RTT parameters - each corresponding to a specific peer ID (the array sizes
-     *                 must be identical). The
-     *                 {@link android.net.wifi.RttManager.RttParams#bssid} member must be set to
-     *                 a peer ID - not to a MAC address.
-     * @param listener The listener to receive the results of the ranging session.
-     * @hide
-     * [TODO: b/28847998 - track RTT API & visilibity]
-     */
-    public void startRanging(RttManager.RttParams[] params, RttManager.RttListener listener) {
-        if (mTerminated) {
-            Log.w(TAG, "startRanging: called on terminated session");
-            return;
-        }
-
-        WifiAwareManager mgr = mMgr.get();
-        if (mgr == null) {
-            Log.w(TAG, "startRanging: called post GC on WifiAwareManager");
-            return;
-        }
-
-        mgr.startRanging(mClientId, mSessionId, params, listener);
-    }
-
-    /**
      * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
      * an unencrypted WiFi Aware connection (link) to the specified peer. The
      * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl
index 30dd64d..b646567 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl
@@ -17,7 +17,6 @@
 package android.net.wifi.aware;
 
 import android.net.wifi.aware.ConfigRequest;
-import android.net.wifi.RttManager;
 
 /**
  * Callback interface that WifiAwareManager implements
@@ -29,8 +28,4 @@
     void onConnectSuccess(int clientId);
     void onConnectFail(int reason);
     void onIdentityChanged(in byte[] mac);
-
-    void onRangingSuccess(int rangingId, in RttManager.ParcelableRttResults results);
-    void onRangingFailure(int rangingId, int reason, in String description);
-    void onRangingAborted(int rangingId);
 }
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl
new file mode 100644
index 0000000..0e7289c
--- /dev/null
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareMacAddressProvider.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net.wifi.aware;
+
+/**
+ * Callback for IWifiAwareManager.getMacAddressFromPeerHandle
+ *
+ * @hide
+ */
+oneway interface IWifiAwareMacAddressProvider
+{
+    void macAddress(in Map peerIdToMacMap);
+}
diff --git a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
index 0f4910f..bad5ce2 100644
--- a/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
+++ b/wifi/java/android/net/wifi/aware/IWifiAwareManager.aidl
@@ -21,10 +21,10 @@
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.Characteristics;
-import android.net.wifi.RttManager;
 
 /**
  * Interface that WifiAwareService implements
@@ -53,5 +53,7 @@
     void sendMessage(int clientId, int discoverySessionId, int peerId, in byte[] message,
         int messageId, int retryCount);
     void terminateSession(int clientId, int discoverySessionId);
-    int startRanging(int clientId, int discoverySessionId, in RttManager.ParcelableRttParams parms);
+
+    // internal APIs: intended to be used between System Services (restricted permissions)
+    void requestMacAddresses(int uid, in List peerIds, in IWifiAwareMacAddressProvider callback);
 }
diff --git a/wifi/java/android/net/wifi/aware/PeerHandle.java b/wifi/java/android/net/wifi/aware/PeerHandle.java
index cd45c52..1b0aba1 100644
--- a/wifi/java/android/net/wifi/aware/PeerHandle.java
+++ b/wifi/java/android/net/wifi/aware/PeerHandle.java
@@ -32,4 +32,24 @@
 
     /** @hide */
     public int peerId;
+
+    /** @hide RTT_API */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PeerHandle)) {
+            return false;
+        }
+
+        return peerId == ((PeerHandle) o).peerId;
+    }
+
+    /** @hide RTT_API */
+    @Override
+    public int hashCode() {
+        return peerId;
+    }
 }
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index df0d9d2..ed6804d 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -26,7 +26,6 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
-import android.net.wifi.RttManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -35,9 +34,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
 
 import libcore.util.HexEncoding;
 
@@ -45,7 +41,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.BufferOverflowException;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -172,9 +167,6 @@
 
     private final Object mLock = new Object(); // lock access to the following vars
 
-    @GuardedBy("mLock")
-    private SparseArray<RttManager.RttListener> mRangingListeners = new SparseArray<>();
-
     /** @hide */
     public WifiAwareManager(Context context, IWifiAwareManager service) {
         mContext = context;
@@ -401,27 +393,6 @@
     }
 
     /** @hide */
-    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
-                             RttManager.RttListener listener) {
-        if (VDBG) {
-            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
-                    + "params=" + Arrays.toString(params) + ", listener=" + listener);
-        }
-
-        int rangingKey = 0;
-        try {
-            rangingKey = mService.startRanging(clientId, sessionId,
-                    new RttManager.ParcelableRttParams(params));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        synchronized (mLock) {
-            mRangingListeners.put(rangingKey, listener);
-        }
-    }
-
-    /** @hide */
     public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
             PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
         if (VDBG) {
@@ -500,29 +471,12 @@
         private static final int CALLBACK_CONNECT_SUCCESS = 0;
         private static final int CALLBACK_CONNECT_FAIL = 1;
         private static final int CALLBACK_IDENTITY_CHANGED = 2;
-        private static final int CALLBACK_RANGING_SUCCESS = 3;
-        private static final int CALLBACK_RANGING_FAILURE = 4;
-        private static final int CALLBACK_RANGING_ABORTED = 5;
 
         private final Handler mHandler;
         private final WeakReference<WifiAwareManager> mAwareManager;
         private final Binder mBinder;
         private final Looper mLooper;
 
-        RttManager.RttListener getAndRemoveRangingListener(int rangingId) {
-            WifiAwareManager mgr = mAwareManager.get();
-            if (mgr == null) {
-                Log.w(TAG, "getAndRemoveRangingListener: called post GC");
-                return null;
-            }
-
-            synchronized (mgr.mLock) {
-                RttManager.RttListener listener = mgr.mRangingListeners.get(rangingId);
-                mgr.mRangingListeners.delete(rangingId);
-                return listener;
-            }
-        }
-
         /**
          * Constructs a {@link AttachCallback} using the specified looper.
          * All callbacks will delivered on the thread of the specified looper.
@@ -567,37 +521,6 @@
                                 identityChangedListener.onIdentityChanged((byte[]) msg.obj);
                             }
                             break;
-                        case CALLBACK_RANGING_SUCCESS: {
-                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
-                            if (listener == null) {
-                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
-                                        + ": no listener registered (anymore)");
-                            } else {
-                                listener.onSuccess(
-                                        ((RttManager.ParcelableRttResults) msg.obj).mResults);
-                            }
-                            break;
-                        }
-                        case CALLBACK_RANGING_FAILURE: {
-                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
-                            if (listener == null) {
-                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
-                                        + ": no listener registered (anymore)");
-                            } else {
-                                listener.onFailure(msg.arg2, (String) msg.obj);
-                            }
-                            break;
-                        }
-                        case CALLBACK_RANGING_ABORTED: {
-                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
-                            if (listener == null) {
-                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
-                                        + ": no listener registered (anymore)");
-                            } else {
-                                listener.onAborted();
-                            }
-                            break;
-                        }
                     }
                 }
             };
@@ -629,43 +552,6 @@
             msg.obj = mac;
             mHandler.sendMessage(msg);
         }
-
-        @Override
-        public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
-            if (VDBG) {
-                Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
-            }
-
-            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_SUCCESS);
-            msg.arg1 = rangingId;
-            msg.obj = results;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onRangingFailure(int rangingId, int reason, String description) {
-            if (VDBG) {
-                Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
-                        + ", description=" + description);
-            }
-
-            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_FAILURE);
-            msg.arg1 = rangingId;
-            msg.arg2 = reason;
-            msg.obj = description;
-            mHandler.sendMessage(msg);
-
-        }
-
-        @Override
-        public void onRangingAborted(int rangingId) {
-            if (VDBG) Log.v(TAG, "onRangingAborted: rangingId=" + rangingId);
-
-            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_ABORTED);
-            msg.arg1 = rangingId;
-            mHandler.sendMessage(msg);
-
-        }
     }
 
     private static class WifiAwareDiscoverySessionCallbackProxy extends
diff --git a/wifi/java/android/net/wifi/rtt/IRttCallback.aidl b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
new file mode 100644
index 0000000..578dd1e
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/IRttCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.net.wifi.rtt;
+
+import android.net.wifi.rtt.RangingResult;
+
+/**
+ * Interface for RTT result callback.
+ *
+ * @hide
+ */
+oneway interface IRttCallback
+{
+    /**
+     * Service to manager callback indicating failure.
+     */
+    void onRangingFailure(int status);
+
+    /**
+     * Service to manager callback indicating success and providing results.
+     */
+    void onRangingResults(in List<RangingResult> results);
+}
diff --git a/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
new file mode 100644
index 0000000..e1ad783
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.net.wifi.rtt;
+
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.RangingRequest;
+
+/**
+ * @hide
+ */
+interface IWifiRttManager
+{
+    boolean isAvailable();
+    void startRanging(in IBinder binder, in String callingPackage, in RangingRequest request,
+            in IRttCallback callback);
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.aidl b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl
new file mode 100644
index 0000000..8053c94
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.net.wifi.rtt;
+
+parcelable RangingRequest;
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
new file mode 100644
index 0000000..a396281
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -0,0 +1,392 @@
+/*
+ * 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.net.wifi.rtt;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareManager;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import libcore.util.HexEncoding;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * Defines the ranging request to other devices. The ranging request is built using
+ * {@link RangingRequest.Builder}.
+ * A ranging request is executed using
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}.
+ * <p>
+ * The ranging request is a batch request - specifying a set of devices (specified using
+ * {@link RangingRequest.Builder#addAccessPoint(ScanResult)} and
+ * {@link RangingRequest.Builder#addAccessPoints(List)}).
+ *
+ * @hide RTT_API
+ */
+public final class RangingRequest implements Parcelable {
+    private static final int MAX_PEERS = 10;
+
+    /**
+     * Returns the maximum number of peers to range which can be specified in a single {@code
+     * RangingRequest}. The limit applies no matter how the peers are added to the request, e.g.
+     * through {@link RangingRequest.Builder#addAccessPoint(ScanResult)} or
+     * {@link RangingRequest.Builder#addAccessPoints(List)}.
+     *
+     * @return Maximum number of peers.
+     */
+    public static int getMaxPeers() {
+        return MAX_PEERS;
+    }
+
+    /** @hide */
+    public final List<RttPeer> mRttPeers;
+
+    /** @hide */
+    private RangingRequest(List<RttPeer> rttPeers) {
+        mRttPeers = rttPeers;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeList(mRttPeers);
+    }
+
+    public static final Creator<RangingRequest> CREATOR = new Creator<RangingRequest>() {
+        @Override
+        public RangingRequest[] newArray(int size) {
+            return new RangingRequest[size];
+        }
+
+        @Override
+        public RangingRequest createFromParcel(Parcel in) {
+            return new RangingRequest(in.readArrayList(null));
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString() {
+        StringJoiner sj = new StringJoiner(", ", "RangingRequest: mRttPeers=[", ",");
+        for (RttPeer rp : mRttPeers) {
+            sj.add(rp.toString());
+        }
+        return sj.toString();
+    }
+
+    /** @hide */
+    public void enforceValidity(boolean awareSupported) {
+        if (mRttPeers.size() > MAX_PEERS) {
+            throw new IllegalArgumentException(
+                    "Ranging to too many peers requested. Use getMaxPeers() API to get limit.");
+        }
+
+        for (RttPeer peer: mRttPeers) {
+            if (peer instanceof RttPeerAp) {
+                RttPeerAp apPeer = (RttPeerAp) peer;
+                if (apPeer.scanResult == null || apPeer.scanResult.BSSID == null) {
+                    throw new IllegalArgumentException("Invalid AP peer specification");
+                }
+            } else if (peer instanceof RttPeerAware) {
+                if (!awareSupported) {
+                    throw new IllegalArgumentException(
+                            "Request contains Aware peers - but Aware isn't supported on this "
+                                    + "device");
+                }
+
+                RttPeerAware awarePeer = (RttPeerAware) peer;
+                if (awarePeer.peerMacAddress == null && awarePeer.peerHandle == null) {
+                    throw new IllegalArgumentException("Invalid Aware peer specification");
+                }
+            } else {
+                throw new IllegalArgumentException(
+                        "Request contains unknown peer specification types");
+            }
+        }
+    }
+
+    /**
+     * Builder class used to construct {@link RangingRequest} objects.
+     */
+    public static final class Builder {
+        private List<RttPeer> mRttPeers = new ArrayList<>();
+
+        /**
+         * Add the device specified by the {@link ScanResult} to the list of devices with
+         * which to measure range. The total number of results added to a request cannot exceed the
+         * limit specified by {@link #getMaxPeers()}.
+         *
+         * @param apInfo Information of an Access Point (AP) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addAccessPoint(ScanResult apInfo) {
+            if (apInfo == null) {
+                throw new IllegalArgumentException("Null ScanResult!");
+            }
+            mRttPeers.add(new RttPeerAp(apInfo));
+            return this;
+        }
+
+        /**
+         * Add the devices specified by the {@link ScanResult}s to the list of devices with
+         * which to measure range. The total number of results added to a request cannot exceed the
+         * limit specified by {@link #getMaxPeers()}.
+         *
+         * @param apInfos Information of an Access Points (APs) obtained in a Scan Result.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addAccessPoints(List<ScanResult> apInfos) {
+            if (apInfos == null) {
+                throw new IllegalArgumentException("Null list of ScanResults!");
+            }
+            for (ScanResult scanResult : apInfos) {
+                addAccessPoint(scanResult);
+            }
+            return this;
+        }
+
+        /**
+         * Add the device specified by the {@code peerMacAddress} to the list of devices with
+         * which to measure range.
+         *
+         * The MAC address may be obtained out-of-band from a peer Wi-Fi Aware device. A Wi-Fi
+         * Aware device may obtain its MAC address using the {@link IdentityChangedListener}
+         * provided to
+         * {@link WifiAwareManager#attach(AttachCallback, IdentityChangedListener, Handler)}.
+         *
+         * * Note: in order to use this API the device must support Wi-Fi Aware
+         * {@link android.net.wifi.aware}.
+         *
+         * @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addWifiAwarePeer(byte[] peerMacAddress) {
+            mRttPeers.add(new RttPeerAware(peerMacAddress));
+            return this;
+        }
+
+        /**
+         * Add a device specified by a {@link PeerHandle} to the list of devices with which to
+         * measure range.
+         *
+         * The {@link PeerHandle} may be obtained as part of the Wi-Fi Aware discovery process. E.g.
+         * using {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle, byte[], List)}.
+         *
+         * Note: in order to use this API the device must support Wi-Fi Aware
+         * {@link android.net.wifi.aware}.
+         *
+         * @param peerHandle The peer handler of the peer Wi-Fi Aware device.
+         * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder addWifiAwarePeer(PeerHandle peerHandle) {
+            mRttPeers.add(new RttPeerAware(peerHandle));
+            return this;
+        }
+
+        /**
+         * Build {@link RangingRequest} given the current configurations made on the
+         * builder.
+         */
+        public RangingRequest build() {
+            return new RangingRequest(mRttPeers);
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof RangingRequest)) {
+            return false;
+        }
+
+        RangingRequest lhs = (RangingRequest) o;
+
+        return mRttPeers.size() == lhs.mRttPeers.size() && mRttPeers.containsAll(lhs.mRttPeers);
+    }
+
+    @Override
+    public int hashCode() {
+        return mRttPeers.hashCode();
+    }
+
+    /** @hide */
+    public interface RttPeer {
+        // empty (marker interface)
+    }
+
+    /** @hide */
+    public static class RttPeerAp implements RttPeer, Parcelable {
+        public final ScanResult scanResult;
+
+        public RttPeerAp(ScanResult scanResult) {
+            this.scanResult = scanResult;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            scanResult.writeToParcel(dest, flags);
+        }
+
+        public static final Creator<RttPeerAp> CREATOR = new Creator<RttPeerAp>() {
+            @Override
+            public RttPeerAp[] newArray(int size) {
+                return new RttPeerAp[size];
+            }
+
+            @Override
+            public RttPeerAp createFromParcel(Parcel in) {
+                return new RttPeerAp(ScanResult.CREATOR.createFromParcel(in));
+            }
+        };
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttPeerAp: scanResult=").append(
+                    scanResult.toString()).toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof RttPeerAp)) {
+                return false;
+            }
+
+            RttPeerAp lhs = (RttPeerAp) o;
+
+            // Note: the only thing which matters for the request identity is the BSSID of the AP
+            return TextUtils.equals(scanResult.BSSID, lhs.scanResult.BSSID);
+        }
+
+        @Override
+        public int hashCode() {
+            return scanResult.hashCode();
+        }
+    }
+
+    /** @hide */
+    public static class RttPeerAware implements RttPeer, Parcelable {
+        public PeerHandle peerHandle;
+        public byte[] peerMacAddress;
+
+        public RttPeerAware(PeerHandle peerHandle) {
+            if (peerHandle == null) {
+                throw new IllegalArgumentException("Null peerHandle");
+            }
+            this.peerHandle = peerHandle;
+            peerMacAddress = null;
+        }
+
+        public RttPeerAware(byte[] peerMacAddress) {
+            if (peerMacAddress == null) {
+                throw new IllegalArgumentException("Null peerMacAddress");
+            }
+
+            this.peerMacAddress = peerMacAddress;
+            peerHandle = null;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            if (peerHandle == null) {
+                dest.writeBoolean(false);
+                dest.writeByteArray(peerMacAddress);
+            } else {
+                dest.writeBoolean(true);
+                dest.writeInt(peerHandle.peerId);
+            }
+        }
+
+        public static final Creator<RttPeerAware> CREATOR = new Creator<RttPeerAware>() {
+            @Override
+            public RttPeerAware[] newArray(int size) {
+                return new RttPeerAware[size];
+            }
+
+            @Override
+            public RttPeerAware createFromParcel(Parcel in) {
+                boolean peerHandleAvail = in.readBoolean();
+                if (peerHandleAvail) {
+                    return new RttPeerAware(new PeerHandle(in.readInt()));
+                } else {
+                    return new RttPeerAware(in.createByteArray());
+                }
+            }
+        };
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttPeerAware: peerHandle=").append(
+                    peerHandle == null ? "<null>" : Integer.toString(peerHandle.peerId)).append(
+                    ", peerMacAddress=").append(peerMacAddress == null ? "<null>"
+                    : new String(HexEncoding.encode(peerMacAddress))).toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (!(o instanceof RttPeerAware)) {
+                return false;
+            }
+
+            RttPeerAware lhs = (RttPeerAware) o;
+
+            return Objects.equals(peerHandle, lhs.peerHandle) && Arrays.equals(peerMacAddress,
+                    lhs.peerMacAddress);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(peerHandle.peerId, peerMacAddress);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.aidl b/wifi/java/android/net/wifi/rtt/RangingResult.aidl
new file mode 100644
index 0000000..ae295a6
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.net.wifi.rtt;
+
+parcelable RangingResult;
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
new file mode 100644
index 0000000..93e52ae
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -0,0 +1,273 @@
+/*
+ * 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.net.wifi.rtt;
+
+import android.annotation.IntDef;
+import android.net.wifi.aware.PeerHandle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import libcore.util.HexEncoding;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Ranging result for a request started by
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. Results are
+ * returned in {@link RangingResultCallback#onRangingResults(List)}.
+ * <p>
+ * A ranging result is the distance measurement result for a single device specified in the
+ * {@link RangingRequest}.
+ *
+ * @hide RTT_API
+ */
+public final class RangingResult implements Parcelable {
+    private static final String TAG = "RangingResult";
+
+    /** @hide */
+    @IntDef({STATUS_SUCCESS, STATUS_FAIL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangeResultStatus {
+    }
+
+    /**
+     * Individual range request status, {@link #getStatus()}. Indicates ranging operation was
+     * successful and distance value is valid.
+     */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * Individual range request status, {@link #getStatus()}. Indicates ranging operation failed
+     * and the distance value is invalid.
+     */
+    public static final int STATUS_FAIL = 1;
+
+    private final int mStatus;
+    private final byte[] mMac;
+    private final PeerHandle mPeerHandle;
+    private final int mDistanceMm;
+    private final int mDistanceStdDevMm;
+    private final int mRssi;
+    private final long mTimestamp;
+
+    /** @hide */
+    public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm,
+            int distanceStdDevMm, int rssi, long timestamp) {
+        mStatus = status;
+        mMac = mac;
+        mPeerHandle = null;
+        mDistanceMm = distanceMm;
+        mDistanceStdDevMm = distanceStdDevMm;
+        mRssi = rssi;
+        mTimestamp = timestamp;
+    }
+
+    /** @hide */
+    public RangingResult(@RangeResultStatus int status, PeerHandle peerHandle, int distanceMm,
+            int distanceStdDevMm, int rssi, long timestamp) {
+        mStatus = status;
+        mMac = null;
+        mPeerHandle = peerHandle;
+        mDistanceMm = distanceMm;
+        mDistanceStdDevMm = distanceStdDevMm;
+        mRssi = rssi;
+        mTimestamp = timestamp;
+    }
+
+    /**
+     * @return The status of ranging measurement: {@link #STATUS_SUCCESS} in case of success, and
+     * {@link #STATUS_FAIL} in case of failure.
+     */
+    @RangeResultStatus
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * @return The MAC address of the device whose range measurement was requested. Will correspond
+     * to the MAC address of the device in the {@link RangingRequest}.
+     * <p>
+     * Will return a {@code null} for results corresponding to requests issued using a {@code
+     * PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
+     */
+    public byte[] getMacAddress() {
+        return mMac;
+    }
+
+    /**
+     * @return The PeerHandle of the device whose reange measurement was requested. Will correspond
+     * to the PeerHandle of the devices requested using
+     * {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)}.
+     * <p>
+     * Will return a {@code null} for results corresponding to requests issued using a MAC address.
+     */
+    public PeerHandle getPeerHandle() {
+        return mPeerHandle;
+    }
+
+    /**
+     * @return The distance (in mm) to the device specified by {@link #getMacAddress()} or
+     * {@link #getPeerHandle()}.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public int getDistanceMm() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getDistanceMm(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
+        return mDistanceMm;
+    }
+
+    /**
+     * @return The standard deviation of the measured distance (in mm) to the device specified by
+     * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated
+     * over the measurements executed in a single RTT burst.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public int getDistanceStdDevMm() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getDistanceStdDevMm(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
+        return mDistanceStdDevMm;
+    }
+
+    /**
+     * @return The average RSSI (in units of -0.5dB) observed during the RTT measurement.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public int getRssi() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getRssi(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
+        return mRssi;
+    }
+
+    /**
+     * @return The timestamp, in us since boot, at which the ranging operation was performed.
+     * <p>
+     * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an
+     * exception.
+     */
+    public long getRangingTimestampUs() {
+        if (mStatus != STATUS_SUCCESS) {
+            throw new IllegalStateException(
+                    "getRangingTimestamp(): invoked on an invalid result: getStatus()=" + mStatus);
+        }
+        return mTimestamp;
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mStatus);
+        dest.writeByteArray(mMac);
+        if (mPeerHandle == null) {
+            dest.writeBoolean(false);
+        } else {
+            dest.writeBoolean(true);
+            dest.writeInt(mPeerHandle.peerId);
+        }
+        dest.writeInt(mDistanceMm);
+        dest.writeInt(mDistanceStdDevMm);
+        dest.writeInt(mRssi);
+        dest.writeLong(mTimestamp);
+    }
+
+    /** @hide */
+    public static final Creator<RangingResult> CREATOR = new Creator<RangingResult>() {
+        @Override
+        public RangingResult[] newArray(int size) {
+            return new RangingResult[size];
+        }
+
+        @Override
+        public RangingResult createFromParcel(Parcel in) {
+            int status = in.readInt();
+            byte[] mac = in.createByteArray();
+            boolean peerHandlePresent = in.readBoolean();
+            PeerHandle peerHandle = null;
+            if (peerHandlePresent) {
+                peerHandle = new PeerHandle(in.readInt());
+            }
+            int distanceMm = in.readInt();
+            int distanceStdDevMm = in.readInt();
+            int rssi = in.readInt();
+            long timestamp = in.readLong();
+            if (peerHandlePresent) {
+                return new RangingResult(status, peerHandle, distanceMm, distanceStdDevMm, rssi,
+                        timestamp);
+            } else {
+                return new RangingResult(status, mac, distanceMm, distanceStdDevMm, rssi,
+                        timestamp);
+            }
+        }
+    };
+
+    /** @hide */
+    @Override
+    public String toString() {
+        return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
+                mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append(
+                ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(
+                ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append(
+                mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append(
+                mTimestamp).append("]").toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof RangingResult)) {
+            return false;
+        }
+
+        RangingResult lhs = (RangingResult) o;
+
+        return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals(
+                mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
+                && mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
+                && mTimestamp == lhs.mTimestamp;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mStatus, mMac, mPeerHandle, mDistanceMm, mDistanceStdDevMm, mRssi,
+                mTimestamp);
+    }
+}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResultCallback.java b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
new file mode 100644
index 0000000..c8aea3c
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/RangingResultCallback.java
@@ -0,0 +1,72 @@
+/*
+ * 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.net.wifi.rtt;
+
+import android.annotation.IntDef;
+import android.os.Handler;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Base class for ranging result callbacks. Should be extended by applications and set when calling
+ * {@link WifiRttManager#startRanging(RangingRequest, RangingResultCallback, Handler)}. If the
+ * ranging operation fails in whole (not attempted) then {@link #onRangingFailure(int)} will be
+ * called with a failure code. If the ranging operation is performed for each of the requested
+ * peers then the {@link #onRangingResults(List)} will be called with the set of results (@link
+ * {@link RangingResult}, each of which has its own success/failure code
+ * {@link RangingResult#getStatus()}.
+ *
+ * @hide RTT_API
+ */
+public abstract class RangingResultCallback {
+    /** @hide */
+    @IntDef({STATUS_CODE_FAIL, STATUS_CODE_FAIL_RTT_NOT_AVAILABLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangingOperationStatus {
+    }
+
+    /**
+     * A failure code for the whole ranging request operation. Indicates a failure.
+     */
+    public static final int STATUS_CODE_FAIL = 1;
+
+    /**
+     * A failure code for the whole ranging request operation. Indicates that the request failed due
+     * to RTT not being available - e.g. Wi-Fi was disabled. Use the
+     * {@link WifiRttManager#isAvailable()} and {@link WifiRttManager#ACTION_WIFI_RTT_STATE_CHANGED}
+     * to track RTT availability.
+     */
+    public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2;
+
+    /**
+     * Called when a ranging operation failed in whole - i.e. no ranging operation to any of the
+     * devices specified in the request was attempted.
+     *
+     * @param code A status code indicating the type of failure.
+     */
+    public abstract void onRangingFailure(@RangingOperationStatus int code);
+
+    /**
+     * Called when a ranging operation was executed. The list of results corresponds to devices
+     * specified in the ranging request.
+     *
+     * @param results List of range measurements, one per requested device.
+     */
+    public abstract void onRangingResults(List<RangingResult> results);
+}
diff --git a/wifi/java/android/net/wifi/rtt/WifiRttManager.java b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
new file mode 100644
index 0000000..c7c0923
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/WifiRttManager.java
@@ -0,0 +1,137 @@
+package android.net.wifi.rtt;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
+import static android.Manifest.permission.CHANGE_WIFI_STATE;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * This class provides the primary API for measuring distance (range) to other devices using the
+ * IEEE 802.11mc Wi-Fi Round Trip Time (RTT) technology.
+ * <p>
+ * The devices which can be ranged include:
+ * <li>Access Points (APs)
+ * <li>Wi-Fi Aware peers
+ * <p>
+ * Ranging requests are triggered using
+ * {@link #startRanging(RangingRequest, RangingResultCallback, Handler)}. Results (in case of
+ * successful operation) are returned in the {@link RangingResultCallback#onRangingResults(List)}
+ * callback.
+ * <p>
+ *     Wi-Fi RTT may not be usable at some points, e.g. when Wi-Fi is disabled. To validate that
+ *     the functionality is available use the {@link #isAvailable()} function. To track
+ *     changes in RTT usability register for the {@link #ACTION_WIFI_RTT_STATE_CHANGED}
+ *     broadcast. Note that this broadcast is not sticky - you should register for it and then
+ *     check the above API to avoid a race condition.
+ *
+ * @hide RTT_API
+ */
+@SystemService(Context.WIFI_RTT2_SERVICE)
+public class WifiRttManager {
+    private static final String TAG = "WifiRttManager";
+    private static final boolean VDBG = true;
+
+    private final Context mContext;
+    private final IWifiRttManager mService;
+
+    /**
+     * Broadcast intent action to indicate that the state of Wi-Fi RTT availability has changed.
+     * Use the {@link #isAvailable()} to query the current status.
+     * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
+     * the broadcast to check the current state of Wi-Fi RTT.
+     * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
+     * components will be launched.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WIFI_RTT_STATE_CHANGED =
+            "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
+
+    /** @hide */
+    public WifiRttManager(Context context, IWifiRttManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns the current status of RTT API: whether or not RTT is available. To track
+     * changes in the state of RTT API register for the
+     * {@link #ACTION_WIFI_RTT_STATE_CHANGED} broadcast.
+     *
+     * @return A boolean indicating whether the app can use the RTT API at this time (true) or
+     * not (false).
+     */
+    public boolean isAvailable() {
+        try {
+            return mService.isAvailable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Initiate a request to range to a set of devices specified in the {@link RangingRequest}.
+     * Results will be returned in the {@link RangingResultCallback} set of callbacks.
+     *
+     * @param request  A request specifying a set of devices whose distance measurements are
+     *                 requested.
+     * @param callback A callback for the result of the ranging request.
+     * @param handler  The Handler on whose thread to execute the callbacks of the {@code
+     *                 callback} object. If a null is provided then the application's main thread
+     *                 will be used.
+     */
+    @RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATE})
+    public void startRanging(RangingRequest request, RangingResultCallback callback,
+            @Nullable Handler handler) {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: request=" + request + ", callback=" + callback + ", handler="
+                    + handler);
+        }
+
+        Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+        Binder binder = new Binder();
+        try {
+            mService.startRanging(binder, mContext.getOpPackageName(), request,
+                    new RttCallbackProxy(looper, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class RttCallbackProxy extends IRttCallback.Stub {
+        private final Handler mHandler;
+        private final RangingResultCallback mCallback;
+
+        RttCallbackProxy(Looper looper, RangingResultCallback callback) {
+            mHandler = new Handler(looper);
+            mCallback = callback;
+        }
+
+        @Override
+        public void onRangingFailure(int status) throws RemoteException {
+            if (VDBG) Log.v(TAG, "RttCallbackProxy: onRangingFailure: status=" + status);
+            mHandler.post(() -> {
+               mCallback.onRangingFailure(status);
+            });
+        }
+
+        @Override
+        public void onRangingResults(List<RangingResult> results) throws RemoteException {
+            if (VDBG) Log.v(TAG, "RttCallbackProxy: onRanginResults: results=" + results);
+            mHandler.post(() -> {
+               mCallback.onRangingResults(results);
+            });
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/rtt/package.html b/wifi/java/android/net/wifi/rtt/package.html
new file mode 100644
index 0000000..221b94b
--- /dev/null
+++ b/wifi/java/android/net/wifi/rtt/package.html
@@ -0,0 +1,39 @@
+<HTML>
+<BODY>
+<p>Provides classes which allow applications to use Wi-Fi RTT (IEEE 802.11mc) to measure distance
+    to supporting Access Points and peer devices.</p>
+<p>The primary entry point to Wi-Fi RTT capabilities is the
+    {@link android.net.wifi.rtt.WifiRttManager} class, which is acquired by calling
+    {@link android.content.Context#getSystemService(String)
+    Context.getSystemService(Context.WIFI_RTT_SERVICE)}</p>
+
+<p>Some APIs may require the following user permissions:</p>
+<ul>
+    <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
+    <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
+    <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi RTT
+    functionality.
+    If your application only works with Wi-Fi RTT (i.e. it should only be installed on devices which
+    support Wi-Fi RTT), declare so with a <a
+            href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
+        {@code <uses-feature>}</a>
+    element in the manifest file:</p>
+<pre>
+&lt;manifest ...>
+    &lt;uses-feature android:name="android.hardware.wifi.rtt" />
+    ...
+&lt;/manifest>
+</pre>
+<p>Alternatively, if your application does not require Wi-Fi RTT but can take advantage of it if
+    available, you can perform
+    the check at run-time in your code using {@link
+    android.content.pm.PackageManager#hasSystemFeature(String)} with {@link
+    android.content.pm.PackageManager#FEATURE_WIFI_RTT}:</p>
+<pre>
+    getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)
+</pre>
+</BODY>
+</HTML>
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 632cfaf..622dce6 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
 import android.os.Parcel;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
@@ -98,4 +100,73 @@
 
         assertEquals(networkSelectionStatus.isNotRecommended(), copy.isNotRecommended());
     }
+
+    @Test
+    public void testIsOpenNetwork_IsOpen_NullWepKeys() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.wepKeys = null;
+
+        assertTrue(config.isOpenNetwork());
+    }
+
+    @Test
+    public void testIsOpenNetwork_IsOpen_ZeroLengthWepKeysArray() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.wepKeys = new String[0];
+
+        assertTrue(config.isOpenNetwork());
+    }
+
+    @Test
+    public void testIsOpenNetwork_IsOpen_NullWepKeysArray() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.wepKeys = new String[1];
+
+        assertTrue(config.isOpenNetwork());
+    }
+
+    @Test
+    public void testIsOpenNetwork_NotOpen_HasWepKeys() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.wepKeys = new String[] {"test"};
+
+        assertFalse(config.isOpenNetwork());
+    }
+
+    @Test
+    public void testIsOpenNetwork_NotOpen_HasNullWepKeyFollowedByNonNullKey() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.clear();
+        config.wepKeys = new String[] {null, null, "test"};
+
+        assertFalse(config.isOpenNetwork());
+    }
+
+    @Test
+    public void testIsOpenNetwork_NotOpen_HasAuthType() {
+        for (int keyMgmt = 0; keyMgmt < WifiConfiguration.KeyMgmt.strings.length; keyMgmt++) {
+            if (keyMgmt == WifiConfiguration.KeyMgmt.NONE) continue;
+            WifiConfiguration config = new WifiConfiguration();
+            config.allowedKeyManagement.clear();
+            config.allowedKeyManagement.set(keyMgmt);
+            config.wepKeys = null;
+
+            assertFalse("Open network reported when key mgmt was set to "
+                            + WifiConfiguration.KeyMgmt.strings[keyMgmt], config.isOpenNetwork());
+        }
+    }
+
+    @Test
+    public void testIsOpenNetwork_NotOpen_HasAuthTypeNoneAndMore() {
+        WifiConfiguration config = new WifiConfiguration();
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+        config.wepKeys = null;
+
+        assertFalse(config.isOpenNetwork());
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index b235ccc7..ee6f12b 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -777,14 +777,4 @@
         mWifiManager.unregisterLocalOnlyHotspotObserver();
         verify(mWifiService).stopWatchLocalOnlyHotspot();
     }
-
-    /**
-     * Verify that calls to setWifiApEnabled return false.
-     */
-    @Test
-    public void testSetWifiApEnabledReturnsFalse() throws Exception {
-        assertFalse(mWifiManager.setWifiApEnabled(null, true));
-        assertFalse(mWifiManager.setWifiApEnabled(null, false));
-        verify(mWifiService, never()).setWifiApEnabled(any(), anyBoolean());
-    }
 }
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index d9433c5..1aeeee3 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -845,88 +845,6 @@
     }
 
     /*
-     * Ranging tests
-     */
-
-    /**
-     * Validate ranging + success flow: (1) connect, (2) create a (publish) session, (3) start
-     * ranging, (4) ranging success callback, (5) ranging aborted callback ignored (since
-     * listener removed).
-     */
-    @Test
-    public void testRangingCallbacks() throws Exception {
-        final int clientId = 4565;
-        final int sessionId = 123;
-        final int rangingId = 3482;
-        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
-        final PublishConfig publishConfig = new PublishConfig.Builder().build();
-        final RttManager.RttParams rttParams = new RttManager.RttParams();
-        rttParams.deviceType = RttManager.RTT_PEER_NAN;
-        rttParams.bssid = Integer.toString(1234);
-        final RttManager.RttResult rttResults = new RttManager.RttResult();
-        rttResults.distance = 10;
-
-        when(mockAwareService.startRanging(anyInt(), anyInt(), any())).thenReturn(rangingId);
-
-        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
-                mockPublishSession, mockRttListener);
-        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
-                WifiAwareSession.class);
-        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
-                .forClass(IWifiAwareEventCallback.class);
-        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
-                .forClass(IWifiAwareDiscoverySessionCallback.class);
-        ArgumentCaptor<PublishDiscoverySession> publishSession = ArgumentCaptor
-                .forClass(PublishDiscoverySession.class);
-        ArgumentCaptor<RttManager.ParcelableRttParams> rttParamCaptor = ArgumentCaptor
-                .forClass(RttManager.ParcelableRttParams.class);
-        ArgumentCaptor<RttManager.RttResult[]> rttResultsCaptor = ArgumentCaptor
-                .forClass(RttManager.RttResult[].class);
-
-        // (1) connect successfully
-        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
-        inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
-                eq(configRequest), eq(false));
-        clientProxyCallback.getValue().onConnectSuccess(clientId);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
-        WifiAwareSession session = sessionCaptor.getValue();
-
-        // (2) publish successfully
-        session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
-        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
-                sessionProxyCallback.capture());
-        sessionProxyCallback.getValue().onSessionStarted(sessionId);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
-
-        // (3) start ranging
-        publishSession.getValue().startRanging(new RttManager.RttParams[]{rttParams},
-                mockRttListener);
-        inOrder.verify(mockAwareService).startRanging(eq(clientId), eq(sessionId),
-                rttParamCaptor.capture());
-        collector.checkThat("RttParams.deviceType", rttParams.deviceType,
-                equalTo(rttParamCaptor.getValue().mParams[0].deviceType));
-        collector.checkThat("RttParams.bssid", rttParams.bssid,
-                equalTo(rttParamCaptor.getValue().mParams[0].bssid));
-
-        // (4) ranging success callback
-        clientProxyCallback.getValue().onRangingSuccess(rangingId,
-                new RttManager.ParcelableRttResults(new RttManager.RttResult[] { rttResults }));
-        mMockLooper.dispatchAll();
-        inOrder.verify(mockRttListener).onSuccess(rttResultsCaptor.capture());
-        collector.checkThat("RttResult.distance", rttResults.distance,
-                equalTo(rttResultsCaptor.getValue()[0].distance));
-
-        // (5) ranging aborted callback (should be ignored since listener cleared on first callback)
-        clientProxyCallback.getValue().onRangingAborted(rangingId);
-        mMockLooper.dispatchAll();
-
-        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
-                mockPublishSession, mockRttListener);
-    }
-
-    /*
      * Data-path tests
      */
 
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
new file mode 100644
index 0000000..33bd982
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.net.wifi.rtt;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.ScanResult;
+import android.net.wifi.aware.PeerHandle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test harness for WifiRttManager class.
+ */
+@SmallTest
+public class WifiRttManagerTest {
+    private WifiRttManager mDut;
+    private TestLooper mMockLooper;
+    private Handler mMockLooperHandler;
+
+    private final String packageName = "some.package.name.for.rtt.app";
+
+    @Mock
+    public Context mockContext;
+
+    @Mock
+    public IWifiRttManager mockRttService;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiRttManager(mockContext, mockRttService);
+        mMockLooper = new TestLooper();
+        mMockLooperHandler = new Handler(mMockLooper.getLooper());
+
+        when(mockContext.getOpPackageName()).thenReturn(packageName);
+    }
+
+    /**
+     * Validate ranging call flow with succesful results.
+     */
+    @Test
+    public void testRangeSuccess() throws Exception {
+        RangingRequest request = new RangingRequest.Builder().build();
+        List<RangingResult> results = new ArrayList<>();
+        results.add(new RangingResult(RangingResult.STATUS_SUCCESS, (byte[]) null, 15, 5, 10, 666));
+        RangingResultCallback callbackMock = mock(RangingResultCallback.class);
+        ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
+
+        // verify ranging request passed to service
+        mDut.startRanging(request, callbackMock, mMockLooperHandler);
+        verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request),
+                callbackCaptor.capture());
+
+        // service calls back with success
+        callbackCaptor.getValue().onRangingResults(results);
+        mMockLooper.dispatchAll();
+        verify(callbackMock).onRangingResults(results);
+
+        verifyNoMoreInteractions(mockRttService, callbackMock);
+    }
+
+    /**
+     * Validate ranging call flow which failed.
+     */
+    @Test
+    public void testRangeFail() throws Exception {
+        int failureCode = RangingResultCallback.STATUS_CODE_FAIL;
+
+        RangingRequest request = new RangingRequest.Builder().build();
+        RangingResultCallback callbackMock = mock(RangingResultCallback.class);
+        ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
+
+        // verify ranging request passed to service
+        mDut.startRanging(request, callbackMock, mMockLooperHandler);
+        verify(mockRttService).startRanging(any(IBinder.class), eq(packageName), eq(request),
+                callbackCaptor.capture());
+
+        // service calls back with failure code
+        callbackCaptor.getValue().onRangingFailure(failureCode);
+        mMockLooper.dispatchAll();
+        verify(callbackMock).onRangingFailure(failureCode);
+
+        verifyNoMoreInteractions(mockRttService, callbackMock);
+    }
+
+    /**
+     * Validate that RangingRequest parcel works (produces same object on write/read).
+     */
+    @Test
+    public void testRangingRequestParcel() {
+        // Note: not validating parcel code of ScanResult (assumed to work)
+        ScanResult scanResult1 = new ScanResult();
+        scanResult1.BSSID = "00:01:02:03:04:05";
+        ScanResult scanResult2 = new ScanResult();
+        scanResult2.BSSID = "06:07:08:09:0A:0B";
+        ScanResult scanResult3 = new ScanResult();
+        scanResult3.BSSID = "AA:BB:CC:DD:EE:FF";
+        List<ScanResult> scanResults2and3 = new ArrayList<>(2);
+        scanResults2and3.add(scanResult2);
+        scanResults2and3.add(scanResult3);
+        final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+        PeerHandle peerHandle1 = new PeerHandle(12);
+
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAccessPoint(scanResult1);
+        builder.addAccessPoints(scanResults2and3);
+        builder.addWifiAwarePeer(mac1);
+        builder.addWifiAwarePeer(peerHandle1);
+        RangingRequest request = builder.build();
+
+        Parcel parcelW = Parcel.obtain();
+        request.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        RangingRequest rereadRequest = RangingRequest.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(request, rereadRequest);
+    }
+
+    /**
+     * Validate that can request as many range operation as the upper limit on number of requests.
+     */
+    @Test
+    public void testRangingRequestAtLimit() {
+        ScanResult scanResult = new ScanResult();
+        scanResult.BSSID = "AA:BB:CC:DD:EE:FF";
+        List<ScanResult> scanResultList = new ArrayList<>();
+        for (int i = 0; i < RangingRequest.getMaxPeers() - 3; ++i) {
+            scanResultList.add(scanResult);
+        }
+        final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAccessPoint(scanResult);
+        builder.addAccessPoints(scanResultList);
+        builder.addAccessPoint(scanResult);
+        builder.addWifiAwarePeer(mac1);
+        RangingRequest request = builder.build();
+
+        // verify request
+        request.enforceValidity(true);
+    }
+
+    /**
+     * Validate that limit on number of requests is applied.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangingRequestPastLimit() {
+        ScanResult scanResult = new ScanResult();
+        List<ScanResult> scanResultList = new ArrayList<>();
+        for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
+            scanResultList.add(scanResult);
+        }
+        final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addAccessPoint(scanResult);
+        builder.addAccessPoints(scanResultList);
+        builder.addAccessPoint(scanResult);
+        builder.addWifiAwarePeer(mac1);
+        RangingRequest request = builder.build();
+
+        // verify request
+        request.enforceValidity(true);
+    }
+
+    /**
+     * Validate that Aware requests are invalid on devices which do not support Aware
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangingRequestWithAwareWithNoAwareSupport() {
+        // create request
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addWifiAwarePeer(new PeerHandle(10));
+        RangingRequest request = builder.build();
+
+        // verify request
+        request.enforceValidity(false);
+    }
+
+    /**
+     * Validate that RangingResults parcel works (produces same object on write/read).
+     */
+    @Test
+    public void testRangingResultsParcel() {
+        // Note: not validating parcel code of ScanResult (assumed to work)
+        int status = RangingResult.STATUS_SUCCESS;
+        final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false);
+        PeerHandle peerHandle = new PeerHandle(10);
+        int distanceCm = 105;
+        int distanceStdDevCm = 10;
+        int rssi = 5;
+        long timestamp = System.currentTimeMillis();
+
+        // RangingResults constructed with a MAC address
+        RangingResult result = new RangingResult(status, mac, distanceCm, distanceStdDevCm, rssi,
+                timestamp);
+
+        Parcel parcelW = Parcel.obtain();
+        result.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        RangingResult rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(result, rereadResult);
+
+        // RangingResults constructed with a PeerHandle
+        result = new RangingResult(status, peerHandle, distanceCm, distanceStdDevCm, rssi,
+                timestamp);
+
+        parcelW = Parcel.obtain();
+        result.writeToParcel(parcelW, 0);
+        bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        rereadResult = RangingResult.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(result, rereadResult);
+    }
+}